From a0af0c1230a1dbc93a28977d6d61180319220c88 Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Thu, 15 Feb 2024 12:22:25 +0000 Subject: [PATCH 001/529] chore(env vars): Stripe vars moved to the Integrations section (#9427) --- .env.example | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.env.example b/.env.example index be16a7edd47..88cdde703de 100644 --- a/.env.example +++ b/.env.example @@ -112,6 +112,9 @@ RETHINKDB_SSL='false' # RECALL_AI_KEY='' # SLACK_CLIENT_ID='key_SLACK_CLIENT_ID' # SLACK_CLIENT_SECRET='key_SLACK_CLIENT_SECRET' +# STRIPE_SECRET_KEY='sk_test_4eC39HqLyjWDarjtT1zdp7dc' +# STRIPE_PUBLISHABLE_KEY='pk_test_TYooMQauvdEDq54NiTphI7jx' +# STRIPE_WEBHOOK_SECRET='sk_test_4eC39HqLyjWDarjtT1zdp7dc' # MAIL # MAIL GLOBALS. PROVIDER: mailgun | google | debug | smtp @@ -127,9 +130,6 @@ MAIL_PROVIDER='debug' # MAIL_SMTP_PASSWORD='key_MAIL_SMTP_PASSWORD' # MAIL_SMTP_USE_TLS='1' # set to '0' for false # MAIL_SMTP_CIPHERS='HIGH:MEDIUM:!aNULL:!eNULL:@STRENGTH:!DH:!kEDH' -# STRIPE_SECRET_KEY='sk_test_4eC39HqLyjWDarjtT1zdp7dc' -# STRIPE_PUBLISHABLE_KEY='pk_test_TYooMQauvdEDq54NiTphI7jx' -# STRIPE_WEBHOOK_SECRET='sk_test_4eC39HqLyjWDarjtT1zdp7dc' # DEVELOPER VARIABLES # CI='true' From c0a2fdf8fb3deaa34f7935ae8a87d30f43381ecd Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Fri, 16 Feb 2024 21:08:31 +0100 Subject: [PATCH 002/529] chore: fix misleading `isLead` field name on `Team` (#9413) * chore: fix misleading `isLead` field name on `Team` The field indicates whether the viewer is the lead, but when used in a query for a different user, the result could be read wrong. * Fix Team.isLead dependencies --- packages/client/components/ReviewRequestToJoinOrgModal.tsx | 2 +- .../teamDashboard/components/ManageTeam/ManageTeamList.tsx | 4 ++-- .../teamDashboard/components/TeamSettings/TeamSettings.tsx | 2 +- packages/client/mutations/PromoteToTeamLeadMutation.ts | 2 +- packages/server/graphql/public/typeDefs/Team.graphql | 2 +- packages/server/graphql/types/Team.ts | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/client/components/ReviewRequestToJoinOrgModal.tsx b/packages/client/components/ReviewRequestToJoinOrgModal.tsx index 25c2bde8de0..2604455e6f6 100644 --- a/packages/client/components/ReviewRequestToJoinOrgModal.tsx +++ b/packages/client/components/ReviewRequestToJoinOrgModal.tsx @@ -21,7 +21,7 @@ const ReviewRequestToJoinOrgModalViewerFragment = graphql` teams { id name - isLead + isViewerLead teamMembers(sortBy: "preferredName") { userId } diff --git a/packages/client/modules/teamDashboard/components/ManageTeam/ManageTeamList.tsx b/packages/client/modules/teamDashboard/components/ManageTeam/ManageTeamList.tsx index a8e8933820e..7ed7de48036 100644 --- a/packages/client/modules/teamDashboard/components/ManageTeam/ManageTeamList.tsx +++ b/packages/client/modules/teamDashboard/components/ManageTeam/ManageTeamList.tsx @@ -25,7 +25,7 @@ const ManageTeamList = (props: Props) => { const team = useFragment( graphql` fragment ManageTeamList_team on Team { - isLead + isViewerLead isOrgAdmin teamMembers(sortBy: "preferredName") { id @@ -36,7 +36,7 @@ const ManageTeamList = (props: Props) => { `, props.team ) - const {isLead: isViewerLead, isOrgAdmin: isViewerOrgAdmin, teamMembers} = team + const {isViewerLead, isOrgAdmin: isViewerOrgAdmin, teamMembers} = team return ( {teamMembers.map((teamMember) => { diff --git a/packages/client/modules/teamDashboard/components/TeamSettings/TeamSettings.tsx b/packages/client/modules/teamDashboard/components/TeamSettings/TeamSettings.tsx index 829308b0006..62ffb8f934f 100644 --- a/packages/client/modules/teamDashboard/components/TeamSettings/TeamSettings.tsx +++ b/packages/client/modules/teamDashboard/components/TeamSettings/TeamSettings.tsx @@ -43,7 +43,7 @@ const query = graphql` viewer { team(teamId: $teamId) { ...ArchiveTeam_team - isLead + isViewerLead id name tier diff --git a/packages/client/mutations/PromoteToTeamLeadMutation.ts b/packages/client/mutations/PromoteToTeamLeadMutation.ts index c47310341c2..af3325b1808 100644 --- a/packages/client/mutations/PromoteToTeamLeadMutation.ts +++ b/packages/client/mutations/PromoteToTeamLeadMutation.ts @@ -5,7 +5,7 @@ import {PromoteToTeamLeadMutation as TPromoteToTeamLeadMutation} from '../__gene graphql` fragment PromoteToTeamLeadMutation_team on PromoteToTeamLeadPayload { team { - isLead + isViewerLead } oldLeader { ...DashboardAvatar_teamMember diff --git a/packages/server/graphql/public/typeDefs/Team.graphql b/packages/server/graphql/public/typeDefs/Team.graphql index 6a6d00c4c76..6e95b1c602f 100644 --- a/packages/server/graphql/public/typeDefs/Team.graphql +++ b/packages/server/graphql/public/typeDefs/Team.graphql @@ -86,7 +86,7 @@ type Team { """ true if the viewer is the team lead, else false """ - isLead: Boolean! + isViewerLead: Boolean! """ true if the viewer is an admin for the team's org, else false diff --git a/packages/server/graphql/types/Team.ts b/packages/server/graphql/types/Team.ts index 6c161bbcb35..7d737ad98b1 100644 --- a/packages/server/graphql/types/Team.ts +++ b/packages/server/graphql/types/Team.ts @@ -142,7 +142,7 @@ const Team: GraphQLObjectType = new GraphQLObjectType({ return dataLoader.get('teamInvitationsByTeamId').load(teamId) } }, - isLead: { + isViewerLead: { type: new GraphQLNonNull(GraphQLBoolean), description: 'true if the viewer is the team lead, else false', resolve: async ( From f042628fef5bbdbf566c49bab729f5b9dec058f1 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Mon, 19 Feb 2024 17:51:59 +0000 Subject: [PATCH 003/529] feat: remove team template limit (#9424) * update error message and increase template limit * remove max team template limits * remove canClone prop from CloneTemplate * remove unused threshold * remove unused threshold --- .../ActivityDetails/TemplateDetails.tsx | 2 +- .../CreateNewActivity/CreateNewActivity.tsx | 34 +------------------ .../components/AddNewPokerTemplate.tsx | 14 +------- .../components/AddNewReflectTemplate.tsx | 11 +----- .../meeting/components/CloneTemplate.tsx | 9 ++--- .../components/PokerTemplateDetails.tsx | 7 ++-- .../components/ReflectTemplateDetails.tsx | 7 ++-- packages/client/types/constEnums.ts | 2 -- .../graphql/mutations/addPokerTemplate.ts | 5 +-- .../graphql/mutations/addReflectTemplate.ts | 5 +-- 10 files changed, 13 insertions(+), 83 deletions(-) diff --git a/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx b/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx index 0c2b77bef15..b86554fb3ef 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx @@ -267,7 +267,7 @@ export const TemplateDetails = (props: Props) => { />
- +
)} diff --git a/packages/client/components/ActivityLibrary/CreateNewActivity/CreateNewActivity.tsx b/packages/client/components/ActivityLibrary/CreateNewActivity/CreateNewActivity.tsx index 2967f5ab866..8346b47a252 100644 --- a/packages/client/components/ActivityLibrary/CreateNewActivity/CreateNewActivity.tsx +++ b/packages/client/components/ActivityLibrary/CreateNewActivity/CreateNewActivity.tsx @@ -4,14 +4,11 @@ import graphql from 'babel-plugin-relay/macro' import * as RadioGroup from '@radix-ui/react-radio-group' import clsx from 'clsx' import {Link} from 'react-router-dom' - import newTemplate from '../../../../../static/images/illustrations/newTemplate.png' import estimatedEffortTemplate from '../../../../../static/images/illustrations/estimatedEffortTemplate.png' - import {CreateNewActivityQuery} from '~/__generated__/CreateNewActivityQuery.graphql' import {ActivityCard, ActivityCardImage} from '../ActivityCard' import {ActivityBadge} from '../ActivityBadge' - import IconLabel from '../../IconLabel' import NewMeetingTeamPicker from '../../NewMeetingTeamPicker' import sortByTier from '../../../utils/sortByTier' @@ -20,7 +17,6 @@ import {AddReflectTemplateMutation$data} from '../../../__generated__/AddReflect import useAtmosphere from '../../../hooks/useAtmosphere' import useMutationProps from '../../../hooks/useMutationProps' import AddReflectTemplateMutation from '../../../mutations/AddReflectTemplateMutation' -import {Threshold} from '../../../types/constEnums' import useRouter from '../../../hooks/useRouter' import {CATEGORY_ID_TO_NAME, CATEGORY_THEMES, CategoryID, DEFAULT_CARD_THEME} from '../Categories' import BaseButton from '../../BaseButton' @@ -111,15 +107,6 @@ const query = graphql` ...NewMeetingTeamPicker_selectedTeam ...NewMeetingTeamPicker_teams } - availableTemplates(first: 2000) @connection(key: "ActivityLibrary_availableTemplates") { - edges { - node { - name - teamId - type - } - } - } } } ` @@ -148,7 +135,7 @@ export const CreateNewActivity = (props: Props) => { return selectedActivity }) const {viewer} = data - const {teams, availableTemplates, preferredTeamId, featureFlags} = viewer + const {teams, preferredTeamId, featureFlags} = viewer const [selectedTeam, setSelectedTeam] = useState( teams.find((team) => team.id === preferredTeamId) ?? sortByTier(teams)[0]! ) @@ -160,16 +147,6 @@ export const CreateNewActivity = (props: Props) => { return } - const teamTemplates = availableTemplates.edges.filter( - (template) => - template.node.teamId === selectedTeam.id && template.node.type === 'retrospective' - ) - - if (teamTemplates.length >= Threshold.MAX_RETRO_TEAM_TEMPLATES) { - onError(new Error('You may only have 20 templates per team. Please remove one first.')) - return - } - submitMutation() AddReflectTemplateMutation( atmosphere, @@ -195,15 +172,6 @@ export const CreateNewActivity = (props: Props) => { return } - const teamTemplates = availableTemplates.edges.filter( - (template) => template.node.teamId === selectedTeam.id && template.node.type === 'poker' - ) - - if (teamTemplates.length >= Threshold.MAX_POKER_TEAM_TEMPLATES) { - onError(new Error('You may only have 20 templates per team. Please remove one first.')) - return - } - submitMutation() AddPokerTemplateMutation( atmosphere, diff --git a/packages/client/modules/meeting/components/AddNewPokerTemplate.tsx b/packages/client/modules/meeting/components/AddNewPokerTemplate.tsx index 766c85dccee..053684315a3 100644 --- a/packages/client/modules/meeting/components/AddNewPokerTemplate.tsx +++ b/packages/client/modules/meeting/components/AddNewPokerTemplate.tsx @@ -7,7 +7,6 @@ import TooltipStyled from '../../../components/TooltipStyled' import useAtmosphere from '../../../hooks/useAtmosphere' import useMutationProps from '../../../hooks/useMutationProps' import AddPokerTemplateMutation from '../../../mutations/AddPokerTemplateMutation' -import {Threshold} from '../../../types/constEnums' import {AddNewPokerTemplate_pokerTemplates$key} from '../../../__generated__/AddNewPokerTemplate_pokerTemplates.graphql' import {AddNewPokerTemplate_team$key} from '../../../__generated__/AddNewPokerTemplate_team.graphql' @@ -79,17 +78,6 @@ const AddNewPokerTemplate = (props: Props) => { displayUpgradeDetails() return } - if (pokerTemplates.length >= Threshold.MAX_RETRO_TEAM_TEMPLATES) { - onError( - new Error( - `You may only have ${Threshold.MAX_RETRO_TEAM_TEMPLATES} templates per team. Please remove one first.` - ) - ) - errorTimerId.current = window.setTimeout(() => { - onCompleted() - }, 8000) - return - } if (pokerTemplates.find((template) => template.name.startsWith('*New Template'))) { onError(new Error('You already have a new template. Try renaming that one first.')) errorTimerId.current = window.setTimeout(() => { @@ -106,7 +94,7 @@ const AddNewPokerTemplate = (props: Props) => { template.name.startsWith('*New Template') ) - if (pokerTemplates.length > Threshold.MAX_POKER_TEAM_TEMPLATES || containsNewTemplate) return null + if (containsNewTemplate) return null return (
{error && {error.message}} diff --git a/packages/client/modules/meeting/components/AddNewReflectTemplate.tsx b/packages/client/modules/meeting/components/AddNewReflectTemplate.tsx index eea0c89ab03..61429332903 100644 --- a/packages/client/modules/meeting/components/AddNewReflectTemplate.tsx +++ b/packages/client/modules/meeting/components/AddNewReflectTemplate.tsx @@ -7,7 +7,6 @@ import TooltipStyled from '../../../components/TooltipStyled' import useAtmosphere from '../../../hooks/useAtmosphere' import useMutationProps from '../../../hooks/useMutationProps' import AddReflectTemplateMutation from '../../../mutations/AddReflectTemplateMutation' -import {Threshold} from '../../../types/constEnums' import {AddNewReflectTemplate_reflectTemplates$key} from '../../../__generated__/AddNewReflectTemplate_reflectTemplates.graphql' import {AddNewReflectTemplate_team$key} from '../../../__generated__/AddNewReflectTemplate_team.graphql' @@ -79,13 +78,6 @@ const AddNewReflectTemplate = (props: Props) => { displayUpgradeDetails() return } - if (reflectTemplates.length >= Threshold.MAX_RETRO_TEAM_TEMPLATES) { - onError(new Error('You may only have 20 templates per team. Please remove one first.')) - errorTimerId.current = window.setTimeout(() => { - onCompleted() - }, 8000) - return - } if (reflectTemplates.find((template) => template.name.startsWith('*New Template'))) { onError(new Error('You already have a new template. Try renaming that one first.')) errorTimerId.current = window.setTimeout(() => { @@ -102,8 +94,7 @@ const AddNewReflectTemplate = (props: Props) => { template.name.startsWith('*New Template') ) - if (reflectTemplates.length > Threshold.MAX_RETRO_TEAM_TEMPLATES || containsNewTemplate) - return null + if (containsNewTemplate) return null return (
{error && {error.message}} diff --git a/packages/client/modules/meeting/components/CloneTemplate.tsx b/packages/client/modules/meeting/components/CloneTemplate.tsx index 66cc6cc7d3e..6fbc5a1460a 100644 --- a/packages/client/modules/meeting/components/CloneTemplate.tsx +++ b/packages/client/modules/meeting/components/CloneTemplate.tsx @@ -2,15 +2,12 @@ import React from 'react' import DetailAction from '../../../components/DetailAction' interface Props { - canClone: boolean onClick: () => void } const CloneTemplate = (props: Props) => { - const {canClone, onClick} = props - const tooltip = canClone ? 'Clone & Edit Template' : 'Too many team templates! Remove one first' - return ( - - ) + const {onClick} = props + const tooltip = 'Clone & Edit Template' + return } export default CloneTemplate diff --git a/packages/client/modules/meeting/components/PokerTemplateDetails.tsx b/packages/client/modules/meeting/components/PokerTemplateDetails.tsx index 3dcb51ec48a..c42794d1508 100644 --- a/packages/client/modules/meeting/components/PokerTemplateDetails.tsx +++ b/packages/client/modules/meeting/components/PokerTemplateDetails.tsx @@ -6,7 +6,6 @@ import useAtmosphere from '../../../hooks/useAtmosphere' import useMutationProps from '../../../hooks/useMutationProps' import AddPokerTemplateMutation from '../../../mutations/AddPokerTemplateMutation' import {PALETTE} from '../../../styles/paletteV3' -import {Threshold} from '../../../types/constEnums' import getTemplateList from '../../../utils/getTemplateList' import useTemplateDescription from '../../../utils/useTemplateDescription' import {PokerTemplateDetails_settings$key} from '../../../__generated__/PokerTemplateDetails_settings.graphql' @@ -108,12 +107,10 @@ const PokerTemplateDetails = (props: Props) => { const lowestScope = getTemplateList(teamId, orgId, activeTemplate) const isOwner = activeTemplate.teamId === teamId const description = useTemplateDescription(lowestScope, activeTemplate, tier) - const templateCount = teamTemplates.length const atmosphere = useAtmosphere() const {onError, onCompleted, submitting, submitMutation} = useMutationProps() - const canClone = templateCount < Threshold.MAX_POKER_TEAM_TEMPLATES const onClone = () => { - if (submitting || !canClone) return + if (submitting) return submitMutation() AddPokerTemplateMutation( atmosphere, @@ -145,7 +142,7 @@ const PokerTemplateDetails = (props: Props) => { type='poker' /> )} - {showClone && } + {showClone && } {description} diff --git a/packages/client/modules/meeting/components/ReflectTemplateDetails.tsx b/packages/client/modules/meeting/components/ReflectTemplateDetails.tsx index dec65509954..2d45402d52e 100644 --- a/packages/client/modules/meeting/components/ReflectTemplateDetails.tsx +++ b/packages/client/modules/meeting/components/ReflectTemplateDetails.tsx @@ -6,7 +6,6 @@ import useAtmosphere from '../../../hooks/useAtmosphere' import useMutationProps from '../../../hooks/useMutationProps' import AddReflectTemplateMutation from '../../../mutations/AddReflectTemplateMutation' import {PALETTE} from '../../../styles/paletteV3' -import {Threshold} from '../../../types/constEnums' import getTemplateList from '../../../utils/getTemplateList' import useTemplateDescription from '../../../utils/useTemplateDescription' import {ReflectTemplateDetails_settings$key} from '../../../__generated__/ReflectTemplateDetails_settings.graphql' @@ -116,12 +115,10 @@ const ReflectTemplateDetails = (props: Props) => { const lowestScope = getTemplateList(teamId, orgId, activeTemplate) const isOwner = activeTemplate.teamId === teamId const description = useTemplateDescription(lowestScope, activeTemplate, tier) - const templateCount = teamTemplates.length const atmosphere = useAtmosphere() const {onError, onCompleted, submitting, submitMutation} = useMutationProps() - const canClone = templateCount < Threshold.MAX_RETRO_TEAM_TEMPLATES const onClone = () => { - if (submitting || !canClone) return + if (submitting) return submitMutation() AddReflectTemplateMutation( atmosphere, @@ -153,7 +150,7 @@ const ReflectTemplateDetails = (props: Props) => { type='retrospective' /> )} - {showClone && } + {showClone && } {description} diff --git a/packages/client/types/constEnums.ts b/packages/client/types/constEnums.ts index fe42a13d6be..28bbd744b85 100644 --- a/packages/client/types/constEnums.ts +++ b/packages/client/types/constEnums.ts @@ -397,8 +397,6 @@ export const enum Threshold { MAX_POKER_TEMPLATE_SCALES = 12, MAX_POKER_SCALE_VALUES = 30, POKER_SCALE_VALUE_MAX_LENGTH = 3, - MAX_RETRO_TEAM_TEMPLATES = 20, - MAX_POKER_TEAM_TEMPLATES = 20, MAX_POKER_DIMENSION_NAME = 50, MAX_QUAL_AI_MEETINGS = 3, MAX_REACTJIS = 12, diff --git a/packages/server/graphql/mutations/addPokerTemplate.ts b/packages/server/graphql/mutations/addPokerTemplate.ts index ec470279865..3b4e6dbccc0 100644 --- a/packages/server/graphql/mutations/addPokerTemplate.ts +++ b/packages/server/graphql/mutations/addPokerTemplate.ts @@ -1,5 +1,5 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' -import {SprintPokerDefaults, SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' +import {SprintPokerDefaults, SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import PokerTemplate from '../../database/types/PokerTemplate' import TemplateDimension from '../../database/types/TemplateDimension' @@ -45,9 +45,6 @@ const addPokerTemplate = { dataLoader.get('teams').load(teamId), dataLoader.get('users').loadNonNull(viewerId) ]) - if (allTemplates.length >= Threshold.MAX_RETRO_TEAM_TEMPLATES) { - return standardError(new Error('Too many templates'), {userId: viewerId}) - } if (!viewerTeam) { return standardError(new Error('Team not found'), {userId: viewerId}) diff --git a/packages/server/graphql/mutations/addReflectTemplate.ts b/packages/server/graphql/mutations/addReflectTemplate.ts index 46c91713490..d4c3825338a 100644 --- a/packages/server/graphql/mutations/addReflectTemplate.ts +++ b/packages/server/graphql/mutations/addReflectTemplate.ts @@ -1,5 +1,5 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' -import {SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' +import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {PALETTE} from '../../../client/styles/paletteV3' import getRethink from '../../database/rethinkDriver' import ReflectTemplate from '../../database/types/ReflectTemplate' @@ -47,9 +47,6 @@ const addReflectTemplate = { dataLoader.get('users').loadNonNull(viewerId) ]) - if (allTemplates.length >= Threshold.MAX_RETRO_TEAM_TEMPLATES) { - return standardError(new Error('Too many templates'), {userId: viewerId}) - } if (!viewerTeam) { return standardError(new Error('Team not found'), {userId: viewerId}) } From 02dc6fa6e4687021bb46a6774eb5f0be859e4d3f Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 20 Feb 2024 10:56:57 +0100 Subject: [PATCH 004/529] feat: Add Google calendar meeting series for recurrence (#9380) * feat: Add recurrence to GCal events * Fun with timezones --- .../mutations/helpers/createGcalEvent.ts | 80 ++++++++++++++++--- .../public/mutations/startRetrospective.ts | 18 ++++- .../mutations/updateRecurrenceSettings.ts | 26 ++++++ packages/server/package.json | 1 + ...06021181176_addGCalEventToMeetingSeries.ts | 22 +++++ yarn.lock | 8 +- 6 files changed, 141 insertions(+), 14 deletions(-) create mode 100644 packages/server/postgres/migrations/1706021181176_addGCalEventToMeetingSeries.ts diff --git a/packages/server/graphql/mutations/helpers/createGcalEvent.ts b/packages/server/graphql/mutations/helpers/createGcalEvent.ts index 6e936a13cab..4382ca7dedd 100644 --- a/packages/server/graphql/mutations/helpers/createGcalEvent.ts +++ b/packages/server/graphql/mutations/helpers/createGcalEvent.ts @@ -3,23 +3,41 @@ import makeAppURL from 'parabol-client/utils/makeAppURL' import appOrigin from '../../../appOrigin' import {DataLoaderWorker} from '../../graphql' import standardError from '../../../utils/standardError' -import {CreateGcalEventInput} from '../../public/resolverTypes' +import {CreateGcalEventInput, StandardMutationError} from '../../public/resolverTypes' +import {RRule} from 'rrule' +import {pick} from 'lodash' const emailRemindMinsBeforeMeeting = 24 * 60 const popupRemindMinsBeforeMeeting = 10 +const convertRruleToGcal = (rrule: RRule | null | undefined) => { + if (!rrule) { + return [] + } + + // Google does not allow for all fields in rrule. For example DTSTART and DTEND are not allowed. + // It also has trouble with BYHOUR, BYMINUTE, and BYSECOND. It's best to stick to fields known to work. + // Also strip TZID as google wants the UNTIL field in Z, but rrule only uses that if no TZID is present. + const options = pick(rrule.options, 'freq', 'interval', 'byweekday', 'until', 'count') + const gcalRule = new RRule(options) + return [gcalRule.toString()] +} + type Input = { gcalInput?: CreateGcalEventInput | null meetingId: string viewerId: string teamId: string + rrule?: RRule | null dataLoader: DataLoaderWorker } -const createGcalEvent = async (input: Input) => { - const {gcalInput, meetingId, viewerId, dataLoader, teamId} = input +const createGcalEvent = async ( + input: Input +): Promise<{gcalSeriesId?: string; error?: StandardMutationError}> => { + const {gcalInput, meetingId, viewerId, dataLoader, teamId, rrule} = input if (!gcalInput) { - return {error: null} + return {} } const {startTimestamp, endTimestamp, title, timeZone, invitees, videoType} = gcalInput @@ -57,8 +75,9 @@ const createGcalEvent = async (input: Input) => { } } : undefined + const recurrence = convertRruleToGcal(rrule) - const event = { + const eventInput = { summary: title, description, start: { @@ -69,6 +88,7 @@ const createGcalEvent = async (input: Input) => { dateTime: endDateTime, timeZone }, + recurrence, attendees: attendeesWithEmailObjects, reminders: { useDefault: false, @@ -81,17 +101,59 @@ const createGcalEvent = async (input: Input) => { } try { - await calendar.events.insert({ + const event = await calendar.events.insert({ calendarId: 'primary', - requestBody: event, + requestBody: eventInput, conferenceDataVersion: 1 }) + return {gcalSeriesId: event.data.id ?? undefined} } catch (err) { const error = err instanceof Error ? err : new Error('Unable to create event in gcal') return standardError(error, {userId: viewerId}) } - return { - error: null +} + +export type UpdateGcalSeriesInput = { + gcalSeriesId: string + title?: string + rrule: RRule | null + userId: string + teamId: string + dataLoader: DataLoaderWorker +} +export const updateGcalSeries = async (input: UpdateGcalSeriesInput) => { + const {gcalSeriesId, title, rrule, userId, teamId, dataLoader} = input + + const gcalAuth = await dataLoader.get('freshGcalAuth').load({teamId, userId}) + if (!gcalAuth) { + return standardError(new Error('Could not retrieve Google Calendar auth'), {userId}) + } + const {accessToken: access_token, refreshToken: refresh_token, expiresAt} = gcalAuth + const CLIENT_ID = process.env.GOOGLE_OAUTH_CLIENT_ID + const CLIENT_SECRET = process.env.GOOGLE_OAUTH_CLIENT_SECRET + const REDIRECT_URI = appOrigin + + const expiry_date = expiresAt ? expiresAt.getTime() : undefined + + const oauth2Client = new google.auth.OAuth2(CLIENT_ID, CLIENT_SECRET, REDIRECT_URI) + oauth2Client.setCredentials({access_token, refresh_token, expiry_date}) + const calendar = google.calendar({version: 'v3', auth: oauth2Client}) + const recurrence = convertRruleToGcal(rrule) + + try { + const event = await calendar.events.patch({ + calendarId: 'primary', + eventId: gcalSeriesId, + requestBody: { + recurrence, + summary: title + }, + conferenceDataVersion: 1 + }) + return {gcalSeriesId: event.data.id ?? undefined} + } catch (err) { + const error = err instanceof Error ? err : new Error('Unable to create event in gcal') + return standardError(error, {userId}) } } diff --git a/packages/server/graphql/public/mutations/startRetrospective.ts b/packages/server/graphql/public/mutations/startRetrospective.ts index 29583f3d755..266664368c1 100644 --- a/packages/server/graphql/public/mutations/startRetrospective.ts +++ b/packages/server/graphql/public/mutations/startRetrospective.ts @@ -16,6 +16,7 @@ import {IntegrationNotifier} from '../../mutations/helpers/notifications/Integra import {startNewMeetingSeries} from './updateRecurrenceSettings' import safeCreateRetrospective from '../../mutations/helpers/safeCreateRetrospective' import {createMeetingSeriesTitle} from '../../mutations/helpers/createMeetingSeriesTitle' +import getKysely from '../../../postgres/getKysely' const startRetrospective: MutationResolvers['startRetrospective'] = async ( _source, @@ -118,7 +119,22 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( } IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting, template) - const {error} = await createGcalEvent({gcalInput, meetingId, teamId, viewerId, dataLoader}) + const {error, gcalSeriesId} = await createGcalEvent({ + gcalInput, + meetingId, + teamId, + viewerId, + rrule: recurrenceSettings?.rrule, + dataLoader + }) + if (meetingSeries && gcalSeriesId) { + const pg = getKysely() + await pg + .updateTable('MeetingSeries') + .set({gcalSeriesId}) + .where('id', '=', meetingSeries.id) + .execute() + } const data = {teamId, meetingId, hasGcalError: !!error?.message} publish(SubscriptionChannel.TEAM, teamId, 'StartRetrospectiveSuccess', data, subOptions) return data diff --git a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts index 335e08a1e84..daee90b7d61 100644 --- a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts +++ b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts @@ -12,6 +12,7 @@ import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' import {MutationResolvers} from '../resolverTypes' import {MeetingTypeEnum} from '../../../postgres/types/Meeting' +import {updateGcalSeries} from '../../mutations/helpers/createGcalEvent' export const startNewMeetingSeries = async ( meeting: { @@ -111,6 +112,16 @@ const stopMeetingSeries = async (meetingSeries: MeetingSeries) => { .run() } +const updateGCalRecurrenceRule = (oldRule: RRule, newRule: RRule | null | undefined) => { + if (!newRule) { + return new RRule({ + ...oldRule.options, + until: new Date() + }) + } + return newRule +} + const updateRecurrenceSettings: MutationResolvers['updateRecurrenceSettings'] = async ( _source, {meetingId, recurrenceSettings}, @@ -138,6 +149,7 @@ const updateRecurrenceSettings: MutationResolvers['updateRecurrenceSettings'] = if (meeting.meetingSeriesId) { const meetingSeries = await dataLoader.get('meetingSeries').loadNonNull(meeting.meetingSeriesId) + const {gcalSeriesId, teamId, facilitatorId, recurrenceRule} = meetingSeries if (!recurrenceSettings.rrule) { await stopMeetingSeries(meetingSeries) @@ -146,6 +158,20 @@ const updateRecurrenceSettings: MutationResolvers['updateRecurrenceSettings'] = await updateMeetingSeries(meetingSeries, recurrenceSettings.rrule) analytics.recurrenceStarted(viewer, meetingSeries) } + if (gcalSeriesId) { + const rrule = updateGCalRecurrenceRule( + RRule.fromString(recurrenceRule), + recurrenceSettings.rrule + ) + await updateGcalSeries({ + gcalSeriesId, + title: recurrenceSettings.name ?? undefined, + rrule, + teamId, + userId: facilitatorId, + dataLoader + }) + } if (recurrenceSettings.name) { await updateMeetingSeriesQuery({title: recurrenceSettings.name}, meetingSeries.id) diff --git a/packages/server/package.json b/packages/server/package.json index ddd4076d65c..1fdd28cfd74 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -112,6 +112,7 @@ "ioredis": "^5.2.3", "jsdom": "^20.0.0", "jsonwebtoken": "^9.0.0", + "lodash.pick": "^4.4.0", "mailcomposer": "^4.0.1", "mailgun.js": "^9.3.0", "mime-types": "^2.1.16", diff --git a/packages/server/postgres/migrations/1706021181176_addGCalEventToMeetingSeries.ts b/packages/server/postgres/migrations/1706021181176_addGCalEventToMeetingSeries.ts new file mode 100644 index 00000000000..919dda62a87 --- /dev/null +++ b/packages/server/postgres/migrations/1706021181176_addGCalEventToMeetingSeries.ts @@ -0,0 +1,22 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + ALTER TABLE "MeetingSeries" + ADD COLUMN IF NOT EXISTS "gcalSeriesId" VARCHAR(100); + `) + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + ALTER TABLE "MeetingSeries" + DROP COLUMN IF EXISTS "gcalSeriesId"; + `) + await client.end() +} diff --git a/yarn.lock b/yarn.lock index 50e12881143..c08a4168a24 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9012,9 +9012,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001517, caniuse-lite@~1.0.0: - version "1.0.30001584" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001584.tgz#5e3ea0625d048d5467670051687655b1f7bf7dfd" - integrity sha512-LOz7CCQ9M1G7OjJOF9/mzmqmj3jE/7VOmrfw6Mgs0E8cjOsbRXQJHsPBfmBOXDskXKrHLyyW3n7kpDW/4BsfpQ== + version "1.0.30001587" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz#a0bce920155fa56a1885a69c74e1163fc34b4881" + integrity sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA== capital-case@^1.0.4: version "1.0.4" @@ -14573,7 +14573,7 @@ lodash.mergewith@4.6.2: lodash.pick@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.pick/-/lodash.pick-4.4.0.tgz#52f05610fff9ded422611441ed1fc123a03001b3" - integrity sha1-UvBWEP/53tQiYRRB7R/BI6AwAbM= + integrity sha512-hXt6Ul/5yWjfklSGvLQl8vM//l3FtyHZeuelpzK6mm99pNvN9yTDruNZPEJZD1oWrqo+izBmB7oUfWgcCX7s4Q== lodash.setwith@^4.3.2: version "4.3.2" From b0b76f9f45789f60b55243f78eba7b656c751658 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 20 Feb 2024 10:57:18 +0100 Subject: [PATCH 005/529] fix: Increase the number of projects fetched per request from Atlassian (#9435) We ran into timeouts in `getAllProjects`, presumably because we're doing too many roundtrips. As a quick fix, increse the number of projects fetched per request from 50 to 500. --- packages/server/utils/AtlassianServerManager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/server/utils/AtlassianServerManager.ts b/packages/server/utils/AtlassianServerManager.ts index d3ed48c166a..dcc29a4a3b9 100644 --- a/packages/server/utils/AtlassianServerManager.ts +++ b/packages/server/utils/AtlassianServerManager.ts @@ -387,6 +387,7 @@ class AtlassianServerManager extends AtlassianManager { })) projects.push(...pagedProjects) if (res.nextPage) { + log('AtlassianServerManager.getAllProjects fetching more results', res.total) return getProjectPage(cloudId, res.nextPage) } } @@ -395,7 +396,7 @@ class AtlassianServerManager extends AtlassianManager { cloudIds.map((cloudId) => getProjectPage( cloudId, - `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/project/search?orderBy=name` + `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/project/search?orderBy=name&maxResults=500` ) ) ) From c2a31e6b8ef2c4f4d375323f8afbef6874024593 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 21 Feb 2024 10:33:32 -0800 Subject: [PATCH 006/529] chore(deps): bump ip from 1.1.8 to 1.1.9 (#9442) Bumps [ip](https://github.com/indutny/node-ip) from 1.1.8 to 1.1.9. - [Commits](https://github.com/indutny/node-ip/compare/v1.1.8...v1.1.9) --- updated-dependencies: - dependency-name: ip dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/yarn.lock b/yarn.lock index c08a4168a24..99d793dc367 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12892,14 +12892,14 @@ ioredis@^5.2.3: standard-as-callback "^2.1.0" ip@^1.1.5, ip@^1.1.8: - version "1.1.8" - resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.8.tgz#ae05948f6b075435ed3307acce04629da8cdbf48" - integrity sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg== + version "1.1.9" + resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.9.tgz#8dfbcc99a754d07f425310b86a99546b1151e396" + integrity sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ== ip@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.0.tgz#4cf4ab182fee2314c75ede1276f8c80b479936da" - integrity sha512-WKa+XuLG1A1R0UWhl2+1XQSi+fZWMsYKffMZTTYsiZaUD8k2yDAj5atimTUD2TZkyCkNEeYE5NhFZmupOGtjYQ== + version "2.0.1" + resolved "https://registry.yarnpkg.com/ip/-/ip-2.0.1.tgz#e8f3595d33a3ea66490204234b77636965307105" + integrity sha512-lJUL9imLTNi1ZfXT+DU6rBBdbiKGBuay9B6xGSPVjUeQwaH1RIGqef8RZkUtHioLmSNpPR5M4HVKJGm1j8FWVQ== ipaddr.js@1.9.1: version "1.9.1" From e4a831ad5e76a16c930ffee7ffe412577040a894 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 22 Feb 2024 17:51:53 +0100 Subject: [PATCH 007/529] chore(release): release v7.17.0 (#9428) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 20 ++++++++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 31 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2dcd7403b8b..0fb11cbd996 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.16.0" + ".": "7.17.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 3de62f10fbe..fd68e143309 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,26 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.17.0](https://github.com/ParabolInc/parabol/compare/v7.16.0...v7.17.0) (2024-02-21) + + +### Added + +* Add Google calendar meeting series for recurrence ([#9380](https://github.com/ParabolInc/parabol/issues/9380)) ([02dc6fa](https://github.com/ParabolInc/parabol/commit/02dc6fa6e4687021bb46a6774eb5f0be859e4d3f)) +* remove team template limit ([#9424](https://github.com/ParabolInc/parabol/issues/9424)) ([f042628](https://github.com/ParabolInc/parabol/commit/f042628fef5bbdbf566c49bab729f5b9dec058f1)) + + +### Fixed + +* Increase the number of projects fetched per request from Atlassian ([#9435](https://github.com/ParabolInc/parabol/issues/9435)) ([b0b76f9](https://github.com/ParabolInc/parabol/commit/b0b76f9f45789f60b55243f78eba7b656c751658)) + + +### Changed + +* **deps:** bump ip from 1.1.8 to 1.1.9 ([#9442](https://github.com/ParabolInc/parabol/issues/9442)) ([c2a31e6](https://github.com/ParabolInc/parabol/commit/c2a31e6b8ef2c4f4d375323f8afbef6874024593)) +* **env vars:** Stripe vars moved to the Integrations section ([#9427](https://github.com/ParabolInc/parabol/issues/9427)) ([a0af0c1](https://github.com/ParabolInc/parabol/commit/a0af0c1230a1dbc93a28977d6d61180319220c88)) +* fix misleading `isLead` field name on `Team` ([#9413](https://github.com/ParabolInc/parabol/issues/9413)) ([c0a2fdf](https://github.com/ParabolInc/parabol/commit/c0a2fdf8fb3deaa34f7935ae8a87d30f43381ecd)) + ## [7.16.0](https://github.com/ParabolInc/parabol/compare/v7.15.2...v7.16.0) (2024-02-14) diff --git a/package.json b/package.json index cc585b841d9..edde1656431 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.16.0", + "version": "7.17.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 1bc7dbf47ef..5295ced9dd0 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.16.0", + "version": "7.17.0", "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.16.0" + "parabol-server": "7.17.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 2f4c9cec77d..9faef2216e4 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.16.0", + "version": "7.17.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 519e5f91834..1675bcaf5ea 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.16.0", + "version": "7.17.0", "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.16.0", - "parabol-server": "7.16.0", + "parabol-client": "7.17.0", + "parabol-server": "7.17.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 90e1da55dfc..0190f526ea8 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.16.0", + "version": "7.17.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 1fdd28cfd74..c1bde9cbe3f 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.16.0", + "version": "7.17.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.11.0", - "parabol-client": "7.16.0", + "parabol-client": "7.17.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 61ba015c8310a72b7e89c64be081cd2f399fc721 Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Thu, 22 Feb 2024 22:52:30 +0000 Subject: [PATCH 008/529] feat(standalone-deployment): Standalone host deployment improved and documented (#9445) * Docker compose stack improved * Remove unused containers from docker-compse and add useful comment on .env.example about PGSSLMODE * Docker compose profiles added. Documentation extended on how to use the profiles to manage the stack. * README fixed as docker compose up and down commands were not working * Typo fixed and docker-compose command replaced by docker compose --- .../parabol-ubi/docker-host-st/.env.example | 1 + docker/parabol-ubi/docker-host-st/README.md | 33 +++- .../docker-host-st/docker-compose.yaml | 156 +++++++++++++++--- .../parabol-ubi/docker-host-st/postgres.conf | 1 - 4 files changed, 166 insertions(+), 25 deletions(-) delete mode 100644 docker/parabol-ubi/docker-host-st/postgres.conf diff --git a/docker/parabol-ubi/docker-host-st/.env.example b/docker/parabol-ubi/docker-host-st/.env.example index 82c7b2b7e29..9eadf7108e5 100644 --- a/docker/parabol-ubi/docker-host-st/.env.example +++ b/docker/parabol-ubi/docker-host-st/.env.example @@ -1 +1,2 @@ # See https://github.com/ParabolInc/parabol/blob/master/.env.example +# DO NOT SET PGSSLMODE to an empty value. Postgres will not be able to start. diff --git a/docker/parabol-ubi/docker-host-st/README.md b/docker/parabol-ubi/docker-host-st/README.md index c743187aa1d..af3a0b244ec 100644 --- a/docker/parabol-ubi/docker-host-st/README.md +++ b/docker/parabol-ubi/docker-host-st/README.md @@ -1,15 +1,42 @@ # Docker Host Single Tenant (ST) -To run the Parabol UBI in single tenant mode (e.g. simple docker-compose on a docker host). +To run Parabol in single tenant mode (e.g. simple docker-compose on a docker host). 1. Build your Parabol UBI using instructions in `docker/ubi/docker-build/README.md` 2. Create a working `.env` from `.env.example` 3. Update docker-compose.yaml `image: #image:tag` with your built image tag from `step (1.)` -4. Run `docker-compose up -d` to deploy the local stack. You can run `docker-compose down` to terminate the local stack -5. Check logs via command `docker logs -app-1` and wait for the following output to appear +4. Run `docker compose --profile databases --profile parabol up -d` to deploy the local stack. You can run `docker compose --profile databases --profile parabol down` to terminate the local stack +5. Check logs via command `docker logs -f` and wait for the following output to appear ```shell 🔥🔥🔥 Server ID: 0. Ready for Sockets: Port 3000 🔥🔥🔥 💧💧💧 Server ID: 0. Ready for GraphQL Execution 💧💧💧 💧💧💧 Server ID: 01. Ready for GraphQL Execution 💧💧💧 ``` + +## Upgrade Parabol version + +1. Edit the `docker-compose.yaml` and change the `#image:tag` changing the tag. Ex: from `v7.15.0` to `v7.15.2`. +2. (optional) In a different terminal, run `docker compose logs -f` to follow the upgrade. +3. Run `docker compose --profile databases --profile parabol up -d`. It will start the `pre-deploy` and, once it is done successfully, then it will stop and recreate the `web-server` and `gql-executor` with the new version of the image. **This step implies a downtime**. +4. Verify the application is still up and running. + +## Running Chronos + +Chronos isn't started by default. If it needs to run, it must be managed using `docker compose --profile databases --profile parabol --profile chronos up`. + +This will run `pre-deploy` and thus it will recreate the `web-server` and the `gql-executor`. + +## Database debug + +Some tools are available to debug the databases is needed: + +- pgadmin +- redis-commander + +To operate them use `docker compose up --profile databases --profile database-debug`. + +## Running the whole stack + +- Start the whole stack: `docker compose --profile databases --profile parabol --profile database-debug --profile chronos up -d`. +- Stop the stack: `docker compose --profile databases --profile parabol --profile database-debug --profile chronos down` diff --git a/docker/parabol-ubi/docker-host-st/docker-compose.yaml b/docker/parabol-ubi/docker-host-st/docker-compose.yaml index c7ff752a95b..52bb8c76a7d 100644 --- a/docker/parabol-ubi/docker-host-st/docker-compose.yaml +++ b/docker/parabol-ubi/docker-host-st/docker-compose.yaml @@ -1,51 +1,165 @@ -version: '3.7' +version: '3.9' services: - db: - image: rethinkdb:latest + postgres: + container_name: postgres + profiles: ["databases"] + image: postgres:15.4 + restart: always + env_file: .env + environment: + - PGUSER=$POSTGRES_USER + ports: + - '5432:5432' + volumes: + - './data/postgres/pgdata:/var/lib/postgresql/data' + healthcheck: + test: ["CMD-SHELL", "pg_isready", "-d", "$POSTGRES_DB", "-U", "$POSTGRES_USER"] + interval: 10s + timeout: 5s + retries: 5 + networks: + - parabol-network + pgadmin: + profiles: ["database-debug"] + container_name: pgadmin + image: dpage/pgadmin4:8.3 + depends_on: + postgres: + condition: service_healthy + env_file: .env + ports: + - "5050:80" + networks: + - parabol-network + rethinkdb: + container_name: rethinkdb + profiles: ["databases"] + image: rethinkdb:2.4.2 restart: always ports: - '8080:8080' - '29015:29015' - '28015:28015' volumes: - - ./rethink-data:/data + - ./data/rethink:/data networks: - parabol-network - postgres: - image: postgres:15.4 + redis: + container_name: redis + profiles: ["databases"] + image: redis:7.0-alpine + healthcheck: + test: "[ $$(redis-cli ping) = 'PONG' ]" + interval: 10s + timeout: 5s + retries: 5 restart: always - env_file: .env ports: - - '5432:5432' + - '6379:6379' volumes: - - './postgres.conf:/usr/local/etc/postgres/postgres.conf' - - './postgres-data/pgdata:/var/lib/postgresql/data' - command: 'postgres -c config_file=/usr/local/etc/postgres/postgres.conf' + - ./data/redis:/data networks: - parabol-network - redis: - image: redis + redis-commander: + profiles: ["database-debug"] + container_name: redis-commander + image: ghcr.io/joeferner/redis-commander:0.8.1 + depends_on: + redis: + condition: service_healthy restart: always + environment: + - REDIS_HOSTS=local:redis:6379 ports: - - '6379:6379' + - "8081:8081" + networks: + - parabol-network + pre-deploy: + container_name: pre-deploy + profiles: ["parabol"] + image: us-central1-docker.pkg.dev/prbl-tooling/parabol-production/parabol:v7.15.2 + command: bash -c "node dist/preDeploy.js" + env_file: .env + environment: + - SERVER_ID=0 volumes: - - ./redis-data:/data + - './.env:/parabol/.env' + depends_on: + rethinkdb: + condition: service_started + postgres: + condition: service_healthy + redis: + condition: service_healthy networks: - parabol-network - app: - image: #image:tag + chronos: + container_name: chronos + profiles: ["chronos"] + image: us-central1-docker.pkg.dev/prbl-tooling/parabol-production/parabol:v7.15.2 restart: always + command: bash -c "node dist/chronos.js" env_file: .env - command: bash -c "yarn predeploy && NODE_ENV=production && yarn start" + environment: + - SERVER_ID=1 + volumes: + - './.env:/parabol/.env' + depends_on: + pre-deploy: + condition: service_completed_successfully + rethinkdb: + condition: service_started + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - parabol-network + web-server: + container_name: web-server + profiles: ["parabol"] + image: us-central1-docker.pkg.dev/prbl-tooling/parabol-production/parabol:v7.15.2 + restart: always + command: bash -c "node dist/web.js" + env_file: .env + environment: + - SERVER_ID=5 ports: - '3000:3000' volumes: - './.env:/parabol/.env' depends_on: - - db - - redis - - postgres + pre-deploy: + condition: service_completed_successfully + rethinkdb: + condition: service_started + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - parabol-network + gql-executor: + container_name: gql-executor + profiles: ["parabol"] + image: us-central1-docker.pkg.dev/prbl-tooling/parabol-production/parabol:v7.15.2 + restart: always + command: bash -c "node dist/gqlExecutor.js" + env_file: .env + environment: + - SERVER_ID=10 + volumes: + - './.env:/parabol/.env' + depends_on: + pre-deploy: + condition: service_completed_successfully + rethinkdb: + condition: service_started + postgres: + condition: service_healthy + redis: + condition: service_healthy networks: - parabol-network networks: diff --git a/docker/parabol-ubi/docker-host-st/postgres.conf b/docker/parabol-ubi/docker-host-st/postgres.conf deleted file mode 100644 index 3357fd28574..00000000000 --- a/docker/parabol-ubi/docker-host-st/postgres.conf +++ /dev/null @@ -1 +0,0 @@ -listen_addresses='*' \ No newline at end of file From 92ab5be298ceb19ca8718c67a0c9da8728b6b0bf Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 26 Feb 2024 12:23:28 -0800 Subject: [PATCH 009/529] feat: support env-defined saml issuer for PPMIs (#9455) * feat: support env-defined saml issuer for PPMIs Signed-off-by: Matt Krick * feat: support single SAML for entire tenant Signed-off-by: Matt Krick --------- Signed-off-by: Matt Krick --- .env.example | 2 ++ .../SAMLHelpers/getURLWithSAMLRequestParam.ts | 3 ++- packages/server/utils/getSAMLURLFromEmail.ts | 20 +++++++++++++++++++ 3 files changed, 24 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 88cdde703de..25c9a9c856e 100644 --- a/.env.example +++ b/.env.example @@ -14,6 +14,8 @@ SOCKET_PORT='3001' # INVITATION_SHORTLINK='example.com' # If true, all new orgs will default to being enterprise tier. Use for PPMIs # IS_ENTERPRISE=false +# PPMI single tenant use only. Will set the SAML issuer to this value. +# SAML_ISSUER='' # AUTHENTICATION # AUTH_INTERNAL_DISABLED='false' diff --git a/packages/server/graphql/public/mutations/helpers/SAMLHelpers/getURLWithSAMLRequestParam.ts b/packages/server/graphql/public/mutations/helpers/SAMLHelpers/getURLWithSAMLRequestParam.ts index bf80ce33ac4..d3fa3b7e98f 100644 --- a/packages/server/graphql/public/mutations/helpers/SAMLHelpers/getURLWithSAMLRequestParam.ts +++ b/packages/server/graphql/public/mutations/helpers/SAMLHelpers/getURLWithSAMLRequestParam.ts @@ -2,13 +2,14 @@ import {v4 as uuid} from 'uuid' import zlib from 'zlib' const getURLWithSAMLRequestParam = (destination: string, slug: string) => { + const issuer = process.env.SAML_ISSUER || `https://${process.env.HOST}/saml-metadata/${slug}` const template = ` - https://${process.env.HOST}/saml-metadata/${slug} + ${issuer} ` diff --git a/packages/server/utils/getSAMLURLFromEmail.ts b/packages/server/utils/getSAMLURLFromEmail.ts index a2e3fc65e11..2c07d74a06d 100644 --- a/packages/server/utils/getSAMLURLFromEmail.ts +++ b/packages/server/utils/getSAMLURLFromEmail.ts @@ -2,6 +2,13 @@ import base64url from 'base64url' import getSSODomainFromEmail from 'parabol-client/utils/getSSODomainFromEmail' import {URL} from 'url' import {DataLoaderWorker} from '../graphql/graphql' +import getKysely from '../postgres/getKysely' + +const isSingleTenantSSO = + process.env.AUTH_INTERNAL_DISABLED === 'true' && + process.env.AUTH_GOOGLE_DISABLED === 'true' && + process.env.AUTH_MICROSOFT_DISABLED === 'true' && + process.env.AUTH_SSO_DISABLED === 'false' const urlWithRelayState = (url: string, isInvited?: boolean | null) => { if (!isInvited) return url @@ -18,6 +25,19 @@ const getSAMLURLFromEmail = async ( ) => { const domainName = getSSODomainFromEmail(email) if (!domainName) return null + if (isSingleTenantSSO) { + // For PPMI use + const pg = getKysely() + const instanceURLres = await pg + .selectFrom('SAML') + .select('url') + .where('url', 'is not', null) + .limit(1) + .executeTakeFirst() + const instanceURL = instanceURLres?.url + if (!instanceURL) return null + return urlWithRelayState(instanceURL, isInvited) + } const saml = await dataLoader.get('samlByDomain').load(domainName) if (!saml) return null const {url} = saml From c77925b1c0e07afc428022008143b8b7f4002280 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 27 Feb 2024 11:09:26 +0100 Subject: [PATCH 010/529] chore: Associate logs with traces (#9444) * chore: Associate logs with traces Add trace information to log output for server side log statements. This does not include logging from code exclusively used for debugging, deploying or development. * Actually add the logger * Fix DD_LOGS_INJECTION check --- .../server/billing/helpers/adjustUserCount.ts | 3 +- .../billing/helpers/terminateSubscription.ts | 3 +- .../server/dataloader/azureDevOpsLoaders.ts | 15 ++++---- packages/server/fileStorage/GCSManager.ts | 3 +- .../server/graphql/mutations/endCheckIn.ts | 3 +- .../graphql/mutations/endSprintPoker.ts | 3 +- .../helpers/activatePrevSlackAuth.ts | 3 +- .../mutations/helpers/generateGroups.ts | 5 +-- .../mutations/helpers/getCCFromCustomer.ts | 3 +- .../mutations/helpers/removeFromOrg.ts | 3 +- .../helpers/resolveDowngradeToStarter.ts | 3 +- .../mutations/helpers/safeEndRetrospective.ts | 3 +- .../mutations/helpers/safeEndTeamPrompt.ts | 3 +- .../server/graphql/mutations/moveTeamToOrg.ts | 3 +- .../graphql/mutations/navigateMeeting.ts | 3 +- .../graphql/mutations/selectTemplate.ts | 3 +- .../updateAzureDevOpsDimensionField.ts | 5 +-- .../mutations/updateGitHubDimensionField.ts | 3 +- .../private/mutations/autopauseUsers.ts | 3 +- .../private/mutations/connectSocket.ts | 3 +- .../private/mutations/runScheduledJobs.ts | 7 ++-- .../mutations/acceptRequestToJoinDomain.ts | 3 +- .../mutations/updateGitLabDimensionField.ts | 3 +- .../graphql/public/types/TeamHealthStage.ts | 3 +- .../queries/helpers/fetchGitHubRepos.ts | 3 +- .../queries/helpers/fetchGitLabProjects.ts | 3 +- .../server/graphql/types/NewMeetingStage.ts | 3 +- packages/server/graphql/types/PokerMeeting.ts | 3 +- .../upsertAzureDevOpsDimensionFieldMap.ts | 3 +- .../safeMutations/acceptTeamInvitation.ts | 3 +- packages/server/safetyPatchRes.ts | 17 ++++----- .../server/utils/AtlassianServerManager.ts | 28 +++------------ packages/server/utils/Logger.ts | 35 +++++++++++++++++++ packages/server/utils/OpenAIServerManager.ts | 5 +-- .../server/utils/RecallAIServerManager.ts | 3 +- packages/server/utils/StaticServer.ts | 3 +- packages/server/utils/publish.ts | 3 +- packages/server/utils/stripe/StripeManager.ts | 3 +- 38 files changed, 130 insertions(+), 77 deletions(-) create mode 100644 packages/server/utils/Logger.ts diff --git a/packages/server/billing/helpers/adjustUserCount.ts b/packages/server/billing/helpers/adjustUserCount.ts index b7cf07df1a5..15c440c2834 100644 --- a/packages/server/billing/helpers/adjustUserCount.ts +++ b/packages/server/billing/helpers/adjustUserCount.ts @@ -14,6 +14,7 @@ import handleEnterpriseOrgQuantityChanges from './handleEnterpriseOrgQuantityCha import handleTeamOrgQuantityChanges from './handleTeamOrgQuantityChanges' import {getUserById} from '../../postgres/queries/getUsersByIds' import {DataLoaderWorker} from '../../graphql/graphql' +import {Logger} from '../../utils/Logger' const maybeUpdateOrganizationActiveDomain = async ( orgId: string, @@ -170,5 +171,5 @@ export default async function adjustUserCount( .run() handleEnterpriseOrgQuantityChanges(paidOrgs, dataLoader).catch() - handleTeamOrgQuantityChanges(paidOrgs).catch(console.error) + handleTeamOrgQuantityChanges(paidOrgs).catch(Logger.error) } diff --git a/packages/server/billing/helpers/terminateSubscription.ts b/packages/server/billing/helpers/terminateSubscription.ts index b5fdd8a8070..0b1c6d3f4c5 100644 --- a/packages/server/billing/helpers/terminateSubscription.ts +++ b/packages/server/billing/helpers/terminateSubscription.ts @@ -1,5 +1,6 @@ import getRethink from '../../database/rethinkDriver' import Organization from '../../database/types/Organization' +import {Logger} from '../../utils/Logger' import {getStripeManager} from '../../utils/stripe' const terminateSubscription = async (orgId: string) => { @@ -30,7 +31,7 @@ const terminateSubscription = async (orgId: string) => { try { await manager.deleteSubscription(stripeSubscriptionId) } catch (e) { - console.error(`cannot delete subscription ${stripeSubscriptionId}`, e) + Logger.error(`cannot delete subscription ${stripeSubscriptionId}`, e) } } return stripeSubscriptionId diff --git a/packages/server/dataloader/azureDevOpsLoaders.ts b/packages/server/dataloader/azureDevOpsLoaders.ts index 2c9a2b4617f..7892bce6f13 100644 --- a/packages/server/dataloader/azureDevOpsLoaders.ts +++ b/packages/server/dataloader/azureDevOpsLoaders.ts @@ -13,6 +13,7 @@ import AzureDevOpsServerManager, { TeamProjectReference, WorkItem } from '../utils/AzureDevOpsServerManager' +import {Logger} from '../utils/Logger' import sendToSentry from '../utils/sendToSentry' import RootDataLoader from './RootDataLoader' @@ -215,7 +216,7 @@ export const azureDevOpsAllWorkItems = ( const {error, workItems} = restResult if (error !== undefined || workItems === undefined) { - console.log(error) + Logger.log(error) return [] as AzureDevOpsWorkItem[] } @@ -264,7 +265,7 @@ export const azureDevUserInfo = ( const restResult = await manager.getMe() const {error, azureDevOpsUser} = restResult if (error !== undefined || azureDevOpsUser === undefined) { - console.log(error) + Logger.log(error) return undefined } return { @@ -303,7 +304,7 @@ export const allAzureDevOpsAccessibleOrgs = ( const results = await manager.getAccessibleOrgs(id) const {error, accessibleOrgs} = results // handle error if defined - console.log(error) + Logger.log(error) return accessibleOrgs.map((resource) => ({ ...resource })) @@ -340,7 +341,7 @@ export const allAzureDevOpsProjects = ( ) const {error, projects} = await manager.getAllUserProjects() if (error !== undefined) { - console.log(error) + Logger.log(error) return [] } if (projects !== null) resultReferences.push(...projects) @@ -383,7 +384,7 @@ export const azureDevOpsProject = ( ) const projectRes = await manager.getProject(instanceId, projectId) if (projectRes instanceof Error) { - console.log(projectRes) + Logger.log(projectRes) return null } return { @@ -475,7 +476,7 @@ export const azureDevOpsUserStory = ( const restResult = await manager.getWorkItemData(instanceId, workItemIds) const {error, workItems} = restResult if (error !== undefined || workItems.length !== 1 || !workItems[0]) { - console.log(error) + Logger.log(error) return null } else { const returnedWorkItem: WorkItem = workItems[0] @@ -637,7 +638,7 @@ export const azureDevOpsWorkItems = ( const workItemData = await manager.getWorkItemData(instanceId, workItemIds) const {error: workItemDataError, workItems: returnedWorkItems} = workItemData if (workItemDataError !== undefined) { - console.log(error) + Logger.log(error) return [] } diff --git a/packages/server/fileStorage/GCSManager.ts b/packages/server/fileStorage/GCSManager.ts index 0a2a6a2dba9..a3a1cb4d03e 100644 --- a/packages/server/fileStorage/GCSManager.ts +++ b/packages/server/fileStorage/GCSManager.ts @@ -1,6 +1,7 @@ import {sign} from 'jsonwebtoken' import mime from 'mime-types' import path from 'path' +import {Logger} from '../utils/Logger' import FileStoreManager from './FileStoreManager' interface CloudKey { @@ -132,7 +133,7 @@ export default class GCSManager extends FileStoreManager { // https://github.com/nodejs/undici/issues/583#issuecomment-1577475664 // GCS will cause undici to error randomly with `SocketError: other side closed` `code: 'UND_ERR_SOCKET'` if ((e as any).cause?.code === 'UND_ERR_SOCKET') { - console.log(' Retrying GCS Post:', fullPath) + Logger.log(' Retrying GCS Post:', fullPath) await this.putFile(file, fullPath) } } diff --git a/packages/server/graphql/mutations/endCheckIn.ts b/packages/server/graphql/mutations/endCheckIn.ts index 53f7a0325c2..e009f7351c2 100644 --- a/packages/server/graphql/mutations/endCheckIn.ts +++ b/packages/server/graphql/mutations/endCheckIn.ts @@ -16,6 +16,7 @@ import removeSuggestedAction from '../../safeMutations/removeSuggestedAction' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' +import {Logger} from '../../utils/Logger' import publish from '../../utils/publish' import standardError from '../../utils/standardError' import {DataLoaderWorker, GQLContext} from '../graphql' @@ -230,7 +231,7 @@ export default { const updatedTaskIds = (result && result.updatedTaskIds) || [] analytics.checkInEnd(completedCheckIn, meetingMembers, team, dataLoader) - sendNewMeetingSummary(completedCheckIn, context).catch(console.log) + sendNewMeetingSummary(completedCheckIn, context).catch(Logger.log) checkTeamsLimit(team.orgId, dataLoader) const events = teamMembers.map( diff --git a/packages/server/graphql/mutations/endSprintPoker.ts b/packages/server/graphql/mutations/endSprintPoker.ts index 220a9d90aa7..21d18816b99 100644 --- a/packages/server/graphql/mutations/endSprintPoker.ts +++ b/packages/server/graphql/mutations/endSprintPoker.ts @@ -20,6 +20,7 @@ import sendNewMeetingSummary from './helpers/endMeeting/sendNewMeetingSummary' import {IntegrationNotifier} from './helpers/notifications/IntegrationNotifier' import removeEmptyTasks from './helpers/removeEmptyTasks' import updateTeamInsights from './helpers/updateTeamInsights' +import {Logger} from '../../utils/Logger' export default { type: new GraphQLNonNull(EndSprintPokerPayload), @@ -114,7 +115,7 @@ export default { analytics.sprintPokerEnd(completedMeeting, meetingMembers, template, dataLoader) const isKill = !!(phase && phase.phaseType !== 'ESTIMATE') if (!isKill) { - sendNewMeetingSummary(completedMeeting, context).catch(console.log) + sendNewMeetingSummary(completedMeeting, context).catch(Logger.log) checkTeamsLimit(team.orgId, dataLoader) } const events = teamMembers.map( diff --git a/packages/server/graphql/mutations/helpers/activatePrevSlackAuth.ts b/packages/server/graphql/mutations/helpers/activatePrevSlackAuth.ts index b2e39709458..030d4a30446 100644 --- a/packages/server/graphql/mutations/helpers/activatePrevSlackAuth.ts +++ b/packages/server/graphql/mutations/helpers/activatePrevSlackAuth.ts @@ -2,6 +2,7 @@ import ms from 'ms' import getRethink from '../../../database/rethinkDriver' import SlackServerManager from '../../../utils/SlackServerManager' import {upsertNotifications} from '../addSlackAuth' +import {Logger} from '../../../utils/Logger' const activatePrevSlackAuth = async (userId: string, teamId: string) => { const r = await getRethink() @@ -29,7 +30,7 @@ const activatePrevSlackAuth = async (userId: string, teamId: string) => { const manager = new SlackServerManager(botAccessToken) const authRes = await manager.isValidAuthToken(botAccessToken) if (!authRes.ok) { - console.error(authRes.error) + Logger.error(authRes.error) return } diff --git a/packages/server/graphql/mutations/helpers/generateGroups.ts b/packages/server/graphql/mutations/helpers/generateGroups.ts index 6d5e972e3b6..f487119ecc0 100644 --- a/packages/server/graphql/mutations/helpers/generateGroups.ts +++ b/packages/server/graphql/mutations/helpers/generateGroups.ts @@ -6,6 +6,7 @@ import {AutogroupReflectionGroupType} from '../../../database/types/MeetingRetro import {SubscriptionChannel} from '../../../../client/types/constEnums' import publish from '../../../utils/publish' import {analytics} from '../../../utils/analytics/analytics' +import {Logger} from '../../../utils/Logger' const generateGroups = async ( reflections: Reflection[], @@ -24,13 +25,13 @@ const generateGroups = async ( const themes = await manager.generateThemes(groupReflectionsInput) if (!themes) { - console.warn('ChatGPT was unable to generate themes') + Logger.warn('ChatGPT was unable to generate themes') return } const groupedReflections = await manager.groupReflections(groupReflectionsInput, themes) if (!groupedReflections) { - console.warn('ChatGPT was unable to group the reflections') + Logger.warn('ChatGPT was unable to group the reflections') return } const autogroupReflectionGroups: AutogroupReflectionGroupType[] = [] diff --git a/packages/server/graphql/mutations/helpers/getCCFromCustomer.ts b/packages/server/graphql/mutations/helpers/getCCFromCustomer.ts index fe6add91d77..de2bdf41696 100644 --- a/packages/server/graphql/mutations/helpers/getCCFromCustomer.ts +++ b/packages/server/graphql/mutations/helpers/getCCFromCustomer.ts @@ -1,6 +1,7 @@ import Stripe from 'stripe' import {getStripeManager} from '../../../utils/stripe' import {stripeCardToDBCard} from './stripeCardToDBCard' +import {Logger} from '../../../utils/Logger' export default async function getCCFromCustomer( customer: Stripe.Customer | Stripe.DeletedCustomer @@ -16,7 +17,7 @@ export default async function getCCFromCustomer( // customers that used Stripe Elements have default_payment_method: https://stripe.com/docs/payments/payment-methods/transitioning?locale=en-GB const cardRes = await manager.retrieveDefaultCardDetails(customer.id) if (cardRes instanceof Error) { - console.error(cardRes) + Logger.error(cardRes) return undefined } return stripeCardToDBCard(cardRes) diff --git a/packages/server/graphql/mutations/helpers/removeFromOrg.ts b/packages/server/graphql/mutations/helpers/removeFromOrg.ts index 3f0efabca2d..6d2747e227b 100644 --- a/packages/server/graphql/mutations/helpers/removeFromOrg.ts +++ b/packages/server/graphql/mutations/helpers/removeFromOrg.ts @@ -8,6 +8,7 @@ import {DataLoaderWorker} from '../../graphql' import removeTeamMember from './removeTeamMember' import resolveDowngradeToStarter from './resolveDowngradeToStarter' import {RDatum} from '../../../database/stricterR' +import {Logger} from '../../../utils/Logger' const removeFromOrg = async ( userId: string, @@ -93,7 +94,7 @@ const removeFromOrg = async ( try { await adjustUserCount(userId, orgId, InvoiceItemType.REMOVE_USER, dataLoader) } catch (e) { - console.log(e) + Logger.log(e) } await setUserTierForUserIds([userId]) return { diff --git a/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts b/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts index e2ebb39d476..ce82907ecfb 100644 --- a/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts +++ b/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts @@ -3,6 +3,7 @@ import Organization from '../../../database/types/Organization' import getKysely from '../../../postgres/getKysely' import updateTeamByOrgId from '../../../postgres/queries/updateTeamByOrgId' import {analytics} from '../../../utils/analytics/analytics' +import {Logger} from '../../../utils/Logger' import setTierForOrgUsers from '../../../utils/setTierForOrgUsers' import setUserTierForOrgId from '../../../utils/setUserTierForOrgId' import {getStripeManager} from '../../../utils/stripe' @@ -22,7 +23,7 @@ const resolveDowngradeToStarter = async ( try { await manager.deleteSubscription(stripeSubscriptionId) } catch (e) { - console.log(e) + Logger.log(e) } const [org] = await Promise.all([ diff --git a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts index 7890e47ef07..cd116c65ea8 100644 --- a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts @@ -29,6 +29,7 @@ import removeEmptyTasks from './removeEmptyTasks' import updateQualAIMeetingsCount from './updateQualAIMeetingsCount' import gatherInsights from './gatherInsights' import NotificationMentioned from '../../../database/types/NotificationMentioned' +import {Logger} from '../../../utils/Logger' const getTranscription = async (recallBotId?: string | null) => { if (!recallBotId) return @@ -245,7 +246,7 @@ const summarizeRetroMeeting = async (meeting: MeetingRetrospective, context: Int dataLoader.get('newMeetings').clear(meetingId) // wait for whole meeting summary to be generated before sending summary email and updating qualAIMeetingCount - sendNewMeetingSummary(meeting, context).catch(console.log) + sendNewMeetingSummary(meeting, context).catch(Logger.log) updateQualAIMeetingsCount(meetingId, teamId, dataLoader) // wait for meeting stats to be generated before sending Slack notification IntegrationNotifier.endMeeting(dataLoader, meetingId, teamId) diff --git a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts index 5b58f178a87..e8e6f1b6cb5 100644 --- a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts +++ b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts @@ -14,6 +14,7 @@ import updateTeamInsights from './updateTeamInsights' import generateStandupMeetingSummary from './generateStandupMeetingSummary' import updateQualAIMeetingsCount from './updateQualAIMeetingsCount' import gatherInsights from './gatherInsights' +import {Logger} from '../../../utils/Logger' const summarizeTeamPrompt = async (meeting: MeetingTeamPrompt, context: InternalContext) => { const {dataLoader} = context @@ -31,7 +32,7 @@ const summarizeTeamPrompt = async (meeting: MeetingTeamPrompt, context: Internal dataLoader.get('newMeetings').clear(meeting.id) // wait for whole meeting summary to be generated before sending summary email and updating qualAIMeetingCount - sendNewMeetingSummary(meeting, context).catch(console.log) + sendNewMeetingSummary(meeting, context).catch(Logger.log) updateQualAIMeetingsCount(meeting.id, meeting.teamId, dataLoader) // wait for meeting stats to be generated before sending Slack notification IntegrationNotifier.endMeeting(dataLoader, meeting.id, meeting.teamId) diff --git a/packages/server/graphql/mutations/moveTeamToOrg.ts b/packages/server/graphql/mutations/moveTeamToOrg.ts index 06be3517417..7fa25b2b41d 100644 --- a/packages/server/graphql/mutations/moveTeamToOrg.ts +++ b/packages/server/graphql/mutations/moveTeamToOrg.ts @@ -13,6 +13,7 @@ import standardError from '../../utils/standardError' import {DataLoaderWorker, GQLContext} from '../graphql' import isValid from '../isValid' import getKysely from '../../postgres/getKysely' +import {Logger} from '../../utils/Logger' const MAX_NUM_TEAMS = 40 @@ -168,7 +169,7 @@ export default { const successes = results.filter((result) => typeof result === 'string') const failures = results.filter((result) => typeof result !== 'string') const successStr = successes.join('\n') - console.error('failures', failures) + Logger.error('failures', failures) return successStr } } diff --git a/packages/server/graphql/mutations/navigateMeeting.ts b/packages/server/graphql/mutations/navigateMeeting.ts index 2f833970921..70340fa2401 100644 --- a/packages/server/graphql/mutations/navigateMeeting.ts +++ b/packages/server/graphql/mutations/navigateMeeting.ts @@ -11,6 +11,7 @@ import {GQLContext} from '../graphql' import NavigateMeetingPayload from '../types/NavigateMeetingPayload' import handleCompletedStage from './helpers/handleCompletedStage' import removeScheduledJobs from './helpers/removeScheduledJobs' +import {Logger} from '../../utils/Logger' export default { type: new GraphQLNonNull(NavigateMeetingPayload), @@ -84,7 +85,7 @@ export default { phaseCompleteData = await handleCompletedStage(stage, meeting, dataLoader) if (stage.scheduledEndTime) { // not critical, no await needed - removeScheduledJobs(stage.scheduledEndTime, {meetingId}).catch(console.error) + removeScheduledJobs(stage.scheduledEndTime, {meetingId}).catch(Logger.error) stage.scheduledEndTime = null } } diff --git a/packages/server/graphql/mutations/selectTemplate.ts b/packages/server/graphql/mutations/selectTemplate.ts index 5877f848b01..4d0ee10f092 100644 --- a/packages/server/graphql/mutations/selectTemplate.ts +++ b/packages/server/graphql/mutations/selectTemplate.ts @@ -8,6 +8,7 @@ import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' import SelectTemplatePayload from '../types/SelectTemplatePayload' import {getFeatureTier} from '../types/helpers/getFeatureTier' +import {Logger} from '../../utils/Logger' const selectTemplate = { description: 'Set the selected template for the upcoming retro meeting', @@ -37,7 +38,7 @@ const selectTemplate = { ]) if (!template || !template.isActive) { - console.log('no template', selectedTemplateId, template) + Logger.log('no template', selectedTemplateId, template) return standardError(new Error('Template not found'), {userId: viewerId}) } diff --git a/packages/server/graphql/mutations/updateAzureDevOpsDimensionField.ts b/packages/server/graphql/mutations/updateAzureDevOpsDimensionField.ts index a366c1afc9e..7e27ad66e14 100644 --- a/packages/server/graphql/mutations/updateAzureDevOpsDimensionField.ts +++ b/packages/server/graphql/mutations/updateAzureDevOpsDimensionField.ts @@ -5,6 +5,7 @@ import upsertAzureDevOpsDimensionFieldMap, { AzureDevOpsFieldMapProps } from '../../postgres/queries/upsertAzureDevOpsDimensionFieldMap' import {isTeamMember} from '../../utils/authorization' +import {Logger} from '../../utils/Logger' import publish from '../../utils/publish' import {GQLContext} from '../graphql' import UpdateAzureDevOpsDimensionFieldPayload from '../types/UpdateAzureDevOpsDimensionFieldPayload' @@ -57,7 +58,7 @@ const updateAzureDevOpsDimensionField = { }, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { - //console.log(`Inside updateAzureDevOpsDimensionField`) + //Logger.log(`Inside updateAzureDevOpsDimensionField`) const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -91,7 +92,7 @@ const updateAzureDevOpsDimensionField = { } as AzureDevOpsFieldMapProps await upsertAzureDevOpsDimensionFieldMap(props) } catch (e) { - console.log(e) + Logger.log(e) } const data = {teamId, meetingId} diff --git a/packages/server/graphql/mutations/updateGitHubDimensionField.ts b/packages/server/graphql/mutations/updateGitHubDimensionField.ts index d6077e4d01d..703f9e601e9 100644 --- a/packages/server/graphql/mutations/updateGitHubDimensionField.ts +++ b/packages/server/graphql/mutations/updateGitHubDimensionField.ts @@ -3,6 +3,7 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import MeetingPoker from '../../database/types/MeetingPoker' import upsertGitHubDimensionFieldMap from '../../postgres/queries/upsertGitHubDimensionFieldMap' import {isTeamMember} from '../../utils/authorization' +import {Logger} from '../../utils/Logger' import publish from '../../utils/publish' import {GQLContext} from '../graphql' import UpdateGitHubDimensionFieldPayload from '../types/UpdateGitHubDimensionFieldPayload' @@ -66,7 +67,7 @@ const updateGitHubDimensionField = { try { await upsertGitHubDimensionFieldMap(teamId, dimensionName, nameWithOwner, labelTemplate) } catch (e) { - console.log(e) + Logger.log(e) } const data = {meetingId, teamId} diff --git a/packages/server/graphql/private/mutations/autopauseUsers.ts b/packages/server/graphql/private/mutations/autopauseUsers.ts index ed92c2e95c9..ba8e3439b0b 100644 --- a/packages/server/graphql/private/mutations/autopauseUsers.ts +++ b/packages/server/graphql/private/mutations/autopauseUsers.ts @@ -3,6 +3,7 @@ import adjustUserCount from '../../../billing/helpers/adjustUserCount' import getRethink from '../../../database/rethinkDriver' import getUserIdsToPause from '../../../postgres/queries/getUserIdsToPause' import {MutationResolvers} from '../resolverTypes' +import {Logger} from '../../../utils/Logger' const autopauseUsers: MutationResolvers['autopauseUsers'] = async ( _source, @@ -32,7 +33,7 @@ const autopauseUsers: MutationResolvers['autopauseUsers'] = async ( try { return await adjustUserCount(userId, orgIds, InvoiceItemType.AUTO_PAUSE_USER, dataLoader) } catch (e) { - console.warn(`Error adjusting user count`) + Logger.warn(`Error adjusting user count`) } return undefined }) diff --git a/packages/server/graphql/private/mutations/connectSocket.ts b/packages/server/graphql/private/mutations/connectSocket.ts index c105d0ef180..4461a506f27 100644 --- a/packages/server/graphql/private/mutations/connectSocket.ts +++ b/packages/server/graphql/private/mutations/connectSocket.ts @@ -6,6 +6,7 @@ import {analytics} from '../../../utils/analytics/analytics' import {getUserId} from '../../../utils/authorization' import getListeningUserIds, {RedisCommand} from '../../../utils/getListeningUserIds' import getRedis from '../../../utils/getRedis' +import {Logger} from '../../../utils/Logger' import publish from '../../../utils/publish' import {MutationResolvers} from '../resolverTypes' @@ -44,7 +45,7 @@ const connectSocket: MutationResolvers['connectSocket'] = async ( .getAll(userId, {index: 'userId'}) .filter({removedAt: null, inactive: true})('orgId') .run() - adjustUserCount(userId, orgIds, InvoiceItemType.UNPAUSE_USER, dataLoader).catch(console.log) + adjustUserCount(userId, orgIds, InvoiceItemType.UNPAUSE_USER, dataLoader).catch(Logger.log) // TODO: re-identify } const datesAreOnSameDay = now.toDateString() === lastSeenAt.toDateString() diff --git a/packages/server/graphql/private/mutations/runScheduledJobs.ts b/packages/server/graphql/private/mutations/runScheduledJobs.ts index 923d4617119..faee38517ec 100644 --- a/packages/server/graphql/private/mutations/runScheduledJobs.ts +++ b/packages/server/graphql/private/mutations/runScheduledJobs.ts @@ -8,6 +8,7 @@ import publish from '../../../utils/publish' import {DataLoaderWorker} from '../../graphql' import {IntegrationNotifier} from '../../mutations/helpers/notifications/IntegrationNotifier' import {MutationResolvers} from '../resolverTypes' +import {Logger} from '../../../utils/Logger' const processMeetingStageTimeLimits = async ( job: ScheduledJobMeetingStageTimeLimit, @@ -48,9 +49,9 @@ const processJob = async (job: ScheduledJobUnion, dataLoader: DataLoaderWorker) return processMeetingStageTimeLimits( job as ScheduledJobMeetingStageTimeLimit, dataLoader - ).catch(console.error) + ).catch(Logger.error) } else if (job.type === 'LOCK_ORGANIZATION' || job.type === 'WARN_ORGANIZATION') { - return processTeamsLimitsJob(job as ScheduledTeamLimitsJob, dataLoader).catch(console.error) + return processTeamsLimitsJob(job as ScheduledTeamLimitsJob, dataLoader).catch(Logger.error) } } @@ -73,7 +74,7 @@ const runScheduledJobs: MutationResolvers['runScheduledJobs'] = async ( const {runAt} = job const timeout = Math.max(0, runAt.getTime() - now.getTime()) setTimeout(() => { - processJob(job, dataLoader).catch(console.error) + processJob(job, dataLoader).catch(Logger.error) }, timeout) }) diff --git a/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts b/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts index b7920912068..ea12206cdd5 100644 --- a/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts +++ b/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts @@ -14,6 +14,7 @@ import publish from '../../../utils/publish' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import DomainJoinRequestId from 'parabol-client/shared/gqlIds/DomainJoinRequestId' import {getUserById} from '../../../postgres/queries/getUsersByIds' +import {Logger} from '../../../utils/Logger' // TODO (EXPERIMENT: prompt-to-join-org): some parts are borrowed from acceptTeamInvitation, create generic functions const acceptRequestToJoinDomain: MutationResolvers['acceptRequestToJoinDomain'] = async ( @@ -106,7 +107,7 @@ const acceptRequestToJoinDomain: MutationResolvers['acceptRequestToJoinDomain'] try { await adjustUserCount(userId, orgId, InvoiceItemType.ADD_USER, dataLoader) } catch (e) { - console.log(e) + Logger.log(e) } await setUserTierForUserIds([userId]) } diff --git a/packages/server/graphql/public/mutations/updateGitLabDimensionField.ts b/packages/server/graphql/public/mutations/updateGitLabDimensionField.ts index f03ebde9493..88ca97eb167 100644 --- a/packages/server/graphql/public/mutations/updateGitLabDimensionField.ts +++ b/packages/server/graphql/public/mutations/updateGitLabDimensionField.ts @@ -2,6 +2,7 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import MeetingPoker from '../../../database/types/MeetingPoker' import upsertGitLabDimensionFieldMap from '../../../postgres/queries/upsertGitLabDimensionFieldMap' import {isTeamMember} from '../../../utils/authorization' +import {Logger} from '../../../utils/Logger' import publish from '../../../utils/publish' import {MutationResolvers} from '../resolverTypes' import {getUserId} from './../../../utils/authorization' @@ -40,7 +41,7 @@ const updateGitLabDimensionField: MutationResolvers['updateGitLabDimensionField' const {providerId} = gitlabAuth await upsertGitLabDimensionFieldMap(teamId, dimensionName, projectId, providerId, labelTemplate) } catch (e) { - console.log(e) + Logger.log(e) } const data = {meetingId, teamId} diff --git a/packages/server/graphql/public/types/TeamHealthStage.ts b/packages/server/graphql/public/types/TeamHealthStage.ts index 29c7834c877..f98202ae131 100644 --- a/packages/server/graphql/public/types/TeamHealthStage.ts +++ b/packages/server/graphql/public/types/TeamHealthStage.ts @@ -2,6 +2,7 @@ import {TeamHealthStageResolvers} from '../resolverTypes' import {getUserId} from '../../../utils/authorization' import TeamHealthStageDB from '../../../database/types/TeamHealthStage' import isValid from '../../isValid' +import {Logger} from '../../../utils/Logger' export type TeamHealthStageSource = TeamHealthStageDB & { meetingId: string @@ -21,7 +22,7 @@ const TeamHealthStage: TeamHealthStageResolvers = { }, readyCount: async ({meetingId, readyToAdvance}, _args, {dataLoader}, ref) => { if (!readyToAdvance) return 0 - if (!meetingId) console.log('no meetingid', ref) + if (!meetingId) Logger.log('no meetingid', ref) const meeting = await dataLoader.get('newMeetings').load(meetingId) const {facilitatorUserId} = meeting return readyToAdvance.filter((userId) => userId !== facilitatorUserId).length diff --git a/packages/server/graphql/queries/helpers/fetchGitHubRepos.ts b/packages/server/graphql/queries/helpers/fetchGitHubRepos.ts index 17fd935ae64..14b951248c0 100644 --- a/packages/server/graphql/queries/helpers/fetchGitHubRepos.ts +++ b/packages/server/graphql/queries/helpers/fetchGitHubRepos.ts @@ -4,6 +4,7 @@ import getGitHubRequest from '../../../utils/getGitHubRequest' import getRepositories from '../../../utils/githubQueries/getRepositories.graphql' import {DataLoaderWorker} from '../../graphql' import {GQLContext} from './../../graphql' +import {Logger} from '../../../utils/Logger' export interface GitHubRepo { id: string @@ -25,7 +26,7 @@ const fetchGitHubRepos = async ( const githubRequest = getGitHubRequest(info, context, {accessToken}) const [data, error] = await githubRequest(getRepositories) if (error) { - console.error(error.message) + Logger.error(error.message) return [] } const {viewer} = data diff --git a/packages/server/graphql/queries/helpers/fetchGitLabProjects.ts b/packages/server/graphql/queries/helpers/fetchGitLabProjects.ts index 8ff83e7a8e3..72da38eddb3 100644 --- a/packages/server/graphql/queries/helpers/fetchGitLabProjects.ts +++ b/packages/server/graphql/queries/helpers/fetchGitLabProjects.ts @@ -2,6 +2,7 @@ import {GraphQLResolveInfo} from 'graphql' import {isNotNull} from 'parabol-client/utils/predicates' import GitLabServerManager from '../../../integrations/gitlab/GitLabServerManager' import {GQLContext} from '../../graphql' +import {Logger} from '../../../utils/Logger' const fetchGitLabProjects = async ( teamId: string, @@ -18,7 +19,7 @@ const fetchGitLabProjects = async ( const manager = new GitLabServerManager(auth, context, info, provider.serverBaseUrl) const [data, error] = await manager.getProjects({}) if (error) { - console.error(error.message) + Logger.error(error.message) return [] } return ( diff --git a/packages/server/graphql/types/NewMeetingStage.ts b/packages/server/graphql/types/NewMeetingStage.ts index 5eecb36a46f..0247e4ebbf2 100644 --- a/packages/server/graphql/types/NewMeetingStage.ts +++ b/packages/server/graphql/types/NewMeetingStage.ts @@ -11,6 +11,7 @@ import GenericMeetingPhase, { NewMeetingPhaseTypeEnum as NewMeetingPhaseTypeEnumType } from '../../database/types/GenericMeetingPhase' import {getUserId} from '../../utils/authorization' +import {Logger} from '../../utils/Logger' import {GQLContext} from '../graphql' import GraphQLISO8601Type from './GraphQLISO8601Type' import NewMeeting from './NewMeeting' @@ -112,7 +113,7 @@ export const newMeetingStageFields = () => ({ ref: any ) => { if (!readyToAdvance) return 0 - if (!meetingId) console.log('no meetingid', ref) + if (!meetingId) Logger.log('no meetingid', ref) const meeting = await dataLoader.get('newMeetings').load(meetingId) const {facilitatorUserId} = meeting return readyToAdvance.filter((userId: string) => userId !== facilitatorUserId).length diff --git a/packages/server/graphql/types/PokerMeeting.ts b/packages/server/graphql/types/PokerMeeting.ts index f9262f8a919..fd0116d26f2 100644 --- a/packages/server/graphql/types/PokerMeeting.ts +++ b/packages/server/graphql/types/PokerMeeting.ts @@ -1,6 +1,7 @@ import {GraphQLID, GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType} from 'graphql' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import {getUserId} from '../../utils/authorization' +import {Logger} from '../../utils/Logger' import {GQLContext} from '../graphql' import NewMeeting from './NewMeeting' import PokerMeetingMember from './PokerMeetingMember' @@ -39,7 +40,7 @@ const PokerMeeting = new GraphQLObjectType({ resolve: async ({id: meetingId}, {storyId: taskId}, {dataLoader}) => { const task = await dataLoader.get('tasks').load(taskId) if (task.meetingId !== meetingId) { - console.log('naughty storyId supplied to PokerMeeting') + Logger.log('naughty storyId supplied to PokerMeeting') return null } return task diff --git a/packages/server/postgres/queries/upsertAzureDevOpsDimensionFieldMap.ts b/packages/server/postgres/queries/upsertAzureDevOpsDimensionFieldMap.ts index e19e9ad7c83..d5099ed387d 100644 --- a/packages/server/postgres/queries/upsertAzureDevOpsDimensionFieldMap.ts +++ b/packages/server/postgres/queries/upsertAzureDevOpsDimensionFieldMap.ts @@ -1,3 +1,4 @@ +import {Logger} from '../../utils/Logger' import getPg from '../getPg' import {upsertAzureDevOpsDimensionFieldMapQuery} from './generated/upsertAzureDevOpsDimensionFieldMapQuery' @@ -22,7 +23,7 @@ const upsertAzureDevOpsDimensionFieldMap = async (props: AzureDevOpsFieldMapProp projectKey, workItemType } = props - console.log(`Inside upsertAzureDevOpsDimensionFieldMap - props:${JSON.stringify(props)}`) + Logger.log(`Inside upsertAzureDevOpsDimensionFieldMap - props:${JSON.stringify(props)}`) return upsertAzureDevOpsDimensionFieldMapQuery.run( { fieldMap: { diff --git a/packages/server/safeMutations/acceptTeamInvitation.ts b/packages/server/safeMutations/acceptTeamInvitation.ts index 98563d9270f..be5ec868c6d 100644 --- a/packages/server/safeMutations/acceptTeamInvitation.ts +++ b/packages/server/safeMutations/acceptTeamInvitation.ts @@ -6,6 +6,7 @@ import generateUID from '../generateUID' import {DataLoaderWorker} from '../graphql/graphql' import {Team} from '../postgres/queries/getTeamsByIds' import getNewTeamLeadUserId from '../safeQueries/getNewTeamLeadUserId' +import {Logger} from '../utils/Logger' import setUserTierForUserIds from '../utils/setUserTierForUserIds' import addTeamIdToTMS from './addTeamIdToTMS' import insertNewTeamMember from './insertNewTeamMember' @@ -92,7 +93,7 @@ const acceptTeamInvitation = async (team: Team, userId: string, dataLoader: Data try { await adjustUserCount(userId, orgId, InvoiceItemType.ADD_USER, dataLoader) } catch (e) { - console.log(e) + Logger.log(e) } await setUserTierForUserIds([userId]) } diff --git a/packages/server/safetyPatchRes.ts b/packages/server/safetyPatchRes.ts index d48e736b5cf..aa974ebb88f 100644 --- a/packages/server/safetyPatchRes.ts +++ b/packages/server/safetyPatchRes.ts @@ -1,4 +1,5 @@ import {HttpResponse, RecognizedString} from 'uWebSockets.js' +import {Logger} from './utils/Logger' type Header = [key: RecognizedString, value: RecognizedString] @@ -36,7 +37,7 @@ const safetyPatchRes = (res: HttpResponse) => { res._end = res.end res.end = (body?: RecognizedString) => { if (res.done) { - console.warn(`uWS: Called end after done`) + Logger.warn(`uWS: Called end after done`) } if (res.done || res.aborted) return res res.done = true @@ -46,7 +47,7 @@ const safetyPatchRes = (res: HttpResponse) => { res._close = res.close res.close = () => { if (res.done) { - console.warn(`uWS: Called close after done`) + Logger.warn(`uWS: Called close after done`) } if (res.done || res.aborted) return res res.done = true @@ -61,7 +62,7 @@ const safetyPatchRes = (res: HttpResponse) => { res._tryEnd = res.tryEnd res.tryEnd = (fullBodyOrChunk: RecognizedString, totalSize: number) => { if (res.done) { - console.warn(`uWS: Called tryEnd after done`) + Logger.warn(`uWS: Called tryEnd after done`) } if (res.done || res.aborted) return [true, true] return flush(() => res._tryEnd(fullBodyOrChunk, totalSize)) @@ -70,7 +71,7 @@ const safetyPatchRes = (res: HttpResponse) => { res._write = res.write res.write = (chunk: RecognizedString) => { if (res.done) { - console.warn(`uWS: Called write after done`) + Logger.warn(`uWS: Called write after done`) } if (res.done || res.aborted) return res return res._write(chunk) @@ -79,7 +80,7 @@ const safetyPatchRes = (res: HttpResponse) => { res._writeHeader = res.writeHeader res.writeHeader = (key: RecognizedString, value: RecognizedString) => { if (res.done) { - console.warn(`uWS: Called writeHeader after done`) + Logger.warn(`uWS: Called writeHeader after done`) } res.headers.push([key, value]) return res @@ -88,7 +89,7 @@ const safetyPatchRes = (res: HttpResponse) => { res._writeStatus = res.writeStatus res.writeStatus = (status: RecognizedString) => { if (res.done) { - console.error(`uWS: Called writeStatus after done ${status}`) + Logger.error(`uWS: Called writeStatus after done ${status}`) } res.status = status return res @@ -97,7 +98,7 @@ const safetyPatchRes = (res: HttpResponse) => { res._upgrade = res.upgrade res.upgrade = (...args) => { if (res.done) { - console.error(`uWS: Called upgrade after done`) + Logger.error(`uWS: Called upgrade after done`) } if (res.done || res.aborted) return return res._cork(() => { @@ -108,7 +109,7 @@ const safetyPatchRes = (res: HttpResponse) => { res._getRemoteAddressAsText = res.getRemoteAddressAsText res.getRemoteAddressAsText = () => { if (res.done) { - console.error(`uWS: Called getRemoteAddressAsText after done`) + Logger.error(`uWS: Called getRemoteAddressAsText after done`) } if (res.done || res.aborted) return Buffer.from('') return res._getRemoteAddressAsText() diff --git a/packages/server/utils/AtlassianServerManager.ts b/packages/server/utils/AtlassianServerManager.ts index dcc29a4a3b9..d84a448f7be 100644 --- a/packages/server/utils/AtlassianServerManager.ts +++ b/packages/server/utils/AtlassianServerManager.ts @@ -11,25 +11,7 @@ import { OAuth2AuthorizationParams, OAuth2RefreshAuthorizationParams } from '../integrations/OAuth2Manager' - -import tracer from 'dd-trace' -import formats from 'dd-trace/ext/formats' -import util from 'util' - -type LogLevel = 'error' | 'warn' | 'info' | 'debug' -function trace(level: LogLevel, message: any, ...optionalParameters: any[]) { - const span = tracer.scope().active() - const time = new Date().toISOString() - const record = {time, level, message: util.format(message, optionalParameters)} - - if (span) { - tracer.inject(span.context(), formats.LOG, record) - } - - console.log(JSON.stringify(record)) -} - -const log = trace.bind(null, 'info') +import {Logger} from './Logger' export interface JiraUser { self: string @@ -343,7 +325,7 @@ class AtlassianServerManager extends AtlassianManager { } else { callback(null, {cloudId, newProjects: res.values}) if (res.nextPage) { - await this.getPaginatedProjects(cloudId, res.nextPage, callback).catch(console.error) + await this.getPaginatedProjects(cloudId, res.nextPage, callback).catch(Logger.error) } } } @@ -355,7 +337,7 @@ class AtlassianServerManager extends AtlassianManager { cloudId, `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/project/search?orderBy=name`, callback - ).catch(console.error) + ).catch(Logger.error) }) ) } @@ -387,7 +369,7 @@ class AtlassianServerManager extends AtlassianManager { })) projects.push(...pagedProjects) if (res.nextPage) { - log('AtlassianServerManager.getAllProjects fetching more results', res.total) + Logger.log('AtlassianServerManager.getAllProjects fetching more results', res.total) return getProjectPage(cloudId, res.nextPage) } } @@ -402,7 +384,7 @@ class AtlassianServerManager extends AtlassianManager { ) if (error) { - log('getAllProjects ERROR:', error) + Logger.log('getAllProjects ERROR:', error) } return projects } diff --git a/packages/server/utils/Logger.ts b/packages/server/utils/Logger.ts new file mode 100644 index 00000000000..3cca7489c69 --- /dev/null +++ b/packages/server/utils/Logger.ts @@ -0,0 +1,35 @@ +import tracer from 'dd-trace' +import formats from 'dd-trace/ext/formats' +import util from 'util' + +type LogLevel = 'error' | 'warn' | 'info' | 'debug' +const LogFun = { + error: console.error, + warn: console.warn, + info: console.info, + debug: console.debug +} satisfies Record + +function trace(level: LogLevel, message: any, ...optionalParameters: any[]) { + if (process.env.DD_LOGS_INJECTION !== 'true') { + return LogFun[level](message, ...optionalParameters) + } + + const span = tracer.scope().active() + const time = new Date().toISOString() + const record = {time, level, message: util.format(message, optionalParameters)} + + if (span) { + tracer.inject(span.context(), formats.LOG, record) + } + + LogFun[level](JSON.stringify(record)) +} + +export const Logger = { + log: trace.bind(null, 'info'), + error: trace.bind(null, 'error'), + warn: trace.bind(null, 'warn'), + info: trace.bind(null, 'info'), + debug: trace.bind(null, 'debug') +} diff --git a/packages/server/utils/OpenAIServerManager.ts b/packages/server/utils/OpenAIServerManager.ts index 3cc48bb3868..251528770e2 100644 --- a/packages/server/utils/OpenAIServerManager.ts +++ b/packages/server/utils/OpenAIServerManager.ts @@ -3,6 +3,7 @@ import JSON5 from 'json5' import sendToSentry from './sendToSentry' import Reflection from '../database/types/Reflection' import {ModifyType} from '../graphql/public/resolverTypes' +import {Logger} from './Logger' type Prompt = { question: string @@ -188,7 +189,7 @@ class OpenAIServerManager { return themes.split(', ') } catch (e) { const error = e instanceof Error ? e : new Error('OpenAI failed to generate themes') - console.error(error.message) + Logger.error(error.message) sendToSentry(error) return null } @@ -226,7 +227,7 @@ class OpenAIServerManager { } catch (e) { const error = e instanceof Error ? e : new Error('OpenAI failed to generate the suggested template') - console.error(error.message) + Logger.error(error.message) sendToSentry(error) return null } diff --git a/packages/server/utils/RecallAIServerManager.ts b/packages/server/utils/RecallAIServerManager.ts index 5c30cfc1ceb..c5dc8e7f781 100644 --- a/packages/server/utils/RecallAIServerManager.ts +++ b/packages/server/utils/RecallAIServerManager.ts @@ -4,6 +4,7 @@ import {ExternalLinks} from '../../client/types/constEnums' import appOrigin from '../appOrigin' import {TranscriptBlock} from '../database/types/MeetingRetrospective' import sendToSentry from './sendToSentry' +import {Logger} from './Logger' const sdk = api('@recallai/v1.6#536jnqlf7d6blh') @@ -19,7 +20,7 @@ const getBase64Image = async () => { const base64Image = buffer.toString('base64') return base64Image } catch (error) { - console.error(error) + Logger.error(error) return null } } diff --git a/packages/server/utils/StaticServer.ts b/packages/server/utils/StaticServer.ts index f8cba007f00..2ba0fd3b3b6 100644 --- a/packages/server/utils/StaticServer.ts +++ b/packages/server/utils/StaticServer.ts @@ -3,6 +3,7 @@ import mime from 'mime-types' import path from 'path' import {brotliCompressSync} from 'zlib' import isCompressible from './isCompressible' +import {Logger} from './Logger' class StaticFileMeta { mtime: string size: number @@ -63,7 +64,7 @@ export default class StaticServer { } makePathnames(dirname, this.pathnames, '') } catch (e) { - console.log(e) + Logger.log(e) } }) } diff --git a/packages/server/utils/publish.ts b/packages/server/utils/publish.ts index 5203852257f..b9c7defb2ff 100644 --- a/packages/server/utils/publish.ts +++ b/packages/server/utils/publish.ts @@ -1,4 +1,5 @@ import getPubSub from './getPubSub' +import {Logger} from './Logger' export interface SubOptions { mutatorId?: string // passing the socket id of the mutator will omit sending a message to that user @@ -18,7 +19,7 @@ const publish = ( const rootValue = {[subName]: {fieldName: type, [type]: payload}} getPubSub() .publish(`${topic}.${channel}`, {rootValue, executorServerId: SERVER_ID!, ...subOptions}) - .catch(console.error) + .catch(Logger.error) } export default publish diff --git a/packages/server/utils/stripe/StripeManager.ts b/packages/server/utils/stripe/StripeManager.ts index 0ab72ac35c3..28db42bd437 100644 --- a/packages/server/utils/stripe/StripeManager.ts +++ b/packages/server/utils/stripe/StripeManager.ts @@ -1,5 +1,6 @@ import {InvoiceItemType} from 'parabol-client/types/constEnums' import Stripe from 'stripe' +import {Logger} from '../Logger' import sendToSentry from '../sendToSentry' export default class StripeManager { @@ -15,7 +16,7 @@ export default class StripeManager { try { return this.stripe.webhooks.constructEvent(rawBody, signature, StripeManager.WEBHOOK_SECRET) } catch (e) { - console.log('StripeWebhookError:', e) + Logger.log('StripeWebhookError:', e) return null } } From bd519c93bebe000ebe34dc6ea7eba2db337e2251 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 10:46:36 -0800 Subject: [PATCH 011/529] chore(release): release v7.18.0 (#9450) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 24 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0fb11cbd996..f6475935835 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.17.0" + ".": "7.18.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index fd68e143309..702f2ff2a5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.18.0](https://github.com/ParabolInc/parabol/compare/v7.17.0...v7.18.0) (2024-02-27) + + +### Added + +* **standalone-deployment:** Standalone host deployment improved and documented ([#9445](https://github.com/ParabolInc/parabol/issues/9445)) ([61ba015](https://github.com/ParabolInc/parabol/commit/61ba015c8310a72b7e89c64be081cd2f399fc721)) +* support env-defined saml issuer for PPMIs ([#9455](https://github.com/ParabolInc/parabol/issues/9455)) ([92ab5be](https://github.com/ParabolInc/parabol/commit/92ab5be298ceb19ca8718c67a0c9da8728b6b0bf)) + + +### Changed + +* Associate logs with traces ([#9444](https://github.com/ParabolInc/parabol/issues/9444)) ([c77925b](https://github.com/ParabolInc/parabol/commit/c77925b1c0e07afc428022008143b8b7f4002280)) + ## [7.17.0](https://github.com/ParabolInc/parabol/compare/v7.16.0...v7.17.0) (2024-02-21) diff --git a/package.json b/package.json index edde1656431..962c45f4873 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.17.0", + "version": "7.18.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 5295ced9dd0..6be287b79d6 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.17.0", + "version": "7.18.0", "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.17.0" + "parabol-server": "7.18.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 9faef2216e4..3776a1094db 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.17.0", + "version": "7.18.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 1675bcaf5ea..64942d20a59 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.17.0", + "version": "7.18.0", "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.17.0", - "parabol-server": "7.17.0", + "parabol-client": "7.18.0", + "parabol-server": "7.18.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 0190f526ea8..a73d3eb3327 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.17.0", + "version": "7.18.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index c1bde9cbe3f..2f4bd7453ff 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.17.0", + "version": "7.18.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.11.0", - "parabol-client": "7.17.0", + "parabol-client": "7.18.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 6d46e1b2aab6731493de2d2547c88ae3921393f0 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 27 Feb 2024 11:09:18 -0800 Subject: [PATCH 012/529] chore: no force-push to prod (#9401) Signed-off-by: Matt Krick --- .github/workflows/release-to-staging.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/release-to-staging.yml b/.github/workflows/release-to-staging.yml index 139b8472fa8..6e9225bf871 100644 --- a/.github/workflows/release-to-staging.yml +++ b/.github/workflows/release-to-staging.yml @@ -99,6 +99,7 @@ jobs: git config user.name github-actions git config user.email github-actions@github.com git checkout -b "release/v${{ env.ACTION_VERSION }}" + git merge -s ours origin/production git push --set-upstream origin "release/v${{ env.ACTION_VERSION }}" gh pr create \ --assignee ${{ github.actor }} \ From b60ff4e87951081fbee48d2a9f31a5e66fafb09b Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 11:15:58 -0800 Subject: [PATCH 013/529] chore(release): release v7.18.1 (#9459) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f6475935835..28707a42151 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.18.0" + ".": "7.18.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 702f2ff2a5a..9d1147fe08f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.18.1](https://github.com/ParabolInc/parabol/compare/v7.18.0...v7.18.1) (2024-02-27) + + +### Changed + +* no force-push to prod ([#9401](https://github.com/ParabolInc/parabol/issues/9401)) ([6d46e1b](https://github.com/ParabolInc/parabol/commit/6d46e1b2aab6731493de2d2547c88ae3921393f0)) + ## [7.18.0](https://github.com/ParabolInc/parabol/compare/v7.17.0...v7.18.0) (2024-02-27) diff --git a/package.json b/package.json index 962c45f4873..179fed1c4cb 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.18.0", + "version": "7.18.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 6be287b79d6..5619754d830 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.18.0", + "version": "7.18.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.18.0" + "parabol-server": "7.18.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index 3776a1094db..141dca1fe36 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.18.0", + "version": "7.18.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 64942d20a59..1f7848776eb 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.18.0", + "version": "7.18.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.18.0", - "parabol-server": "7.18.0", + "parabol-client": "7.18.1", + "parabol-server": "7.18.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index a73d3eb3327..3331444dcae 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.18.0", + "version": "7.18.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 2f4bd7453ff..18ca0f00370 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.18.0", + "version": "7.18.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.11.0", - "parabol-client": "7.18.0", + "parabol-client": "7.18.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 55faa17ada5b1bd49182a29341b3465a82d2ddfd Mon Sep 17 00:00:00 2001 From: Jordan Husney Date: Tue, 27 Feb 2024 14:54:40 -0800 Subject: [PATCH 014/529] feat: embedder service (#9417) * feat: add embedder service --------- Signed-off-by: Matt Krick Co-authored-by: Matt Krick --- .env.example | 5 + docker/dev.yml | 17 +- package.json | 4 +- packages/embedder/.eslintrc.js | 11 + packages/embedder/README.md | 71 ++ packages/embedder/ai_models/AbstractModel.ts | 75 ++ packages/embedder/ai_models/ModelManager.ts | 153 ++++ .../ai_models/TextEmbeddingsInference.ts | 71 ++ .../ai_models/TextGenerationInference.ts | 87 ++ .../ai_models/helpers/fetchWithRetry.ts | 65 ++ packages/embedder/embedder.ts | 253 ++++++ packages/embedder/indexing/countWords.ts | 17 + .../indexing/createEmbeddingTextFrom.ts | 15 + .../embedder/indexing/embeddingsTablesOps.ts | 198 +++++ packages/embedder/indexing/getRedisClient.ts | 11 + .../embedder/indexing/getRootDataLoader.ts | 10 + .../embedder/indexing/numberVectorToString.ts | 5 + .../indexing/orgIdsWithFeatureFlag.ts | 15 + .../indexing/retrospectiveDiscussionTopic.ts | 326 ++++++++ packages/embedder/package.json | 31 + packages/embedder/tsconfig.json | 15 + .../server/dataloader/customLoaderMakers.ts | 10 +- .../server/dataloader/customRedisQueries.ts | 10 +- packages/server/postgres/getKysely.ts | 7 + .../1708127504000_updateEmbeddingMetadata.ts | 20 + pm2.config.js | 17 +- pm2.dev.config.js | 29 +- scripts/generateGraphQLArtifacts.js | 7 +- scripts/prod.js | 28 +- scripts/runEmbedder.js | 13 + scripts/webpack/dev.servers.config.js | 2 + scripts/webpack/prod.servers.config.js | 2 + scripts/webpack/toolbox.config.js | 20 + scripts/webpack/utils/transformRules.js | 3 +- yarn.lock | 759 +++++++++++++++++- 35 files changed, 2303 insertions(+), 79 deletions(-) create mode 100644 packages/embedder/.eslintrc.js create mode 100644 packages/embedder/README.md create mode 100644 packages/embedder/ai_models/AbstractModel.ts create mode 100644 packages/embedder/ai_models/ModelManager.ts create mode 100644 packages/embedder/ai_models/TextEmbeddingsInference.ts create mode 100644 packages/embedder/ai_models/TextGenerationInference.ts create mode 100644 packages/embedder/ai_models/helpers/fetchWithRetry.ts create mode 100644 packages/embedder/embedder.ts create mode 100644 packages/embedder/indexing/countWords.ts create mode 100644 packages/embedder/indexing/createEmbeddingTextFrom.ts create mode 100644 packages/embedder/indexing/embeddingsTablesOps.ts create mode 100644 packages/embedder/indexing/getRedisClient.ts create mode 100644 packages/embedder/indexing/getRootDataLoader.ts create mode 100644 packages/embedder/indexing/numberVectorToString.ts create mode 100644 packages/embedder/indexing/orgIdsWithFeatureFlag.ts create mode 100644 packages/embedder/indexing/retrospectiveDiscussionTopic.ts create mode 100644 packages/embedder/package.json create mode 100644 packages/embedder/tsconfig.json create mode 100644 packages/server/postgres/migrations/1708127504000_updateEmbeddingMetadata.ts create mode 100644 scripts/runEmbedder.js diff --git a/.env.example b/.env.example index 25c9a9c856e..0d0a44b41f0 100644 --- a/.env.example +++ b/.env.example @@ -8,6 +8,11 @@ SERVER_ID='1' # Websocket port for the websocket server, only used in development (yarn dev) SOCKET_PORT='3001' +# AI MODELS +AI_EMBEDDING_MODELS='[{"model": "text-embeddings-inference:llmrails/ember-v1", "url": "http://localhost:3040/"}]' +AI_GENERATION_MODELS='[{"model": "text-generation-inference:TheBloke/zephyr-7b-beta", "url": "http://localhost:3050/"}]' +AI_EMBEDDER_ENABLED='true' + # APPLICATION # AMPLITUDE_WRITE_KEY='key_AMPLITUDE_WRITE_KEY' # Enter a short url redirect service for invitations, it needs to redirecto to /invitation-link diff --git a/docker/dev.yml b/docker/dev.yml index 0b7de79ddb8..c45ba6b6f6b 100644 --- a/docker/dev.yml +++ b/docker/dev.yml @@ -13,8 +13,6 @@ services: - /var/run/docker.sock:/var/run/docker.sock - /proc/:/host/proc/:ro - /sys/fs/cgroup:/host/sys/fs/cgroup:ro - - "./dd-conf.d:/etc/datadog-agent/conf.d/local.d/" - - "../dev/logs:/var/log/datadog/logs" db: image: rethinkdb:2.4.2 restart: unless-stopped @@ -72,6 +70,20 @@ services: - "8082:8081" networks: parabol-network: + text-embeddings-inference: + container_name: text-embeddings-inference + image: ghcr.io/huggingface/text-embeddings-inference:cpu-0.6 + command: + - "--model-id=llmrails/ember-v1" + platform: linux/x86_64 + hostname: text-embeddings-inference + restart: unless-stopped + ports: + - "3040:80" + volumes: + - text-embeddings-inference-data:/data + networks: + parabol-network: networks: parabol-network: volumes: @@ -79,3 +91,4 @@ volumes: rethink-data: {} postgres-data: {} pgadmin-data: {} + text-embeddings-inference-data: {} diff --git a/package.json b/package.json index 179fed1c4cb..fbc0c896925 100644 --- a/package.json +++ b/package.json @@ -103,8 +103,8 @@ "html-webpack-plugin": "^5.5.0", "husky": "^7.0.4", "jscodeshift": "^0.14.0", - "kysely": "^0.26.3", - "kysely-codegen": "^0.10.0", + "kysely": "^0.27.2", + "kysely-codegen": "^0.11.0", "lerna": "^6.4.1", "mini-css-extract-plugin": "^2.7.2", "minimist": "^1.2.5", diff --git a/packages/embedder/.eslintrc.js b/packages/embedder/.eslintrc.js new file mode 100644 index 00000000000..a6a5d110f1e --- /dev/null +++ b/packages/embedder/.eslintrc.js @@ -0,0 +1,11 @@ +module.exports = { + extends: [ + '../../.eslintrc.js' + ], + parserOptions: { + project: './tsconfig.json', + ecmaVersion: 2020, + sourceType: 'module' + }, + "ignorePatterns": ["**/lib", "*.js"] +} diff --git a/packages/embedder/README.md b/packages/embedder/README.md new file mode 100644 index 00000000000..fc3fc68f335 --- /dev/null +++ b/packages/embedder/README.md @@ -0,0 +1,71 @@ +# `Embedder` + +This service builds embedding vectors for semantic search and for other AI/ML +use cases. It does so by: + +1. Updating a list of all possible items to create embedding vectors for and + storing that list in the `EmbeddingsMetadata` table +2. Adding these items in batches to the `EmbeddingsJobQueue` table and a redis + priority queue called `embedder:queue` +3. Allowing one or more parallel embedding services to calculate embedding + vectors (EmbeddingJobQueue states transistion from `queued` -> `embedding`, + then `embedding` -> [deleting the `EmbeddingJobQueue` row] + + In addition to deleteing the `EmbeddingJobQueue` row, when a job completes + successfully: + + - A row is added to the model table with the embedding vector; the + `EmbeddingMetadataId` field on this row points the appropriate + metadata row on `EmbeddingsMetadata` + - The `EmbeddingsMetadata.models` array is updated with the name of the + table that the embedding has been generated for + +4. This process repeats forever using a silly polling loop + +In the future, it would be wonderful to enhance this service such that it were +event driven. + +## Prerequisites + +The Embedder service depends on pgvector being available in Postgres. + +The predeploy script checks for an environment variable +`POSTGRES_USE_PGVECTOR=true` to enable this extension in production. + +## Configuration + +The Embedder service takes no arguments and is controlled by the following +environment variables, here given with example configuration: + +- `AI_EMBEDDER_ENABLE`: enable/disable the embedder service from + performing work, or sleeping indefinitely + +`AI_EMBEDDER_ENABLED='true'` + +- `AI_EMBEDDING_MODELS`: JSON configuration for which embedding models + are enabled. Each model in the array will be instantiated by + `ai_models/ModelManager`. Each model instance will have its own + database table created for it (if it does not exist already) used + to store calculated vectors. See `ai_models/ModelManager` for + which configurations are supported. + + Example: + +`AI_EMBEDDING_MODELS='[{"model": "text-embeddings-inference:llmrails/ember-v1", "url": "http://localhost:3040/"}]'` + +- `AI_GENERATION_MODELS`: JSON configuration for which AI generation + models (i.e. GPTS are enabled). These models are used for summarization + text to be embedded by an embedding model if the text length would be + greater than the context window of the embedding model. Each model in + the array will be instantiated by `ai_models/ModelManager`. + See `ai_models/ModelManager` for which configurations are supported. + + Example: + +`AI_GENERATION_MODELS='[{"model": "text-generation-inference:TheBloke/zephyr-7b-beta", "url": "http://localhost:3050/"}]'` + +## Usage + +The Embedder service is stateless and takes no arguments. Multiple instances +of the service may be started in order to match embedding load, or to +catch up on history more quickly. diff --git a/packages/embedder/ai_models/AbstractModel.ts b/packages/embedder/ai_models/AbstractModel.ts new file mode 100644 index 00000000000..b0f709ce485 --- /dev/null +++ b/packages/embedder/ai_models/AbstractModel.ts @@ -0,0 +1,75 @@ +export interface ModelConfig { + model: string + url: string +} + +export interface EmbeddingModelConfig extends ModelConfig { + tableSuffix: string +} + +export interface GenerationModelConfig extends ModelConfig {} + +export abstract class AbstractModel { + public readonly url?: string + public modelInstance: any + + constructor(config: ModelConfig) { + this.url = this.normalizeUrl(config.url) + } + + // removes a trailing slash from the inputUrl + private normalizeUrl(inputUrl: string | undefined) { + if (!inputUrl) return undefined + const regex = /[/]+$/ + return inputUrl.replace(regex, '') + } +} + +export interface EmbeddingModelParams { + embeddingDimensions: number + maxInputTokens: number + tableSuffix: string +} + +export abstract class AbstractEmbeddingsModel extends AbstractModel { + readonly embeddingDimensions: number + readonly maxInputTokens: number + readonly tableName: string + constructor(config: EmbeddingModelConfig) { + super(config) + const modelParams = this.constructModelParams(config) + this.embeddingDimensions = modelParams.embeddingDimensions + this.maxInputTokens = modelParams.maxInputTokens + this.tableName = `Embeddings_${modelParams.tableSuffix}` + } + protected abstract constructModelParams(config: EmbeddingModelConfig): EmbeddingModelParams + abstract getEmbedding(content: string): Promise +} + +export interface GenerationModelParams { + maxInputTokens: number +} + +export interface GenerationOptions { + maxNewTokens?: number + seed?: number + stop?: string + temperature?: number + topK?: number + topP?: number + truncate?: boolean +} + +export abstract class AbstractGenerationModel extends AbstractModel { + readonly maxInputTokens: number + constructor(config: GenerationModelConfig) { + super(config) + const modelParams = this.constructModelParams(config) + this.maxInputTokens = modelParams.maxInputTokens + } + + protected abstract constructModelParams(config: GenerationModelConfig): GenerationModelParams + abstract summarize(content: string, options: GenerationOptions): Promise +} + +export default AbstractModel diff --git a/packages/embedder/ai_models/ModelManager.ts b/packages/embedder/ai_models/ModelManager.ts new file mode 100644 index 00000000000..ac8f04cc891 --- /dev/null +++ b/packages/embedder/ai_models/ModelManager.ts @@ -0,0 +1,153 @@ +import {Kysely, sql} from 'kysely' + +import { + AbstractEmbeddingsModel, + AbstractGenerationModel, + EmbeddingModelConfig, + GenerationModelConfig, + ModelConfig +} from './AbstractModel' +import TextEmbeddingsInference from './TextEmbeddingsInference' +import TextGenerationInference from './TextGenerationInference' + +interface ModelManagerConfig { + embeddingModels: EmbeddingModelConfig[] + generationModels: GenerationModelConfig[] +} + +export type EmbeddingsModelType = 'text-embeddings-inference' +export type GenerationModelType = 'text-generation-inference' + +export class ModelManager { + embeddingModels: AbstractEmbeddingsModel[] + embeddingModelsMapByTable: {[key: string]: AbstractEmbeddingsModel} + generationModels: AbstractGenerationModel[] + + private isValidConfig( + maybeConfig: Partial + ): maybeConfig is ModelManagerConfig { + if (!maybeConfig.embeddingModels || !Array.isArray(maybeConfig.embeddingModels)) { + throw new Error('Invalid configuration: embedding_models is missing or not an array') + } + if (!maybeConfig.generationModels || !Array.isArray(maybeConfig.generationModels)) { + throw new Error('Invalid configuration: summarization_models is missing or not an array') + } + + maybeConfig.embeddingModels.forEach((model: ModelConfig) => { + this.isValidModelConfig(model) + }) + + maybeConfig.generationModels.forEach((model: ModelConfig) => { + this.isValidModelConfig(model) + }) + + return true + } + + private isValidModelConfig(model: ModelConfig): model is ModelConfig { + if (typeof model.model !== 'string') { + throw new Error('Invalid ModelConfig: model field should be a string') + } + if (model.url !== undefined && typeof model.url !== 'string') { + throw new Error('Invalid ModelConfig: url field should be a string') + } + + return true + } + + constructor(config: ModelManagerConfig) { + // Validate configuration + this.isValidConfig(config) + + // Initialize embeddings models + this.embeddingModelsMapByTable = {} + this.embeddingModels = config.embeddingModels.map((modelConfig) => { + const [modelType] = modelConfig.model.split(':') as [EmbeddingsModelType, string] + + switch (modelType) { + case 'text-embeddings-inference': { + const embeddingsModel = new TextEmbeddingsInference(modelConfig) + this.embeddingModelsMapByTable[embeddingsModel.tableName] = embeddingsModel + return embeddingsModel + } + default: + throw new Error(`unsupported embeddings model '${modelType}'`) + } + }) + + // Initialize summarization models + this.generationModels = config.generationModels.map((modelConfig) => { + const [modelType, _] = modelConfig.model.split(':') as [GenerationModelType, string] + + switch (modelType) { + case 'text-generation-inference': { + const generator = new TextGenerationInference(modelConfig) + return generator + } + default: + throw new Error(`unsupported summarization model '${modelType}'`) + } + }) + } + + async maybeCreateTables(pg: Kysely) { + const maybePromises = this.embeddingModels.map(async (embeddingsModel) => { + const tableName = embeddingsModel.tableName + const hasTable = + ( + await sql`SELECT 1 FROM ${sql.id('pg_catalog', 'pg_tables')} WHERE ${sql.id( + 'tablename' + )} = ${tableName}`.execute(pg) + ).rows.length > 0 + if (hasTable) return undefined + const vectorDimensions = embeddingsModel.embeddingDimensions + console.log(`ModelManager: creating ${tableName} with ${vectorDimensions} dimensions`) + const query = sql` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS ${sql.id(tableName)} ( + "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + "embedText" TEXT, + "embedding" vector(${sql.raw(vectorDimensions.toString())}), + "embeddingsMetadataId" INTEGER NOT NULL, + FOREIGN KEY ("embeddingsMetadataId") + REFERENCES "EmbeddingsMetadata"("id") + ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS "idx_${sql.raw(tableName)}_embedding_vector_cosign_ops" + ON ${sql.id(tableName)} + USING hnsw ("embedding" vector_cosine_ops); + END $$; + + ` + return query.execute(pg) + }) + Promise.all(maybePromises) + } +} + +let modelManager: ModelManager | undefined +export function getModelManager() { + if (modelManager) return modelManager + const {AI_EMBEDDING_MODELS, AI_GENERATION_MODELS} = process.env + const config: ModelManagerConfig = { + embeddingModels: [], + generationModels: [] + } + try { + config.embeddingModels = AI_EMBEDDING_MODELS && JSON.parse(AI_EMBEDDING_MODELS) + } catch (e) { + throw new Error(`Invalid AI_EMBEDDING_MODELS .env JSON: ${e}`) + } + try { + config.generationModels = AI_GENERATION_MODELS && JSON.parse(AI_GENERATION_MODELS) + } catch (e) { + throw new Error(`Invalid AI_GENERATION_MODELS .env JSON: ${e}`) + } + + modelManager = new ModelManager(config) + + return modelManager +} + +export default getModelManager diff --git a/packages/embedder/ai_models/TextEmbeddingsInference.ts b/packages/embedder/ai_models/TextEmbeddingsInference.ts new file mode 100644 index 00000000000..93bb2c88c2f --- /dev/null +++ b/packages/embedder/ai_models/TextEmbeddingsInference.ts @@ -0,0 +1,71 @@ +import {AbstractEmbeddingsModel, EmbeddingModelConfig, EmbeddingModelParams} from './AbstractModel' +import fetchWithRetry from './helpers/fetchWithRetry' + +const MAX_REQUEST_TIME_S = 3 * 60 + +export type ModelId = 'BAAI/bge-large-en-v1.5' | 'llmrails/ember-v1' + +const modelIdDefinitions: Record = { + 'BAAI/bge-large-en-v1.5': { + embeddingDimensions: 1024, + maxInputTokens: 512, + tableSuffix: 'bge_l_en_1p5' + }, + 'llmrails/ember-v1': { + embeddingDimensions: 1024, + maxInputTokens: 512, + tableSuffix: 'ember_1' + } +} + +function isValidModelId(object: any): object is ModelId { + return Object.keys(modelIdDefinitions).includes(object) +} + +export class TextEmbeddingsInference extends AbstractEmbeddingsModel { + constructor(config: EmbeddingModelConfig) { + super(config) + } + + public async getEmbedding(content: string) { + const fetchOptions = { + body: JSON.stringify({inputs: content}), + deadline: new Date(new Date().getTime() + MAX_REQUEST_TIME_S * 1000), + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json; charset=utf-8' + }, + method: 'POST' + } + + try { + const res = await fetchWithRetry(`${this.url}/embed`, fetchOptions) + const listOfVectors = (await res.json()) as Array + if (!listOfVectors) + throw new Error('TextEmbeddingsInference.getEmbeddings(): listOfVectors is undefined') + if (listOfVectors.length !== 1 || !listOfVectors[0]) + throw new Error( + `TextEmbeddingsInference.getEmbeddings(): listOfVectors list length !== 1 (length: ${listOfVectors.length})` + ) + return listOfVectors[0] + } catch (e) { + console.log(`TextEmbeddingsInference.getEmbeddings() timeout: `, e) + throw e + } + } + + protected constructModelParams(config: EmbeddingModelConfig): EmbeddingModelParams { + const modelConfigStringSplit = config.model.split(':') + if (modelConfigStringSplit.length != 2) { + throw new Error('TextGenerationInference model string must be colon-delimited and len 2') + } + + if (!this.url) throw new Error('TextGenerationInferenceSummarizer model requires url') + const maybeModelId = modelConfigStringSplit[1] + if (!isValidModelId(maybeModelId)) + throw new Error(`TextGenerationInference model subtype unknown: ${maybeModelId}`) + return modelIdDefinitions[maybeModelId] + } +} + +export default TextEmbeddingsInference diff --git a/packages/embedder/ai_models/TextGenerationInference.ts b/packages/embedder/ai_models/TextGenerationInference.ts new file mode 100644 index 00000000000..6f12ce09974 --- /dev/null +++ b/packages/embedder/ai_models/TextGenerationInference.ts @@ -0,0 +1,87 @@ +import { + AbstractGenerationModel, + GenerationModelConfig, + GenerationModelParams, + GenerationOptions +} from './AbstractModel' +import fetchWithRetry from './helpers/fetchWithRetry' + +const MAX_REQUEST_TIME_S = 3 * 60 + +export type ModelId = 'TheBloke/zephyr-7b-beta' + +const modelIdDefinitions: Record = { + 'TheBloke/zephyr-7b-beta': { + maxInputTokens: 512 + } +} + +function isValidModelId(object: any): object is ModelId { + return Object.keys(modelIdDefinitions).includes(object) +} + +export class TextGenerationInference extends AbstractGenerationModel { + constructor(config: GenerationModelConfig) { + super(config) + } + + public async summarize(content: string, options: GenerationOptions) { + const { + maxNewTokens: max_new_tokens = 512, + seed, + stop, + temperature = 0.8, + topP, + topK, + truncate + } = options + const parameters = { + max_new_tokens, + seed, + stop, + temperature, + topP, + topK, + truncate + } + const prompt = `Create a brief, one-paragraph summary of the following: ${content}` + const fetchOptions = { + body: JSON.stringify({ + inputs: prompt, + parameters + }), + deadline: new Date(new Date().getTime() + MAX_REQUEST_TIME_S * 1000), + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json; charset=utf-8' + }, + method: 'POST' + } + + try { + // console.log(`TextGenerationInterface.summarize(): summarizing from ${this.url}/generate`) + const res = await fetchWithRetry(`${this.url}/generate`, fetchOptions) + const json = await res.json() + if (!json || !json.generated_text) + throw new Error('TextGenerationInterface.summarize(): malformed response') + return json.generated_text as string + } catch (e) { + console.log('TextGenerationInterfaceSummarizer.summarize(): timeout') + throw e + } + } + protected constructModelParams(config: GenerationModelConfig): GenerationModelParams { + const modelConfigStringSplit = config.model.split(':') + if (modelConfigStringSplit.length != 2) { + throw new Error('TextGenerationInterface model string must be colon-delimited and len 2') + } + + if (!this.url) throw new Error('TextGenerationInterfaceSummarizer model requires url') + const maybeModelId = modelConfigStringSplit[1] + if (!isValidModelId(maybeModelId)) + throw new Error(`TextGenerationInterface model subtype unknown: ${maybeModelId}`) + return modelIdDefinitions[maybeModelId] + } +} + +export default TextGenerationInference diff --git a/packages/embedder/ai_models/helpers/fetchWithRetry.ts b/packages/embedder/ai_models/helpers/fetchWithRetry.ts new file mode 100644 index 00000000000..98343ef26e6 --- /dev/null +++ b/packages/embedder/ai_models/helpers/fetchWithRetry.ts @@ -0,0 +1,65 @@ +interface FetchWithRetryOptions extends RequestInit { + deadline: Date // Deadline for the request to complete + debug?: boolean // Enable debug tracing + retryStatusCodes?: number[] // Array of status codes to retry on +} + +export default async (url: RequestInfo, options: FetchWithRetryOptions): Promise => { + const {deadline, debug = false, retryStatusCodes = [429], ...fetchOptions} = options + let attempt = 0 + const controller = new AbortController() + fetchOptions.signal = controller.signal + + const timeout = deadline.getTime() - Date.now() + if (timeout <= 0) { + throw new Error('Deadline has already passed') + } + + const timeoutId = setTimeout(() => controller.abort(), timeout) + + try { + while (Date.now() < deadline.getTime()) { + attempt++ + + if (debug) { + console.log(`Attempt ${attempt}: Fetching ${url}`) + } + + const response = await fetch(url, fetchOptions) + + if (!retryStatusCodes.includes(response.status)) { + clearTimeout(timeoutId) + return response + } + + const retryAfter = response.headers.get('Retry-After') + // if Retry-After specified, use it; else fallback to exponential backoff + let waitTime = retryAfter ? parseInt(retryAfter, 10) * 1000 : Math.pow(2, attempt) * 1000 + + // cap waitTime to prevent exceeding the deadline + waitTime = Math.min(waitTime, deadline.getTime() - Date.now()) + + if (debug) { + console.log( + `Waiting ${waitTime / 1000} seconds before retrying due to status ${response.status}...` + ) + } + await new Promise((resolve) => setTimeout(resolve, waitTime)) + } + + throw new Error('Deadline exceeded') + } catch (error) { + clearTimeout(timeoutId) + if (error instanceof Error && error.name === 'AbortError') { + throw new Error('Request aborted due to deadline') + } + if (debug) { + console.error(`Attempt ${attempt} failed: ${error}`) + } + const currentTime = Date.now() + if (currentTime >= deadline.getTime()) { + throw new Error('Deadline exceeded before a successful request') + } + throw error // Re-throw the error if it's not related to deadline exceeding + } +} diff --git a/packages/embedder/embedder.ts b/packages/embedder/embedder.ts new file mode 100644 index 00000000000..1072a546d00 --- /dev/null +++ b/packages/embedder/embedder.ts @@ -0,0 +1,253 @@ +import {Insertable} from 'kysely' +import tracer from 'dd-trace' +import Redlock, {RedlockAbortSignal} from 'redlock' + +import 'parabol-server/initSentry' +import getKysely from 'parabol-server/postgres/getKysely' +import {DB} from 'parabol-server/postgres/pg' +import {refreshRetroDiscussionTopicsMeta as refreshRetroDiscussionTopicsMeta} from './indexing/retrospectiveDiscussionTopic' +import {orgIdsWithFeatureFlag} from './indexing/orgIdsWithFeatureFlag' +import getModelManager, {ModelManager} from './ai_models/ModelManager' +import {countWords} from './indexing/countWords' +import {createEmbeddingTextFrom} from './indexing/createEmbeddingTextFrom' +import { + selectJobQueueItemById, + selectMetadataByJobQueueId, + updateJobState +} from './indexing/embeddingsTablesOps' +import {selectMetaToQueue} from './indexing/embeddingsTablesOps' +import {insertNewJobs} from './indexing/embeddingsTablesOps' +import {completeJobTxn} from './indexing/embeddingsTablesOps' +import {getRootDataLoader} from './indexing/getRootDataLoader' +import {getRedisClient} from './indexing/getRedisClient' + +/* + * TODO List + * - [ ] implement a clean-up function that re-queues items that haven't transitioned + * to a completed state, or that failed + */ + +export type DBInsert = { + [K in keyof DB]: Insertable +} + +const POLLING_PERIOD_SEC = 60 // How often do we try to grab the lock and re-index? +const Q_MAX_LENGTH = 100 // How many EmbeddingIndex items do we batch in redis? +const WORD_COUNT_TO_TOKEN_RATIO = 3.0 / 2 // We multiple the word count by this to estimate token count + +const {AI_EMBEDDER_ENABLED} = process.env +const {SERVER_ID} = process.env + +tracer.init({ + service: `embedder`, + appsec: process.env.DD_APPSEC_ENABLED === 'true', + plugins: false, + version: process.env.npm_package_version +}) +tracer.use('pg') + +const refreshMetadata = async () => { + const dataLoader = getRootDataLoader() + await refreshRetroDiscussionTopicsMeta(dataLoader) + // In the future, other sorts of objects to index could be added here... +} +const maybeQueueMetadataItems = async (modelManager: ModelManager) => { + const redisClient = getRedisClient() + const queueLength = await redisClient.zcard('embedder:queue') + if (queueLength >= Q_MAX_LENGTH) return + const itemCountToQueue = Q_MAX_LENGTH - queueLength + const modelTables = modelManager.embeddingModels.map((m) => m.tableName) + const orgIds = await orgIdsWithFeatureFlag() + + // For each configured embedding model, select rows from EmbeddingsMetadata + // that haven't been calculated nor exist in the EmbeddingsJobQueue yet + // + // Notes: + // * `em.models @> ARRAY[v.model]` is an indexed query + // * I don't love all overrides, I wish there was a better way + // see: https://github.com/kysely-org/kysely/issues/872 + + const batchToQueue = await selectMetaToQueue(modelTables, orgIds, itemCountToQueue) + + if (!batchToQueue.length) { + console.log(`embedder: no new items to queue`) + return + } + + const ejqHash: { + [key: string]: { + refUpdatedAt: Date + } + } = {} + const makeKey = (item: {objectType: string; refId: string}) => `${item.objectType}:${item.refId}` + + const ejqValues = batchToQueue.map((item) => { + ejqHash[makeKey(item)] = { + refUpdatedAt: item.refUpdatedAt + } + return { + objectType: item.objectType, + refId: item.refId as string, + model: item.model, + state: 'queued' as const + } + }) + + const ejqRows = await insertNewJobs(ejqValues) + + ejqRows.forEach((item) => { + const {refUpdatedAt} = ejqHash[makeKey(item)]! + const score = new Date(refUpdatedAt).getTime() + redisClient.zadd('embedder:queue', score, item.id) + }) + + console.log(`embedder: queued ${batchToQueue.length} items`) +} + +const dequeueAndEmbedUntilEmpty = async (modelManager: ModelManager) => { + const dataLoader = getRootDataLoader() + const redisClient = getRedisClient() + while (true) { + const maybeRedisQItem = await redisClient.zpopmax('embedder:queue', 1) + if (maybeRedisQItem.length < 2) return // Q is empty, all done! + + const [id, _] = maybeRedisQItem + if (!id) { + console.log(`embedder: de-queued undefined item from embedder:queue`) + continue + } + const jobQueueId = parseInt(id, 10) + const jobQueueItem = await selectJobQueueItemById(jobQueueId) + if (!jobQueueItem) { + console.log(`embedder: unable to fetch EmbeddingsJobQueue.id = ${id}`) + continue + } + + const metadata = await selectMetadataByJobQueueId(jobQueueId) + if (!metadata) { + await updateJobState(jobQueueId, 'failed', { + stateMessage: `unable to fetch metadata by EmbeddingsJobQueue.id = ${id}` + }) + continue + } + + let fullText = metadata?.fullText + try { + if (!fullText) { + fullText = await createEmbeddingTextFrom(jobQueueItem, dataLoader) + } + } catch (e) { + await updateJobState(jobQueueId, 'failed', { + stateMessage: `unable to create embedding text: ${e}` + }) + continue + } + + const wordCount = countWords(fullText) + + const embeddingModel = modelManager.embeddingModelsMapByTable[jobQueueItem.model] + if (!embeddingModel) { + await updateJobState(jobQueueId, 'failed', { + stateMessage: `embedding model ${jobQueueItem.model} not available` + }) + continue + } + const itemKey = `${jobQueueItem.objectType}:${jobQueueItem.refId}` + const modelTable = embeddingModel.tableName + + let embedText = fullText + const maxInputTokens = embeddingModel.maxInputTokens + // we're using word count as an appoximation of tokens + if (wordCount * WORD_COUNT_TO_TOKEN_RATIO > maxInputTokens) { + try { + const generator = modelManager.generationModels[0] // use 1st generator + if (!generator) throw new Error(`Generator unavailable`) + const summarizeOptions = {maxInputTokens, truncate: true} + console.log(`embedder: ...summarizing ${itemKey} for ${modelTable}`) + embedText = await generator.summarize(fullText, summarizeOptions) + } catch (e) { + await updateJobState(jobQueueId, 'failed', { + stateMessage: `unable to summarize long embed text: ${e}` + }) + continue + } + } + // console.log(`embedText: ${embedText}`) + + let embeddingVector: number[] + try { + embeddingVector = await embeddingModel.getEmbedding(embedText) + } catch (e) { + await updateJobState(jobQueueId, 'failed', { + stateMessage: `unable to get embeddings: ${e}` + }) + continue + } + + // complete job, do the following atomically + // (1) update EmbeddingsMetadata to reflect model completion + // (2) upsert model table row with embedding + // (3) delete EmbeddingsJobQueue row + await completeJobTxn(modelTable, jobQueueId, metadata, fullText, embedText, embeddingVector) + console.log(`embedder: completed ${itemKey} -> ${modelTable}`) + } +} + +const tick = async (modelManager: ModelManager) => { + console.log(`embedder: tick`) + const redisClient = getRedisClient() + const redlock = new Redlock([redisClient], { + driftFactor: 0.01, + retryCount: 10, + retryDelay: 250, + retryJitter: 50, + automaticExtensionThreshold: 500 + }) + + await redlock + .using(['embedder:lock'], 10000, async (signal: RedlockAbortSignal) => { + console.log(`embedder: acquired index queue lock`) + // N.B. one of the many benefits of using redlock is the using() interface + // will automatically extend the lock if these operations exceed the + // original redis timeout time + await refreshMetadata() + await maybeQueueMetadataItems(modelManager) + + if (signal.aborted) { + // Not certain which conditions this would happen, it would + // happen after operations took place, so nothing much to do here. + console.log('embedder: lock was lost!') + } + }) + .catch((err: string) => { + // Handle errors (including lock acquisition errors) + console.error('embedder: an error occurred ', err) + }) + console.log('embedder: index queue lock released') + + // get the highest priority item and embed it + await dequeueAndEmbedUntilEmpty(modelManager) + + setTimeout(() => tick(modelManager), POLLING_PERIOD_SEC * 1000) +} + +function parseEnvBoolean(envVarValue: string | undefined): boolean { + return envVarValue === 'true' +} + +const run = async () => { + console.log(`embedder: run()`) + const embedderEnabled = parseEnvBoolean(AI_EMBEDDER_ENABLED) + const modelManager = getModelManager() + if (embedderEnabled && modelManager) { + const pg = getKysely() + await modelManager.maybeCreateTables(pg) + console.log(`\n⚡⚡⚡️️ Server ID: ${SERVER_ID}. Embedder is ready ⚡⚡⚡️️️`) + tick(modelManager) + } else { + console.log(`embedder: no valid configuration (check AI_EMBEDDER_ENABLED in .env)`) + // exit + } +} + +run() diff --git a/packages/embedder/indexing/countWords.ts b/packages/embedder/indexing/countWords.ts new file mode 100644 index 00000000000..75dae3effa2 --- /dev/null +++ b/packages/embedder/indexing/countWords.ts @@ -0,0 +1,17 @@ +export function countWords(text: string) { + let count = 0 + let inWord = false + + for (const char of text) { + if (/\w/.test(char)) { + if (!inWord) { + count++ + inWord = true + } + } else { + inWord = false + } + } + + return count +} diff --git a/packages/embedder/indexing/createEmbeddingTextFrom.ts b/packages/embedder/indexing/createEmbeddingTextFrom.ts new file mode 100644 index 00000000000..9d6e66b60e7 --- /dev/null +++ b/packages/embedder/indexing/createEmbeddingTextFrom.ts @@ -0,0 +1,15 @@ +import {Selectable} from 'kysely' +import {DB} from 'parabol-server/postgres/pg' +import {DataLoaderWorker} from 'parabol-server/graphql/graphql' + +import {createText as createTextFromRetrospectiveDiscussionTopic} from './retrospectiveDiscussionTopic' + +export const createEmbeddingTextFrom = async ( + item: Selectable, + dataLoader: DataLoaderWorker +): Promise => { + switch (item.objectType) { + case 'retrospectiveDiscussionTopic': + return createTextFromRetrospectiveDiscussionTopic(item, dataLoader) + } +} diff --git a/packages/embedder/indexing/embeddingsTablesOps.ts b/packages/embedder/indexing/embeddingsTablesOps.ts new file mode 100644 index 00000000000..b68bc21ccbe --- /dev/null +++ b/packages/embedder/indexing/embeddingsTablesOps.ts @@ -0,0 +1,198 @@ +import {Insertable, Selectable, Updateable, sql} from 'kysely' +import getKysely from 'parabol-server/postgres/getKysely' +import {DB} from 'parabol-server/postgres/pg' +import {DBInsert} from '../embedder' +import {RawBuilder} from 'kysely' +import numberVectorToString from './numberVectorToString' + +function unnestedArray(maybeArray: T[] | T): RawBuilder { + let a: T[] = Array.isArray(maybeArray) ? maybeArray : [maybeArray] + return sql`unnest(ARRAY[${sql.join(a)}]::varchar[])` +} + +export const selectJobQueueItemById = async ( + id: number +): Promise | undefined> => { + const pg = getKysely() + return pg.selectFrom('EmbeddingsJobQueue').selectAll().where('id', '=', id).executeTakeFirst() +} +export const selectMetadataByJobQueueId = async ( + id: number +): Promise | undefined> => { + const pg = getKysely() + return pg + .selectFrom('EmbeddingsMetadata as em') + .selectAll() + .leftJoin('EmbeddingsJobQueue as ejq', (join) => + join.onRef('em.objectType', '=', 'ejq.objectType').onRef('em.refId', '=', 'ejq.refId') + ) + .where('ejq.id', '=', id) + .executeTakeFirstOrThrow() +} + +// For each configured embedding model, select rows from EmbeddingsMetadata +// that haven't been calculated nor exist in the EmbeddingsJobQueue yet +// +// Notes: +// * `em.models @> ARRAY[v.model]` is an indexed query +// * I don't love all overrides, I wish there was a better way +// see: https://github.com/kysely-org/kysely/issues/872 +export async function selectMetaToQueue( + configuredModels: string[], + orgIds: any[], + itemCountToQueue: number +) { + const pg = getKysely() + const maybeMetaToQueue = (await pg + .selectFrom('EmbeddingsMetadata as em') + .selectAll('em') + .leftJoinLateral(unnestedArray(configuredModels).as('model'), (join) => join.onTrue()) + .leftJoin('Team as t', 'em.teamId', 't.id') + .select('model' as any) + .where(({eb, not, or, and, exists, selectFrom}) => + and([ + or([ + not(eb('em.models', '<@', sql`ARRAY[${sql.ref('model')}]::varchar[]` as any) as any), + eb('em.models' as any, 'is', null) + ]), + not( + exists( + selectFrom('EmbeddingsJobQueue as ejq') + .select('ejq.id') + .whereRef('em.objectType', '=', 'ejq.objectType') + .whereRef('em.refId', '=', 'ejq.refId') + .whereRef('ejq.model', '=', 'model' as any) + ) + ), + eb('t.orgId', 'in', orgIds) + ]) + ) + .limit(itemCountToQueue) + .execute()) as unknown as Selectable[] + + type MetadataToQueue = Selectable< + Omit & { + refId: NonNullable + } & {model: string} + > + + return maybeMetaToQueue.filter( + (item) => item.refId !== null && item.refId !== undefined + ) as MetadataToQueue[] +} + +export const updateJobState = async ( + id: number, + state: Updateable['state'], + jobQueueFields: Updateable = {} +) => { + const pg = getKysely() + const jobQueueColumns: Updateable = { + ...jobQueueFields, + state + } + if (state === 'failed') console.log(`embedder: failed job ${id}, ${jobQueueFields.stateMessage}`) + return pg + .updateTable('EmbeddingsJobQueue') + .set(jobQueueColumns) + .where('id', '=', id) + .executeTakeFirstOrThrow() +} + +export function insertNewJobs(ejqValues: Insertable[]) { + const pg = getKysely() + return pg + .insertInto('EmbeddingsJobQueue') + .values(ejqValues) + .returning(['id', 'objectType', 'refId']) + .execute() +} + +// complete job, do the following atomically +// (1) update EmbeddingsMetadata to reflect model completion +// (2) upsert model table row with embedding +// (3) delete EmbeddingsJobQueue row +export function completeJobTxn( + modelTable: string, + jobQueueId: number, + metadata: Updateable, + fullText: string, + embedText: string, + embeddingVector: number[] +) { + const pg = getKysely() + return pg.transaction().execute(async (trx) => { + // get fields to update correct metadata row + const jobQueueItem = await trx + .selectFrom('EmbeddingsJobQueue') + .select(['objectType', 'refId', 'model']) + .where('id', '=', jobQueueId) + .executeTakeFirstOrThrow() + + // (1) update metadata row + const metadataColumnsToUpdate: { + models: RawBuilder + fullText?: string | null | undefined + } = { + // update models as a set + models: sql`( +SELECT array_agg(DISTINCT value) +FROM ( + SELECT unnest(COALESCE("models", '{}')) AS value + UNION + SELECT unnest(ARRAY[${modelTable}]::VARCHAR[]) AS value +) AS combined_values +)` + } + + if (metadata?.fullText !== fullText) { + metadataColumnsToUpdate.fullText = fullText + } + + const updatedMetadata = await trx + .updateTable('EmbeddingsMetadata') + .set(metadataColumnsToUpdate) + .where('objectType', '=', jobQueueItem.objectType) + .where('refId', '=', jobQueueItem.refId) + .returning(['id']) + .executeTakeFirstOrThrow() + + // (2) upsert into model table + await trx + .insertInto(modelTable as any) + .values({ + embedText: fullText !== embedText ? embedText : null, + embedding: numberVectorToString(embeddingVector), + embeddingsMetadataId: updatedMetadata.id + }) + .onConflict((oc) => + oc.column('id').doUpdateSet((eb) => ({ + embedText: eb.ref('excluded.embedText'), + embeddingsMetadataId: eb.ref('excluded.embeddingsMetadataId') + })) + ) + .executeTakeFirstOrThrow() + + // (3) delete completed job queue item + return await trx + .deleteFrom('EmbeddingsJobQueue') + .where('id', '=', jobQueueId) + .executeTakeFirstOrThrow() + }) +} +export async function upsertEmbeddingsMetaRows( + embeddingsMetaRows: DBInsert['EmbeddingsMetadata'][] +) { + const pg = getKysely() + return pg + .insertInto('EmbeddingsMetadata') + .values(embeddingsMetaRows) + .onConflict((oc) => + oc.columns(['objectType', 'refId']).doUpdateSet((eb) => ({ + objectType: eb.ref('excluded.objectType'), + refId: eb.ref('excluded.refId'), + refUpdatedAt: eb.ref('excluded.refUpdatedAt') + })) + ) + .execute() +} diff --git a/packages/embedder/indexing/getRedisClient.ts b/packages/embedder/indexing/getRedisClient.ts new file mode 100644 index 00000000000..7aaf65be33c --- /dev/null +++ b/packages/embedder/indexing/getRedisClient.ts @@ -0,0 +1,11 @@ +import RedisInstance from 'parabol-server/utils/RedisInstance' + +const {SERVER_ID} = process.env + +let redisClient: RedisInstance +export const getRedisClient = () => { + if (!redisClient) { + redisClient = new RedisInstance(`embedder-${SERVER_ID}`) + } + return redisClient +} diff --git a/packages/embedder/indexing/getRootDataLoader.ts b/packages/embedder/indexing/getRootDataLoader.ts new file mode 100644 index 00000000000..304c0c01058 --- /dev/null +++ b/packages/embedder/indexing/getRootDataLoader.ts @@ -0,0 +1,10 @@ +import getDataLoader from 'parabol-server/graphql/getDataLoader' +import {DataLoaderWorker} from 'parabol-server/graphql/graphql' + +let rootDataLoader: DataLoaderWorker +export const getRootDataLoader = () => { + if (!rootDataLoader) { + rootDataLoader = getDataLoader() as DataLoaderWorker + } + return rootDataLoader +} diff --git a/packages/embedder/indexing/numberVectorToString.ts b/packages/embedder/indexing/numberVectorToString.ts new file mode 100644 index 00000000000..df49a716d57 --- /dev/null +++ b/packages/embedder/indexing/numberVectorToString.ts @@ -0,0 +1,5 @@ +function numberVectorToString(vector: number[]): string { + return '[' + vector.join(', ') + ']' +} + +export default numberVectorToString diff --git a/packages/embedder/indexing/orgIdsWithFeatureFlag.ts b/packages/embedder/indexing/orgIdsWithFeatureFlag.ts new file mode 100644 index 00000000000..82d86702e67 --- /dev/null +++ b/packages/embedder/indexing/orgIdsWithFeatureFlag.ts @@ -0,0 +1,15 @@ +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/indexing/retrospectiveDiscussionTopic.ts b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts new file mode 100644 index 00000000000..f31aab74cc2 --- /dev/null +++ b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts @@ -0,0 +1,326 @@ +import {Selectable} from 'kysely' +import prettier from 'prettier' + +import getRethink, {RethinkSchema} from 'parabol-server/database/rethinkDriver' +import {DataLoaderWorker} from 'parabol-server/graphql/graphql' +import getKysely from 'parabol-server/postgres/getKysely' +import {DB} from 'parabol-server/postgres/pg' + +import Comment from 'parabol-server/database/types/Comment' +import DiscussStage from 'parabol-server/database/types/DiscussStage' +import MeetingRetrospective, { + isMeetingRetrospective +} from 'parabol-server/database/types/MeetingRetrospective' + +import {upsertEmbeddingsMetaRows} from './embeddingsTablesOps' +import {AnyMeeting} from 'parabol-server/postgres/types/Meeting' + +const BATCH_SIZE = 1000 + +export interface EmbeddingsJobQueueRetrospectiveDiscussionTopic + extends Omit { + objectType: 'retrospectiveDiscussionTopic' +} + +// Here's a generic reprentation of the text generated here: + +// A topic "" was discussed during the meeting "" +// that followed the "" template. +// +// +// Participants were prompted with, ": ". +// - wrote, "" +// +// +// +// +// A discussion was held. +// + +const IGNORE_COMMENT_USER_IDS = ['parabolAIUser'] + +const pg = getKysely() + +export async function refreshRetroDiscussionTopicsMeta(dataLoader: DataLoaderWorker) { + const r = await getRethink() + const {createdAt: newestMeetingDate} = (await r + .table('NewMeeting') + .max({index: 'createdAt'}) + .run()) as unknown as RethinkSchema['NewMeeting']['type'] + const {createdAt: oldestMeetingDate} = (await r + .table('NewMeeting') + .min({index: 'createdAt'}) + .run()) as unknown as RethinkSchema['NewMeeting']['type'] + + const {newestMetaDate} = (await pg + .selectFrom('EmbeddingsMetadata') + .select(pg.fn.max('refUpdatedAt').as('newestMetaDate')) + .where('objectType', '=', 'retrospectiveDiscussionTopic') + .executeTakeFirst()) ?? {newestMetaDate: null} + let startDateTime = newestMetaDate || oldestMeetingDate + + if (startDateTime.getTime() === newestMeetingDate.getTime()) return + + console.log( + `refreshRetroDiscussionTopicsMeta(): ` + + `will consider adding items from ${startDateTime.toISOString()} to ` + + `${newestMeetingDate.toISOString()}` + ) + + let totalAdded = 0 + do { + // Process history in batches. + // + // N.B. We add historical meetings to the EmbeddingsMetadata table here. + // This query will intentionally miss meetings that haven't been completed + // (`summarySentAt` is null). These meetings will need to be added to the + // EmbeddingsMetadata table by a hook that runs when the meetings complete. + const {maxCreatedAt, completedNewMeetings} = await r + .table('NewMeeting') + .between(startDateTime, newestMeetingDate, {rightBound: 'closed', index: 'createdAt'}) + .orderBy({index: 'createdAt'}) + .limit(BATCH_SIZE) + .coerceTo('array') + .do((rows: any) => ({ + maxCreatedAt: r.expr(rows).max('createdAt')('createdAt'), // Then find the max createdAt value + completedNewMeetings: r.expr(rows).filter((r: any) => + r('meetingType') + .eq('retrospective') + .and( + r('endedAt').gt(0), + r + .hasFields('phases') + .and(r('phases').count().gt(0)) + .and( + r('phases') + .filter((phase: any) => phase('phaseType').eq('discuss')) + .filter((phase: any) => + phase.hasFields('stages').and(phase('stages').count().gt(0)) + ) + .count() + .gt(0) + ) + ) + ) + })) + .run() + const embeddingsMetaRows = ( + await Promise.all( + completedNewMeetings.map((m: AnyMeeting) => + newRetroDiscussionTopicsFromNewMeeting(m, dataLoader) + ) + ) + ).flat() + if (embeddingsMetaRows.length > 0) { + await upsertEmbeddingsMetaRows(embeddingsMetaRows) + totalAdded += embeddingsMetaRows.length + console.log( + `refreshRetroDiscussionTopicsMeta(): synced to ${maxCreatedAt.toISOString()}, added` + + ` ${embeddingsMetaRows.length} retrospectiveDiscussionTopics` + ) + } + + // N.B. In the unlikely event that we have >=BATCH_SIZE meetings that end at _exactly_ + // the same timetsamp, this will loop forever. + if ( + startDateTime.getTime() === newestMeetingDate.getTime() && + completedNewMeetings.length < BATCH_SIZE + ) + break + startDateTime = maxCreatedAt + } while (true) + + console.log( + `refreshRetroDiscussionTopicsMeta(): added ${totalAdded} total retrospectiveDiscussionTopics` + ) +} + +async function getPreferredNameByUserId(userId: string, dataLoader: DataLoaderWorker) { + const user = await dataLoader.get('users').load(userId) + return !user ? 'Unknown' : user.preferredName +} + +async function formatThread( + dataLoader: DataLoaderWorker, + comments: Comment[], + parentId: string | null = null, + depth = 0 +): Promise { + // Filter and sort comments as before + const filteredComments = comments + .filter((comment) => comment.threadParentId === parentId) + .sort((a, b) => (a.threadSortOrder < b.threadSortOrder ? -1 : 1)) + + // Use map to create an array of promises for each formatted comment string + const formattedCommentsPromises = filteredComments.map(async (comment) => { + const indent = ' '.repeat(depth + 1) + const author = comment.isAnonymous + ? 'Anonymous' + : comment.createdBy + ? await getPreferredNameByUserId(comment.createdBy, dataLoader) + : 'Unknown' + const how = depth === 0 ? 'wrote' : 'replied' + const content = comment.plaintextContent + const formattedPost = `${indent}- ${author} ${how}, "${content}"\n` + + // Recursively format child threads + const childThread = await formatThread(dataLoader, comments, comment.id, depth + 1) + return formattedPost + '\n' + childThread + }) + + // Resolve all promises and join the results + const formattedComments = await Promise.all(formattedCommentsPromises) + return formattedComments.join('') +} + +export const createTextFromNewMeetingDiscussionStage = async ( + newMeeting: MeetingRetrospective, + stageId: string, + dataLoader: DataLoaderWorker, + textForReranking: boolean = false +) => { + if (!newMeeting) throw 'newMeeting is undefined' + if (!isMeetingRetrospective(newMeeting)) throw 'newMeeting is not retrospective' + if (!newMeeting.templateId) throw 'template is undefined' + const template = await dataLoader.get('meetingTemplates').load(newMeeting.templateId) + if (!template) throw 'template is undefined' + const discussPhase = newMeeting.phases.find((phase) => phase.phaseType === 'discuss') + if (!discussPhase) throw 'newMeeting discuss phase is undefined' + if (!discussPhase.stages) throw 'newMeeting discuss phase has no stages' + const discussStage = discussPhase.stages.find((stage) => stage.id === stageId) as DiscussStage + if (!discussStage) throw 'newMeeting discuss stage not found' + const {summary: discussionSummary} = discussStage.discussionId + ? (await dataLoader.get('discussions').load(discussStage.discussionId)) ?? {summary: null} + : {summary: null} + const r = await getRethink() + if (!discussStage.reflectionGroupId) throw 'newMeeting discuss stage has no reflectionGroupId' + const reflectionGroup = await r + .table('RetroReflectionGroup') + .get(discussStage.reflectionGroupId) + .run() + if (!reflectionGroup.id) throw 'newMeeting reflectionGroup has no id' + const reflections = await r + .table('RetroReflection') + .getAll(reflectionGroup.id, {index: 'reflectionGroupId'}) + .run() + const promptIds = [...new Set(reflections.map((r) => r.promptId))] + let markdown = '' + if (!textForReranking) + markdown = + `A topic "${reflectionGroup.title}" was discussed during ` + + `the meeting "${newMeeting.name}" that followed the "${template.name}" template.\n` + + `\n` + const prompts = await dataLoader.get('reflectPrompts').loadMany(promptIds) + for (const prompt of prompts) { + if (!prompt || prompt instanceof Error) continue + if (!textForReranking) { + markdown += `Participants were prompted with, "${prompt.question}` + if (prompt.description) markdown += `: ${prompt.description}` + markdown += `".\n` + } + if (newMeeting.disableAnonymity) { + for (const reflection of reflections.filter((r) => r.promptId === prompt.id)) { + const author = await getPreferredNameByUserId(reflection.creatorId, dataLoader) + markdown += ` - ${author} wrote, "${reflection.plaintextContent}"\n` + } + } else { + for (const reflection of reflections.filter((r) => r.promptId === prompt.id)) { + markdown += ` - Anonymous wrote, "${reflection.plaintextContent}"\n` + } + } + markdown += `\n` + } + + markdown += `\n` + + /** + * The choice I made here was default to the summary of the discussion if it exists in order to make this textual + * representation of a retrospective discussion a shorter token count. Using the summary almost certainly ensures + * this text won't need to be sent to be summarized again before an embedding vector is calculated. + * + * If we included the comments all the time, then we're maximizing the chance that rarer tokens might end up in the + * embed text and these tokens might affect the final embed vector in a useful way. However, we increase the odds that + * the embed text will need to be summarized before the vector is calculated. + * + * I decided to "just be cheap" and try and minimize calls to the summarizer. + * + * If we wanted to compare and contrast these approaches, we could always generate a second set of embed vectors + * objectType: 'retrospectiveDiscussionNoSummary' or something and do a bit of testing. + */ + + if (discussionSummary) { + markdown += `Further discussion was made. ` + ` ${discussionSummary}` + } else { + const comments = await dataLoader.get('commentsByDiscussionId').load(stageId) + + const sortedComments = comments + .map((comment) => { + if (!comment.threadParentId) { + return { + ...comment, + threadParentId: null + } + } + return comment + }) + .sort((a, b) => { + if (a.threadParentId === b.threadParentId) { + return a.threadSortOrder - b.threadSortOrder + } + if (a.threadParentId == null) return 1 + if (b.threadParentId == null) return -1 + return a.threadParentId > b.threadParentId ? 1 : -1 + }) as Comment[] + + const filteredComments = sortedComments.filter( + (c) => !IGNORE_COMMENT_USER_IDS.includes(c.createdBy) + ) + if (filteredComments.length) { + markdown += `Futher discussion was made:\n` + markdown += await formatThread(dataLoader, filteredComments) + // TODO: if the discussion threads are too long, summarize them + } + } + + markdown = prettier.format(markdown, { + parser: 'markdown', + proseWrap: 'always', + printWidth: 72 + }) + + return markdown +} + +export const createText = async ( + item: Selectable, + dataLoader: DataLoaderWorker +): Promise => { + if (!item.refId) throw 'refId is undefined' + const [newMeetingId, discussionId] = item.refId.split(':') + if (!newMeetingId) throw new Error('newMeetingId cannot be undefined') + if (!discussionId) throw new Error('discussionId cannot be undefined') + const newMeeting = await dataLoader.get('newMeetings').load(newMeetingId) + return createTextFromNewMeetingDiscussionStage( + newMeeting as MeetingRetrospective, + discussionId, + dataLoader + ) +} + +export const newRetroDiscussionTopicsFromNewMeeting = async ( + newMeeting: RethinkSchema['NewMeeting']['type'], + dataLoader: DataLoaderWorker +) => { + const discussPhase = newMeeting.phases.find((phase) => phase.phaseType === 'discuss') + const orgId = (await dataLoader.get('teams').load(newMeeting.teamId))?.orgId + if (orgId && discussPhase && discussPhase.stages) { + return discussPhase.stages.map((stage) => ({ + objectType: 'retrospectiveDiscussionTopic' as const, + teamId: newMeeting.teamId, + refId: `${newMeeting.id}:${stage.id}`, + refUpdatedAt: newMeeting.createdAt + })) + } else { + return [] + } +} diff --git a/packages/embedder/package.json b/packages/embedder/package.json new file mode 100644 index 00000000000..47af8737dac --- /dev/null +++ b/packages/embedder/package.json @@ -0,0 +1,31 @@ +{ + "name": "parabol-embedder", + "version": "7.10.0", + "description": "A service that computes embedding vectors from Parabol objects", + "author": "Jordan Husney ", + "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/embedder#readme", + "license": "AGPL-3.0-or-later", + "repository": { + "type": "git", + "url": "git+https://github.com/ParabolInc/parabol.git" + }, + "scripts": { + "typecheck": "yarn tsc --noEmit -p tsconfig.json" + }, + "bugs": { + "url": "https://github.com/ParabolInc/parabol/issues" + }, + "devDependencies": { + "@babel/cli": "7.18.6", + "@babel/core": "7.18.6", + "@types/node": "^16.11.62", + "babel-plugin-inline-import": "^3.0.0", + "sucrase": "^3.32.0", + "ts-node-dev": "^1.0.0-pre.44", + "typescript": "4.9.5" + }, + "dependencies": { + "dd-trace": "^4.2.0", + "redlock": "^5.0.0-beta.2" + } +} diff --git a/packages/embedder/tsconfig.json b/packages/embedder/tsconfig.json new file mode 100644 index 00000000000..1d179d08096 --- /dev/null +++ b/packages/embedder/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "baseUrl": "../", + "paths": { + // when we import from lib, make goto-definition point to the src + "parabol-server/*": ["server/*"], + "parabol-client/*": ["client/*"] + }, + "outDir": "lib", + "lib": ["esnext"], + "types": ["node"] + }, + "files": ["../server/types/modules.d.ts"] +} diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index b39e1e42d30..8bd64c6250b 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -1,5 +1,5 @@ import DataLoader from 'dataloader' -import {Selectable, sql} from 'kysely' +import {Selectable, SqlBool, sql} from 'kysely' import {PARABOL_AI_USER_ID} from '../../client/utils/constants' import getRethink, {RethinkSchema} from '../database/rethinkDriver' import {RDatum} from '../database/stricterR' @@ -478,11 +478,11 @@ export const meetingTemplatesByOrgId = (parent: RootDataLoader) => { .selectAll() .where('orgId', 'in', orgIds) .where('isActive', '=', true) - .where(({or, cmpr}) => + .where(({or, eb}) => or([ - cmpr('hideStartingAt', 'is', null), - sql`make_date(2020 , extract(month from current_date)::integer, extract(day from current_date)::integer) between "hideEndingAt" and "hideStartingAt"`, - sql`make_date(2019 , extract(month from current_date)::integer, extract(day from current_date)::integer) between "hideEndingAt" and "hideStartingAt"` + eb('hideStartingAt', 'is', null), + sql`make_date(2020 , extract(month from current_date)::integer, extract(day from current_date)::integer) between "hideEndingAt" and "hideStartingAt"`, + sql`make_date(2019 , extract(month from current_date)::integer, extract(day from current_date)::integer) between "hideEndingAt" and "hideStartingAt"` ]) ) .orderBy('createdAt', 'desc') diff --git a/packages/server/dataloader/customRedisQueries.ts b/packages/server/dataloader/customRedisQueries.ts index b3ed754dcab..a461202ca5a 100644 --- a/packages/server/dataloader/customRedisQueries.ts +++ b/packages/server/dataloader/customRedisQueries.ts @@ -1,7 +1,7 @@ // Sometimes, a value cached is redis is harder to get than simply querying the primary key on a table // this allows redis to cache the results of arbitrarily complex rethinkdb queries -import {sql} from 'kysely' +import {sql, SqlBool} from 'kysely' import ms from 'ms' import getRethink from '../database/rethinkDriver' import {RDatum} from '../database/stricterR' @@ -38,11 +38,11 @@ const customRedisQueries = { .where('teamId', '=', 'aGhostTeam') .where('isActive', '=', true) .where('type', '=', templateType) - .where(({or, cmpr}) => + .where(({or, eb}) => or([ - cmpr('hideStartingAt', 'is', null), - sql`make_date(2020 , extract(month from current_date)::integer, extract(day from current_date)::integer) between "hideEndingAt" and "hideStartingAt"`, - sql`make_date(2019 , extract(month from current_date)::integer, extract(day from current_date)::integer) between "hideEndingAt" and "hideStartingAt"` + eb('hideStartingAt', 'is', null), + sql`make_date(2020 , extract(month from current_date)::integer, extract(day from current_date)::integer) between "hideEndingAt" and "hideStartingAt"`, + sql`make_date(2019 , extract(month from current_date)::integer, extract(day from current_date)::integer) between "hideEndingAt" and "hideStartingAt"` ]) ) .execute() diff --git a/packages/server/postgres/getKysely.ts b/packages/server/postgres/getKysely.ts index 0a8632a3eb6..f2824a800d0 100644 --- a/packages/server/postgres/getKysely.ts +++ b/packages/server/postgres/getKysely.ts @@ -10,6 +10,13 @@ const getKysely = () => { dialect: new PostgresDialect({ pool: pg }) + // query logging, if you'd like it: + // log(event) { + // if (event.level === 'query') { + // console.log(event.query.sql) + // console.log(event.query.parameters) + // } + // } }) } return kysely diff --git a/packages/server/postgres/migrations/1708127504000_updateEmbeddingMetadata.ts b/packages/server/postgres/migrations/1708127504000_updateEmbeddingMetadata.ts new file mode 100644 index 00000000000..3c93617c0b1 --- /dev/null +++ b/packages/server/postgres/migrations/1708127504000_updateEmbeddingMetadata.ts @@ -0,0 +1,20 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + ALTER TABLE "EmbeddingsMetadata" RENAME COLUMN "embedText" TO "fullText"; + `) + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + ALTER TABLE "EmbeddingsMetadata" RENAME COLUMN "fullText" TO "embedText"; + `) + await client.end() +} diff --git a/pm2.config.js b/pm2.config.js index 4d270b745ea..fdf09ea9ca2 100644 --- a/pm2.config.js +++ b/pm2.config.js @@ -30,6 +30,21 @@ module.exports = { NODE_ENV: 'production' } }, + { + name: 'Embedder', + script: 'dist/embedder.js', + instances: 1, + increment_var: 'SERVER_ID', + autorestart: true, + watch: false, + max_memory_restart: '4096M', + env: { + SERVER_ID: 5 + }, + env_production: { + NODE_ENV: 'production' + } + }, { name: 'GQL Executor', script: 'dist/gqlExecutor.js', @@ -39,7 +54,7 @@ module.exports = { watch: false, max_memory_restart: '24576M', env: { - SERVER_ID: 5 + SERVER_ID: 6 }, env_production: { NODE_ENV: 'production' diff --git a/pm2.dev.config.js b/pm2.dev.config.js index c5f04196dff..6cd457059ef 100644 --- a/pm2.dev.config.js +++ b/pm2.dev.config.js @@ -4,6 +4,19 @@ module.exports = { name: 'Webpack Servers', script: 'scripts/buildServers.js' }, + { + name: 'Socket Server', + script: 'scripts/runSocketServer.js', + // increase this to test scaling + instances: 1, + increment_var: 'SERVER_ID', + env: { + SERVER_ID: 0 + }, + watch: ['dev/web.js'], + // if the watched file doeesn't exist, wait for it instead of restarting + autorestart: false + }, { name: 'GraphQL Executor', script: 'scripts/runExecutor.js', @@ -15,24 +28,20 @@ module.exports = { }, watch: ['dev/gqlExecutor.js'], // if the watched file doeesn't exist, wait for it instead of restarting - autorestart: false, - log_file: 'dev/logs/gqlExecutor.log', - combine_logs: true + autorestart: false }, { - name: 'Socket Server', - script: 'scripts/runSocketServer.js', + name: 'Embedder', + script: 'scripts/runEmbedder.js', // increase this to test scaling instances: 1, increment_var: 'SERVER_ID', env: { - SERVER_ID: 0 + SERVER_ID: 6 }, - watch: ['dev/web.js'], + watch: ['dev/embedder.js'], // if the watched file doeesn't exist, wait for it instead of restarting - autorestart: false, - log_file: 'dev/logs/web.log', - combine_logs: true + autorestart: false }, { name: 'Dev Server', diff --git a/scripts/generateGraphQLArtifacts.js b/scripts/generateGraphQLArtifacts.js index de2fd452e67..9ca5cd43067 100644 --- a/scripts/generateGraphQLArtifacts.js +++ b/scripts/generateGraphQLArtifacts.js @@ -16,8 +16,11 @@ const generateGraphQLArtifacts = async () => { relayCompiler?.kill() }) }) - - await Promise.all([generate(codegenSchema), runCompiler()]) + console.log('gen graphql artifacts start') + await generate(codegenSchema) + console.log('codegen complete') + await runCompiler() + console.log('relay compiler complete') persistServer.close() } diff --git a/scripts/prod.js b/scripts/prod.js index 65d121446d8..94a7565363b 100644 --- a/scripts/prod.js +++ b/scripts/prod.js @@ -10,15 +10,25 @@ const runChild = (cmd) => { const prod = async (isDeploy, noDeps) => { console.log('🙏🙏🙏 Building Production Server 🙏🙏🙏') - await generateGraphQLArtifacts() - await Promise.all([ - runChild( - `yarn webpack --config ./scripts/webpack/prod.servers.config.js --no-stats --env=noDeps=${noDeps}` - ), - runChild( - `yarn webpack --config ./scripts/webpack/prod.client.config.js --no-stats --env=minimize=${isDeploy}` - ) - ]) + try { + await generateGraphQLArtifacts() + } catch (e) { + console.log('ERR generating artifacts', e) + } + + console.log('starting webpack build') + try { + await Promise.all([ + runChild( + `yarn webpack --config ./scripts/webpack/prod.servers.config.js --no-stats --env=noDeps=${noDeps}` + ), + runChild( + `yarn webpack --config ./scripts/webpack/prod.client.config.js --no-stats --env=minimize=${isDeploy}` + ) + ]) + } catch (e) { + console.log('error webpackifying', e) + } } if (require.main === module) { diff --git a/scripts/runEmbedder.js b/scripts/runEmbedder.js new file mode 100644 index 00000000000..c235888f861 --- /dev/null +++ b/scripts/runEmbedder.js @@ -0,0 +1,13 @@ +/** + Starts up an instance of the stateless Embedder service. + When you modify a server file, the webpack watcher in + {@link buildServers} writes the change to {@link ../dev/embedder}. + Pm2 reloads this file whenever {@link embedder} changes +*/ + +try { + require('../dev/embedder.js') +} catch (e) { + // webpack has not created the file yet + // pm2 will restart this process when the file changes +} diff --git a/scripts/webpack/dev.servers.config.js b/scripts/webpack/dev.servers.config.js index b32e586562c..80591f24b0f 100644 --- a/scripts/webpack/dev.servers.config.js +++ b/scripts/webpack/dev.servers.config.js @@ -7,6 +7,7 @@ const webpack = require('webpack') const PROJECT_ROOT = getProjectRoot() const CLIENT_ROOT = path.join(PROJECT_ROOT, 'packages', 'client') const SERVER_ROOT = path.join(PROJECT_ROOT, 'packages', 'server') +const EMBEDDER_ROOT = path.join(PROJECT_ROOT, 'packages', 'embedder') const GQL_ROOT = path.join(PROJECT_ROOT, 'packages', 'gql-executor') const DOTENV = path.join(PROJECT_ROOT, 'scripts', 'webpack', 'utils', 'dotenv.js') // const CircularDependencyPlugin = require('circular-dependency-plugin') @@ -26,6 +27,7 @@ module.exports = { }, entry: { web: [DOTENV, path.join(SERVER_ROOT, 'server.ts')], + embedder: [DOTENV, path.join(EMBEDDER_ROOT, 'embedder.ts')], gqlExecutor: [DOTENV, path.join(GQL_ROOT, 'gqlExecutor.ts')] }, output: { diff --git a/scripts/webpack/prod.servers.config.js b/scripts/webpack/prod.servers.config.js index 5db575b51e3..52feb216089 100644 --- a/scripts/webpack/prod.servers.config.js +++ b/scripts/webpack/prod.servers.config.js @@ -11,6 +11,7 @@ const cp = require('child_process') const PROJECT_ROOT = getProjectRoot() const CLIENT_ROOT = path.join(PROJECT_ROOT, 'packages', 'client') const SERVER_ROOT = path.join(PROJECT_ROOT, 'packages', 'server') +const EMBEDDER_ROOT = path.join(PROJECT_ROOT, 'packages', 'embedder') const GQL_ROOT = path.join(PROJECT_ROOT, 'packages', 'gql-executor') const DOTENV = path.join(PROJECT_ROOT, 'scripts/webpack/utils/dotenv.js') const distPath = path.join(PROJECT_ROOT, 'dist') @@ -32,6 +33,7 @@ module.exports = (config) => { path.join(PROJECT_ROOT, 'scripts/toolboxSrc/applyEnvVarsToClientAssets.ts'), path.join(SERVER_ROOT, 'server.ts') ], + embedder: [DOTENV, path.join(EMBEDDER_ROOT, 'embedder.ts')], gqlExecutor: [DOTENV, path.join(GQL_ROOT, 'gqlExecutor.ts')], preDeploy: [DOTENV, path.join(PROJECT_ROOT, 'scripts/toolboxSrc/preDeploy.ts')], pushToCDN: [DOTENV, path.join(PROJECT_ROOT, 'scripts/toolboxSrc/pushToCDN.ts')], diff --git a/scripts/webpack/toolbox.config.js b/scripts/webpack/toolbox.config.js index 3edcb77ed56..d63a8f6505c 100644 --- a/scripts/webpack/toolbox.config.js +++ b/scripts/webpack/toolbox.config.js @@ -56,6 +56,26 @@ module.exports = { new webpack.DefinePlugin({ __PRODUCTION__: true }) + // new CircularDependencyPlugin({ + // // `onStart` is called before the cycle detection starts + // onStart({compilation}) { + // console.log('start detecting webpack modules cycles') + // }, + // // `onDetected` is called for each module that is cyclical + // onDetected({module: webpackModuleRecord, paths, compilation}) { + // // `paths` will be an Array of the relative module paths that make up the cycle + // // `module` is the module record that caused the cycle + // compilation.errors.push(new Error(paths.join(' -> '))) + // }, + // // `onEnd` is called before the cycle detection ends + // onEnd({compilation}) { + // console.log('end detecting webpack modules cycles') + // }, + // // set to false to only detect cycles that include an entrypoint + // allowAsyncCycles: false, + // // set to true to detect cycles in node_modules + // cwd: process.cwd() // set the current working directory for displaying module paths + // }) ], module: { rules: [ diff --git a/scripts/webpack/utils/transformRules.js b/scripts/webpack/utils/transformRules.js index 0c44e2ae3e6..e6bc0c9dadf 100644 --- a/scripts/webpack/utils/transformRules.js +++ b/scripts/webpack/utils/transformRules.js @@ -3,6 +3,7 @@ const path = require('path') const transformRules = (projectRoot, isProd) => { const CLIENT_ROOT = path.join(projectRoot, 'packages', 'client') const SERVER_ROOT = path.join(projectRoot, 'packages', 'server') + const EMBEDDER_ROOT = path.join(projectRoot, 'packages', 'embedder') const GQL_ROOT = path.join(projectRoot, 'packages', 'gql-executor') const CHRONOS_ROOT = path.join(projectRoot, 'packages', 'chronos') const TOOLBOX_SRC = path.join(projectRoot, 'scripts', 'toolboxSrc') @@ -48,7 +49,7 @@ const transformRules = (projectRoot, isProd) => { { test: /\.(tsx?|js)$/, // things that don't need babel - include: [SERVER_ROOT, GQL_ROOT, CHRONOS_ROOT, TOOLBOX_SRC], + include: [SERVER_ROOT, EMBEDDER_ROOT, GQL_ROOT, CHRONOS_ROOT, TOOLBOX_SRC], // things that need babel exclude: path.join(SERVER_ROOT, 'email'), use: { diff --git a/yarn.lock b/yarn.lock index 99d793dc367..69cf14bcce1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1317,11 +1317,34 @@ "@babel/highlight" "^7.22.13" chalk "^2.4.2" -"@babel/compat-data@^7.13.11", "@babel/compat-data@^7.18.6", "@babel/compat-data@^7.22.9": +"@babel/code-frame@^7.16.7", "@babel/code-frame@^7.21.4", "@babel/code-frame@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.23.5.tgz#9009b69a8c602293476ad598ff53e4562e15c244" + integrity sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA== + dependencies: + "@babel/highlight" "^7.23.4" + chalk "^2.4.2" + +"@babel/compat-data@^7.13.11": + version "7.16.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.16.4.tgz#081d6bbc336ec5c2435c6346b2ae1fb98b5ac68e" + integrity sha512-1o/jo7D+kC9ZjHX5v+EHrdjl3PhxMrLSOTGsOdHJ+KL8HCaEK6ehrVL2RS6oHDZp+L7xLirLrPmQtEng769J/Q== + +"@babel/compat-data@^7.18.6": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.18.8.tgz#2483f565faca607b8535590e84e7de323f27764d" + integrity sha512-HSmX4WZPPK3FUxYp7g2T6EyO8j96HlZJlxmKPSh6KAcqwyDrfx7hKjXpAW/0FhFfTJsR0Yt4lAjLI2coMptIHQ== + +"@babel/compat-data@^7.22.9": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.9.tgz#71cdb00a1ce3a329ce4cbec3a44f9fef35669730" integrity sha512-5UamI7xkUcJ3i9qVDS+KFDEK8/7oJ55/sJMB1Ge7IEapr7KfdfV/HErR+koZwOfd+SgtFKOKRhRakdg++DcJpQ== +"@babel/compat-data@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.23.5.tgz#ffb878728bb6bdcb6f4510aa51b1be9afb8cfd98" + integrity sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw== + "@babel/core@7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.6.tgz#54a107a3c298aee3fe5e1947a6464b9b6faca03d" @@ -1343,7 +1366,91 @@ json5 "^2.2.1" semver "^6.3.0" -"@babel/core@^7.11.1", "@babel/core@^7.11.6", "@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.14.0", "@babel/core@^7.20.12", "@babel/core@^7.22.9": +"@babel/core@^7.11.1": + version "7.18.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.18.2.tgz#87b2fcd7cce9becaa7f5acebdc4f09f3dd19d876" + integrity sha512-A8pri1YJiC5UnkdrWcmfZTJTV85b4UXTAfImGmCfYmax4TR9Cw8sDS0MOk++Gp2mE/BefVJ5nwy5yzqNJbP/DQ== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.18.2" + "@babel/helper-compilation-targets" "^7.18.2" + "@babel/helper-module-transforms" "^7.18.0" + "@babel/helpers" "^7.18.2" + "@babel/parser" "^7.18.0" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.18.2" + "@babel/types" "^7.18.2" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.1" + semver "^6.3.0" + +"@babel/core@^7.11.6": + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.21.8.tgz#2a8c7f0f53d60100ba4c32470ba0281c92aa9aa4" + integrity sha512-YeM22Sondbo523Sz0+CirSPnbj9bG3P0CdHcBZdqUuaeOaYEFbOLoGU7lebvGP6P5J/WE9wOn7u7C4J9HvS1xQ== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.21.4" + "@babel/generator" "^7.21.5" + "@babel/helper-compilation-targets" "^7.21.5" + "@babel/helper-module-transforms" "^7.21.5" + "@babel/helpers" "^7.21.5" + "@babel/parser" "^7.21.8" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.21.5" + "@babel/types" "^7.21.5" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.2" + semver "^6.3.0" + +"@babel/core@^7.12.3", "@babel/core@^7.13.16", "@babel/core@^7.14.0": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.16.7.tgz#db990f931f6d40cb9b87a0dc7d2adc749f1dcbcf" + integrity sha512-aeLaqcqThRNZYmbMqtulsetOQZ/5gbR/dWruUCJcpas4Qoyy+QeagfDsPdMrqwsPRDNxJvBlRiZxxX7THO7qtA== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/generator" "^7.16.7" + "@babel/helper-compilation-targets" "^7.16.7" + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helpers" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/template" "^7.16.7" + "@babel/traverse" "^7.16.7" + "@babel/types" "^7.16.7" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.1.2" + semver "^6.3.0" + source-map "^0.5.0" + +"@babel/core@^7.20.12": + version "7.20.12" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.20.12.tgz#7930db57443c6714ad216953d1356dac0eb8496d" + integrity sha512-XsMfHovsUYHFMdrIHkZphTN/2Hzzi78R08NuHfDBehym2VsPDL6Zn/JAD/JQdnRvbSsbQc4mVaU1m6JgtTEElg== + dependencies: + "@ampproject/remapping" "^2.1.0" + "@babel/code-frame" "^7.18.6" + "@babel/generator" "^7.20.7" + "@babel/helper-compilation-targets" "^7.20.7" + "@babel/helper-module-transforms" "^7.20.11" + "@babel/helpers" "^7.20.7" + "@babel/parser" "^7.20.7" + "@babel/template" "^7.20.7" + "@babel/traverse" "^7.20.12" + "@babel/types" "^7.20.7" + convert-source-map "^1.7.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.2" + semver "^6.3.0" + +"@babel/core@^7.22.9": version "7.22.10" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.10.tgz#aad442c7bcd1582252cb4576747ace35bc122f35" integrity sha512-fTmqbbUBAwCcre6zPzNngvsI0aNrPZe77AeqvDxWM9Nm+04RrJ3CAmGHA9f7lJQY6ZMhRztNemy4uslDxTX4Qw== @@ -1374,7 +1481,24 @@ "@jridgewell/trace-mapping" "^0.3.17" jsesc "^2.5.1" -"@babel/helper-annotate-as-pure@^7.16.7", "@babel/helper-annotate-as-pure@^7.18.6": +"@babel/generator@^7.16.7", "@babel/generator@^7.18.2", "@babel/generator@^7.20.7", "@babel/generator@^7.21.5", "@babel/generator@^7.23.6": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.23.6.tgz#9e1fca4811c77a10580d17d26b57b036133f3c2e" + integrity sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw== + dependencies: + "@babel/types" "^7.23.6" + "@jridgewell/gen-mapping" "^0.3.2" + "@jridgewell/trace-mapping" "^0.3.17" + jsesc "^2.5.1" + +"@babel/helper-annotate-as-pure@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.16.7.tgz#bb2339a7534a9c128e3102024c60760a3a7f3862" + integrity sha512-s6t2w/IPQVTAET1HitoowRGXooX8mCgtuP5195wD/QJPV6wYjpujCGF7JuMODVX2ZAJOf1GT6DT9MHEZvLOFSw== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-annotate-as-pure@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz#eaa49f6f80d5a33f9a5dd2276e6d6e451be0a6bb" integrity sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA== @@ -1400,7 +1524,31 @@ lru-cache "^5.1.1" semver "^6.3.1" -"@babel/helper-create-class-features-plugin@^7.16.7", "@babel/helper-create-class-features-plugin@^7.18.6": +"@babel/helper-compilation-targets@^7.16.7", "@babel/helper-compilation-targets@^7.18.2", "@babel/helper-compilation-targets@^7.20.7", "@babel/helper-compilation-targets@^7.21.5": + version "7.23.6" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz#4d79069b16cbcf1461289eccfbbd81501ae39991" + integrity sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ== + dependencies: + "@babel/compat-data" "^7.23.5" + "@babel/helper-validator-option" "^7.23.5" + browserslist "^4.22.2" + lru-cache "^5.1.1" + semver "^6.3.1" + +"@babel/helper-create-class-features-plugin@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.16.7.tgz#9c5b34b53a01f2097daf10678d65135c1b9f84ba" + integrity sha512-kIFozAvVfK05DM4EVQYKK+zteWvY85BFdGBRQBytRyY3y+6PX0DkDOn/CZ3lEuczCfrCxEzwt0YtP/87YPTWSw== + dependencies: + "@babel/helper-annotate-as-pure" "^7.16.7" + "@babel/helper-environment-visitor" "^7.16.7" + "@babel/helper-function-name" "^7.16.7" + "@babel/helper-member-expression-to-functions" "^7.16.7" + "@babel/helper-optimise-call-expression" "^7.16.7" + "@babel/helper-replace-supers" "^7.16.7" + "@babel/helper-split-export-declaration" "^7.16.7" + +"@babel/helper-create-class-features-plugin@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.18.6.tgz#6f15f8459f3b523b39e00a99982e2c040871ed72" integrity sha512-YfDzdnoxHGV8CzqHGyCbFvXg5QESPFkXlHtvdCkesLjjVMT2Adxe4FGUR5ChIb3DxSaXO12iIOCWoXdsUVwnqw== @@ -1435,7 +1583,19 @@ resolve "^1.14.2" semver "^6.1.2" -"@babel/helper-environment-visitor@^7.18.6", "@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5": +"@babel/helper-environment-visitor@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.16.7.tgz#ff484094a839bde9d89cd63cba017d7aae80ecd7" + integrity sha512-SLLb0AAn6PkUeAfKJCCOl9e1R53pQlGAfc4y4XuMRZfqeMYLE0dM1LMhqbGAlGQY0lfw5/ohoYWAe9V1yibRag== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-environment-visitor@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.6.tgz#b7eee2b5b9d70602e59d1a6cad7dd24de7ca6cd7" + integrity sha512-8n6gSfn2baOY+qlp+VSzsosjCVGFqWKmDF0cCWOybh52Dw3SEyoWR1KrhMJASjLwIEkkAufZ0xvr+SxLHSpy2Q== + +"@babel/helper-environment-visitor@^7.22.20", "@babel/helper-environment-visitor@^7.22.5": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz#96159db61d34a29dba454c959f5ae4a649ba9167" integrity sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA== @@ -1447,7 +1607,7 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-function-name@^7.18.6", "@babel/helper-function-name@^7.23.0": +"@babel/helper-function-name@^7.16.7", "@babel/helper-function-name@^7.18.6", "@babel/helper-function-name@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz#1f9a3cdbd5b2698a670c30d2735f9af95ed52759" integrity sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw== @@ -1462,6 +1622,13 @@ dependencies: "@babel/types" "^7.22.5" +"@babel/helper-member-expression-to-functions@^7.16.7", "@babel/helper-member-expression-to-functions@^7.22.15": + version "7.23.0" + resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.23.0.tgz#9263e88cc5e41d39ec18c9a3e0eced59a3e7d366" + integrity sha512-6gfrPwh7OuT6gZyJZvd6WbTfrqAo7vm4xCzAXOusKqq/vWdKXphTpj5klHKNmRUU6/QRGlBsyU9mAIPaWHlqJA== + dependencies: + "@babel/types" "^7.23.0" + "@babel/helper-member-expression-to-functions@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.18.6.tgz#44802d7d602c285e1692db0bad9396d007be2afc" @@ -1469,13 +1636,45 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.7", "@babel/helper-module-imports@^7.18.6", "@babel/helper-module-imports@^7.22.5": +"@babel/helper-module-imports@^7.0.0", "@babel/helper-module-imports@^7.10.4", "@babel/helper-module-imports@^7.12.13", "@babel/helper-module-imports@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.16.7.tgz#25612a8091a999704461c8a222d0efec5d091437" + integrity sha512-LVtS6TqjJHFc+nYeITRo6VLXve70xmq7wPhWTqDJusJEgGmkAACWwMiTNrvfoQo6hEhFwAIixNkvB0jPXDL8Wg== + dependencies: + "@babel/types" "^7.16.7" + +"@babel/helper-module-imports@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz#1e3ebdbbd08aad1437b428c50204db13c5a3ca6e" + integrity sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA== + dependencies: + "@babel/types" "^7.18.6" + +"@babel/helper-module-imports@^7.22.15": + version "7.22.15" + resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz#16146307acdc40cc00c3b2c647713076464bdbf0" + integrity sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w== + dependencies: + "@babel/types" "^7.22.15" + +"@babel/helper-module-imports@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c" integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg== dependencies: "@babel/types" "^7.22.5" +"@babel/helper-module-transforms@^7.16.7", "@babel/helper-module-transforms@^7.18.0", "@babel/helper-module-transforms@^7.20.11", "@babel/helper-module-transforms@^7.21.5": + version "7.23.3" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz#d7d12c3c5d30af5b3c0fcab2a6d5217773e2d0f1" + integrity sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-module-imports" "^7.22.15" + "@babel/helper-simple-access" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/helper-validator-identifier" "^7.22.20" + "@babel/helper-module-transforms@^7.18.6", "@babel/helper-module-transforms@^7.22.9": version "7.22.9" resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.9.tgz#92dfcb1fbbb2bc62529024f72d942a8c97142129" @@ -1487,6 +1686,13 @@ "@babel/helper-split-export-declaration" "^7.22.6" "@babel/helper-validator-identifier" "^7.22.5" +"@babel/helper-optimise-call-expression@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.16.7.tgz#a34e3560605abbd31a18546bd2aad3e6d9a174f2" + integrity sha512-EtgBhg7rd/JcnpZFXpBy0ze1YRfdm7BnBX4uKMBd3ixa3RGAE002JZB66FJyNH7g0F38U05pXmA5P8cBh7z+1w== + dependencies: + "@babel/types" "^7.16.7" + "@babel/helper-optimise-call-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz#9369aa943ee7da47edab2cb4e838acf09d290ffe" @@ -1494,7 +1700,29 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.18.6", "@babel/helper-plugin-utils@^7.20.2", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": +"@babel/helper-optimise-call-expression@^7.22.5": + version "7.22.5" + resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.22.5.tgz#f21531a9ccbff644fdd156b4077c16ff0c3f609e" + integrity sha512-HBwaojN0xFRx4yIvpwGqxiV2tUfl7401jlok564NgB9EHS1y6QT17FmKWm4ztqjeVdXLuC4fSvHc5ePpQjoTbw== + dependencies: + "@babel/types" "^7.22.5" + +"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.13.0", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.16.7", "@babel/helper-plugin-utils@^7.8.0", "@babel/helper-plugin-utils@^7.8.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5" + integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA== + +"@babel/helper-plugin-utils@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.18.6.tgz#9448974dd4fb1d80fefe72e8a0af37809cd30d6d" + integrity sha512-gvZnm1YAAxh13eJdkb9EWHBnF3eAub3XTLCZEehHT2kWxiKVRL64+ae5Y6Ivne0mVHmMYKT+xWgZO+gQhuLUBg== + +"@babel/helper-plugin-utils@^7.20.2": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.21.5.tgz#345f2377d05a720a4e5ecfa39cbf4474a4daed56" + integrity sha512-0WDaIlXKOX/3KfBK/dwP1oQGiPh6rjMkT7HIRv7i5RR2VUMwrx5ZL0dwBkKx7+SW1zwNdgjHd34IMk5ZjTeHVg== + +"@babel/helper-plugin-utils@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295" integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg== @@ -1509,6 +1737,15 @@ "@babel/helper-wrap-function" "^7.18.6" "@babel/types" "^7.18.6" +"@babel/helper-replace-supers@^7.16.7": + version "7.22.20" + resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.22.20.tgz#e37d367123ca98fe455a9887734ed2e16eb7a793" + integrity sha512-qsW0In3dbwQUbK8kejJ4R7IHVGwHJlV6lpG6UA7a9hSa2YEiAib+N1T2kr6PEeUT+Fl7najmSOS6SmAwCHK6Tw== + dependencies: + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-member-expression-to-functions" "^7.22.15" + "@babel/helper-optimise-call-expression" "^7.22.5" + "@babel/helper-replace-supers@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-replace-supers/-/helper-replace-supers-7.18.6.tgz#efedf51cfccea7b7b8c0f00002ab317e7abfe420" @@ -1520,7 +1757,7 @@ "@babel/traverse" "^7.18.6" "@babel/types" "^7.18.6" -"@babel/helper-simple-access@^7.18.6", "@babel/helper-simple-access@^7.22.5": +"@babel/helper-simple-access@^7.16.7", "@babel/helper-simple-access@^7.18.6", "@babel/helper-simple-access@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de" integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w== @@ -1534,19 +1771,39 @@ dependencies: "@babel/types" "^7.18.6" -"@babel/helper-split-export-declaration@^7.18.6", "@babel/helper-split-export-declaration@^7.22.6": +"@babel/helper-split-export-declaration@^7.16.7", "@babel/helper-split-export-declaration@^7.18.6", "@babel/helper-split-export-declaration@^7.22.6": version "7.22.6" resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c" integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g== dependencies: "@babel/types" "^7.22.5" +"@babel/helper-string-parser@^7.19.4", "@babel/helper-string-parser@^7.21.5", "@babel/helper-string-parser@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz#9478c707febcbbe1ddb38a3d91a2e054ae622d83" + integrity sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ== + "@babel/helper-string-parser@^7.22.5": version "7.22.5" resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f" integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw== -"@babel/helper-validator-identifier@^7.18.6", "@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.22.5": +"@babel/helper-validator-identifier@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.16.7.tgz#e8c602438c4a8195751243da9031d1607d247cad" + integrity sha512-hsEnFemeiW4D08A5gUAZxLBTXpZ39P+a+DGDsHw1yxqyQ/jzFEnxf5uTEGp+3bzAbNOxU1paTgYS4ECU/IgfDw== + +"@babel/helper-validator-identifier@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.18.6.tgz#9c97e30d31b2b8c72a1d08984f2ca9b574d7a076" + integrity sha512-MmetCkz9ej86nJQV+sFCxoGGrUbU3q02kgLciwkrt9QqEB7cP39oKEY0PakknEO0Gu20SskMRi+AYZ3b1TpN9g== + +"@babel/helper-validator-identifier@^7.19.1": + version "7.19.1" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz#7eea834cf32901ffdc1a7ee555e2f9c27e249ca2" + integrity sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w== + +"@babel/helper-validator-identifier@^7.22.20", "@babel/helper-validator-identifier@^7.22.5": version "7.22.20" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz#c4ae002c61d2879e724581d96665583dbc1dc0e0" integrity sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A== @@ -1556,6 +1813,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac" integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw== +"@babel/helper-validator-option@^7.23.5": + version "7.23.5" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz#907a3fbd4523426285365d1206c423c4c5520307" + integrity sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw== + "@babel/helper-wrap-function@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.18.6.tgz#ec44ea4ad9d8988b90c3e465ba2382f4de81a073" @@ -1566,6 +1828,15 @@ "@babel/traverse" "^7.18.6" "@babel/types" "^7.18.6" +"@babel/helpers@^7.16.7", "@babel/helpers@^7.18.2", "@babel/helpers@^7.20.7", "@babel/helpers@^7.21.5": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.23.9.tgz#c3e20bbe7f7a7e10cb9b178384b4affdf5995c7d" + integrity sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ== + dependencies: + "@babel/template" "^7.23.9" + "@babel/traverse" "^7.23.9" + "@babel/types" "^7.23.9" + "@babel/helpers@^7.18.6", "@babel/helpers@^7.22.10": version "7.22.10" resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.10.tgz#ae6005c539dfbcb5cd71fb51bfc8a52ba63bc37a" @@ -1584,11 +1855,55 @@ chalk "^2.4.2" js-tokens "^4.0.0" -"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.8", "@babel/parser@^7.18.6", "@babel/parser@^7.20.15", "@babel/parser@^7.22.10", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0": +"@babel/highlight@^7.23.4": + version "7.23.4" + resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.23.4.tgz#edaadf4d8232e1a961432db785091207ead0621b" + integrity sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A== + dependencies: + "@babel/helper-validator-identifier" "^7.22.20" + chalk "^2.4.2" + js-tokens "^4.0.0" + +"@babel/parser@^7.1.0", "@babel/parser@^7.13.16", "@babel/parser@^7.14.0", "@babel/parser@^7.14.7", "@babel/parser@^7.16.7": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.16.7.tgz#d372dda9c89fcec340a82630a9f533f2fe15877e" + integrity sha512-sR4eaSrnM7BV7QPzGfEX5paG/6wrZM3I0HDzfIAK06ESvo9oy3xBuVBxE3MbQaKNhvg8g/ixjMWo2CGpzpHsDA== + +"@babel/parser@^7.16.8", "@babel/parser@^7.18.6": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.8.tgz#822146080ac9c62dac0823bb3489622e0bc1cbdf" + integrity sha512-RSKRfYX20dyH+elbJK2uqAkVyucL+xXzhqlMD5/ZXx+dAAwpyB7HsvnHe/ZUGOF+xLr5Wx9/JoXVTj6BQE2/oA== + +"@babel/parser@^7.18.0": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.18.4.tgz#6774231779dd700e0af29f6ad8d479582d7ce5ef" + integrity sha512-FDge0dFazETFcxGw/EXzOkN8uJp0PC7Qbm+Pe9T+av2zlBpOgunFHkQPPn+eRuClU73JF+98D531UgayY89tow== + +"@babel/parser@^7.20.15", "@babel/parser@^7.22.15", "@babel/parser@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.0.tgz#da950e622420bf96ca0d0f2909cdddac3acd8719" integrity sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw== +"@babel/parser@^7.20.7": + version "7.20.15" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.20.15.tgz#eec9f36d8eaf0948bb88c87a46784b5ee9fd0c89" + integrity sha512-DI4a1oZuf8wC+oAJA9RW6ga3Zbe8RZFt7kD9i4qAspz3I/yHet1VvC3DiSy/fsUvv5pvJuNPh0LPOdCcqinDPg== + +"@babel/parser@^7.21.8": + version "7.21.8" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.21.8.tgz#642af7d0333eab9c0ad70b14ac5e76dbde7bfdf8" + integrity sha512-6zavDGdzG3gUqAdWvlLFfk+36RilI+Pwyuuh7HItyeScCWP3k6i8vKclAQ0bM/0y/Kz/xiwvxhMv9MgTJP5gmA== + +"@babel/parser@^7.22.10": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.10.tgz#e37634f9a12a1716136c44624ef54283cabd3f55" + integrity sha512-lNbdGsQb9ekfsnjFGhEiF4hfFqGgfOP3H3d27re3n+CGhNuTSUEQdfWk556sTLNTloczcdM5TYF2LhzmDQKyvQ== + +"@babel/parser@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.23.9.tgz#7b903b6149b0f8fa7ad564af646c4c38a77fc44b" + integrity sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA== + "@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-safari-id-destructuring-collision-in-function-expression/-/plugin-bugfix-safari-id-destructuring-collision-in-function-expression-7.18.6.tgz#da5b8f9a580acdfbe53494dba45ea389fb09a4d2" @@ -2002,7 +2317,17 @@ "@babel/helper-plugin-utils" "^7.18.6" babel-plugin-dynamic-import-node "^2.3.3" -"@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.13.8", "@babel/plugin-transform-modules-commonjs@^7.18.6": +"@babel/plugin-transform-modules-commonjs@^7.0.0", "@babel/plugin-transform-modules-commonjs@^7.13.8": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.16.7.tgz#fd119e6a433c527d368425b45df361e1e95d3c1a" + integrity sha512-h2RP2kE7He1ZWKyAlanMZrAbdv+Acw1pA8dQZhE025WJZE2z0xzFADAinXA9fxd5bn7JnM+SdOGcndGx1ARs9w== + dependencies: + "@babel/helper-module-transforms" "^7.16.7" + "@babel/helper-plugin-utils" "^7.16.7" + "@babel/helper-simple-access" "^7.16.7" + babel-plugin-dynamic-import-node "^2.3.3" + +"@babel/plugin-transform-modules-commonjs@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.18.6.tgz#afd243afba166cca69892e24a8fd8c9f2ca87883" integrity sha512-Qfv2ZOWikpvmedXQJDSbxNqy7Xr/j2Y8/KfijM0iJyKkBTmWuvCA1yeH1yDM7NJhBW/2aXxeucLj6i80/LAJ/Q== @@ -2101,7 +2426,14 @@ dependencies: "@babel/helper-plugin-utils" "^7.18.6" -"@babel/plugin-transform-shorthand-properties@^7.0.0", "@babel/plugin-transform-shorthand-properties@^7.18.6": +"@babel/plugin-transform-shorthand-properties@^7.0.0": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.16.7.tgz#e8549ae4afcf8382f711794c0c7b6b934c5fbd2a" + integrity sha512-hah2+FEnoRoATdIb05IOXf+4GzXYTq75TVhIn1PewihbpyrNWUt2JbudKQOETWw6QpLe+AIUpJ5MVLYTQbeeUg== + dependencies: + "@babel/helper-plugin-utils" "^7.16.7" + +"@babel/plugin-transform-shorthand-properties@^7.18.6": version "7.18.6" resolved "https://registry.yarnpkg.com/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.18.6.tgz#6d6df7983d67b195289be24909e3f12a8f664dc9" integrity sha512-eCLXXJqv8okzg86ywZJbRn19YJHU4XUa55oz2wbHhaQVn/MM+XhukiT7SYqp/7o00dg52Rj51Ny+Ecw4oyoygw== @@ -2289,14 +2621,83 @@ dependencies: regenerator-runtime "^0.12.0" -"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.15.4", "@babel/runtime@^7.17.2", "@babel/runtime@^7.18.3", "@babel/runtime@^7.2.0", "@babel/runtime@^7.21.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": +"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.13", "@babel/runtime@^7.15.4", "@babel/runtime@^7.2.0", "@babel/runtime@^7.5.5", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.16.7.tgz#03ff99f64106588c9c403c6ecb8c3bafbbdff1fa" + integrity sha512-9E9FJowqAsytyOY6LG+1KuueckRL+aQW+mKvXRXnuFGyRAyepJPmEo9vgMfXUA6O9u3IeEdv9MAkppFcaQwogQ== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.10.2", "@babel/runtime@^7.12.5", "@babel/runtime@^7.18.3": + version "7.19.0" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.19.0.tgz#22b11c037b094d27a8a2504ea4dcff00f50e2259" + integrity sha512-eR8Lo9hnDS7tqkO7NsV+mKvCmv5boaXFSZ70DnfhcgiEne8hv9oCEd36Klw74EtizEqLsy4YnW8UWwpBVolHZA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.10.5": + version "7.17.8" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.8.tgz#3e56e4aff81befa55ac3ac6a0967349fd1c5bca2" + integrity sha512-dQpEpK0O9o6lj6oPu0gRDbbnk+4LeHlNcBpspf6Olzt3GIX4P1lWF1gS+pHLDFlaJvbR6q7jCfQ08zA4QJBnmA== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.11.2": + version "7.17.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.17.9.tgz#d19fbf802d01a8cb6cf053a64e472d42c434ba72" + integrity sha512-lSiBBvodq29uShpWGNbgFdKYNiFDo5/HIYsaCEY9ff4sb10x9jizo2+pRrSyF4jKZCXqgzuqBOQKbUm90gQwJg== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.13.10": + version "7.20.13" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.20.13.tgz#7055ab8a7cff2b8f6058bf6ae45ff84ad2aded4b" + integrity sha512-gt3PKXs0DBoL9xCvOIIZ2NEqAGZqHjAnmVbfQtB620V0uReIQutpel14KcneZuer7UioY8ALKZ7iocavvzTNFA== + dependencies: + regenerator-runtime "^0.13.11" + +"@babel/runtime@^7.17.2": + version "7.18.9" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.9.tgz#b4fcfce55db3d2e5e080d2490f608a3b9f407f4a" + integrity sha512-lkqXDcvlFT5rvEjiu6+QYO+1GXrEHRo2LOtS7E4GtX5ESIZOgepqsZBVIj6Pv+a6zqsya9VCgiK1KAK4BvJDAw== + dependencies: + regenerator-runtime "^0.13.4" + +"@babel/runtime@^7.21.0": version "7.21.5" resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.21.5.tgz#8492dddda9644ae3bda3b45eabe87382caee7200" integrity sha512-8jI69toZqqcsnqGGqwGS4Qb1VwLOEp4hz+CXPywcvjs60u3B4Pom/U/7rm4W8tMOYEB+E9wgD0mW1l3r8qlI9Q== dependencies: regenerator-runtime "^0.13.11" -"@babel/template@^7.18.10", "@babel/template@^7.18.6", "@babel/template@^7.22.15", "@babel/template@^7.22.5", "@babel/template@^7.3.3": +"@babel/template@^7.16.7", "@babel/template@^7.3.3": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" + integrity sha512-I8j/x8kHUrbYRTUxXrrMbfCa7jxkE7tZre39x3kjr9hvI82cK1FfqLygotcWN5kdPGWcLdWMHpSBavse5tWw3w== + dependencies: + "@babel/code-frame" "^7.16.7" + "@babel/parser" "^7.16.7" + "@babel/types" "^7.16.7" + +"@babel/template@^7.18.10", "@babel/template@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.20.7.tgz#a15090c2839a83b02aa996c0b4994005841fd5a8" + integrity sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.20.7" + "@babel/types" "^7.20.7" + +"@babel/template@^7.18.6": + version "7.18.6" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.18.6.tgz#1283f4993e00b929d6e2d3c72fdc9168a2977a31" + integrity sha512-JoDWzPe+wgBsTTgdnIma3iHNFC7YVJoPssVBDjiHfNlyt4YcunDtcDOUmfVDfCK5MfdsaIoX9PkijPhjH3nYUw== + dependencies: + "@babel/code-frame" "^7.18.6" + "@babel/parser" "^7.18.6" + "@babel/types" "^7.18.6" + +"@babel/template@^7.22.15": version "7.22.15" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.15.tgz#09576efc3830f0430f4548ef971dde1350ef2f38" integrity sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w== @@ -2305,6 +2706,15 @@ "@babel/parser" "^7.22.15" "@babel/types" "^7.22.15" +"@babel/template@^7.22.5", "@babel/template@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.23.9.tgz#f881d0487cba2828d3259dcb9ef5005a9731011a" + integrity sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/parser" "^7.23.9" + "@babel/types" "^7.23.9" + "@babel/traverse@^7.13.0", "@babel/traverse@^7.14.0", "@babel/traverse@^7.16.8", "@babel/traverse@^7.18.6", "@babel/traverse@^7.22.10", "@babel/traverse@^7.7.2": version "7.23.2" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.2.tgz#329c7a06735e144a506bdb2cad0268b7f46f4ad8" @@ -2321,7 +2731,82 @@ debug "^4.1.0" globals "^11.1.0" -"@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.16.8", "@babel/types@^7.18.13", "@babel/types@^7.18.6", "@babel/types@^7.22.10", "@babel/types@^7.22.15", "@babel/types@^7.22.5", "@babel/types@^7.23.0", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": +"@babel/traverse@^7.16.7", "@babel/traverse@^7.18.2", "@babel/traverse@^7.20.12", "@babel/traverse@^7.21.5", "@babel/traverse@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.23.9.tgz#2f9d6aead6b564669394c5ce0f9302bb65b9d950" + integrity sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg== + dependencies: + "@babel/code-frame" "^7.23.5" + "@babel/generator" "^7.23.6" + "@babel/helper-environment-visitor" "^7.22.20" + "@babel/helper-function-name" "^7.23.0" + "@babel/helper-hoist-variables" "^7.22.5" + "@babel/helper-split-export-declaration" "^7.22.6" + "@babel/parser" "^7.23.9" + "@babel/types" "^7.23.9" + debug "^4.3.1" + globals "^11.1.0" + +"@babel/types@^7.0.0", "@babel/types@^7.16.7", "@babel/types@^7.3.0", "@babel/types@^7.3.3", "@babel/types@^7.4.4": + version "7.16.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.7.tgz#4ed19d51f840ed4bd5645be6ce40775fecf03159" + integrity sha512-E8HuV7FO9qLpx6OtoGfUQ2cjIYnbFwvZWYBS+87EwtdMvmUPJSwykpovFB+8insbpF0uJcpr8KMUi64XZntZcg== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + +"@babel/types@^7.16.8": + version "7.16.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.16.8.tgz#0ba5da91dd71e0a4e7781a30f22770831062e3c1" + integrity sha512-smN2DQc5s4M7fntyjGtyIPbRJv6wW4rU/94fmYJ7PKQuZkC0qGMHXJbg6sNGt12JmVr4k5YaptI/XtiLJBnmIg== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + +"@babel/types@^7.18.13", "@babel/types@^7.20.7": + version "7.20.7" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.20.7.tgz#54ec75e252318423fc07fb644dc6a58a64c09b7f" + integrity sha512-69OnhBxSSgK0OzTJai4kyPDiKTIe3j+ctaHdIGVbRahTLAT7L3R9oeXHC2aVSuGYt3cVnoAMDmOCgJ2yaiLMvg== + dependencies: + "@babel/helper-string-parser" "^7.19.4" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + +"@babel/types@^7.18.2": + version "7.18.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.4.tgz#27eae9b9fd18e9dccc3f9d6ad051336f307be354" + integrity sha512-ThN1mBcMq5pG/Vm2IcBmPPfyPXbd8S02rS+OBIDENdufvqC7Z/jHPCv9IcP01277aKtDI8g/2XysBN4hA8niiw== + dependencies: + "@babel/helper-validator-identifier" "^7.16.7" + to-fast-properties "^2.0.0" + +"@babel/types@^7.18.6": + version "7.18.8" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.18.8.tgz#c5af199951bf41ba4a6a9a6d0d8ad722b30cd42f" + integrity sha512-qwpdsmraq0aJ3osLJRApsc2ouSJCdnMeZwB0DhbtHAtRpZNZCdlbRnHIgcRKzdE1g0iOGg644fzjOBcdOz9cPw== + dependencies: + "@babel/helper-validator-identifier" "^7.18.6" + to-fast-properties "^2.0.0" + +"@babel/types@^7.21.5": + version "7.21.5" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.21.5.tgz#18dfbd47c39d3904d5db3d3dc2cc80bedb60e5b6" + integrity sha512-m4AfNvVF2mVC/F7fDEdH2El3HzUg9It/XsCxZiOTTA3m3qYfcSVSbTfM6Q9xG+hYDniZssYhlXKKUMD5m8tF4Q== + dependencies: + "@babel/helper-string-parser" "^7.21.5" + "@babel/helper-validator-identifier" "^7.19.1" + to-fast-properties "^2.0.0" + +"@babel/types@^7.22.10", "@babel/types@^7.22.5": + version "7.22.10" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.10.tgz#4a9e76446048f2c66982d1a989dd12b8a2d2dc03" + integrity sha512-obaoigiLrlDZ7TUQln/8m4mSqIW2QFeOrCQc9r+xsaHGNoplVNYlRVpsfE8Vj35GEm2ZH4ZhrNYogs/3fj85kg== + dependencies: + "@babel/helper-string-parser" "^7.22.5" + "@babel/helper-validator-identifier" "^7.22.5" + to-fast-properties "^2.0.0" + +"@babel/types@^7.22.15", "@babel/types@^7.23.0": version "7.23.0" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.0.tgz#8c1f020c9df0e737e4e247c0619f58c68458aaeb" integrity sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg== @@ -2330,6 +2815,15 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@babel/types@^7.23.6", "@babel/types@^7.23.9": + version "7.23.9" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.23.9.tgz#1dd7b59a9a2b5c87f8b41e52770b5ecbf492e002" + integrity sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q== + dependencies: + "@babel/helper-string-parser" "^7.23.4" + "@babel/helper-validator-identifier" "^7.22.20" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -7968,13 +8462,22 @@ agent-base@^7.0.2, agent-base@^7.1.0: dependencies: debug "^4.3.4" -agentkeepalive@^4.1.3, agentkeepalive@^4.2.1: +agentkeepalive@^4.1.3: version "4.5.0" resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.5.0.tgz#2673ad1389b3c418c5a20c5d7364f93ca04be923" integrity sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew== dependencies: humanize-ms "^1.2.1" +agentkeepalive@^4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/agentkeepalive/-/agentkeepalive-4.2.1.tgz#a7975cbb9f83b367f06c90cc51ff28fe7d499717" + integrity sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA== + dependencies: + debug "^4.1.0" + depd "^1.1.2" + humanize-ms "^1.2.1" + aggregate-error@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a" @@ -8408,7 +8911,16 @@ axios@0.21.4, axios@^0.21.0, axios@^0.21.4: dependencies: follow-redirects "^1.14.0" -axios@^1.0.0, axios@^1.3.3: +axios@^1.0.0: + version "1.3.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.2.tgz#7ac517f0fa3ec46e0e636223fd973713a09c72b3" + integrity sha512-1M3O703bYqYuPhbHeya5bnhpYVsDDRyQSabNja04mZtboLNSuZ4YrltestrLXfHgmzua4TpUqRiVKbiQuo2epw== + dependencies: + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" + +axios@^1.3.3: version "1.6.0" resolved "https://registry.yarnpkg.com/axios/-/axios-1.6.0.tgz#f1e5292f26b2fd5c2e66876adc5b06cdbd7d2102" integrity sha512-EZ1DYihju9pwVB+jg67ogm+Tmqc6JmhamRN6I4Zt8DfZu5lbcQGw3ozH9lFejSJgs/ibaef3A9PMXPLeefFGJg== @@ -8790,6 +9302,16 @@ browserslist@^4.14.5, browserslist@^4.21.1, browserslist@^4.21.4, browserslist@^ node-releases "^2.0.13" update-browserslist-db "^1.0.11" +browserslist@^4.22.2: + version "4.22.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.3.tgz#299d11b7e947a6b843981392721169e27d60c5a6" + integrity sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A== + dependencies: + caniuse-lite "^1.0.30001580" + electron-to-chromium "^1.4.648" + node-releases "^2.0.14" + update-browserslist-db "^1.0.13" + bs-logger@0.x: version "0.2.6" resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8" @@ -9011,10 +9533,10 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001517, caniuse-lite@~1.0.0: - version "1.0.30001587" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001587.tgz#a0bce920155fa56a1885a69c74e1163fc34b4881" - integrity sha512-HMFNotUmLXn71BQxg8cijvqxnIAofforZOwGsxyXJ0qugTdspUF4sPSJ2vhgprHCB996tIDzEq1ubumPDV8ULA== +caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001580, caniuse-lite@~1.0.0: + version "1.0.30001571" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001571.tgz#4182e93d696ff42930f4af7eba515ddeb57917ac" + integrity sha512-tYq/6MoXhdezDLFZuCO/TKboTzuQ/xR5cFdgXPfDtM7/kchBO3b4VWghE/OAi/DV7tTdhmLjZiZBZi1fA/GheQ== capital-case@^1.0.4: version "1.0.4" @@ -9075,7 +9597,7 @@ chalk@4.1.2, chalk@^4.0.0, chalk@^4.0.2, chalk@^4.1.0, chalk@^4.1.1, chalk@^4.1. ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^2.4.2: +chalk@^2.3.2, chalk@^2.4.2: version "2.4.2" resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424" integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ== @@ -9177,7 +9699,22 @@ cheerio@^1.0.0-rc.10: parse5-htmlparser2-tree-adapter "^6.0.1" tslib "^2.2.0" -chokidar@^3.3.1, chokidar@^3.4.0, chokidar@^3.5.1, chokidar@^3.5.3: +chokidar@^3.3.1, chokidar@^3.4.0, chokidar@^3.5.1: + version "3.5.2" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.2.tgz#dba3976fcadb016f66fd365021d91600d01c1e75" + integrity sha512-ekGhOnNVPgT77r4K/U3GDhu+FQ2S8TnK/s2KbIGXi0SZWuwkZ2QNyfWdZW+TVfn84DpEP7rLeCt2UI6bJ8GwbQ== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + +chokidar@^3.5.3: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -10307,10 +10844,10 @@ depd@2.0.0: resolved "https://registry.yarnpkg.com/depd/-/depd-2.0.0.tgz#b696163cc757560d09cf22cc8fad1571b79e76df" integrity sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw== -depd@~1.1.2: +depd@^1.1.2, depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" - integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak= + integrity sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ== dependency-graph@^0.11.0: version "0.11.0" @@ -10394,6 +10931,11 @@ diff-sequences@^29.4.3: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-29.4.3.tgz#9314bc1fabe09267ffeca9cbafc457d8499a13f2" integrity sha512-ofrBgwpPhCD85kMKtE9RYFFq6OC1A89oW2vvgWZNCwxrUpRUILopY7lsYyMDSjc8g6U6aiO0Qubg6r4Wgt5ZnA== +diff@^3.5.0: + version "3.5.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" + integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== + diff@^4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" @@ -10588,6 +11130,7 @@ draft-js-utils@^1.4.0: "draft-js@https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6": version "0.10.5" + uid "025fddba56f21aaf3383aee778e0b17025c9a7bc" resolved "https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6#025fddba56f21aaf3383aee778e0b17025c9a7bc" dependencies: fbjs "^0.8.15" @@ -10658,6 +11201,11 @@ electron-to-chromium@^1.4.477: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.490.tgz#d99286f6e915667fa18ea4554def1aa60eb4d5f1" integrity sha512-6s7NVJz+sATdYnIwhdshx/N/9O6rvMxmhVoDSDFdj6iA45gHR8EQje70+RYsF4GeB+k0IeNSBnP7yG9ZXJFr7A== +electron-to-chromium@^1.4.648: + version "1.4.660" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.660.tgz#80be71d08c1224980e645904ab9155f3fa54a1ea" + integrity sha512-1BqvQG0BBQrAA7FVL2EMrb5A1sVyXF3auwJneXjGWa1TpN+g0C4KbUsYWePz6OZ0mXZfXGy+RmQDELJWwE8v/Q== + email-addresses@^3.0.1: version "3.1.0" resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-3.1.0.tgz#cabf7e085cbdb63008a70319a74e6136188812fb" @@ -11640,7 +12188,7 @@ fs-constants@^1.0.0: resolved "https://registry.yarnpkg.com/fs-constants/-/fs-constants-1.0.0.tgz#6be0de9be998ce16af8afc24497b9ee9b7ccd9ad" integrity sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow== -fs-extra@^11.0.0, fs-extra@^11.1.0: +fs-extra@^11.0.0: version "11.1.1" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.1.tgz#da69f7c39f3b002378b0954bb6ae7efdc0876e2d" integrity sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ== @@ -11649,6 +12197,15 @@ fs-extra@^11.0.0, fs-extra@^11.1.0: jsonfile "^6.0.1" universalify "^2.0.0" +fs-extra@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-11.1.0.tgz#5784b102104433bb0e090f48bfc4a30742c357ed" + integrity sha512-0rcTq621PD5jM/e0a3EJoGC/1TC5ZBCERW82LQuwfGnCa1V8w7dpYH1yNu+SLb6E5dkeCBzKEyLGlFrnr+dUyw== + dependencies: + graceful-fs "^4.2.0" + jsonfile "^6.0.1" + universalify "^2.0.0" + fs-extra@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0" @@ -11715,6 +12272,11 @@ function-bind@^1.1.1: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +function-bind@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" + integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== + functional-red-black-tree@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" @@ -11900,6 +12462,17 @@ getpass@^0.1.1: dependencies: assert-plus "^1.0.0" +git-diff@^2.0.6: + version "2.0.6" + resolved "https://registry.yarnpkg.com/git-diff/-/git-diff-2.0.6.tgz#4a8ece670d64d1f9f4e68191ad8b1013900f6c1e" + integrity sha512-/Iu4prUrydE3Pb3lCBMbcSNIf81tgGt0W1ZwknnyF62t3tHmtiJTRj0f+1ZIhp3+Rh0ktz1pJVoa7ZXUCskivA== + dependencies: + chalk "^2.3.2" + diff "^3.5.0" + loglevel "^1.6.1" + shelljs "^0.8.1" + shelljs.exec "^1.1.7" + git-node-fs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/git-node-fs/-/git-node-fs-1.0.0.tgz#49b215e242ebe43aa4c7561bbba499521752080f" @@ -12350,6 +12923,13 @@ has@^1.0.3: dependencies: function-bind "^1.1.1" +hasown@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.0.tgz#f4c513d454a57b7c7e1650778de226b11700546c" + integrity sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA== + dependencies: + function-bind "^1.1.2" + hdr-histogram-js@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz#0b860534655722b6e3f3e7dca7b78867cf43dcb5" @@ -12852,6 +13432,11 @@ internal-slot@^1.0.3: has "^1.0.3" side-channel "^1.0.4" +interpret@^1.0.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.4.0.tgz#665ab8bc4da27a774a40584e812e3e0fa45b1a1e" + integrity sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA== + interpret@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/interpret/-/interpret-2.2.0.tgz#1a78a0b5965c40a5416d007ad6f50ad27c417df9" @@ -12968,6 +13553,13 @@ is-ci@^2.0.0: dependencies: ci-info "^2.0.0" +is-core-module@^2.13.0: + version "2.13.1" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.13.1.tgz#ad0d7532c6fea9da1ebdc82742d74525c6273384" + integrity sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw== + dependencies: + hasown "^2.0.0" + is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.1, is-core-module@^2.9.0: version "2.11.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" @@ -14172,20 +14764,21 @@ koalas@^1.0.2: resolved "https://registry.yarnpkg.com/koalas/-/koalas-1.0.2.tgz#318433f074235db78fae5661a02a8ca53ee295cd" integrity sha1-MYQz8HQjXbePrlZhoCqMpT7ilc0= -kysely-codegen@^0.10.0: - version "0.10.0" - resolved "https://registry.yarnpkg.com/kysely-codegen/-/kysely-codegen-0.10.0.tgz#ccc4a1d9c1a2f334a35d820554880251f2386524" - integrity sha512-EV2v9yr9N9WrUPrURvFl5FPTtz39npsi1p1tLpJwEcFeOAKAPJruBpAASvnZWdqu5Pw/aaTssStE3qcI2sfxMQ== +kysely-codegen@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/kysely-codegen/-/kysely-codegen-0.11.0.tgz#7506955c4c09201b571d528b42ffec8a1869160b" + integrity sha512-8aklzXygjANshk5BoGSQ0BWukKIoPL4/k1iFWyteGUQ/VtB1GlyrELBZv1GglydjLGECSSVDpsOgEXyWQmuksg== dependencies: chalk "4.1.2" dotenv "^16.0.3" + git-diff "^2.0.6" micromatch "^4.0.5" minimist "^1.2.8" -kysely@^0.26.3: - version "0.26.3" - resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.26.3.tgz#45fdd0153d8c9418b0ea9a6f05ed46b95ed27678" - integrity sha512-yWSgGi9bY13b/W06DD2OCDDHQmq1kwTGYlQ4wpZkMOJqMGCstVCFIvxCCVG4KfY1/3G0MhDAcZsip/Lw8/vJWw== +kysely@^0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.27.2.tgz#b289ce5e561064ec613a17149b7155783d2b36de" + integrity sha512-DmRvEfiR/NLpgsTbSxma2ldekhsdcd65+MNiKXyd/qj7w7X5e3cLkXxcj+MypsRDjPhHQ/CD5u3Eq1sBYzX0bw== launch-editor@^2.6.0: version "2.6.0" @@ -14628,6 +15221,11 @@ log-update@^4.0.0: slice-ansi "^4.0.0" wrap-ansi "^6.2.0" +loglevel@^1.6.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/loglevel/-/loglevel-1.9.1.tgz#d63976ac9bcd03c7c873116d41c2a85bafff1be7" + integrity sha512-hP3I3kCrDIMuRwAwHltphhDM1r8i55H33GgqjXbrisuJhF4kRhW1dNuxsRklp4bXl8DSdLaNLuiL4A/LWRfxvg== + long@^5.0.0: version "5.2.3" resolved "https://registry.yarnpkg.com/long/-/long-5.2.3.tgz#a3ba97f3877cf1d778eccbcb048525ebb77499e1" @@ -15512,6 +16110,11 @@ node-releases@^2.0.13: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== +node-releases@^2.0.14: + version "2.0.14" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" + integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== + node-rsa@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/node-rsa/-/node-rsa-1.1.1.tgz#efd9ad382097782f506153398496f79e4464434d" @@ -17853,6 +18456,13 @@ recast@^0.21.0: source-map "~0.6.1" tslib "^2.0.1" +rechoir@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" + integrity sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw== + dependencies: + resolve "^1.1.6" + rechoir@^0.7.0: version "0.7.1" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.7.1.tgz#9478a96a1ca135b5e88fc027f03ee92d6c645686" @@ -17895,6 +18505,13 @@ redis-parser@^3.0.0: dependencies: redis-errors "^1.0.0" +redlock@^5.0.0-beta.2: + version "5.0.0-beta.2" + resolved "https://registry.yarnpkg.com/redlock/-/redlock-5.0.0-beta.2.tgz#a629c07e07d001c0fdd9f2efa614144c4416fe44" + integrity sha512-2RDWXg5jgRptDrB1w9O/JgSZC0j7y4SlaXnor93H/UJm/QyDiFgBKNtrh0TI6oCXqYSaSoXxFh6Sd3VtYfhRXw== + dependencies: + node-abort-controller "^3.0.1" + redux@^4.0.0, redux@^4.0.4: version "4.1.2" resolved "https://registry.yarnpkg.com/redux/-/redux-4.1.2.tgz#140f35426d99bb4729af760afcf79eaaac407104" @@ -17924,11 +18541,16 @@ regenerator-runtime@^0.12.0: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.12.1.tgz#fa1a71544764c036f8c49b13a08b2594c9f8a0de" integrity sha512-odxIc1/vDlo4iZcfXqRYFj0vpXFNoGdKMAUieAlFYO6m/nl5e9KR/beGf41z4a1FI+aQgtjhuaSlDxQ0hmkrHg== -regenerator-runtime@^0.13.11, regenerator-runtime@^0.13.5: +regenerator-runtime@^0.13.11: version "0.13.11" resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz#f6dca3e7ceec20590d07ada785636a90cdca17f9" integrity sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg== +regenerator-runtime@^0.13.4, regenerator-runtime@^0.13.5: + version "0.13.9" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" + integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== + regenerator-transform@^0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" @@ -18133,6 +18755,15 @@ resolve@^1.0.0, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14. path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" +resolve@^1.1.6: + version "1.22.8" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" + integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== + dependencies: + is-core-module "^2.13.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^2.0.0-next.3: version "2.0.0-next.3" resolved "https://registry.yarnpkg.com/resolve/-/resolve-2.0.0-next.3.tgz#d41016293d4a8586a39ca5d9b5f15cbea1f55e46" @@ -18560,6 +19191,20 @@ shell-quote@^1.7.3, shell-quote@^1.8.0: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== +shelljs.exec@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/shelljs.exec/-/shelljs.exec-1.1.8.tgz#6f3c8dd017cb96d2dea82e712b758eab4fc2f68c" + integrity sha512-vFILCw+lzUtiwBAHV8/Ex8JsFjelFMdhONIsgKNLgTzeRckp2AOYRQtHJE/9LhNvdMmE27AGtzWx0+DHpwIwSw== + +shelljs@^0.8.1: + version "0.8.5" + resolved "https://registry.yarnpkg.com/shelljs/-/shelljs-0.8.5.tgz#de055408d8361bed66c669d2f000538ced8ee20c" + integrity sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow== + dependencies: + glob "^7.0.0" + interpret "^1.0.0" + rechoir "^0.6.2" + shimmer@^1.1.0, shimmer@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" @@ -18831,10 +19476,10 @@ source-map-support@0.5.21, source-map-support@^0.5.12, source-map-support@^0.5.1 buffer-from "^1.0.0" source-map "^0.6.0" -source-map@^0.5.7: +source-map@^0.5.0, source-map@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" - integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w= + integrity sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ== source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, source-map@~0.6.1: version "0.6.1" @@ -19127,7 +19772,16 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -string-width@^5.0.0, string-width@^5.0.1, string-width@^5.1.2: +string-width@^5.0.0: + version "5.1.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.0.tgz#5ab00980cfb29f43e736b113a120a73a0fb569d3" + integrity sha512-7x54QnN21P+XL/v8SuNKvfgsUre6PXpN7mc77N3HlZv+f1SBRGmjxtOud2Z6FZ8DmdkD/IdjCaf9XXbnqmTZGQ== + dependencies: + eastasianwidth "^0.2.0" + emoji-regex "^9.2.2" + strip-ansi "^7.0.1" + +string-width@^5.0.1, string-width@^5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== @@ -20244,6 +20898,14 @@ update-browserslist-db@^1.0.11: escalade "^3.1.1" picocolors "^1.0.0" +update-browserslist-db@^1.0.13: + version "1.0.13" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" + integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== + dependencies: + escalade "^3.1.1" + picocolors "^1.0.0" + upper-case-first@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/upper-case-first/-/upper-case-first-2.0.2.tgz#992c3273f882abd19d1e02894cc147117f844324" @@ -21239,7 +21901,7 @@ yargs-parser@20.2.4: resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== -yargs-parser@21.1.1, yargs-parser@^21.0.1, yargs-parser@^21.1.1: +yargs-parser@21.1.1, yargs-parser@^21.0.0, yargs-parser@^21.0.1, yargs-parser@^21.1.1: version "21.1.1" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== @@ -21287,7 +21949,20 @@ yargs@^16.2.0, yargs@~16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.0.0, yargs@^17.0.1, yargs@^17.3.1, yargs@^17.6.2, yargs@^17.7.1, yargs@^17.7.2: +yargs@^17.0.0, yargs@^17.0.1: + version "17.3.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.3.1.tgz#da56b28f32e2fd45aefb402ed9c26f42be4c07b9" + integrity sha512-WUANQeVgjLbNsEmGk20f+nlHgOqzRFpiGWVaBrYGYIGANIIu3lWjoyi0fNlFmJkvfhCZ6BXINe7/W2O2bV4iaA== + dependencies: + cliui "^7.0.2" + escalade "^3.1.1" + get-caller-file "^2.0.5" + require-directory "^2.1.1" + string-width "^4.2.3" + y18n "^5.0.5" + yargs-parser "^21.0.0" + +yargs@^17.3.1, yargs@^17.6.2, yargs@^17.7.1, yargs@^17.7.2: version "17.7.2" resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.7.2.tgz#991df39aca675a192b816e1e0363f9d75d2aa269" integrity sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w== From 1008578fe38979940659d239bab584e2524efcad Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 27 Feb 2024 15:47:13 -0800 Subject: [PATCH 015/529] merge production to avoid force push (#9461) Signed-off-by: Matt Krick --- .github/workflows/release-to-staging.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-to-staging.yml b/.github/workflows/release-to-staging.yml index 6e9225bf871..23428e40d7c 100644 --- a/.github/workflows/release-to-staging.yml +++ b/.github/workflows/release-to-staging.yml @@ -99,7 +99,7 @@ jobs: git config user.name github-actions git config user.email github-actions@github.com git checkout -b "release/v${{ env.ACTION_VERSION }}" - git merge -s ours origin/production + git merge -s ours production git push --set-upstream origin "release/v${{ env.ACTION_VERSION }}" gh pr create \ --assignee ${{ github.actor }} \ From 20ca927985830c95e9288bc97de4d667312d5e96 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 15:52:39 -0800 Subject: [PATCH 016/529] chore(release): release v7.19.0 (#9460) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 28707a42151..5987f97c1f9 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.18.1" + ".": "7.19.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d1147fe08f..95e59988ef7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.19.0](https://github.com/ParabolInc/parabol/compare/v7.18.1...v7.19.0) (2024-02-27) + + +### Added + +* embedder service ([#9417](https://github.com/ParabolInc/parabol/issues/9417)) ([55faa17](https://github.com/ParabolInc/parabol/commit/55faa17ada5b1bd49182a29341b3465a82d2ddfd)) + ## [7.18.1](https://github.com/ParabolInc/parabol/compare/v7.18.0...v7.18.1) (2024-02-27) diff --git a/package.json b/package.json index fbc0c896925..ac443d0a50b 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.18.1", + "version": "7.19.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 5619754d830..0a26dc6eab0 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.18.1", + "version": "7.19.0", "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.18.1" + "parabol-server": "7.19.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 141dca1fe36..d87a2711aa8 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.18.1", + "version": "7.19.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 1f7848776eb..eafe4276efd 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.18.1", + "version": "7.19.0", "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.18.1", - "parabol-server": "7.18.1", + "parabol-client": "7.19.0", + "parabol-server": "7.19.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 3331444dcae..be7381348af 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.18.1", + "version": "7.19.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 18ca0f00370..482d4e39efd 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.18.1", + "version": "7.19.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.11.0", - "parabol-client": "7.18.1", + "parabol-client": "7.19.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 7bd880314f6f48c897a9a708b2d6435b257fae90 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 27 Feb 2024 15:59:07 -0800 Subject: [PATCH 017/529] fix: checkout prod before merging it (#9463) Signed-off-by: Matt Krick --- .github/workflows/release-to-staging.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/release-to-staging.yml b/.github/workflows/release-to-staging.yml index 23428e40d7c..965fc86deaa 100644 --- a/.github/workflows/release-to-staging.yml +++ b/.github/workflows/release-to-staging.yml @@ -14,6 +14,10 @@ jobs: id-token: "write" pull-requests: "write" steps: + - name: Checkout production + uses: actions/checkout@v3 + with: + ref: production - name: Checkout uses: actions/checkout@v3 - name: Setup environment variables From 12ba80ec5fd41496126988507929279fb15e9999 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:09:11 -0800 Subject: [PATCH 018/529] chore(release): release v7.19.1 (#9464) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 5987f97c1f9..0b5216f29bb 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.19.0" + ".": "7.19.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 95e59988ef7..8fd3aabcae1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.19.1](https://github.com/ParabolInc/parabol/compare/v7.19.0...v7.19.1) (2024-02-27) + + +### Fixed + +* checkout prod before merging it ([#9463](https://github.com/ParabolInc/parabol/issues/9463)) ([7bd8803](https://github.com/ParabolInc/parabol/commit/7bd880314f6f48c897a9a708b2d6435b257fae90)) + ## [7.19.0](https://github.com/ParabolInc/parabol/compare/v7.18.1...v7.19.0) (2024-02-27) diff --git a/package.json b/package.json index ac443d0a50b..c0a3b7c159d 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.19.0", + "version": "7.19.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 0a26dc6eab0..93a7eb54c77 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.19.0", + "version": "7.19.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.19.0" + "parabol-server": "7.19.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index d87a2711aa8..bc0bce70b96 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.19.0", + "version": "7.19.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index eafe4276efd..b6354eaa7c9 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.19.0", + "version": "7.19.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.19.0", - "parabol-server": "7.19.0", + "parabol-client": "7.19.1", + "parabol-server": "7.19.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index be7381348af..926879401ae 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.19.0", + "version": "7.19.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 482d4e39efd..3cc17f95bd4 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.19.0", + "version": "7.19.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.11.0", - "parabol-client": "7.19.0", + "parabol-client": "7.19.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 9e90b9df95b8505c0e1e50d4e8e4f18c73ef17cd Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 27 Feb 2024 16:13:57 -0800 Subject: [PATCH 019/529] fix: mrege origin/production strategy (#9465) Signed-off-by: Matt Krick --- .github/workflows/release-to-staging.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-to-staging.yml b/.github/workflows/release-to-staging.yml index 965fc86deaa..22efc4326c4 100644 --- a/.github/workflows/release-to-staging.yml +++ b/.github/workflows/release-to-staging.yml @@ -103,7 +103,7 @@ jobs: git config user.name github-actions git config user.email github-actions@github.com git checkout -b "release/v${{ env.ACTION_VERSION }}" - git merge -s ours production + git merge -s ours origin/production git push --set-upstream origin "release/v${{ env.ACTION_VERSION }}" gh pr create \ --assignee ${{ github.actor }} \ From e67ca9100406b1da5e757ceebe7954525e889091 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:19:34 -0800 Subject: [PATCH 020/529] chore(release): release v7.19.2 (#9466) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0b5216f29bb..f7e3c7439f1 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.19.1" + ".": "7.19.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fd3aabcae1..b59c8a24c45 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.19.2](https://github.com/ParabolInc/parabol/compare/v7.19.1...v7.19.2) (2024-02-28) + + +### Fixed + +* mrege origin/production strategy ([#9465](https://github.com/ParabolInc/parabol/issues/9465)) ([9e90b9d](https://github.com/ParabolInc/parabol/commit/9e90b9df95b8505c0e1e50d4e8e4f18c73ef17cd)) + ## [7.19.1](https://github.com/ParabolInc/parabol/compare/v7.19.0...v7.19.1) (2024-02-27) diff --git a/package.json b/package.json index c0a3b7c159d..0b090221999 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.19.1", + "version": "7.19.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 93a7eb54c77..9b0cdf6e408 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.19.1", + "version": "7.19.2", "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.19.1" + "parabol-server": "7.19.2" } } diff --git a/packages/client/package.json b/packages/client/package.json index bc0bce70b96..fde694d8712 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.19.1", + "version": "7.19.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index b6354eaa7c9..90360640290 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.19.1", + "version": "7.19.2", "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.19.1", - "parabol-server": "7.19.1", + "parabol-client": "7.19.2", + "parabol-server": "7.19.2", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 926879401ae..6bf9e8fb58f 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.19.1", + "version": "7.19.2", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 3cc17f95bd4..03c796234f6 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.19.1", + "version": "7.19.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.11.0", - "parabol-client": "7.19.1", + "parabol-client": "7.19.2", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 581f0cfa2255bbeb438c53b2b5f4d8ceb6a0b0cc Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 27 Feb 2024 16:28:34 -0800 Subject: [PATCH 021/529] fix: force push 5 (#9467) Signed-off-by: Matt Krick --- .github/workflows/release-to-staging.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-to-staging.yml b/.github/workflows/release-to-staging.yml index 22efc4326c4..c6e3601f2ff 100644 --- a/.github/workflows/release-to-staging.yml +++ b/.github/workflows/release-to-staging.yml @@ -103,7 +103,7 @@ jobs: git config user.name github-actions git config user.email github-actions@github.com git checkout -b "release/v${{ env.ACTION_VERSION }}" - git merge -s ours origin/production + git merge -s ours origin/production --allow-unrelated-histories git push --set-upstream origin "release/v${{ env.ACTION_VERSION }}" gh pr create \ --assignee ${{ github.actor }} \ From b52faf2accf88492fdf641825e554f8451bd2fd7 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:34:40 -0800 Subject: [PATCH 022/529] chore(release): release v7.19.3 (#9468) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f7e3c7439f1..517aeb7a63e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.19.2" + ".": "7.19.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b59c8a24c45..579182dc3f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.19.3](https://github.com/ParabolInc/parabol/compare/v7.19.2...v7.19.3) (2024-02-28) + + +### Fixed + +* force push 5 ([#9467](https://github.com/ParabolInc/parabol/issues/9467)) ([581f0cf](https://github.com/ParabolInc/parabol/commit/581f0cfa2255bbeb438c53b2b5f4d8ceb6a0b0cc)) + ## [7.19.2](https://github.com/ParabolInc/parabol/compare/v7.19.1...v7.19.2) (2024-02-28) diff --git a/package.json b/package.json index 0b090221999..cc2eb303c5d 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.19.2", + "version": "7.19.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 9b0cdf6e408..ff73a275826 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.19.2", + "version": "7.19.3", "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.19.2" + "parabol-server": "7.19.3" } } diff --git a/packages/client/package.json b/packages/client/package.json index fde694d8712..364dbda0932 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.19.2", + "version": "7.19.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 90360640290..84dbff4bdfb 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.19.2", + "version": "7.19.3", "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.19.2", - "parabol-server": "7.19.2", + "parabol-client": "7.19.3", + "parabol-server": "7.19.3", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 6bf9e8fb58f..f105f56d7da 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.19.2", + "version": "7.19.3", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 03c796234f6..625dc427b3f 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.19.2", + "version": "7.19.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.11.0", - "parabol-client": "7.19.2", + "parabol-client": "7.19.3", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 5b9526c092f7f8675ad2a442da4440e2507cbdcc Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Wed, 28 Feb 2024 09:59:59 +0000 Subject: [PATCH 023/529] fix: limit invites from spammers (#9416) * fix: limit invites from spammers * update where we check pending emails * check total plus pending invites * use invitees instead of pending --- .../mutations/helpers/inviteToTeamHelper.ts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts b/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts index c78036add14..053ca0f381f 100644 --- a/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts +++ b/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts @@ -37,6 +37,21 @@ const inviteToTeamHelper = async ( const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} + const [total, pending] = await Promise.all([ + r.table('TeamInvitation').getAll(teamId, {index: 'teamId'}).count().run(), + r + .table('TeamInvitation') + .getAll(teamId, {index: 'teamId'}) + .filter({acceptedAt: null}) + .count() + .run() + ]) + const accepted = total - pending + // if no one has accepted one of their 100+ invites, don't trust them + if (accepted === 0 && total + invitees.length >= 100) { + return standardError(new Error('Exceeded unaccepted invitation limit'), {userId: viewerId}) + } + const untrustedDomains = ['tempmail.cn', 'qq.com'] const filteredInvitees = invitees.filter( (invitee) => !untrustedDomains.includes(getDomainFromEmail(invitee).toLowerCase()) From 9cec00a5fd0b46c73ebdde27e6d966b485216132 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Wed, 28 Feb 2024 19:20:49 +0100 Subject: [PATCH 024/529] fix: Fetch Jira projects in parallel (#9456) Previously we tried to fetch more projects per page, but Jira only ever returns 50 max. Instead once we know how many projects there are after fetching the first page, we fetch all remaining pages in parallel. --- .../server/utils/AtlassianServerManager.ts | 54 +++++++++++++++---- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/packages/server/utils/AtlassianServerManager.ts b/packages/server/utils/AtlassianServerManager.ts index d84a448f7be..2cd82d3e8e4 100644 --- a/packages/server/utils/AtlassianServerManager.ts +++ b/packages/server/utils/AtlassianServerManager.ts @@ -358,7 +358,38 @@ class AtlassianServerManager extends AtlassianManager { async getAllProjects(cloudIds: string[]) { const projects = [] as (JiraProject & {cloudId: string})[] let error: Error | undefined - const getProjectPage = async (cloudId: string, url: string): Promise => { + const getProjectsPage = async ( + cloudId: string, + startAt: number, + maxResults: number + ): Promise => { + const url = `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/project/search?orderBy=name&startAt=${startAt}` + const res = await this.get(url) + if (res instanceof Error || res instanceof RateLimitError) { + error = res + } else { + const pagedProjects = res.values.map((project) => ({ + ...project, + cloudId + })) + projects.push(...pagedProjects) + + if (pagedProjects.length < maxResults && res.nextPage) { + Logger.log( + 'Underfetched in getAllProjects, requested', + maxResults, + 'got', + pagedProjects.length + ) + const nextStart = res.startAt + pagedProjects.length + const nextMaxResults = maxResults - pagedProjects.length + return getProjectsPage(cloudId, nextStart, nextMaxResults) + } + } + } + + const getProjects = async (cloudId: string) => { + const url = `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/project/search?orderBy=name` const res = await this.get(url) if (res instanceof Error || res instanceof RateLimitError) { error = res @@ -369,19 +400,20 @@ class AtlassianServerManager extends AtlassianManager { })) projects.push(...pagedProjects) if (res.nextPage) { - Logger.log('AtlassianServerManager.getAllProjects fetching more results', res.total) - return getProjectPage(cloudId, res.nextPage) + const {total} = res + const nextStart = res.startAt + pagedProjects.length + const fetches = [] as Array> + // 50 is the default maxResults for Jira, Jira does not respond with more than that + const maxResults = 50 + for (let i = nextStart; i < total; i += maxResults) { + fetches.push(getProjectsPage(cloudId, i, maxResults)) + } + await Promise.all(fetches) } } } - await Promise.all( - cloudIds.map((cloudId) => - getProjectPage( - cloudId, - `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/project/search?orderBy=name&maxResults=500` - ) - ) - ) + + await Promise.all(cloudIds.map((cloudId) => getProjects(cloudId))) if (error) { Logger.log('getAllProjects ERROR:', error) From 00092ec55659d1441e9566d501940dcc6fcf07f4 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 28 Feb 2024 12:56:50 -0800 Subject: [PATCH 025/529] fix: replace lone surrogates in draft-js content (#9415) * fix: replace lone surrogates in draft-js content Signed-off-by: Matt Krick * fix typo Signed-off-by: Matt Krick * fix: eslint errors Signed-off-by: Matt Krick --------- Signed-off-by: Matt Krick --- .eslintrc.js | 1 + package.json | 7 +- packages/chronos/package.json | 2 +- packages/client/package.json | 2 +- packages/client/utils/AtlassianManager.ts | 2 +- .../draftjs/extractTextFromDraftString.ts | 3 +- packages/gql-executor/package.json | 4 +- packages/integration-tests/package.json | 5 +- packages/server/package.json | 4 +- packages/server/tsconfig.json | 15 +- yarn.lock | 249 ++++++++++++------ 11 files changed, 181 insertions(+), 113 deletions(-) diff --git a/.eslintrc.js b/.eslintrc.js index 111e259c651..1962c0f1b60 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -20,6 +20,7 @@ module.exports = { '@typescript-eslint/no-use-before-define': 'off', '@typescript-eslint/explicit-member-accessibility': 'off', '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/no-duplicate-enum-values': 'off', '@typescript-eslint/no-unused-vars': 'off', '@typescript-eslint/no-var-requires': 'off', '@typescript-eslint/no-parameter-properties': 'off', diff --git a/package.json b/package.json index cc2eb303c5d..5b7253fec2e 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "test:server": "yarn workspace parabol-server test" }, "resolutions": { - "typescript": "4.9.5", + "typescript": "^5.3.3", "hoist-non-react-statics": "^3.3.0", "@types/react": "16.9.11", "@types/react-dom": "16.9.4", @@ -92,8 +92,8 @@ "@types/dotenv": "^6.1.1", "@types/jscodeshift": "^0.11.3", "@types/lodash.toarray": "^4.4.7", - "@typescript-eslint/eslint-plugin": "5.17.0", - "@typescript-eslint/parser": "5.17.0", + "@typescript-eslint/eslint-plugin": "^6.21.0", + "@typescript-eslint/parser": "^6.21.0", "autoprefixer": "^10.4.13", "babel-loader": "^9.1.2", "concurrently": "^8.0.1", @@ -123,6 +123,7 @@ "tailwindcss": "^3.2.7", "terser-webpack-plugin": "^5.3.9", "ts-loader": "9.2.6", + "typescript": "^5.3.3", "vscode-apollo-relay": "^1.5.0", "webpack": "^5.89.0", "webpack-cli": "4.9.1", diff --git a/packages/chronos/package.json b/packages/chronos/package.json index ff73a275826..39bec48dcf6 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -21,7 +21,7 @@ }, "devDependencies": { "@types/cron": "^2.0.1", - "@types/node": "^16.11.62" + "@types/node": "^20.11.17" }, "dependencies": { "cron": "^2.3.1", diff --git a/packages/client/package.json b/packages/client/package.json index 364dbda0932..19a38863130 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -60,7 +60,7 @@ "prettier": "^2.8.8", "react-refresh": "^0.9.0", "strict-event-emitter-types": "^2.0.0", - "typescript": "4.9.5", + "typescript": "^5.3.3", "webpack-dev-server": "^4.15.1" }, "dependencies": { diff --git a/packages/client/utils/AtlassianManager.ts b/packages/client/utils/AtlassianManager.ts index c501b9bc768..5726a0e5636 100644 --- a/packages/client/utils/AtlassianManager.ts +++ b/packages/client/utils/AtlassianManager.ts @@ -40,7 +40,7 @@ export type JiraPermissionScope = export class RateLimitError { retryAt: Date - name: 'RateLimitError' = 'RateLimitError' + name = 'RateLimitError' as const message: string constructor(message: string, retryAt: Date) { diff --git a/packages/client/utils/draftjs/extractTextFromDraftString.ts b/packages/client/utils/draftjs/extractTextFromDraftString.ts index a7ac9f22b4f..95e03014184 100644 --- a/packages/client/utils/draftjs/extractTextFromDraftString.ts +++ b/packages/client/utils/draftjs/extractTextFromDraftString.ts @@ -2,7 +2,8 @@ import {RawDraftContentState} from 'draft-js' const extractTextFromDraftString = (content: string) => { const parsedContent = JSON.parse(content) as RawDraftContentState - const textBlocks = parsedContent.blocks.map(({text}) => text) + // toWellFormed replaces lone surrogates with replacement char (e.g. emoji that only has its first code point) + const textBlocks = parsedContent.blocks.map(({text}) => (text as any).toWellFormed()) return textBlocks.join('\n') } diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 84dbff4bdfb..10852a68c4a 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -18,12 +18,12 @@ "devDependencies": { "@babel/cli": "7.18.6", "@babel/core": "7.18.6", - "@types/node": "^16.11.62", + "@types/node": "^20.11.17", "babel-plugin-inline-import": "^3.0.0", "chokidar": "^3.3.1", "sucrase": "^3.32.0", "ts-node-dev": "^1.0.0-pre.44", - "typescript": "4.9.5" + "typescript": "^5.3.3" }, "dependencies": { "dd-trace": "^4.2.0", diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index f105f56d7da..a70463e1320 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -11,13 +11,10 @@ }, "devDependencies": { "@playwright/test": "^1.34.3", - "@types/node": "^16.11.62", - "@typescript-eslint/eslint-plugin": "^5.17.0", - "@typescript-eslint/parser": "^5.17.0", "eslint": "^8.8.0", "eslint-config-prettier": "^8.5.0", "lint-staged": "^12.3.3", "ts-app-env": "^1.4.2", - "typescript": "^4.5.5" + "typescript": "^5.3.3" } } diff --git a/packages/server/package.json b/packages/server/package.json index 625dc427b3f..79b79743566 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -39,7 +39,7 @@ "@types/jsonwebtoken": "^8.3.0", "@types/mime-types": "^2.1.0", "@types/ms": "^0.7.30", - "@types/node": "^16.11.62", + "@types/node": "^20.11.17", "@types/nodemailer": "^6.4.14", "@types/relay-runtime": "^14.1.9", "@types/sharp": "^0.32.0", @@ -68,7 +68,7 @@ "sucrase": "^3.32.0", "ts-jest": "^29.1.0", "ts-node": "^8.6.2", - "typescript": "4.9.5", + "typescript": "^5.3.3", "url-loader": "4.1.1", "vscode-apollo-relay": "^1.5.0", "webpack-bundle-analyzer": "4.3.0", diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index 4a095c384e0..a1a9c3130df 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -8,15 +8,8 @@ "parabol-client/*": ["client/*"], "~/*": ["client/*"] }, - "lib": [ - "esnext", - "dom" - ], - "types": [ - "node", - "jest", - "jest-extended" - ] + "lib": ["esnext", "dom"], + "types": ["node", "jest", "jest-extended"] }, "exclude": [ @@ -31,9 +24,7 @@ "server.ts", "../client/modules/email/components/SummaryEmail/MeetingSummaryEmail/MeetingSummaryEmail.tsx" ], - "include": [ - "graphql/**/*.ts", - ] + "include": ["graphql/**/*.ts"] // if "include" or "files" is added, even if they are empty arrays, then strictNullChecks breaks // repro: https://www.typescriptlang.org/play?strictFunctionTypes=false&strictPropertyInitialization=false&strictBindCallApply=false&noImplicitThis=false&noImplicitReturns=false&alwaysStrict=false&declaration=false&experimentalDecorators=false&emitDecoratorMetadata=false&target=6&ts=3.5.1#code/C4TwDgpgBA8gRgKygXigbwFBSgWwIYhwQDKwATgIJlkD8AXFAM7kCWAdgOYDaAuhgL4ZQkKFTIpYiLgHJ8hEuTHS+AYwD2bZlDwS2AVwA2B7Y21sQJ0dQx4AdADM1ZAKJ4VACwAUngF4BKFAA+KH8MIA diff --git a/yarn.lock b/yarn.lock index 69cf14bcce1..5054b6dcddb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3159,6 +3159,18 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb" integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg== +"@eslint-community/eslint-utils@^4.4.0": + version "4.4.0" + resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" + integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== + dependencies: + eslint-visitor-keys "^3.3.0" + +"@eslint-community/regexpp@^4.5.1": + version "4.10.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" + integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== + "@eslint/eslintrc@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" @@ -7572,6 +7584,11 @@ resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== +"@types/json-schema@^7.0.12": + version "7.0.15" + resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" + integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== + "@types/json-stable-stringify@^1.0.32": version "1.0.33" resolved "https://registry.yarnpkg.com/@types/json-stable-stringify/-/json-stable-stringify-1.0.33.tgz#099b0712d824d15e2660c20e1c16e6a8381f308c" @@ -7699,15 +7716,22 @@ undici-types "~5.25.1" "@types/node@^16.11.62": - version "16.11.62" - resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.62.tgz#bab2e6208531321d147eda20c38e389548cd5ffc" - integrity sha512-K/ggecSdwAAy2NUW4WKmF4Rc03GKbsfP+k326UWgckoS+Rzd2PaWbjk76dSmqdLQvLTJAO9axiTUJ6488mFsYQ== + version "16.18.85" + resolved "https://registry.yarnpkg.com/@types/node/-/node-16.18.85.tgz#17b5338c958efd67b064b92fbef41ad0333c397b" + integrity sha512-un7Bj6CPCRLxG2qp+9enNVFuRWCDKKOS6Q/FSpJ4xyrpLNJnRdAQERM2sJ6esaGvl02nK6kiGcMTb0pqknm62g== "@types/node@^18.11.18": version "18.17.18" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.17.18.tgz#acae19ad9011a2ab3d792232501c95085ba1838f" integrity sha512-/4QOuy3ZpV7Ya1GTRz5CYSz3DgkKpyUptXuQ5PPce7uuyJAOR7r9FhkmxJfvcNUXyklbC63a+YvB3jxy7s9ngw== +"@types/node@^20.11.17": + version "20.11.17" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.11.17.tgz#cdd642d0e62ef3a861f88ddbc2b61e32578a9292" + integrity sha512-QmgQZGWu1Yw9TDyAP9ZzpFJKynYNeOvwMJmaxABfieQoVoiVOS6MN1WSpqpRcbeA5+RW82kraAVxCCJg+780Qw== + dependencies: + undici-types "~5.26.4" + "@types/nodemailer@^6.4.14": version "6.4.14" resolved "https://registry.yarnpkg.com/@types/nodemailer/-/nodemailer-6.4.14.tgz#5c81a5e856db7f8ede80013e6dbad7c5fb2283e2" @@ -7892,6 +7916,11 @@ "@types/glob" "*" "@types/node" "*" +"@types/semver@^7.5.0": + version "7.5.6" + resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339" + integrity sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A== + "@types/send@*": version "0.17.1" resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301" @@ -8044,85 +8073,91 @@ resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.11.tgz#d654a112973f5e004bf8438122bd7e56a8e5cd7e" integrity sha512-9cwk3c87qQKZrT251EDoibiYRILjCmxBvvcb4meofCmx1vdnNcR9gyildy5vOHASpOKMsn42CugxUvcwK5eu1g== -"@typescript-eslint/eslint-plugin@5.17.0", "@typescript-eslint/eslint-plugin@^5.17.0": - version "5.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.17.0.tgz#704eb4e75039000531255672bf1c85ee85cf1d67" - integrity sha512-qVstvQilEd89HJk3qcbKt/zZrfBZ+9h2ynpAGlWjWiizA7m/MtLT9RoX6gjtpE500vfIg8jogAkDzdCxbsFASQ== +"@typescript-eslint/eslint-plugin@^6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3" + integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA== dependencies: - "@typescript-eslint/scope-manager" "5.17.0" - "@typescript-eslint/type-utils" "5.17.0" - "@typescript-eslint/utils" "5.17.0" - debug "^4.3.2" - functional-red-black-tree "^1.0.1" - ignore "^5.1.8" - regexpp "^3.2.0" - semver "^7.3.5" - tsutils "^3.21.0" + "@eslint-community/regexpp" "^4.5.1" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/type-utils" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + graphemer "^1.4.0" + ignore "^5.2.4" + natural-compare "^1.4.0" + semver "^7.5.4" + ts-api-utils "^1.0.1" -"@typescript-eslint/parser@5.17.0", "@typescript-eslint/parser@^5.17.0": - version "5.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.17.0.tgz#7def77d5bcd8458d12d52909118cf3f0a45f89d5" - integrity sha512-aRzW9Jg5Rlj2t2/crzhA2f23SIYFlF9mchGudyP0uiD6SenIxzKoLjwzHbafgHn39dNV/TV7xwQkLfFTZlJ4ig== +"@typescript-eslint/parser@^6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" + integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== dependencies: - "@typescript-eslint/scope-manager" "5.17.0" - "@typescript-eslint/types" "5.17.0" - "@typescript-eslint/typescript-estree" "5.17.0" - debug "^4.3.2" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" -"@typescript-eslint/scope-manager@5.17.0": - version "5.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.17.0.tgz#4cea7d0e0bc0e79eb60cad431c89120987c3f952" - integrity sha512-062iCYQF/doQ9T2WWfJohQKKN1zmmXVfAcS3xaiialiw8ZUGy05Em6QVNYJGO34/sU1a7a+90U3dUNfqUDHr3w== +"@typescript-eslint/scope-manager@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" + integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg== dependencies: - "@typescript-eslint/types" "5.17.0" - "@typescript-eslint/visitor-keys" "5.17.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" -"@typescript-eslint/type-utils@5.17.0": - version "5.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.17.0.tgz#1c4549d68c89877662224aabb29fbbebf5fc9672" - integrity sha512-3hU0RynUIlEuqMJA7dragb0/75gZmwNwFf/QJokWzPehTZousP/MNifVSgjxNcDCkM5HI2K22TjQWUmmHUINSg== +"@typescript-eslint/type-utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e" + integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag== dependencies: - "@typescript-eslint/utils" "5.17.0" - debug "^4.3.2" - tsutils "^3.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/utils" "6.21.0" + debug "^4.3.4" + ts-api-utils "^1.0.1" -"@typescript-eslint/types@5.17.0": - version "5.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.17.0.tgz#861ec9e669ffa2aa9b873dd4d28d9b1ce26d216f" - integrity sha512-AgQ4rWzmCxOZLioFEjlzOI3Ch8giDWx8aUDxyNw9iOeCvD3GEYAB7dxWGQy4T/rPVe8iPmu73jPHuaSqcjKvxw== +"@typescript-eslint/types@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" + integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== -"@typescript-eslint/typescript-estree@5.17.0": - version "5.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.17.0.tgz#a7cba7dfc8f9cc2ac78c18584e684507df4f2488" - integrity sha512-X1gtjEcmM7Je+qJRhq7ZAAaNXYhTgqMkR10euC4Si6PIjb+kwEQHSxGazXUQXFyqfEXdkGf6JijUu5R0uceQzg== +"@typescript-eslint/typescript-estree@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" + integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ== dependencies: - "@typescript-eslint/types" "5.17.0" - "@typescript-eslint/visitor-keys" "5.17.0" - debug "^4.3.2" - globby "^11.0.4" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/visitor-keys" "6.21.0" + debug "^4.3.4" + globby "^11.1.0" is-glob "^4.0.3" - semver "^7.3.5" - tsutils "^3.21.0" - -"@typescript-eslint/utils@5.17.0": - version "5.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.17.0.tgz#549a9e1d491c6ccd3624bc3c1b098f5cfb45f306" - integrity sha512-DVvndq1QoxQH+hFv+MUQHrrWZ7gQ5KcJzyjhzcqB1Y2Xes1UQQkTRPUfRpqhS8mhTWsSb2+iyvDW1Lef5DD7vA== - dependencies: - "@types/json-schema" "^7.0.9" - "@typescript-eslint/scope-manager" "5.17.0" - "@typescript-eslint/types" "5.17.0" - "@typescript-eslint/typescript-estree" "5.17.0" - eslint-scope "^5.1.1" - eslint-utils "^3.0.0" + minimatch "9.0.3" + semver "^7.5.4" + ts-api-utils "^1.0.1" + +"@typescript-eslint/utils@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" + integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== + dependencies: + "@eslint-community/eslint-utils" "^4.4.0" + "@types/json-schema" "^7.0.12" + "@types/semver" "^7.5.0" + "@typescript-eslint/scope-manager" "6.21.0" + "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/typescript-estree" "6.21.0" + semver "^7.5.4" -"@typescript-eslint/visitor-keys@5.17.0": - version "5.17.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.17.0.tgz#52daae45c61b0211b4c81b53a71841911e479128" - integrity sha512-6K/zlc4OfCagUu7Am/BD5k8PSWQOgh34Nrv9Rxe2tBzlJ7uOeJ/h7ugCGDCeEZHT6k2CJBhbk9IsbkPI0uvUkA== +"@typescript-eslint/visitor-keys@6.21.0": + version "6.21.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" + integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A== dependencies: - "@typescript-eslint/types" "5.17.0" - eslint-visitor-keys "^3.0.0" + "@typescript-eslint/types" "6.21.0" + eslint-visitor-keys "^3.4.1" "@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5": version "1.11.5" @@ -11130,7 +11165,6 @@ draft-js-utils@^1.4.0: "draft-js@https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6": version "0.10.5" - uid "025fddba56f21aaf3383aee778e0b17025c9a7bc" resolved "https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6#025fddba56f21aaf3383aee778e0b17025c9a7bc" dependencies: fbjs "^0.8.15" @@ -11498,7 +11532,7 @@ eslint-plugin-react@^7.16.0: semver "^6.3.0" string.prototype.matchall "^4.0.6" -eslint-scope@5.1.1, eslint-scope@^5.1.1: +eslint-scope@5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c" integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw== @@ -11526,7 +11560,7 @@ eslint-visitor-keys@^2.0.0: resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== -eslint-visitor-keys@^3.0.0, eslint-visitor-keys@^3.2.0, eslint-visitor-keys@^3.4.1: +eslint-visitor-keys@^3.2.0, eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== @@ -11836,6 +11870,17 @@ fast-glob@^3.1.1, fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.4: merge2 "^1.3.0" micromatch "^4.0.4" +fast-glob@^3.2.9: + version "3.3.2" + resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" + integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== + dependencies: + "@nodelib/fs.stat" "^2.0.2" + "@nodelib/fs.walk" "^1.2.3" + glob-parent "^5.1.2" + merge2 "^1.3.0" + micromatch "^4.0.4" + fast-json-patch@^3.0.0-1: version "3.1.1" resolved "https://registry.yarnpkg.com/fast-json-patch/-/fast-json-patch-3.1.1.tgz#85064ea1b1ebf97a3f7ad01e23f9337e72c66947" @@ -12619,7 +12664,7 @@ globals@^13.6.0, globals@^13.9.0: dependencies: type-fest "^0.20.2" -globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.0.4: +globby@^11.0.1, globby@^11.0.2, globby@^11.0.3: version "11.0.4" resolved "https://registry.yarnpkg.com/globby/-/globby-11.0.4.tgz#2cbaff77c2f2a62e71e9b2813a67b97a3a3001a5" integrity sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg== @@ -12631,6 +12676,18 @@ globby@^11.0.1, globby@^11.0.2, globby@^11.0.3, globby@^11.0.4: merge2 "^1.3.0" slash "^3.0.0" +globby@^11.1.0: + version "11.1.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" + integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== + dependencies: + array-union "^2.1.0" + dir-glob "^3.0.1" + fast-glob "^3.2.9" + ignore "^5.2.0" + merge2 "^1.4.1" + slash "^3.0.0" + globby@^13.1.1: version "13.1.3" resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.3.tgz#f62baf5720bcb2c1330c8d4ef222ee12318563ff" @@ -12726,6 +12783,11 @@ grapheme-splitter@^1.0.4: resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e" integrity sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ== +graphemer@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6" + integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag== + graphiql@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/graphiql/-/graphiql-3.0.0.tgz#9ea10cb552759ae69a14c72bf219e9f425a607d7" @@ -13269,11 +13331,16 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.0.4, ignore@^5.1.4, ignore@^5.1.8, ignore@^5.2.0: +ignore@^5.0.4, ignore@^5.1.4, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== +ignore@^5.2.4: + version "5.3.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" + integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== + image-size@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/image-size/-/image-size-1.0.0.tgz#58b31fe4743b1cec0a0ac26f5c914d3c5b2f0750" @@ -15637,6 +15704,13 @@ minimatch@3.0.5: dependencies: brace-expansion "^1.1.7" +minimatch@9.0.3: + version "9.0.3" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" + integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== + dependencies: + brace-expansion "^2.0.1" + minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -20457,6 +20531,11 @@ ts-algebra@^1.2.0: resolved "https://registry.yarnpkg.com/ts-algebra/-/ts-algebra-1.2.0.tgz#f91c481207a770f0d14d055c376cbee040afdfc9" integrity sha512-kMuJJd8B2N/swCvIvn1hIFcIOrLGbWl9m/J6O3kHx9VRaevh00nvgjPiEGaRee7DRaAczMYR2uwWvXU22VFltw== +ts-api-utils@^1.0.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.2.1.tgz#f716c7e027494629485b21c0df6180f4d08f5e8b" + integrity sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA== + ts-app-env@^1.4.2: version "1.4.2" resolved "https://registry.yarnpkg.com/ts-app-env/-/ts-app-env-1.4.2.tgz#2a76d19d61b66c6bc92ff90e3a0e6db6c545a776" @@ -20574,7 +20653,7 @@ tslib@1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.9.3.tgz#d7e4dd79245d85428c4d7e4822a79917954ca286" integrity sha512-4krF8scpejhaOgqzBEcGM7yDIEfi0/8+8zDRZhNZZ2kjmHJ4hv3zCbQWxoJGz1iw5U0Jl0nma13xzHXcncMavQ== -tslib@^1.10.0, tslib@^1.11.1, tslib@^1.8.1, tslib@^1.9.0, tslib@^1.9.3: +tslib@^1.10.0, tslib@^1.11.1, tslib@^1.9.0, tslib@^1.9.3: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== @@ -20599,13 +20678,6 @@ tslib@~2.5.0: resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.5.0.tgz#42bfed86f5787aeb41d031866c8f402429e0fddf" integrity sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg== -tsutils@^3.21.0: - version "3.21.0" - resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623" - integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA== - dependencies: - tslib "^1.8.1" - tunnel-agent@^0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" @@ -20729,10 +20801,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@4.9.5, "typescript@^3 || ^4", typescript@^4.2.4, typescript@^4.5.5: - version "4.9.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a" - integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g== +typescript@4.9.5, "typescript@^3 || ^4", typescript@^4.2.4, typescript@^5.3.3: + version "5.3.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" + integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== uWebSockets.js@uNetworking/uWebSockets.js#v20.34.0: version "20.34.0" @@ -20778,6 +20850,11 @@ undici-types@~5.25.1: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.25.3.tgz#e044115914c85f0bcbb229f346ab739f064998c3" integrity sha512-Ga1jfYwRn7+cP9v8auvEXN1rX3sWqlayd4HP7OKk4mZWylEmu3KzXDUGrQUN6Ol7qo1gPvB2e5gX6udnyEPgdA== +undici-types@~5.26.4: + version "5.26.5" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" + integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== + undici@^5.26.2: version "5.26.3" resolved "https://registry.yarnpkg.com/undici/-/undici-5.26.3.tgz#ab3527b3d5bb25b12f898dfd22165d472dd71b79" From 92f0be917d4bd182bc6ea249f5dc40c05b98320a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 12:57:26 -0800 Subject: [PATCH 026/529] chore(deps): bump es5-ext from 0.10.62 to 0.10.64 (#9457) Bumps [es5-ext](https://github.com/medikoo/es5-ext) from 0.10.62 to 0.10.64. - [Release notes](https://github.com/medikoo/es5-ext/releases) - [Changelog](https://github.com/medikoo/es5-ext/blob/main/CHANGELOG.md) - [Commits](https://github.com/medikoo/es5-ext/compare/v0.10.62...v0.10.64) --- updated-dependencies: - dependency-name: es5-ext dependency-type: indirect ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 5054b6dcddb..5b2a827fe6d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11408,13 +11408,14 @@ es-to-primitive@^1.2.1: is-date-object "^1.0.1" is-symbol "^1.0.2" -es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: - version "0.10.62" - resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.62.tgz#5e6adc19a6da524bf3d1e02bbc8960e5eb49a9a5" - integrity sha512-BHLqn0klhEpnOKSrzn/Xsz2UIW8j+cGmo9JLzr8BiUapV8hPL9+FliFqjwr9ngW7jWdnxv6eO+/LqyhJVqgrjA== +es5-ext@^0.10.35, es5-ext@^0.10.46, es5-ext@^0.10.50, es5-ext@^0.10.53, es5-ext@^0.10.62, es5-ext@~0.10.14, es5-ext@~0.10.2, es5-ext@~0.10.46: + version "0.10.64" + resolved "https://registry.yarnpkg.com/es5-ext/-/es5-ext-0.10.64.tgz#12e4ffb48f1ba2ea777f1fcdd1918ef73ea21714" + integrity sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg== dependencies: es6-iterator "^2.0.3" es6-symbol "^3.1.3" + esniff "^2.0.1" next-tick "^1.1.0" es6-iterator@^2.0.3: @@ -11606,6 +11607,16 @@ eslint@^8.2.0, eslint@^8.8.0: text-table "^0.2.0" v8-compile-cache "^2.0.3" +esniff@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/esniff/-/esniff-2.0.1.tgz#a4d4b43a5c71c7ec51c51098c1d8a29081f9b308" + integrity sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg== + dependencies: + d "^1.0.1" + es5-ext "^0.10.62" + event-emitter "^0.3.5" + type "^2.7.2" + espree@^9.0.0, espree@^9.2.0, espree@^9.3.0: version "9.6.1" resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" From 1e0075e843ce3cf52966a0b77293d72f1d9c60b9 Mon Sep 17 00:00:00 2001 From: adaniels-parabol <71724289+adaniels-parabol@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:58:35 -0600 Subject: [PATCH 027/529] fix: packages/server/package.json to reduce vulnerabilities (#9434) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-UNDICI-6252336 Co-authored-by: snyk-bot --- packages/server/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/package.json b/packages/server/package.json index 79b79743566..0da2cdd6a75 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -137,6 +137,6 @@ "stripe": "^9.13.0", "tslib": "^2.4.0", "uWebSockets.js": "uNetworking/uWebSockets.js#v20.34.0", - "undici": "^5.26.2" + "undici": "^5.28.3" } } From fd833f541ef7f915b40331c9d12e94243c8fa24f Mon Sep 17 00:00:00 2001 From: adaniels-parabol <71724289+adaniels-parabol@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:59:29 -0600 Subject: [PATCH 028/529] fix: packages/server/package.json to reduce vulnerabilities (#9392) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-NODEMAILER-6219989 Co-authored-by: snyk-bot From fd75d3f2a907888bb461d55ac945d9449071a414 Mon Sep 17 00:00:00 2001 From: adaniels-parabol <71724289+adaniels-parabol@users.noreply.github.com> Date: Wed, 28 Feb 2024 15:01:26 -0600 Subject: [PATCH 029/529] fix: packages/server/package.json to reduce vulnerabilities (#9298) The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/SNYK-JS-FOLLOWREDIRECTS-6141137 Co-authored-by: snyk-bot --- packages/server/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/package.json b/packages/server/package.json index 0da2cdd6a75..29150a626a0 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -87,7 +87,7 @@ "@sentry/integrations": "^7.74.1", "@sentry/node": "^7.74.1", "adaptivecards": "^2.10.0", - "analytics-node": "^5.0.0", + "analytics-node": "^6.0.0", "api": "^5.0.7", "base64url": "^3.0.1", "bcryptjs": "^2.4.3", From 9441b2727deefb7e27e4015f37d64ff933415c8d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 28 Feb 2024 13:01:41 -0800 Subject: [PATCH 030/529] chore(deps): bump follow-redirects from 1.14.8 to 1.15.4 (#9312) Bumps [follow-redirects](https://github.com/follow-redirects/follow-redirects) from 1.14.8 to 1.15.4. - [Release notes](https://github.com/follow-redirects/follow-redirects/releases) - [Commits](https://github.com/follow-redirects/follow-redirects/compare/v1.14.8...v1.15.4) --- updated-dependencies: - dependency-name: follow-redirects dependency-type: indirect ... Signed-off-by: dependabot[bot] Signed-off-by: Matt Krick Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Matt Krick --- yarn.lock | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/yarn.lock b/yarn.lock index 5b2a827fe6d..b240f3b0964 100644 --- a/yarn.lock +++ b/yarn.lock @@ -20873,6 +20873,13 @@ undici@^5.26.2: dependencies: "@fastify/busboy" "^2.0.0" +undici@^5.28.3: + version "5.28.3" + resolved "https://registry.yarnpkg.com/undici/-/undici-5.28.3.tgz#a731e0eff2c3fcfd41c1169a869062be222d1e5b" + integrity sha512-3ItfzbrhDlINjaP0duwnNsKpDQk3acHI3gVJ1z4fmwMK31k5G9OVIAMLSIaP6w4FaGkaAkN6zaQO9LUvZ1t7VA== + dependencies: + "@fastify/busboy" "^2.0.0" + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" From 7bfec9188a42b38eb69930fdd86e6fb39249ed7e Mon Sep 17 00:00:00 2001 From: Dale Bumblis <135627447+dbumblis-parabol@users.noreply.github.com> Date: Wed, 28 Feb 2024 14:30:17 -0800 Subject: [PATCH 031/529] chore: add upload to GCS step in ironbank (#9471) * add upload to GCS step in ironbank * update workflow name --- .github/workflows/ironbank.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ironbank.yml b/.github/workflows/ironbank.yml index b6a15acd539..e9782493f43 100644 --- a/.github/workflows/ironbank.yml +++ b/.github/workflows/ironbank.yml @@ -1,4 +1,4 @@ -name: Ironbank S3 Upload +name: Ironbank Image Upload on: workflow_dispatch: @@ -64,3 +64,11 @@ jobs: - name: Upload to S3 run: | aws s3 cp ${{ github.event.inputs.version_number }}.zip s3://ironbank-proving-ground-action-files.parabol.co/${{ github.event.inputs.version_number }}.zip + + - name: Upload to GCS + uses: actions-hub/gcloud@master + env: + CLOUDSDK_AUTH_ACCESS_TOKEN: "${{ steps.auth.outputs.access_token }}" + with: + args: storage cp ${{ github.event.inputs.version_number }}.zip gs://ironbank-proving-ground/${{ github.event.inputs.version_number }}.zip + cli: gcloud From c1da6baf2d3dbead396561b6da41d43c46cbe3c0 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 09:36:22 +0100 Subject: [PATCH 032/529] chore(release): release v7.19.4 (#9470) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 19 +++++++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 30 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 517aeb7a63e..909f7972a63 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.19.3" + ".": "7.19.4" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 579182dc3f5..56ea22e2f03 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,25 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.19.4](https://github.com/ParabolInc/parabol/compare/v7.19.3...v7.19.4) (2024-02-28) + + +### Fixed + +* Fetch Jira projects in parallel ([#9456](https://github.com/ParabolInc/parabol/issues/9456)) ([9cec00a](https://github.com/ParabolInc/parabol/commit/9cec00a5fd0b46c73ebdde27e6d966b485216132)) +* limit invites from spammers ([#9416](https://github.com/ParabolInc/parabol/issues/9416)) ([5b9526c](https://github.com/ParabolInc/parabol/commit/5b9526c092f7f8675ad2a442da4440e2507cbdcc)) +* packages/server/package.json to reduce vulnerabilities ([#9298](https://github.com/ParabolInc/parabol/issues/9298)) ([fd75d3f](https://github.com/ParabolInc/parabol/commit/fd75d3f2a907888bb461d55ac945d9449071a414)) +* packages/server/package.json to reduce vulnerabilities ([#9392](https://github.com/ParabolInc/parabol/issues/9392)) ([fd833f5](https://github.com/ParabolInc/parabol/commit/fd833f541ef7f915b40331c9d12e94243c8fa24f)) +* packages/server/package.json to reduce vulnerabilities ([#9434](https://github.com/ParabolInc/parabol/issues/9434)) ([1e0075e](https://github.com/ParabolInc/parabol/commit/1e0075e843ce3cf52966a0b77293d72f1d9c60b9)) +* replace lone surrogates in draft-js content ([#9415](https://github.com/ParabolInc/parabol/issues/9415)) ([00092ec](https://github.com/ParabolInc/parabol/commit/00092ec55659d1441e9566d501940dcc6fcf07f4)) + + +### Changed + +* add upload to GCS step in ironbank ([#9471](https://github.com/ParabolInc/parabol/issues/9471)) ([7bfec91](https://github.com/ParabolInc/parabol/commit/7bfec9188a42b38eb69930fdd86e6fb39249ed7e)) +* **deps:** bump es5-ext from 0.10.62 to 0.10.64 ([#9457](https://github.com/ParabolInc/parabol/issues/9457)) ([92f0be9](https://github.com/ParabolInc/parabol/commit/92f0be917d4bd182bc6ea249f5dc40c05b98320a)) +* **deps:** bump follow-redirects from 1.14.8 to 1.15.4 ([#9312](https://github.com/ParabolInc/parabol/issues/9312)) ([9441b27](https://github.com/ParabolInc/parabol/commit/9441b2727deefb7e27e4015f37d64ff933415c8d)) + ## [7.19.3](https://github.com/ParabolInc/parabol/compare/v7.19.2...v7.19.3) (2024-02-28) diff --git a/package.json b/package.json index 5b7253fec2e..2603f250ca4 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.19.3", + "version": "7.19.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 39bec48dcf6..c53c697fef2 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.19.3", + "version": "7.19.4", "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.19.3" + "parabol-server": "7.19.4" } } diff --git a/packages/client/package.json b/packages/client/package.json index 19a38863130..2d0ec1344e3 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.19.3", + "version": "7.19.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 10852a68c4a..bfb366858cf 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.19.3", + "version": "7.19.4", "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.19.3", - "parabol-server": "7.19.3", + "parabol-client": "7.19.4", + "parabol-server": "7.19.4", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index a70463e1320..7c7c4ffe520 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.19.3", + "version": "7.19.4", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 29150a626a0..1699bdf5d11 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.19.3", + "version": "7.19.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.11.0", - "parabol-client": "7.19.3", + "parabol-client": "7.19.4", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 419d104757d905c468d6a72ce607430d01f3b97f Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Thu, 29 Feb 2024 11:20:04 +0100 Subject: [PATCH 033/529] fix: Fix seasonal templates for leap years (#9476) * fix: Fix seasonal templates for leap years It would produce invalid dates on February 29th. * Master was not clean --- .../server/dataloader/customLoaderMakers.ts | 4 +-- yarn.lock | 25 ++++++++++++++----- 2 files changed, 21 insertions(+), 8 deletions(-) diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 8bd64c6250b..ac7e1ea6ec7 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -481,8 +481,8 @@ export const meetingTemplatesByOrgId = (parent: RootDataLoader) => { .where(({or, eb}) => or([ eb('hideStartingAt', 'is', null), - sql`make_date(2020 , extract(month from current_date)::integer, extract(day from current_date)::integer) between "hideEndingAt" and "hideStartingAt"`, - sql`make_date(2019 , extract(month from current_date)::integer, extract(day from current_date)::integer) between "hideEndingAt" and "hideStartingAt"` + sql`DATE '2020-01-01' + EXTRACT(DOY FROM CURRENT_DATE)::INTEGER - 1 between "hideEndingAt" and "hideStartingAt"`, + sql`DATE '2019-01-01' + EXTRACT(DOY FROM CURRENT_DATE)::INTEGER - 1 between "hideEndingAt" and "hideStartingAt"` ]) ) .orderBy('createdAt', 'desc') diff --git a/yarn.lock b/yarn.lock index b240f3b0964..17637e0e348 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8577,13 +8577,13 @@ amp@0.3.1, amp@~0.3.1: resolved "https://registry.yarnpkg.com/amp/-/amp-0.3.1.tgz#6adf8d58a74f361e82c1fa8d389c079e139fc47d" integrity sha1-at+NWKdPNh6CwfqNOJwHnhOfxH0= -analytics-node@^5.0.0: - version "5.2.0" - resolved "https://registry.yarnpkg.com/analytics-node/-/analytics-node-5.2.0.tgz#ef167bbf0d51f630e96d3abe604ce449b50a2584" - integrity sha512-JAc0K7J//QKGGX2mfwBE7wyG/Rh4W0BATB6CncwYhVX1qLjiXwHmB7bYr9PgJIer5M6jKMOhZoO5lxiruQk9BQ== +analytics-node@^6.0.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/analytics-node/-/analytics-node-6.2.0.tgz#8ae2ebc73d85e5b2aac8d366b974ad36996f629d" + integrity sha512-NLU4tCHlWt0tzEaFQL7NIoWhq2KmQSmz0JvyS2lYn6fc4fEjTMSabhJUx8H1r5995FX8fE3rZ15uIHU6u+ovlQ== dependencies: "@segment/loosely-validate-event" "^2.0.0" - axios "^0.21.4" + axios "^0.27.2" axios-retry "3.2.0" lodash.isstring "^4.0.1" md5 "^2.2.1" @@ -8939,13 +8939,21 @@ axios-retry@3.2.0: dependencies: is-retry-allowed "^1.1.0" -axios@0.21.4, axios@^0.21.0, axios@^0.21.4: +axios@0.21.4, axios@^0.21.0: version "0.21.4" resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.4.tgz#c67b90dc0568e5c1cf2b0b858c43ba28e2eda575" integrity sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg== dependencies: follow-redirects "^1.14.0" +axios@^0.27.2: + version "0.27.2" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.27.2.tgz#207658cc8621606e586c85db4b41a750e756d972" + integrity sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ== + dependencies: + follow-redirects "^1.14.9" + form-data "^4.0.0" + axios@^1.0.0: version "1.3.2" resolved "https://registry.yarnpkg.com/axios/-/axios-1.3.2.tgz#7ac517f0fa3ec46e0e636223fd973713a09c72b3" @@ -12154,6 +12162,11 @@ follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.15.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== +follow-redirects@^1.14.9: + version "1.15.5" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" + integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== + foreground-child@^3.1.0: version "3.1.1" resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.1.1.tgz#1d173e776d75d2772fed08efe4a0de1ea1b12d0d" From ba67da80878dab73a32ba187c418cc6ac3f2dfef Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 11:20:39 +0100 Subject: [PATCH 034/529] chore(release): release v7.19.5 (#9477) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 909f7972a63..6e9dcf42d00 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.19.4" + ".": "7.19.5" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 56ea22e2f03..080f2ea136c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.19.5](https://github.com/ParabolInc/parabol/compare/v7.19.4...v7.19.5) (2024-02-29) + + +### Fixed + +* Fix seasonal templates for leap years ([#9476](https://github.com/ParabolInc/parabol/issues/9476)) ([419d104](https://github.com/ParabolInc/parabol/commit/419d104757d905c468d6a72ce607430d01f3b97f)) + ## [7.19.4](https://github.com/ParabolInc/parabol/compare/v7.19.3...v7.19.4) (2024-02-28) diff --git a/package.json b/package.json index 2603f250ca4..ee9f994f9d9 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.19.4", + "version": "7.19.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index c53c697fef2..f9c5675491e 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.19.4", + "version": "7.19.5", "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.19.4" + "parabol-server": "7.19.5" } } diff --git a/packages/client/package.json b/packages/client/package.json index 2d0ec1344e3..b5e0427873c 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.19.4", + "version": "7.19.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index bfb366858cf..0e97990948c 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.19.4", + "version": "7.19.5", "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.19.4", - "parabol-server": "7.19.4", + "parabol-client": "7.19.5", + "parabol-server": "7.19.5", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 7c7c4ffe520..1b7a9030731 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.19.4", + "version": "7.19.5", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 1699bdf5d11..5dc86873eb0 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.19.4", + "version": "7.19.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.11.0", - "parabol-client": "7.19.4", + "parabol-client": "7.19.5", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 052acd14035fe7c96af8d17ca4763be91d863a80 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Thu, 29 Feb 2024 13:56:44 +0100 Subject: [PATCH 035/529] fix: After parameter for meetingCount was ignored (#9479) --- packages/server/graphql/public/types/Company.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/graphql/public/types/Company.ts b/packages/server/graphql/public/types/Company.ts index 2e01f3b8beb..351d4fb7981 100644 --- a/packages/server/graphql/public/types/Company.ts +++ b/packages/server/graphql/public/types/Company.ts @@ -139,7 +139,7 @@ const Company: CompanyResolvers = { const teams = await getTeamsByOrgIds(orgIds, dataLoader, true) const teamIds = teams.map(({id}) => id) if (teamIds.length === 0) return 0 - const filterFn = after ? () => true : (meeting: any) => meeting('createdAt').ge(after) + const filterFn = after ? (meeting: any) => meeting('createdAt').ge(after) : () => true return r .table('NewMeeting') .getAll(r.args(teamIds), {index: 'teamId'}) From 5e356c2566db8e32e45a1393e1b1ea27c4be0a5c Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Thu, 29 Feb 2024 16:02:45 +0000 Subject: [PATCH 036/529] chore(docker-build): simplify the docker build process and reduce docker image size (#9447) * Dockerfile basic created. Improvements added to reduce build time and size (down from 795MB to 445MB, depending on systemtap). Readme reduced, removing the old process used to build the image. * basic-env file using a RethinkDB database name that is clearly dedicated to the building proces. * Readme improved to run all three components * Unused dockerfiles removed. Docker entrypoint renamed. Docker Readme adapted * Legacy build kept in both dockerfile and env file. Readme adapted to use the new basic image. Build GH workflow adapted to use the new basic.dockerfile. --- .github/workflows/build.yml | 3 +- docker/parabol-ubi/docker-build/README.md | 82 ++++-------- .../docker-build/dockerfiles/basic.dockerfile | 26 ++++ .../dockerfiles/parabol.dockerfile | 5 +- .../dockerfiles/pipeline.dockerfile | 117 ------------------ .../dockerfiles/security-test.dockerfile | 60 --------- .../{buildenv => docker-entrypoint.sh} | 0 .../docker-build/environments/basic-env | 17 +++ .../environments/{buildenv => legacy-build} | 0 .../docker-build/environments/local-buildenv | 54 -------- 10 files changed, 69 insertions(+), 295 deletions(-) create mode 100644 docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile delete mode 100644 docker/parabol-ubi/docker-build/dockerfiles/pipeline.dockerfile delete mode 100644 docker/parabol-ubi/docker-build/dockerfiles/security-test.dockerfile rename docker/parabol-ubi/docker-build/entrypoints/{buildenv => docker-entrypoint.sh} (100%) create mode 100644 docker/parabol-ubi/docker-build/environments/basic-env rename docker/parabol-ubi/docker-build/environments/{buildenv => legacy-build} (100%) delete mode 100644 docker/parabol-ubi/docker-build/environments/local-buildenv diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8b7cf97ce90..9e2e74b3df6 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,7 +11,7 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: true env: - PARABOL_DOCKERFILE: ./docker/parabol-ubi/docker-build/dockerfiles/pipeline.dockerfile + PARABOL_DOCKERFILE: ./docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile PARABOL_BUILD_ENV_PATH: docker/parabol-ubi/docker-build/environments/pipeline jobs: build: @@ -114,7 +114,6 @@ jobs: context: . build-args: | "_NODE_VERSION=${{ env.NODE_VERSION }}" - "_SECURITY_ENABLED=true" push: true tags: | "${{ secrets.GCP_AR_PARABOL_DEV }}:${{github.sha}}" diff --git a/docker/parabol-ubi/docker-build/README.md b/docker/parabol-ubi/docker-build/README.md index 57233e4dbcb..85c1a2d42b3 100644 --- a/docker/parabol-ubi/docker-build/README.md +++ b/docker/parabol-ubi/docker-build/README.md @@ -1,17 +1,8 @@ # docker-image-parabol -This repo was created to build a **secure** Parabol base image that is **agnostic to configuration and can be used anywhere**. Once an image is built, it can be pushed to any repository. +This repo was created to build a Parabol base image that is **agnostic to configuration and can be used anywhere**. Once an image is built, it can be pushed to any repository. -There are two possible ways to build the application: - -- **Standard build:** duild using local files, using the same Dockerfile and process used in our Docker Build pipeline. -- **Build from git:** build using a simplified process that downloads the source code and builds from scratch. - -The processes are different and the details of it can be checked in both dockerfiles. - -## Standard build - -### Requirements +## Requirements Required: @@ -21,37 +12,35 @@ Required: Recommended: -- jq installed. +- [jq](https://jqlang.github.io/jq/) installed. It is used to get the version of the application. -### Variables +## Variables | Name | Description | Possible values | Recommended value | | -------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------- | | `postgresql_tag` | PostgreSQL version from the [Docker image](https://hub.docker.com/_/postgres) | `Any tag` | `15.4` | | `rethinkdb_tag` | RethinkDB version from the [Docker image](https://hub.docker.com/_/rethinkdb) | `Any tag` | `2.4.2` | | `redis_tag` | Redis version from the [Docker image](https://hub.docker.com/_/redis) | `Any tag` | `7.0-alpine` | -| `_BUILD_ENV_PATH` | File `.env` used by the application during the build process | `Relative path from the root level of the repository` | `docker/parabol-ubi/docker-build/environments/pipeline` | +| `_BUILD_ENV_PATH` | File `.env` used by the application during the build process | `Relative path from the root level of the repository` | `docker/parabol-ubi/docker-build/environments/basic-env` | | `_NODE_VERSION` | Node version, used by Docker to use the Docker image node:\_NODE_VERSION as base image to build | `Same as in root package.json` | | -| `_DOCKERFILE` | Dockerfile used to build the image | `Relative path from the root level of the repository` | `./docker/parabol-ubi/docker-build/dockerfiles/pipeline.dockerfile` | -| `_SECURITY_ENABLED` | Enable or disable security configurations. It will add some MBs to the final image, but it will produce a secured image | `true/false` | `true` | +| `_DOCKERFILE` | Dockerfile used to build the image | `Relative path from the root level of the repository` | `./docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile` | | `_DOCKER_REPOSITORY` | The destination repository | `String` | `parabol` | | `_DOCKER_TAG` | Tag for the produced image | `String` | | Example of variables: ```commandLine -export postgresql_tag=15.4-alpine; \ +export postgresql_tag=15.4; \ export rethinkdb_tag=2.4.2; \ export redis_tag=7.0-alpine; \ -export _BUILD_ENV_PATH=docker/parabol-ubi/docker-build/environments/pipeline; \ +export _BUILD_ENV_PATH=docker/parabol-ubi/docker-build/environments/basic-env; \ export _NODE_VERSION=$(jq -r -j '.engines.node|ltrimstr("^")' package.json); \ -export _DOCKERFILE=./docker/parabol-ubi/docker-build/dockerfiles/pipeline.dockerfile; \ -export _SECURITY_ENABLED=true; \ +export _DOCKERFILE=./docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile; \ export _DOCKER_REPOSITORY=parabol; \ export _DOCKER_TAG=test-image ``` -### Building the image +## Building the image The application must be already built locally using the command `yarn build --no-deps` mode. @@ -90,7 +79,7 @@ yarn build --no-deps - **Build the docker image:** ```commandLine -docker build -t $_DOCKER_REPOSITORY:$_DOCKER_TAG -f $_DOCKERFILE --build-arg _NODE_VERSION=$_NODE_VERSION --build-arg _SECURITY_ENABLED=$_SECURITY_ENABLED . +docker build -t $_DOCKER_REPOSITORY:$_DOCKER_TAG -f $_DOCKERFILE --build-arg _NODE_VERSION=$_NODE_VERSION . ``` > Some build tips @@ -119,57 +108,30 @@ It will produce a Docker image tagged as `${_DOCKER_REPOSITORY}:${_DOCKER_TAG}`. docker images $_DOCKER_REPOSITORY:$_DOCKER_TAG ``` -## Build from git - -This version of the Dockerfile downloads the application during the docker build process and differs in other +## Run the application using a docker image -Modify the version export below e.g. update vX.X.X and run the export command and the docker command. The command below will create a temp postgres container (this allows pgtype files to be generated) and then build the docker image with a temp .env file. +_Assumes redis, rethinkdb, and postgres already running to have operational stack._ -- Change `environments/buildenv` connection string names form container names to localhost for local image build. -- Use `_PARABOL_GIT_REF` to select the reference in Parabol's Git repository. It can be any tag or branch, but it is recommended to use released tags as `v6.69.0`. By default it buils a local image using only `parabol` as repository. -- Use `_DOCKER_REPOSITORY` to build the image for a remote repository (ex: `gcr.io/parabol-proving-ground/parabol`) -- Use `_DOCKER_TAG` to define the tag for the new image. +The commands below will start a Parabol container on the target tag specified in \_DOCKER_TAG export. It will volume mount a .env in your current working directory to the container, so you can pass in any .env in your current working directory. -```commandLine -export postgresql_tag=15.4-alpine; \ -export rethinkdb_tag=2.4.2; \ -export redis_tag=7.0-alpine; \ -export _BUILD_ENV_PATH=environments/local-buildenv \ -export _NODE_VERSION=20.11.0 \ -export _DOCKER_REPOSITORY=parabol \ -export _PARABOL_GIT_REF=vX.X.X \ -export _DOCKER_TAG=vX.X.X -``` +For a more detailed how-to deploy Parabol, please go to the section [docker-host-st](https://github.com/ParabolInc/parabol/tree/master/docker/parabol-ubi/docker-host-st) -Now you can build the image +- Run the PreDeploy script ```commandLine -docker run --name temp-postgres --network=host -e POSTGRES_PASSWORD=temppassword -e POSTGRES_USER=tempuser -e POSTGRES_DB=tempdb -d -p 5432:5432 postgres:${postgresql_tag} && \ -docker run --name temp-rethinkdb --network=host -d -p 28015:28015 -p 29015:29015 -p 8080:8080 rethinkdb:${rethinkdb_tag} && \ -docker run --name temp-redis --network=host -d -p 6379:6379 redis:${redis_tag} && \ -docker build --no-cache --network=host -t ${_DOCKER_REPOSITORY}:${_DOCKER_TAG} -f ./dockerfiles/parabol.dockerfile --build-arg _PARABOL_GIT_REF=${_PARABOL_GIT_REF} --build-arg _NODE_VERSION=$_NODE_VERSION --build-arg _BUILD_ENV_PATH=${_BUILD_ENV_PATH} . && \ -docker stop temp-postgres temp-rethinkdb temp-redis && docker rm temp-postgres temp-rethinkdb temp-redis -f || docker stop temp-postgres temp-rethinkdb temp-redis && docker rm temp-postgres temp-rethinkdb temp-redis -f -``` - -If `_DOCKER_REPOSITORY` wasn't local and you want to push the image, you can run then: +export _DOCKER_REPOSITORY=parabol; \ +export _DOCKER_TAG=vX.X.X -```commandLine -docker push ${_DOCKER_REPOSITORY}:${_DOCKER_TAG} +docker run --name=parabol-predeploy --network=host -v $(pwd)/.env:/home/node/parabol/.env ${_DOCKER_REPOSITORY}:${_DOCKER_TAG} /bin/bash -c "node dist/preDeploy.js" ``` -## Run the application using a docker image - -_Assumes redis, rethinkdb, and postgres already running to have operational stack._ - -The commands below will start a Parabol container on the target tag specified in \_DOCKER_TAG export. It will volume mount a .env in your current working directory to the container, so you can pass in any .env in your current working directory. - - Start GraphQL ```commandLine export _DOCKER_REPOSITORY=parabol; \ export _DOCKER_TAG=vX.X.X -docker run --name=parabolgraphql --network=host -v $(pwd)/.env:/home/node/parabol/.env ${_DOCKER_REPOSITORY}:${_DOCKER_TAG} /bin/bash -c "yarn predeploy && NODE_ENV=production && node ./dist/gqlExecutor.js" || docker container rm parabolgraphql -f +docker run --name=parabol-gql-executor --network=host -v $(pwd)/.env:/home/node/parabol/.env ${_DOCKER_REPOSITORY}:${_DOCKER_TAG} /bin/bash -c "node ./dist/gqlExecutor.js" || docker container rm parabol-gql-executor -f ``` - Start Web Server @@ -178,7 +140,7 @@ docker run --name=parabolgraphql --network=host -v $(pwd)/.env:/home/node/parabo export _DOCKER_REPOSITORY=parabol; \ export _DOCKER_TAG=vX.X.X -docker run --name=parabol --network=host -v $(pwd)/.env:/home/node/parabol/.env -p 3000:3000 ${_DOCKER_REPOSITORY}:${_DOCKER_TAG} /bin/bash -c "yarn predeploy && NODE_ENV=production && node ./dist/web.js" || docker container rm parabol -f +docker run --name=parabol-web-server --network=host -v $(pwd)/.env:/home/node/parabol/.env -p 3000:3000 ${_DOCKER_REPOSITORY}:${_DOCKER_TAG} /bin/bash -c "node ./dist/web.js" || docker container rm parabol-web-server -f ``` -To stop the container, just open another terminal and enter `docker container stop parabol` +To stop the container, just open another terminal and enter `docker container stop parabol-COMPONENT` diff --git a/docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile b/docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile new file mode 100644 index 00000000000..025862bedfe --- /dev/null +++ b/docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile @@ -0,0 +1,26 @@ +ARG _NODE_VERSION=${_NODE_VERSION} +FROM node:${_NODE_VERSION}-bookworm-slim as base + +ENV NPM_CONFIG_PREFIX=/home/node/.npm-global +ENV PORT=3000 + +COPY --chown=node --chmod=755 docker/parabol-ubi/docker-build/entrypoints/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +COPY --chown=node docker/parabol-ubi/docker-build/tools/ip-to-server_id ${HOME}/tools/ip-to-server_id + +# Required for pushToCDN to work with FILE_STORE_PROVIDER set to 'local' +RUN mkdir -p ${HOME}/parabol/self-hosted && \ + chown node:node ${HOME}/parabol/self-hosted + +COPY --chown=node .env.example ${HOME}/parabol/.env.example + +# The application requires a yarn.lock file on the root folder to identify it +COPY --chown=node yarn.lock ${HOME}/parabol/yarn.lock +COPY --chown=node build ${HOME}/parabol/build +COPY --chown=node dist ${HOME}/parabol/dist + +WORKDIR ${HOME}/parabol/ + +USER node +EXPOSE ${PORT} + +ENTRYPOINT ["docker-entrypoint.sh"] diff --git a/docker/parabol-ubi/docker-build/dockerfiles/parabol.dockerfile b/docker/parabol-ubi/docker-build/dockerfiles/parabol.dockerfile index f2f7466af8b..2688d19ee8d 100644 --- a/docker/parabol-ubi/docker-build/dockerfiles/parabol.dockerfile +++ b/docker/parabol-ubi/docker-build/dockerfiles/parabol.dockerfile @@ -1,9 +1,10 @@ +# DO NOT DELETE. Legacy docker file for versions still in use. Delete only when all Parabol instances are using the newest docker image. ARG _NODE_VERSION=${_NODE_VERSION} #base build for dev deps FROM node:${_NODE_VERSION} as base ARG _PARABOL_GIT_REF=${_PARABOL_GIT_REF} -ARG _BUILD_ENV_PATH=environments/buildenv +ARG _BUILD_ENV_PATH=environments/legacy-build ENV NPM_CONFIG_PREFIX=/home/node/.npm-global WORKDIR /home/node @@ -45,7 +46,7 @@ COPY --from=base /usr/local/lib/node_modules /usr/local/lib/node_modules COPY --from=base /opt /opt COPY --from=base /home/node/parabol/ ${HOME}/parabol RUN rm -rf ${HOME}/parabol/.env -COPY entrypoints/buildenv /usr/local/bin/docker-entrypoint.sh +COPY entrypoints/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh COPY security /security COPY ./tools/ip-to-server_id /home/node/tools/ip-to-server_id diff --git a/docker/parabol-ubi/docker-build/dockerfiles/pipeline.dockerfile b/docker/parabol-ubi/docker-build/dockerfiles/pipeline.dockerfile deleted file mode 100644 index 5194efcbe4d..00000000000 --- a/docker/parabol-ubi/docker-build/dockerfiles/pipeline.dockerfile +++ /dev/null @@ -1,117 +0,0 @@ -ARG _NODE_VERSION=${_NODE_VERSION} -FROM node:${_NODE_VERSION} as base - -ENV NPM_CONFIG_PREFIX=/home/node/.npm-global -ENV PORT=3000 - -RUN apt update -y && \ - apt install systemtap -y - -USER node -EXPOSE ${PORT} - -ENTRYPOINT ["docker-entrypoint.sh"] - -# Final image - copies in parabol build and applies all security configurations to container if enabled -FROM redhat/ubi9:9.2 - -ARG _SECURITY_ENABLED="true" - -ENV HOME=/home/node \ - USER=node - -ENV PORT=3000 - -RUN groupadd -g 1000 node && \ - useradd -r -u 1000 -m -s /sbin/nologin -g node node - -COPY --from=base /usr/local/bin /usr/local/bin -COPY --from=base /usr/local/include /usr/local/include -COPY --from=base /usr/local/share/man /usr/local/share/man -COPY --from=base /usr/local/share/doc /usr/local/share/doc -COPY --from=base /usr/share/systemtap /usr/local/share/systemtap -COPY --from=base /usr/local/lib/node_modules /usr/local/lib/node_modules -COPY --from=base /opt /opt - -# Security -COPY docker/parabol-ubi/docker-build/security /security - -RUN if [ "$_SECURITY_ENABLED" = "true" ]; then \ - echo Update packages and install security patches && \ - sed -i "s/enabled=1/enabled=0/" /etc/dnf/plugins/subscription-manager.conf && \ - echo "exclude=filesystem-*" >> /etc/dnf/dnf.conf && \ - chmod +x /security/*.sh && \ - dnf repolist && \ - dnf update -y && \ - echo "* hard maxlogins 10" > /etc/security/limits.d/maxlogins.conf && \ - /security/xccdf_org.ssgproject.content_rule_account_disable_post_pw_expiration.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_logon_fail_delay.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_max_concurrent_login_sessions.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_maximum_age_login_defs.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_minimum_age_login_defs.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_minlen_login_defs.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_dcredit.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_dictcheck.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_difok.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_lcredit.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_maxclassrepeat.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_maxrepeat.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_minclass.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_minlen.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_ocredit.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_password_auth.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_system_auth.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_ucredit.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_unix_remember.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny_root.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_interval.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_unlock_time.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_bashrc.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_csh_cshrc.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_login_defs.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_profile.sh && \ - /security/xccdf_org.ssgproject.content_rule_banner_etc_issue.sh && \ - /security/xccdf_org.ssgproject.content_rule_configure_kerberos_crypto_policy.sh && \ - /security/xccdf_org.ssgproject.content_rule_configure_openssl_crypto_policy.sh && \ - /security/xccdf_org.ssgproject.content_rule_coredump_disable_backtraces.sh && \ - /security/xccdf_org.ssgproject.content_rule_coredump_disable_storage.sh && \ - /security/xccdf_org.ssgproject.content_rule_disable_ctrlaltdel_burstaction.sh && \ - /security/xccdf_org.ssgproject.content_rule_disable_users_coredumps.sh && \ - /security/xccdf_org.ssgproject.content_rule_display_login_attempts.sh && \ - /security/xccdf_org.ssgproject.content_rule_ensure_gpgcheck_local_packages.sh && \ - /security/xccdf_org.ssgproject.content_rule_file_groupownership_system_commands_dirs.sh && \ - /security/xccdf_org.ssgproject.content_rule_no_empty_passwords.sh && \ - /security/xccdf_org.ssgproject.content_rule_openssl_use_strong_entropy.sh && \ - /security/xccdf_org.ssgproject.content_rule_package_crypto-policies_installed.sh && \ - /security/xccdf_org.ssgproject.content_rule_package_iptables_installed.sh && \ - dnf clean all && \ - rm -rf /var/cache/dnf/ /var/tmp/* /tmp/* /var/tmp/.???* /tmp/.???* && \ - chmod g-s /opt/yarn-v*/bin /opt/yarn-v*/lib && \ - chgrp -R root /opt/yarn-v* && \ - chgrp root /opt/yarn-v*/lib/* /opt/yarn-v*/bin/* /opt/yarn-v*/*; \ - else \ - echo "Security checks disabled."; \ - fi - -RUN rm -rf /security/ - -COPY --chown=node --chmod=755 docker/parabol-ubi/docker-build/entrypoints/buildenv /usr/local/bin/docker-entrypoint.sh -COPY --chown=node docker/parabol-ubi/docker-build/tools/ip-to-server_id ${HOME}/tools/ip-to-server_id - -# The application requires a yarn.lock file on the root folder to identify it -COPY --chown=node yarn.lock ${HOME}/parabol/yarn.lock -# Required for pushToCDN to work with FILE_STORE_PROVIDER set to 'local' -RUN mkdir -p ${HOME}/parabol/self-hosted && \ - chown node:node ${HOME}/parabol/self-hosted - -COPY --chown=node .env.example ${HOME}/parabol/.env.example -COPY --chown=node build ${HOME}/parabol/build -COPY --chown=node dist ${HOME}/parabol/dist - -WORKDIR ${HOME}/parabol/ -USER node -EXPOSE ${PORT} - -ENTRYPOINT ["docker-entrypoint.sh"] diff --git a/docker/parabol-ubi/docker-build/dockerfiles/security-test.dockerfile b/docker/parabol-ubi/docker-build/dockerfiles/security-test.dockerfile deleted file mode 100644 index 246eba76add..00000000000 --- a/docker/parabol-ubi/docker-build/dockerfiles/security-test.dockerfile +++ /dev/null @@ -1,60 +0,0 @@ -#final image -FROM redhat/ubi9:9.2 - -COPY entrypoints/buildenv /usr/local/bin/docker-entrypoint.sh -COPY security /security - -RUN echo Update packages and install security patches && \ - sed -i "s/enabled=1/enabled=0/" /etc/dnf/plugins/subscription-manager.conf && \ - echo "exclude=filesystem-*" >> /etc/dnf/dnf.conf && \ - chmod +x /security/*.sh && \ - dnf repolist && \ - dnf update -y && \ - echo "* hard maxlogins 10" > /etc/security/limits.d/maxlogins.conf && \ - /security/xccdf_org.ssgproject.content_rule_account_disable_post_pw_expiration.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_logon_fail_delay.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_max_concurrent_login_sessions.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_maximum_age_login_defs.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_minimum_age_login_defs.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_minlen_login_defs.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_dcredit.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_dictcheck.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_difok.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_lcredit.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_maxclassrepeat.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_maxrepeat.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_minclass.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_minlen.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_ocredit.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_password_auth.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_system_auth.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_ucredit.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_password_pam_unix_remember.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny_root.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_interval.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_unlock_time.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_bashrc.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_csh_cshrc.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_login_defs.sh && \ - /security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_profile.sh && \ - /security/xccdf_org.ssgproject.content_rule_banner_etc_issue.sh && \ - /security/xccdf_org.ssgproject.content_rule_configure_kerberos_crypto_policy.sh && \ - /security/xccdf_org.ssgproject.content_rule_configure_openssl_crypto_policy.sh && \ - /security/xccdf_org.ssgproject.content_rule_coredump_disable_backtraces.sh && \ - /security/xccdf_org.ssgproject.content_rule_coredump_disable_storage.sh && \ - /security/xccdf_org.ssgproject.content_rule_disable_ctrlaltdel_burstaction.sh && \ - /security/xccdf_org.ssgproject.content_rule_disable_users_coredumps.sh && \ - /security/xccdf_org.ssgproject.content_rule_display_login_attempts.sh && \ - /security/xccdf_org.ssgproject.content_rule_ensure_gpgcheck_local_packages.sh && \ - /security/xccdf_org.ssgproject.content_rule_file_groupownership_system_commands_dirs.sh && \ - /security/xccdf_org.ssgproject.content_rule_no_empty_passwords.sh && \ - /security/xccdf_org.ssgproject.content_rule_openssl_use_strong_entropy.sh && \ - /security/xccdf_org.ssgproject.content_rule_package_crypto-policies_installed.sh && \ - /security/xccdf_org.ssgproject.content_rule_package_iptables_installed.sh && \ - dnf clean all && \ - rm -rf /security/ /var/cache/dnf/ /var/tmp/* /tmp/* /var/tmp/.???* /tmp/.???* && \ - chmod 755 /usr/local/bin/docker-entrypoint.sh - -ENTRYPOINT ["docker-entrypoint.sh"] diff --git a/docker/parabol-ubi/docker-build/entrypoints/buildenv b/docker/parabol-ubi/docker-build/entrypoints/docker-entrypoint.sh similarity index 100% rename from docker/parabol-ubi/docker-build/entrypoints/buildenv rename to docker/parabol-ubi/docker-build/entrypoints/docker-entrypoint.sh diff --git a/docker/parabol-ubi/docker-build/environments/basic-env b/docker/parabol-ubi/docker-build/environments/basic-env new file mode 100644 index 00000000000..86bbad9252d --- /dev/null +++ b/docker/parabol-ubi/docker-build/environments/basic-env @@ -0,0 +1,17 @@ +FILE_STORE_PROVIDER='local' +HOST='localhost' +NODE_ENV='production' +NODE_EXTRA_CA_CERTS='' +PROTO='https' +PORT='3000' +# Database configurations must be the same used in the docker-build.yml Github workflow +POSTGRES_PASSWORD='temppassword' +POSTGRES_USER='tempuser' +POSTGRES_DB='tempdb' +POSTGRES_HOST='localhost' +POSTGRES_PORT='5432' +REDIS_URL='redis://localhost:6379' +RETHINKDB_SSL='' +RETHINKDB_URL='rethinkdb://localhost:28015/buildDB' +SERVER_ID='0' +SERVER_SECRET='FAKE_VALUE' diff --git a/docker/parabol-ubi/docker-build/environments/buildenv b/docker/parabol-ubi/docker-build/environments/legacy-build similarity index 100% rename from docker/parabol-ubi/docker-build/environments/buildenv rename to docker/parabol-ubi/docker-build/environments/legacy-build diff --git a/docker/parabol-ubi/docker-build/environments/local-buildenv b/docker/parabol-ubi/docker-build/environments/local-buildenv deleted file mode 100644 index f5f7c27ab83..00000000000 --- a/docker/parabol-ubi/docker-build/environments/local-buildenv +++ /dev/null @@ -1,54 +0,0 @@ -ATLASSIAN_CLIENT_ID='' -ATLASSIAN_CLIENT_SECRET='' -AWS_ACCESS_KEY_ID='' -AWS_REGION='' -AWS_S3_BUCKET='' -AWS_SECRET_ACCESS_KEY='' -CDN_BASE_URL='' -FILE_STORE_PROVIDER='local' -GITHUB_CLIENT_ID='' -GITHUB_CLIENT_SECRET='' -GITHUB_WEBHOOK_SECRET='' -GITLAB_CLIENT_ID='' -GITLAB_CLIENT_SECRET='' -GOOGLE_CLOUD_CLIENT_EMAIL='' -GOOGLE_CLOUD_PRIVATE_KEY='' -GOOGLE_CLOUD_PRIVATE_KEY_ID='' -GOOGLE_OAUTH_CLIENT_ID='' -GOOGLE_OAUTH_CLIENT_SECRET='' -GOOGLE_TAG_MANAGER_CONTAINER_ID='' -GRAPHQL_HOST='' -GRAPHQL_PROTOCOL='' -HOST='' -INVITATION_SHORTLINK='' -MAIL_PROVIDER='' -MAIL_GOOGLE_USER='' -MAIL_GOOGLE_PASS='' -MAILGUN_API_KEY='' -MAILGUN_DOMAIN='' -MAILGUN_PUBLIC_KEY='' -MAIL_FROM='' -NODE_ENV='production' -NODE_EXTRA_CA_CERTS='' -PROTO='https' -PGADMIN_DEFAULT_EMAIL='' -PGADMIN_DEFAULT_PASSWORD='' -PGSSLMODE='' -PORT='' -POSTGRES_PASSWORD='temppassword' -POSTGRES_USER='tempuser' -POSTGRES_DB='tempdb' -POSTGRES_HOST='localhost' -POSTGRES_PORT='5432' -REDIS_URL='redis://localhost:6379' -RETHINKDB_SSL='' -RETHINKDB_URL='rethinkdb://localhost:28015/actionProduction' -SENTRY_DSN='' -SERVER_ID='' -SERVER_SECRET='FAKE_VALUE' -SLACK_CLIENT_ID='' -SLACK_CLIENT_SECRET='' -STRIPE_PUBLISHABLE_KEY='' -STRIPE_SECRET_KEY='' -STRIPE_WEBHOOK_SECRET='' -HUBSPOT_API_KEY='' From aa88da0205d2434ab3376169ce9cd37e5e5f0db1 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 16:08:39 +0000 Subject: [PATCH 037/529] chore(release): release v7.19.6 (#9480) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 23 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6e9dcf42d00..a1ce62b5bc5 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.19.5" + ".": "7.19.6" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 080f2ea136c..8fbd69d25fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.19.6](https://github.com/ParabolInc/parabol/compare/v7.19.5...v7.19.6) (2024-02-29) + + +### Fixed + +* After parameter for meetingCount was ignored ([#9479](https://github.com/ParabolInc/parabol/issues/9479)) ([052acd1](https://github.com/ParabolInc/parabol/commit/052acd14035fe7c96af8d17ca4763be91d863a80)) + + +### Changed + +* **docker-build:** simplify the docker build process and reduce docker image size ([#9447](https://github.com/ParabolInc/parabol/issues/9447)) ([5e356c2](https://github.com/ParabolInc/parabol/commit/5e356c2566db8e32e45a1393e1b1ea27c4be0a5c)) + ## [7.19.5](https://github.com/ParabolInc/parabol/compare/v7.19.4...v7.19.5) (2024-02-29) diff --git a/package.json b/package.json index ee9f994f9d9..f582fbc4f5d 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.19.5", + "version": "7.19.6", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index f9c5675491e..8a2a986bd03 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.19.5", + "version": "7.19.6", "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.19.5" + "parabol-server": "7.19.6" } } diff --git a/packages/client/package.json b/packages/client/package.json index b5e0427873c..7dac5ea4e99 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.19.5", + "version": "7.19.6", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 0e97990948c..8bc4c541782 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.19.5", + "version": "7.19.6", "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.19.5", - "parabol-server": "7.19.5", + "parabol-client": "7.19.6", + "parabol-server": "7.19.6", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 1b7a9030731..ad76a9338b1 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.19.5", + "version": "7.19.6", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 5dc86873eb0..439b2f4ab0f 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.19.5", + "version": "7.19.6", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.11.0", - "parabol-client": "7.19.5", + "parabol-client": "7.19.6", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 2ff4a6e6328bf437a31e9ac7984af4a55aae3d11 Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Thu, 29 Feb 2024 17:08:29 +0000 Subject: [PATCH 038/529] fix(docker-build): home folder is /home/node now (#9482) --- docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile b/docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile index 025862bedfe..077d95a8fb6 100644 --- a/docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile +++ b/docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile @@ -1,6 +1,9 @@ ARG _NODE_VERSION=${_NODE_VERSION} FROM node:${_NODE_VERSION}-bookworm-slim as base +ENV HOME=/home/node \ + USER=node + ENV NPM_CONFIG_PREFIX=/home/node/.npm-global ENV PORT=3000 From 9c44e23c4d223735551dbae2a0897040840b9e10 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 29 Feb 2024 17:17:19 +0000 Subject: [PATCH 039/529] chore(release): release v7.19.7 (#9483) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a1ce62b5bc5..57085bcbc6b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.19.6" + ".": "7.19.7" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fbd69d25fa..7191952d8a0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.19.7](https://github.com/ParabolInc/parabol/compare/v7.19.6...v7.19.7) (2024-02-29) + + +### Fixed + +* **docker-build:** home folder is /home/node now ([#9482](https://github.com/ParabolInc/parabol/issues/9482)) ([2ff4a6e](https://github.com/ParabolInc/parabol/commit/2ff4a6e6328bf437a31e9ac7984af4a55aae3d11)) + ## [7.19.6](https://github.com/ParabolInc/parabol/compare/v7.19.5...v7.19.6) (2024-02-29) diff --git a/package.json b/package.json index f582fbc4f5d..8749898ad6a 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.19.6", + "version": "7.19.7", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 8a2a986bd03..f44740aee53 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.19.6", + "version": "7.19.7", "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.19.6" + "parabol-server": "7.19.7" } } diff --git a/packages/client/package.json b/packages/client/package.json index 7dac5ea4e99..a065260e9ad 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.19.6", + "version": "7.19.7", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 8bc4c541782..ea18a30925c 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.19.6", + "version": "7.19.7", "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.19.6", - "parabol-server": "7.19.6", + "parabol-client": "7.19.7", + "parabol-server": "7.19.7", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index ad76a9338b1..a1780c8c5b4 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.19.6", + "version": "7.19.7", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 439b2f4ab0f..4e47bbb8ad0 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.19.6", + "version": "7.19.7", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.11.0", - "parabol-client": "7.19.6", + "parabol-client": "7.19.7", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 807e34718d8a7939b7be84438900ef200a6ca896 Mon Sep 17 00:00:00 2001 From: Jordan Husney Date: Thu, 29 Feb 2024 11:44:15 -0800 Subject: [PATCH 040/529] feat: OpenAIGeneration model for embedder (#9474) --- packages/embedder/ai_models/AbstractModel.ts | 2 - packages/embedder/ai_models/ModelManager.ts | 9 +- .../embedder/ai_models/OpenAIGeneration.ts | 94 +++++++++++++++++++ .../ai_models/TextEmbeddingsInference.ts | 2 +- .../ai_models/TextGenerationInference.ts | 26 ++--- packages/embedder/embedder.ts | 3 +- .../embedder/indexing/embeddingsTablesOps.ts | 2 +- 7 files changed, 112 insertions(+), 26 deletions(-) create mode 100644 packages/embedder/ai_models/OpenAIGeneration.ts diff --git a/packages/embedder/ai_models/AbstractModel.ts b/packages/embedder/ai_models/AbstractModel.ts index b0f709ce485..b57d220cd35 100644 --- a/packages/embedder/ai_models/AbstractModel.ts +++ b/packages/embedder/ai_models/AbstractModel.ts @@ -11,7 +11,6 @@ export interface GenerationModelConfig extends ModelConfig {} export abstract class AbstractModel { public readonly url?: string - public modelInstance: any constructor(config: ModelConfig) { this.url = this.normalizeUrl(config.url) @@ -57,7 +56,6 @@ export interface GenerationOptions { temperature?: number topK?: number topP?: number - truncate?: boolean } export abstract class AbstractGenerationModel extends AbstractModel { diff --git a/packages/embedder/ai_models/ModelManager.ts b/packages/embedder/ai_models/ModelManager.ts index ac8f04cc891..bf6888378c8 100644 --- a/packages/embedder/ai_models/ModelManager.ts +++ b/packages/embedder/ai_models/ModelManager.ts @@ -7,6 +7,7 @@ import { GenerationModelConfig, ModelConfig } from './AbstractModel' +import OpenAIGeneration from './OpenAIGeneration' import TextEmbeddingsInference from './TextEmbeddingsInference' import TextGenerationInference from './TextGenerationInference' @@ -16,7 +17,7 @@ interface ModelManagerConfig { } export type EmbeddingsModelType = 'text-embeddings-inference' -export type GenerationModelType = 'text-generation-inference' +export type GenerationModelType = 'openai' | 'text-generation-inference' export class ModelManager { embeddingModels: AbstractEmbeddingsModel[] @@ -80,9 +81,11 @@ export class ModelManager { const [modelType, _] = modelConfig.model.split(':') as [GenerationModelType, string] switch (modelType) { + case 'openai': { + return new OpenAIGeneration(modelConfig) + } case 'text-generation-inference': { - const generator = new TextGenerationInference(modelConfig) - return generator + return new TextGenerationInference(modelConfig) } default: throw new Error(`unsupported summarization model '${modelType}'`) diff --git a/packages/embedder/ai_models/OpenAIGeneration.ts b/packages/embedder/ai_models/OpenAIGeneration.ts new file mode 100644 index 00000000000..b5614b608c5 --- /dev/null +++ b/packages/embedder/ai_models/OpenAIGeneration.ts @@ -0,0 +1,94 @@ +import OpenAI from 'openai' +import { + AbstractGenerationModel, + GenerationModelConfig, + GenerationModelParams, + GenerationOptions +} from './AbstractModel' + +const MAX_REQUEST_TIME_S = 3 * 60 + +export type ModelId = 'gpt-3.5-turbo-0125' | 'gpt-4-turbo-preview' + +type OpenAIGenerationOptions = Omit + +const modelIdDefinitions: Record = { + 'gpt-3.5-turbo-0125': { + maxInputTokens: 4096 + }, + 'gpt-4-turbo-preview': { + maxInputTokens: 128000 + } +} + +function isValidModelId(object: any): object is ModelId { + return Object.keys(modelIdDefinitions).includes(object) +} + +export class OpenAIGeneration extends AbstractGenerationModel { + private openAIApi: OpenAI | null + private modelId: ModelId + + constructor(config: GenerationModelConfig) { + super(config) + if (!process.env.OPEN_AI_API_KEY) { + this.openAIApi = null + return + } + this.openAIApi = new OpenAI({ + apiKey: process.env.OPEN_AI_API_KEY, + organization: process.env.OPEN_AI_ORG_ID + }) + } + + async summarize(content: string, options: OpenAIGenerationOptions) { + if (!this.openAIApi) { + const eMsg = 'OpenAI is not configured' + console.log('OpenAIGenerationSummarizer.summarize(): ', eMsg) + throw new Error(eMsg) + } + const {maxNewTokens: max_tokens = 512, seed, stop, temperature = 0.8, topP: top_p} = options + const prompt = `Create a brief, one-paragraph summary of the following: ${content}` + + try { + const response = await this.openAIApi.chat.completions.create({ + frequency_penalty: 0, + max_tokens, + messages: [ + { + role: 'user', + content: prompt + } + ], + model: this.modelId, + presence_penalty: 0, + temperature, + seed, + stop, + top_p + }) + const maybeSummary = response.choices[0]?.message?.content?.trim() + if (!maybeSummary) throw new Error('OpenAI returned empty summary') + return maybeSummary + } catch (e) { + console.log('OpenAIGenerationSummarizer.summarize(): ', e) + throw e + } + } + protected constructModelParams(config: GenerationModelConfig): GenerationModelParams { + const modelConfigStringSplit = config.model.split(':') + if (modelConfigStringSplit.length != 2) { + throw new Error('OpenAIGeneration model string must be colon-delimited and len 2') + } + + const maybeModelId = modelConfigStringSplit[1] + if (!isValidModelId(maybeModelId)) + throw new Error(`OpenAIGeneration model id unknown: ${maybeModelId}`) + + this.modelId = maybeModelId + + return modelIdDefinitions[maybeModelId] + } +} + +export default OpenAIGeneration diff --git a/packages/embedder/ai_models/TextEmbeddingsInference.ts b/packages/embedder/ai_models/TextEmbeddingsInference.ts index 93bb2c88c2f..549fadcd6fd 100644 --- a/packages/embedder/ai_models/TextEmbeddingsInference.ts +++ b/packages/embedder/ai_models/TextEmbeddingsInference.ts @@ -63,7 +63,7 @@ export class TextEmbeddingsInference extends AbstractEmbeddingsModel { if (!this.url) throw new Error('TextGenerationInferenceSummarizer model requires url') const maybeModelId = modelConfigStringSplit[1] if (!isValidModelId(maybeModelId)) - throw new Error(`TextGenerationInference model subtype unknown: ${maybeModelId}`) + throw new Error(`TextGenerationInference model id unknown: ${maybeModelId}`) return modelIdDefinitions[maybeModelId] } } diff --git a/packages/embedder/ai_models/TextGenerationInference.ts b/packages/embedder/ai_models/TextGenerationInference.ts index 6f12ce09974..bcf1daa6303 100644 --- a/packages/embedder/ai_models/TextGenerationInference.ts +++ b/packages/embedder/ai_models/TextGenerationInference.ts @@ -25,16 +25,8 @@ export class TextGenerationInference extends AbstractGenerationModel { super(config) } - public async summarize(content: string, options: GenerationOptions) { - const { - maxNewTokens: max_new_tokens = 512, - seed, - stop, - temperature = 0.8, - topP, - topK, - truncate - } = options + async summarize(content: string, options: GenerationOptions) { + const {maxNewTokens: max_new_tokens = 512, seed, stop, temperature = 0.8, topP, topK} = options const parameters = { max_new_tokens, seed, @@ -42,7 +34,7 @@ export class TextGenerationInference extends AbstractGenerationModel { temperature, topP, topK, - truncate + truncate: true } const prompt = `Create a brief, one-paragraph summary of the following: ${content}` const fetchOptions = { @@ -59,27 +51,27 @@ export class TextGenerationInference extends AbstractGenerationModel { } try { - // console.log(`TextGenerationInterface.summarize(): summarizing from ${this.url}/generate`) + // console.log(`TextGenerationInference.summarize(): summarizing from ${this.url}/generate`) const res = await fetchWithRetry(`${this.url}/generate`, fetchOptions) const json = await res.json() if (!json || !json.generated_text) - throw new Error('TextGenerationInterface.summarize(): malformed response') + throw new Error('TextGenerationInference.summarize(): malformed response') return json.generated_text as string } catch (e) { - console.log('TextGenerationInterfaceSummarizer.summarize(): timeout') + console.log('TextGenerationInferenceSummarizer.summarize(): timeout') throw e } } protected constructModelParams(config: GenerationModelConfig): GenerationModelParams { const modelConfigStringSplit = config.model.split(':') if (modelConfigStringSplit.length != 2) { - throw new Error('TextGenerationInterface model string must be colon-delimited and len 2') + throw new Error('TextGenerationInference model string must be colon-delimited and len 2') } - if (!this.url) throw new Error('TextGenerationInterfaceSummarizer model requires url') + if (!this.url) throw new Error('TextGenerationInferenceSummarizer model requires url') const maybeModelId = modelConfigStringSplit[1] if (!isValidModelId(maybeModelId)) - throw new Error(`TextGenerationInterface model subtype unknown: ${maybeModelId}`) + throw new Error(`TextGenerationInference model id unknown: ${maybeModelId}`) return modelIdDefinitions[maybeModelId] } } diff --git a/packages/embedder/embedder.ts b/packages/embedder/embedder.ts index 1072a546d00..f6762013d08 100644 --- a/packages/embedder/embedder.ts +++ b/packages/embedder/embedder.ts @@ -162,9 +162,8 @@ const dequeueAndEmbedUntilEmpty = async (modelManager: ModelManager) => { try { const generator = modelManager.generationModels[0] // use 1st generator if (!generator) throw new Error(`Generator unavailable`) - const summarizeOptions = {maxInputTokens, truncate: true} console.log(`embedder: ...summarizing ${itemKey} for ${modelTable}`) - embedText = await generator.summarize(fullText, summarizeOptions) + embedText = await generator.summarize(fullText, {maxNewTokens: maxInputTokens}) } catch (e) { await updateJobState(jobQueueId, 'failed', { stateMessage: `unable to summarize long embed text: ${e}` diff --git a/packages/embedder/indexing/embeddingsTablesOps.ts b/packages/embedder/indexing/embeddingsTablesOps.ts index b68bc21ccbe..c74eb709708 100644 --- a/packages/embedder/indexing/embeddingsTablesOps.ts +++ b/packages/embedder/indexing/embeddingsTablesOps.ts @@ -52,7 +52,7 @@ export async function selectMetaToQueue( .where(({eb, not, or, and, exists, selectFrom}) => and([ or([ - not(eb('em.models', '<@', sql`ARRAY[${sql.ref('model')}]::varchar[]` as any) as any), + not(eb('em.models', '@>', sql`ARRAY[${sql.ref('model')}]::varchar[]` as any) as any), eb('em.models' as any, 'is', null) ]), not( From 4e2e2ca00f237a7a8c94dc2e7f0d2f7d9ef9210d Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 29 Feb 2024 17:58:38 -0800 Subject: [PATCH 041/529] fix: support single-tenant saml record (#9486) Signed-off-by: Matt Krick --- packages/server/graphql/private/mutations/loginSAML.ts | 7 +++++-- packages/server/utils/getSAMLURLFromEmail.ts | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/packages/server/graphql/private/mutations/loginSAML.ts b/packages/server/graphql/private/mutations/loginSAML.ts index c14201f35eb..c3137a14205 100644 --- a/packages/server/graphql/private/mutations/loginSAML.ts +++ b/packages/server/graphql/private/mutations/loginSAML.ts @@ -16,6 +16,7 @@ import getSignOnURL from '../../public/mutations/helpers/SAMLHelpers/getSignOnUR import {SSORelayState} from '../../queries/SAMLIdP' import {MutationResolvers} from '../resolverTypes' import standardError from '../../../utils/standardError' +import {isSingleTenantSSO} from '../../../utils/getSAMLURLFromEmail' const serviceProvider = samlify.ServiceProvider({}) samlify.setSchemaValidator(samlXMLValidator) @@ -104,8 +105,10 @@ const loginSAML: MutationResolvers['loginSAML'] = async ( } const ssoDomain = getSSODomainFromEmail(email) if (!ssoDomain || !domains.includes(ssoDomain)) { - // don't blindly trust the IdP - return {error: {message: `${email} does not belong to ${domains.join(', ')}`}} + if (!isSingleTenantSSO) { + // don't blindly trust the IdP unless there is only 1 + return {error: {message: `${email} does not belong to ${domains.join(', ')}`}} + } } if (newMetadata) { diff --git a/packages/server/utils/getSAMLURLFromEmail.ts b/packages/server/utils/getSAMLURLFromEmail.ts index 2c07d74a06d..896a479b483 100644 --- a/packages/server/utils/getSAMLURLFromEmail.ts +++ b/packages/server/utils/getSAMLURLFromEmail.ts @@ -4,7 +4,7 @@ import {URL} from 'url' import {DataLoaderWorker} from '../graphql/graphql' import getKysely from '../postgres/getKysely' -const isSingleTenantSSO = +export const isSingleTenantSSO = process.env.AUTH_INTERNAL_DISABLED === 'true' && process.env.AUTH_GOOGLE_DISABLED === 'true' && process.env.AUTH_MICROSOFT_DISABLED === 'true' && From ba7d7246c8db632f8016ec632c7841ae9a847dd2 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Fri, 1 Mar 2024 08:37:27 -0800 Subject: [PATCH 042/529] chore(release): release v7.20.0 (#9485) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 23 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 57085bcbc6b..7f814dacbc5 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.19.7" + ".": "7.20.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 7191952d8a0..9d762695706 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.20.0](https://github.com/ParabolInc/parabol/compare/v7.19.7...v7.20.0) (2024-03-01) + + +### Added + +* OpenAIGeneration model for embedder ([#9474](https://github.com/ParabolInc/parabol/issues/9474)) ([807e347](https://github.com/ParabolInc/parabol/commit/807e34718d8a7939b7be84438900ef200a6ca896)) + + +### Fixed + +* support single-tenant saml record ([#9486](https://github.com/ParabolInc/parabol/issues/9486)) ([4e2e2ca](https://github.com/ParabolInc/parabol/commit/4e2e2ca00f237a7a8c94dc2e7f0d2f7d9ef9210d)) + ## [7.19.7](https://github.com/ParabolInc/parabol/compare/v7.19.6...v7.19.7) (2024-02-29) diff --git a/package.json b/package.json index 8749898ad6a..a2b1c1f913c 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.19.7", + "version": "7.20.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index f44740aee53..b0166f09a82 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.19.7", + "version": "7.20.0", "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.19.7" + "parabol-server": "7.20.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index a065260e9ad..085bd29d0b3 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.19.7", + "version": "7.20.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index ea18a30925c..5fc6dbb0961 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.19.7", + "version": "7.20.0", "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.19.7", - "parabol-server": "7.19.7", + "parabol-client": "7.20.0", + "parabol-server": "7.20.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index a1780c8c5b4..9053ded2078 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.19.7", + "version": "7.20.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 4e47bbb8ad0..1d09f43fca0 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.19.7", + "version": "7.20.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.11.0", - "parabol-client": "7.19.7", + "parabol-client": "7.20.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From a95fb88b9a76e04eb73630404e29e6325dcf1a12 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Mon, 4 Mar 2024 21:35:15 +0100 Subject: [PATCH 043/529] chore: Update reviewers (#9504) --- .github/reviewers.yml | 3 --- 1 file changed, 3 deletions(-) diff --git a/.github/reviewers.yml b/.github/reviewers.yml index 2b92d6ef8f6..8d2732ba714 100644 --- a/.github/reviewers.yml +++ b/.github/reviewers.yml @@ -1,19 +1,16 @@ reviewers: groups: reviewers: - - igorlesnenko - nickoferrall maintainers: - mattkrick - Dschoordsch data: - tianrunhe - - tghanken designers: - ackernaut devops: - rafaelromcar-parabol - - adaniels-parabol - dbumblis-parabol none: From 58c5817463bcb73dbcaa83b05a0d2a201262de77 Mon Sep 17 00:00:00 2001 From: Jordan Husney Date: Tue, 5 Mar 2024 14:17:30 -0800 Subject: [PATCH 044/529] chore: bump ts node (#9498) * chore: bump ts-node * chore: fixup implicit any in migrations * chore: regenerate yarn.lock --- packages/server/package.json | 2 +- .../1693991480688_truncateReflectPromptIds.ts | 9 +- .../migrations/1694191002164_migrateSAML.ts | 6 +- ...ameEmailVerificationSegmentIdToPseudoId.ts | 6 +- yarn.lock | 82 +++++++++++++++++-- 5 files changed, 84 insertions(+), 21 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index 1d09f43fca0..776adbfb414 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -67,7 +67,7 @@ "style-loader": "2.0.0", "sucrase": "^3.32.0", "ts-jest": "^29.1.0", - "ts-node": "^8.6.2", + "ts-node": "10.9.2", "typescript": "^5.3.3", "url-loader": "4.1.1", "vscode-apollo-relay": "^1.5.0", diff --git a/packages/server/postgres/migrations/1693991480688_truncateReflectPromptIds.ts b/packages/server/postgres/migrations/1693991480688_truncateReflectPromptIds.ts index f2f5d42472d..a17dd2f0de1 100644 --- a/packages/server/postgres/migrations/1693991480688_truncateReflectPromptIds.ts +++ b/packages/server/postgres/migrations/1693991480688_truncateReflectPromptIds.ts @@ -1,5 +1,4 @@ -import {Client} from 'pg' -import {r} from 'rethinkdb-ts' +import {r, RDatum} from 'rethinkdb-ts' import connectRethinkDB from '../../database/connectRethinkDB' /** @@ -12,13 +11,13 @@ export async function up() { .insert( r .table('ReflectPrompt') - .filter((row) => row('id').count().gt(100)) - .map((row) => row.merge({id: row('id').slice(0, 100)})) + .filter((row: RDatum) => row('id').count().gt(100)) + .map((row: RDatum) => row.merge({id: row('id').slice(0, 100)})) ) .run() await r .table('ReflectPrompt') - .filter((row) => row('id').count().gt(100)) + .filter((row: RDatum) => row('id').count().gt(100)) .delete() .run() await r.getPoolMaster()?.drain() diff --git a/packages/server/postgres/migrations/1694191002164_migrateSAML.ts b/packages/server/postgres/migrations/1694191002164_migrateSAML.ts index f8824407f67..1b2a41bfbc1 100644 --- a/packages/server/postgres/migrations/1694191002164_migrateSAML.ts +++ b/packages/server/postgres/migrations/1694191002164_migrateSAML.ts @@ -1,6 +1,6 @@ import {Kysely, PostgresDialect, sql} from 'kysely' import {Client} from 'pg' -import {r} from 'rethinkdb-ts' +import {r, RDatum} from 'rethinkdb-ts' import getPg from '../getPg' import getPgConfig from '../getPgConfig' @@ -57,7 +57,7 @@ export async function up() { await r .table('SAML') .update( - (saml) => ({ + (saml: RDatum) => ({ orgId: r .table('Organization') .getAll(r.args(saml('domains')), {index: 'activeDomain'}) @@ -78,7 +78,7 @@ export async function up() { const nextSAMLDomains = [] as {domain: string; samlId: string}[] existingSAMLs.forEach((saml) => { - saml.domains.forEach((domain) => { + saml.domains.forEach((domain: any) => { nextSAMLDomains.push({domain, samlId: saml.id}) }) }) diff --git a/packages/server/postgres/migrations/1698265572466_renameEmailVerificationSegmentIdToPseudoId.ts b/packages/server/postgres/migrations/1698265572466_renameEmailVerificationSegmentIdToPseudoId.ts index c6cc5c2659d..694fa70b2fb 100644 --- a/packages/server/postgres/migrations/1698265572466_renameEmailVerificationSegmentIdToPseudoId.ts +++ b/packages/server/postgres/migrations/1698265572466_renameEmailVerificationSegmentIdToPseudoId.ts @@ -1,4 +1,4 @@ -import {r} from 'rethinkdb-ts' +import {r, RDatum} from 'rethinkdb-ts' const connectRethinkDB = async () => { const {hostname: host, port, pathname} = new URL(process.env.RETHINKDB_URL!) @@ -13,7 +13,7 @@ export async function up() { await connectRethinkDB() await r .table('EmailVerification') - .replace((row) => row.without('segmentId').merge({pseudoId: row('segmentId')})) + .replace((row: RDatum) => row.without('segmentId').merge({pseudoId: row('segmentId')})) .run() } @@ -21,6 +21,6 @@ export async function down() { await connectRethinkDB() await r .table('EmailVerification') - .replace((row) => row.without('pseudoId').merge({segmentId: row('pseudoId')})) + .replace((row: RDatum) => row.without('pseudoId').merge({segmentId: row('pseudoId')})) .run() } diff --git a/yarn.lock b/yarn.lock index 17637e0e348..0678b62b736 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2829,6 +2829,13 @@ resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + "@datadog/browser-core@3.6.12": version "3.6.12" resolved "https://registry.yarnpkg.com/@datadog/browser-core/-/browser-core-3.6.12.tgz#6fcdbd6809656544289f7684f03b88a598cc3345" @@ -4087,6 +4094,11 @@ resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.2" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz#7a0ee601f60f99a20c7c7c5ff0c80388c1189bd6" + integrity sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw== + "@jridgewell/set-array@^1.0.1": version "1.1.2" resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72" @@ -4105,6 +4117,14 @@ resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@jridgewell/trace-mapping@^0.3.12", "@jridgewell/trace-mapping@^0.3.15", "@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.8", "@jridgewell/trace-mapping@^0.3.9": version "0.3.18" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6" @@ -7259,6 +7279,26 @@ mkdirp "^1.0.4" path-browserify "^1.0.1" +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.4.tgz#0b92dcc0cc1c81f6f306a381f28e31b1a56536e9" + integrity sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA== + "@types/analytics-node@^3.1.3": version "3.1.7" resolved "https://registry.yarnpkg.com/@types/analytics-node/-/analytics-node-3.1.7.tgz#cb97c80ee505094e44a0188c3ad25f70c67e3c65" @@ -8458,6 +8498,11 @@ acorn-walk@^8.0.0, acorn-walk@^8.0.2, acorn-walk@^8.2.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== +acorn-walk@^8.1.1: + version "8.3.2" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.3.2.tgz#7703af9415f1b6db9315d6895503862e231d34aa" + integrity sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A== + acorn@^7.0.0: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" @@ -8468,6 +8513,11 @@ acorn@^8.0.4, acorn@^8.1.0, acorn@^8.7.0, acorn@^8.7.1, acorn@^8.8.0, acorn@^8.8 resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +acorn@^8.4.1: + version "8.11.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" + integrity sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg== + adaptivecards@^2.10.0: version "2.10.0" resolved "https://registry.yarnpkg.com/adaptivecards/-/adaptivecards-2.10.0.tgz#1c94e84491afe5a4f2d4060f6e0fc57b895205e3" @@ -9577,9 +9627,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001580, caniuse-lite@~1.0.0: - version "1.0.30001571" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001571.tgz#4182e93d696ff42930f4af7eba515ddeb57917ac" - integrity sha512-tYq/6MoXhdezDLFZuCO/TKboTzuQ/xR5cFdgXPfDtM7/kchBO3b4VWghE/OAi/DV7tTdhmLjZiZBZi1fA/GheQ== + version "1.0.30001591" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz#16745e50263edc9f395895a7cd468b9f3767cf33" + integrity sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ== capital-case@^1.0.4: version "1.0.4" @@ -11173,6 +11223,7 @@ draft-js-utils@^1.4.0: "draft-js@https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6": version "0.10.5" + uid "025fddba56f21aaf3383aee778e0b17025c9a7bc" resolved "https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6#025fddba56f21aaf3383aee778e0b17025c9a7bc" dependencies: fbjs "^0.8.15" @@ -20623,15 +20674,23 @@ ts-node-dev@^1.0.0-pre.44: ts-node "^9.0.0" tsconfig "^7.0.0" -ts-node@^8.6.2: - version "8.10.2" - resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-8.10.2.tgz#eee03764633b1234ddd37f8db9ec10b75ec7fb8d" - integrity sha512-ISJJGgkIpDdBhWVu3jufsWpK3Rzo7bdiIXJjQc0ynKxVOVcg2oIrf2H2cejminGrptVc6q6/uynAHNCuWGbpVA== - dependencies: +ts-node@10.9.2: + version "10.9.2" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.2.tgz#70f021c9e185bccdca820e26dc413805c101c71f" + integrity sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" arg "^4.1.0" + create-require "^1.1.0" diff "^4.0.1" make-error "^1.1.1" - source-map-support "^0.5.17" + v8-compile-cache-lib "^3.0.1" yn "3.1.1" ts-node@^9.0.0: @@ -21144,6 +21203,11 @@ uuid@^9.0.0: resolved "https://registry.yarnpkg.com/uuid/-/uuid-9.0.0.tgz#592f550650024a38ceb0c562f2f6aa435761efb5" integrity sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg== +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + v8-compile-cache@2.3.0, v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" From 06c1f7eec63c6de343181ee1324635c2ae8d286a Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 5 Mar 2024 15:25:26 -0800 Subject: [PATCH 045/529] chore: put server assets on CDN (#9278) * chore: put server assets on CDN Signed-off-by: Matt Krick * fix: stray uncorked res Signed-off-by: Matt Krick * prod support for public path Signed-off-by: Matt Krick * support nested dirs Signed-off-by: Matt Krick * log bg processes Signed-off-by: Matt Krick * fix failed test path Signed-off-by: Matt Krick * simplify webpack build Signed-off-by: Matt Krick * change proto to http for CI localhost Signed-off-by: Matt Krick * fix: remove comment Signed-off-by: Matt Krick * add embedder back to build Signed-off-by: Matt Krick --------- Signed-off-by: Matt Krick --- .github/workflows/test.yml | 13 ++-- .../docker-build/environments/pipeline | 2 +- .../EmailDiscussionMentioned.tsx | 4 +- .../EmailResponseReplied.tsx | 4 +- packages/client/serviceWorker/sw.ts | 2 +- .../styles/theme/images/anonymous-avatar.png | Bin 0 -> 1843 bytes .../server/fileStorage/FileStoreManager.ts | 6 +- packages/server/fileStorage/GCSManager.ts | 12 +-- .../server/fileStorage/S3FileStoreManager.ts | 12 +-- packages/server/initPublicPath.ts | 16 ++++ packages/server/jiraImagesHandler.ts | 11 +-- packages/server/selfHostedHandler.ts | 2 +- packages/server/types/modules.d.ts | 2 +- packages/server/utils/serveStatic.ts | 6 +- .../toolboxSrc/applyEnvVarsToClientAssets.ts | 27 +++---- scripts/toolboxSrc/pushToCDN.ts | 73 +++++++++++------- scripts/webpack/dev.servers.config.js | 9 ++- scripts/webpack/prod.client.config.js | 2 +- scripts/webpack/prod.servers.config.js | 46 ++--------- 19 files changed, 124 insertions(+), 125 deletions(-) create mode 100644 packages/client/styles/theme/images/anonymous-avatar.png create mode 100644 packages/server/initPublicPath.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a4cc2323d8c..634787a6813 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -93,11 +93,12 @@ jobs: run: yarn predeploy - name: Start testing server in background - run: | - yarn start & - - - name: Wait for testing server to be healthy - run: curl -4 --retry 30 --retry-connrefused --retry-delay 2 http://localhost:3000/graphql + uses: JarvusInnovations/background-action@v1 + with: + run: | + yarn start & + wait-on: | + http://localhost:3000/graphql - name: Run server tests run: yarn test:server -- --reporters=default --reporters=jest-junit @@ -130,5 +131,5 @@ jobs: uses: actions/upload-artifact@v2 with: name: test-results - path: test-results/ + path: packages/integration-tests/test-results/ retention-days: 7 diff --git a/docker/parabol-ubi/docker-build/environments/pipeline b/docker/parabol-ubi/docker-build/environments/pipeline index 8fd2ac929d9..5641d43725f 100644 --- a/docker/parabol-ubi/docker-build/environments/pipeline +++ b/docker/parabol-ubi/docker-build/environments/pipeline @@ -31,7 +31,7 @@ MAILGUN_PUBLIC_KEY='' MAIL_FROM='' NODE_ENV='production' NODE_EXTRA_CA_CERTS='' -PROTO='https' +PROTO='http' PGADMIN_DEFAULT_EMAIL='' PGADMIN_DEFAULT_PASSWORD='' PGSSLMODE='' diff --git a/packages/client/modules/email/components/EmailNotifications/EmailDiscussionMentioned.tsx b/packages/client/modules/email/components/EmailNotifications/EmailDiscussionMentioned.tsx index 876f800860e..87e69cfd1dc 100644 --- a/packages/client/modules/email/components/EmailNotifications/EmailDiscussionMentioned.tsx +++ b/packages/client/modules/email/components/EmailNotifications/EmailDiscussionMentioned.tsx @@ -1,17 +1,17 @@ import graphql from 'babel-plugin-relay/macro' import {convertFromRaw, Editor, EditorState} from 'draft-js' -import editorDecorators from 'parabol-client/components/TaskEditor/decorators' import {EmailDiscussionMentioned_notification$key} from 'parabol-client/__generated__/EmailDiscussionMentioned_notification.graphql' +import editorDecorators from 'parabol-client/components/TaskEditor/decorators' import React, {useMemo, useRef} from 'react' import {useFragment} from 'react-relay' import {cardShadow} from '../../../../styles/elevation' import {PALETTE} from '../../../../styles/paletteV3' +import anonymousAvatar from '../../../../styles/theme/images/anonymous-avatar.png' import {FONT_FAMILY} from '../../../../styles/typographyV2' import makeAppURL from '../../../../utils/makeAppURL' import fromStageIdToUrl from '../../../../utils/meetings/fromStageIdToUrl' import {notificationSummaryUrlParams} from '../NotificationSummaryEmail' import EmailNotificationTemplate from './EmailNotificationTemplate' -import anonymousAvatar from '../../../../styles/theme/images/anonymous-avatar.svg' const editorStyles: React.CSSProperties = { backgroundColor: '#FFFFFF', diff --git a/packages/client/modules/email/components/EmailNotifications/EmailResponseReplied.tsx b/packages/client/modules/email/components/EmailNotifications/EmailResponseReplied.tsx index 7fc99507586..d18009f4d02 100644 --- a/packages/client/modules/email/components/EmailNotifications/EmailResponseReplied.tsx +++ b/packages/client/modules/email/components/EmailNotifications/EmailResponseReplied.tsx @@ -1,16 +1,16 @@ import graphql from 'babel-plugin-relay/macro' import {convertFromRaw, Editor, EditorState} from 'draft-js' -import editorDecorators from 'parabol-client/components/TaskEditor/decorators' import {EmailResponseReplied_notification$key} from 'parabol-client/__generated__/EmailResponseReplied_notification.graphql' +import editorDecorators from 'parabol-client/components/TaskEditor/decorators' import React, {useMemo, useRef} from 'react' import {useFragment} from 'react-relay' import {cardShadow} from '../../../../styles/elevation' import {PALETTE} from '../../../../styles/paletteV3' +import anonymousAvatar from '../../../../styles/theme/images/anonymous-avatar.png' import {FONT_FAMILY} from '../../../../styles/typographyV2' import makeAppURL from '../../../../utils/makeAppURL' import {notificationSummaryUrlParams} from '../NotificationSummaryEmail' import EmailNotificationTemplate from './EmailNotificationTemplate' -import anonymousAvatar from '../../../../styles/theme/images/anonymous-avatar.svg' const editorStyles = { backgroundColor: '#FFFFFF', diff --git a/packages/client/serviceWorker/sw.ts b/packages/client/serviceWorker/sw.ts index 3fba470695b..e35d7f86db7 100644 --- a/packages/client/serviceWorker/sw.ts +++ b/packages/client/serviceWorker/sw.ts @@ -18,7 +18,7 @@ const DYNAMIC_CACHE = `parabol-dynamic-${__APP_VERSION__}` const cacheList = [STATIC_CACHE, DYNAMIC_CACHE] // this gets built in applyEnvVarToClientAssets -const PUBLIC_PATH = `__PUBLIC_PATH__`.replace(/\/{2,}/, 'https://') +const PUBLIC_PATH = `__PUBLIC_PATH__`.replace(/^\/{2,}/, 'https://') const waitUntil = (cb: (e: ExtendableEvent) => void) => (e: ExtendableEvent) => { e.waitUntil(cb(e)) } diff --git a/packages/client/styles/theme/images/anonymous-avatar.png b/packages/client/styles/theme/images/anonymous-avatar.png new file mode 100644 index 0000000000000000000000000000000000000000..791dcd86512fdd5030d2c560ef7fa6dd1238a5a2 GIT binary patch literal 1843 zcmV-32h8}1P);M(*l7D2&O8eB&ZVo_Y@!P>i;Jtj)HU+Hrt71+X2^z41hdg_0|4!IHyD<#@dBIWIqm_^ zvDYo^T#H7drqhXBP5|TaFW)Yzm5E5>rc>DIf z3)|bfpKL4YWTRoZoPZsb+f6l3OM!$x;Qvh|GP?1N0dFoOK795p`&&X9%r^l$Du!Xm z*|XAL=jNyW&=p`j9_I^rb19q2PkftTMw!!>*owN(5X&Q35oLzV1 zaye+)iMPnf{)+m;<>j@BeZW$&__31=kxr-KBtU^8Ct4;Z>FYJzE+*mkgQ0YIzjS7=SEk2wkI^?Gfe$fNRlJdXS|T_2-0O_!Vm38Dx= z6dg^}t#pmZv{5(sZa>g$HenbB48wp)Of;+p0KiST!7wx!rUxv?b`4ms6M?p@Cf&)g z*=(Yysz_(Ds8n`Qt=3SfR#4XsG@I==K1I2}vK&NFgeZ#edOZk*Lh$?jaJkywY^G_t z+rp`Z0-ij5ic~6vTFw4Ndr+3ugr;fGG!6ND9suy*!G9p|0tU|XWAN-*1p4}38c$P{ z%WO1oEXCDoH4xK8e}6yv9pqA{BBqI2tp-65j>FM(gYIE_G(xmvfPx^L7^LYsGU*Hq z!+^)@K{ylwMIEc!005R{A(PHvw^Bh5%OD&IgX8S?S}e=dXrAYkYDGI`Ct!2)G1k`Z zf~MUNL;<@yyI|QK{AhL-exL7BD=RA)A0Nla$eREFViK&b+`;0_7=H5ZPr>%|008RsI%129kYyQ@ zQxjn5y#;#o=qs!wRv`!?WU2G*CJ4fPn&;T-0Dj)ddHvx-3=a+M!vO$8Gnk&9!e^h| zz&~%?K)t?K6S6D=mSr(LJq3z#@B5F8yopk&g!T1xdw?SMDP>{j8pp96OJG%15e|os z*(u76P$&eJumUC`** zD9WWWmX?=MDwRP9fubl_jKv@d0?rM;Xat9bh9QUo7Gp6eiUL9iO3!1<E*|)z04m3@-Zap0Q`m4?Vwd+et#S#*$D=4Zj_RyI#XD~52v44VFsv8FGtgK=y zncTMp0)9+QPQm9td?g(e!!YE+$k2Y%bf|H^xv=u*jg92*+x~k1D%C3LnhuZ0)3F$+ z*XyY4R>1NcJT|JJ;qMImX?}j{kFCFty)69W?@PC{nfwnre(4n`6b{DUeeZJQpzYXk zdhchKXM;ii*RKz2Pr*LlFaZy-!{})}Dev-(x8(O`bo0zJ7G9 z?em*jFmGnk`RP{)7YYaCBNsz+C&9G^Y-wR3@j+^9`*%)W9qg!wWsLp->BEBuqANgP z@7-oQSN=rFs?(Nbbxs3qL{XF_$$>BYf#9zrk++_9@^|!zzPx<*d%HW@$Ax_9a#xS& zZ}|S@_3~F+m#`f<0&FQ3i_va(?=Q5fenr#uF - abstract prependPath(partialPath: string): string + abstract checkExists(fileName: string, assetDir?: FileAssetDir): Promise + abstract prependPath(partialPath: string, assetDir?: FileAssetDir): string abstract getPublicFileLocation(fullPath: string): string protected abstract putFile(file: Buffer, fullPath: string): Promise diff --git a/packages/server/fileStorage/GCSManager.ts b/packages/server/fileStorage/GCSManager.ts index a3a1cb4d03e..fe952eed138 100644 --- a/packages/server/fileStorage/GCSManager.ts +++ b/packages/server/fileStorage/GCSManager.ts @@ -2,7 +2,7 @@ import {sign} from 'jsonwebtoken' import mime from 'mime-types' import path from 'path' import {Logger} from '../utils/Logger' -import FileStoreManager from './FileStoreManager' +import FileStoreManager, {FileAssetDir} from './FileStoreManager' interface CloudKey { clientEmail: string @@ -141,18 +141,18 @@ export default class GCSManager extends FileStoreManager { } putBuildFile(file: Buffer, partialPath: string): Promise { - const fullPath = path.join(this.envSubDir, 'build', partialPath) + const fullPath = this.prependPath(partialPath, 'build') return this.putFile(file, fullPath) } - prependPath(partialPath: string) { - return path.join(this.envSubDir, 'store', partialPath) + prependPath(partialPath: string, assetDir: FileAssetDir = 'store') { + return path.join(this.envSubDir, assetDir, partialPath) } getPublicFileLocation(fullPath: string) { return encodeURI(`${this.baseUrl}${fullPath}`) } - async checkExists(partialPath: string) { - const fullPath = encodeURIComponent(this.prependPath(partialPath)) + async checkExists(partialPath: string, assetDir?: FileAssetDir) { + const fullPath = encodeURIComponent(this.prependPath(partialPath, assetDir)) const url = `https://storage.googleapis.com/storage/v1/b/${this.bucket}/o/${fullPath}` const res = await fetch(url) return res.status !== 404 diff --git a/packages/server/fileStorage/S3FileStoreManager.ts b/packages/server/fileStorage/S3FileStoreManager.ts index caace24f9c9..3971ea80eea 100644 --- a/packages/server/fileStorage/S3FileStoreManager.ts +++ b/packages/server/fileStorage/S3FileStoreManager.ts @@ -1,7 +1,7 @@ import {HeadObjectCommand, PutObjectCommand, S3Client} from '@aws-sdk/client-s3' import mime from 'mime-types' import path from 'path' -import FileStoreManager from './FileStoreManager' +import FileStoreManager, {FileAssetDir} from './FileStoreManager' export default class S3Manager extends FileStoreManager { // e.g. development, production @@ -55,8 +55,8 @@ export default class S3Manager extends FileStoreManager { return this.getPublicFileLocation(fullPath) } - prependPath(partialPath: string) { - return path.join(this.envSubDir, 'store', partialPath) + prependPath(partialPath: string, assetDir: FileAssetDir = 'store') { + return path.join(this.envSubDir, assetDir, partialPath) } getPublicFileLocation(fullPath: string) { @@ -64,11 +64,11 @@ export default class S3Manager extends FileStoreManager { } putBuildFile(file: Buffer, partialPath: string): Promise { - const fullPath = path.join(this.envSubDir, 'build', partialPath) + const fullPath = this.prependPath(partialPath, 'build') return this.putFile(file, fullPath) } - async checkExists(key: string) { - const Key = this.prependPath(key) + async checkExists(key: string, assetDir?: FileAssetDir) { + const Key = this.prependPath(key, assetDir) try { await this.s3.send(new HeadObjectCommand({Bucket: this.bucket, Key})) } catch (e) { diff --git a/packages/server/initPublicPath.ts b/packages/server/initPublicPath.ts new file mode 100644 index 00000000000..79d626cf1e3 --- /dev/null +++ b/packages/server/initPublicPath.ts @@ -0,0 +1,16 @@ +import appOrigin from './appOrigin' + +declare let __webpack_public_path__: string +declare const __PRODUCTION__: boolean + +const {CDN_BASE_URL, SOCKET_PORT} = process.env + +if (CDN_BASE_URL) { + // pushToCDN#pushServerAssetsToCDN ensures all assets will be available on the CDN + __webpack_public_path__ = `${CDN_BASE_URL.replace(/^\/{2,}/, 'https://')}/build/` +} else { + const url = new URL('/static/', appOrigin) + // the webpack dev server uses /static on PORT, so fetch assets at SOCKET_PORT + url.port = __PRODUCTION__ ? url.port : SOCKET_PORT! + __webpack_public_path__ = url.toString() +} diff --git a/packages/server/jiraImagesHandler.ts b/packages/server/jiraImagesHandler.ts index 64d7c787b83..8aea88d2cdb 100644 --- a/packages/server/jiraImagesHandler.ts +++ b/packages/server/jiraImagesHandler.ts @@ -1,5 +1,3 @@ -import {promises as fsp} from 'fs' -import path from 'path' import {HttpRequest, HttpResponse} from 'uWebSockets.js' import jiraPlaceholder from '../../static/images/illustrations/imageNotFound.png' import sleep from '../client/utils/sleep' @@ -31,9 +29,12 @@ const getImageFromCache = async ( let jiraPlaceholderBuffer: Buffer | undefined const servePlaceholderImage = async (res: HttpResponse) => { if (!jiraPlaceholderBuffer) { - jiraPlaceholderBuffer = await fsp.readFile( - path.join(__dirname, jiraPlaceholder.slice(__webpack_public_path__.length)) - ) + try { + const res = await fetch(jiraPlaceholder) + jiraPlaceholderBuffer = Buffer.from(await res.arrayBuffer()) + } catch (e) { + console.error('Jira Placeholder image could not be fetched', e) + } } res.writeStatus('200').writeHeader('Content-Type', 'image/png').end(jiraPlaceholderBuffer) } diff --git a/packages/server/selfHostedHandler.ts b/packages/server/selfHostedHandler.ts index 4174e125f23..2387a01d87e 100644 --- a/packages/server/selfHostedHandler.ts +++ b/packages/server/selfHostedHandler.ts @@ -21,7 +21,7 @@ const selfHostedHandler = async (res: HttpResponse, req: HttpRequest) => { try { stats = fs.statSync(url) } catch (e) { - res.writeStatus('404').end() + res.cork(() => res.writeStatus('404').end()) return } const {size} = stats diff --git a/packages/server/types/modules.d.ts b/packages/server/types/modules.d.ts index 14b0e362636..c79e5a0b011 100644 --- a/packages/server/types/modules.d.ts +++ b/packages/server/types/modules.d.ts @@ -26,7 +26,7 @@ declare module 'object-hash' declare module 'string-score' declare const __APP_VERSION__: string -declare const __PRODUCTION__: string +declare const __PRODUCTION__: boolean declare const __SOCKET_PORT__: string declare const __webpack_public_path__: string diff --git a/packages/server/utils/serveStatic.ts b/packages/server/utils/serveStatic.ts index 2448123077b..4ff491881da 100644 --- a/packages/server/utils/serveStatic.ts +++ b/packages/server/utils/serveStatic.ts @@ -18,9 +18,9 @@ const getProjectRoot = () => { const PROJECT_ROOT = getProjectRoot() const staticPaths = { [path.join(PROJECT_ROOT, 'build')]: true, - [path.join(PROJECT_ROOT, 'dist')]: !__PRODUCTION__, - [path.join(PROJECT_ROOT, 'static')]: !__PRODUCTION__, - [path.join(PROJECT_ROOT, 'dev', 'dll')]: !__PRODUCTION__ + // publish server assets at /static + [path.join(PROJECT_ROOT, 'dist')]: __PRODUCTION__, + [path.join(PROJECT_ROOT, 'dev')]: !__PRODUCTION__ } const staticServer = new StaticServer({staticPaths}) diff --git a/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts b/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts index 76f61d59878..1723bab9515 100644 --- a/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts +++ b/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts @@ -5,35 +5,33 @@ import logo192 from '../../static/images/brand/mark-cropped-192.png' import logo512 from '../../static/images/brand/mark-cropped-512.png' import getProjectRoot from '../webpack/utils/getProjectRoot' +declare const __webpack_public_path__: string + const PROJECT_ROOT = getProjectRoot() const clientDir = path.join(PROJECT_ROOT, 'build') -const serverDir = path.join(PROJECT_ROOT, 'dist') - -const getCDNURL = () => { - const {CDN_BASE_URL} = process.env - return CDN_BASE_URL ? `${CDN_BASE_URL}/build` : '/static' -} const rewriteServiceWorker = () => { const skeleton = fs.readFileSync(path.join(clientDir, 'swSkeleton.js'), 'utf-8') - const deploySpecificServiceWorker = skeleton.replaceAll('__PUBLIC_PATH__', getCDNURL()) + const deploySpecificServiceWorker = skeleton.replaceAll( + '__PUBLIC_PATH__', + __webpack_public_path__ + ) fs.writeFileSync(path.join(clientDir, 'sw.js'), deploySpecificServiceWorker) } const writeManifest = () => { // If src is relative, then it will be relative to the manifest location, so manifest.json must be at root / - const cdn = getCDNURL() const manifest = { short_name: 'Parabol', name: 'Parabol', icons: [ { - src: `${cdn}/${logo192}`, + src: logo192, type: 'image/png', sizes: '192x192' }, { - src: `${cdn}/${logo512}`, + src: logo512, type: 'image/png', sizes: '512x512' } @@ -46,9 +44,6 @@ const writeManifest = () => { } const manifestPath = path.join(clientDir, 'manifest.json') fs.writeFileSync(manifestPath, JSON.stringify(manifest)) - // move the referenced icons into the client build - fs.copyFileSync(path.join(serverDir, logo192), path.join(clientDir, logo192)) - fs.copyFileSync(path.join(serverDir, logo512), path.join(clientDir, logo512)) } const rewriteIndexHTML = () => { @@ -63,7 +58,7 @@ const rewriteIndexHTML = () => { sentry: process.env.SENTRY_DSN, slack: process.env.SLACK_CLIENT_ID, stripe: process.env.STRIPE_PUBLISHABLE_KEY, - publicPath: getCDNURL() + '/', + publicPath: __webpack_public_path__, oauth2Redirect: process.env.OAUTH2_REDIRECT, prblIn: process.env.INVITATION_SHORTLINK, AUTH_INTERNAL_ENABLED: process.env.AUTH_INTERNAL_DISABLED !== 'true', @@ -72,7 +67,7 @@ const rewriteIndexHTML = () => { AUTH_SSO_ENABLED: process.env.AUTH_SSO_DISABLED !== 'true', AMPLITUDE_WRITE_KEY: process.env.AMPLITUDE_WRITE_KEY, microsoftTenantId: process.env.MICROSOFT_TENANT_ID, - microsoft: process.env.MICROSOFT_CLIENT_ID, + microsoft: process.env.MICROSOFT_CLIENT_ID } const skeleton = fs.readFileSync(path.join(clientDir, 'skeleton.html'), 'utf8') @@ -82,7 +77,7 @@ const rewriteIndexHTML = () => { const keys = `` const rawHTML = skeleton .replace('', `${noindex}${keys}`) - .replaceAll('__PUBLIC_PATH__', getCDNURL()) + .replaceAll('__PUBLIC_PATH__', __webpack_public_path__.replace(/\/$/, '')) const minifiedHTML = minify(rawHTML, { collapseBooleanAttributes: true, collapseWhitespace: true, diff --git a/scripts/toolboxSrc/pushToCDN.ts b/scripts/toolboxSrc/pushToCDN.ts index e646fcc3c03..df6d52515df 100644 --- a/scripts/toolboxSrc/pushToCDN.ts +++ b/scripts/toolboxSrc/pushToCDN.ts @@ -25,46 +25,63 @@ const pushClientAssetsToCDN = async () => { console.log(`⛅️ Uploaded ${dirEnts.length} client assets to CDN`) } -const pushTemplatesToCDN = async () => { +const pushServerAssetsToCDN = async () => { const fileStoreManager = getFileStoreManager() - const collector = {} as Record - const context = (require as any).context( + const templatesContext = (require as any).context( '../../static/images/illustrations', false, /\/action.png$|\/teamPrompt.png$|Template.png$/ ) - - context.keys().forEach((relativePath: string) => { - const {name, ext} = path.parse(relativePath) - // This path only exists on the build machine - const builtPath = context(relativePath).default - // sub out the build machine path prefix with the __dirname - // e.g. /Users/CI/dist/templates/X.png -> /app/dist/templates/X.png - const absPath = builtPath.replace(/^.+\/dist(\/.+$)/, __dirname + '$1') - collector[`${name}${ext}`] = absPath + const templatePaths = new Set() + templatesContext.keys().forEach((relativePath: `./${string}`) => { + const {base} = path.parse(relativePath) + templatePaths.add(base) }) - const results = await Promise.all( - Object.entries(collector).map(async ([fileName, pathName]) => { - // store meeting templates under our Parabol ghost organization - const partialPath = `Organization/aGhostOrg/template/${fileName}` - const exists = await fileStoreManager.checkExists(partialPath) - if (exists) return false - const buffer = await fs.promises.readFile(pathName as string) - const {name, ext} = path.parse(fileName) - return fileStoreManager.putTemplateIllustration(buffer, 'aGhostOrg', ext, name) - }) - ) - const urls = results.filter(Boolean) + const isTemplate = (filename: string) => templatePaths.has(filename) + + const localServerAssetsDir = path.join(PROJECT_ROOT, 'dist', 'images') - if (urls.length > 0) { - console.log(urls.join('\n')) + // Use this pattern if this is a user asset (including aGhostUser) & kept in the DB + const templateFileUploader = async (filename: string) => { + const partialPath = `Organization/aGhostOrg/template/${filename}` + const exists = await fileStoreManager.checkExists(partialPath) + if (exists) return false + const buffer = await fs.promises.readFile(path.join(localServerAssetsDir, filename)) + const {name, ext} = path.parse(filename) + const url = await fileStoreManager.putTemplateIllustration(buffer, 'aGhostOrg', ext, name) + console.log(`⛅️ Uploaded template ${filename} to ${url}`) + return true } - console.log(`⛅️ Uploaded ${urls.length} Meeting Templates to CDN`) + // Use this pattern if the asset is publicly available + const defaultFileUploader = async (filename: string) => { + // static assets in /dist/images are already hosted at /static/images + if (process.env.FILE_STORE_PROVIDER === 'local') return + const targetObject = `images/${filename}` + const exists = await fileStoreManager.checkExists(targetObject) + if (exists) return false + const buffer = await fs.promises.readFile(path.join(localServerAssetsDir, filename)) + const url = await fileStoreManager.putBuildFile(buffer, targetObject) + console.log(`⛅️ Uploaded server asset ${targetObject} to ${url}`) + return true + } + + const dirEnts = await fs.promises.readdir(localServerAssetsDir, {withFileTypes: true}) + const entries = await Promise.all( + dirEnts.map(async (dirent) => { + const {name} = dirent + if (!dirent.isFile()) throw new Error(`⛅️ Expected ${name} to be a file`) + return isTemplate(name) ? templateFileUploader(name) : defaultFileUploader(name) + }) + ) + + const pushed = entries.filter(Boolean).length + console.log(`⛅️ Server upload complete. Pushed ${pushed} assets to CDN`) } + const pushToCDN = async () => { console.log('⛅️ Push to CDN Started') - await Promise.all([pushClientAssetsToCDN(), pushTemplatesToCDN()]) + await Promise.all([pushClientAssetsToCDN(), pushServerAssetsToCDN()]) console.log('⛅️ Push to CDN Complete') } diff --git a/scripts/webpack/dev.servers.config.js b/scripts/webpack/dev.servers.config.js index 80591f24b0f..9632b40f66f 100644 --- a/scripts/webpack/dev.servers.config.js +++ b/scripts/webpack/dev.servers.config.js @@ -10,6 +10,7 @@ const SERVER_ROOT = path.join(PROJECT_ROOT, 'packages', 'server') const EMBEDDER_ROOT = path.join(PROJECT_ROOT, 'packages', 'embedder') const GQL_ROOT = path.join(PROJECT_ROOT, 'packages', 'gql-executor') const DOTENV = path.join(PROJECT_ROOT, 'scripts', 'webpack', 'utils', 'dotenv.js') +const INIT_PUBLIC_PATH = path.join(SERVER_ROOT, 'initPublicPath.ts') // const CircularDependencyPlugin = require('circular-dependency-plugin') module.exports = { @@ -26,9 +27,9 @@ module.exports = { __dirname: false }, entry: { - web: [DOTENV, path.join(SERVER_ROOT, 'server.ts')], - embedder: [DOTENV, path.join(EMBEDDER_ROOT, 'embedder.ts')], - gqlExecutor: [DOTENV, path.join(GQL_ROOT, 'gqlExecutor.ts')] + web: [DOTENV, INIT_PUBLIC_PATH, path.join(SERVER_ROOT, 'server.ts')], + embedder: [DOTENV, INIT_PUBLIC_PATH, path.join(EMBEDDER_ROOT, 'embedder.ts')], + gqlExecutor: [DOTENV, INIT_PUBLIC_PATH, path.join(GQL_ROOT, 'gqlExecutor.ts')] }, output: { filename: '[name].js', @@ -81,7 +82,7 @@ module.exports = { { loader: 'file-loader', options: { - publicPath: `http://localhost:${process.env.PORT}/static/` + name: '[name].[ext]' } } ] diff --git a/scripts/webpack/prod.client.config.js b/scripts/webpack/prod.client.config.js index 080cf489c0c..4cc8788abcf 100644 --- a/scripts/webpack/prod.client.config.js +++ b/scripts/webpack/prod.client.config.js @@ -116,7 +116,7 @@ module.exports = (config) => { // Trying to keep GraphqlContainer out of here is difficult because there are a lot of common dependencies exclude: [/\.map$/, /^manifest.*\.js$/, /skeleton.html$/], modifyURLPrefix: { - '': '__PUBLIC_PATH__/' + '': '__PUBLIC_PATH__' } }), new MiniCssExtractPlugin({ diff --git a/scripts/webpack/prod.servers.config.js b/scripts/webpack/prod.servers.config.js index 52feb216089..b1b914ba7c2 100644 --- a/scripts/webpack/prod.servers.config.js +++ b/scripts/webpack/prod.servers.config.js @@ -15,6 +15,7 @@ const EMBEDDER_ROOT = path.join(PROJECT_ROOT, 'packages', 'embedder') const GQL_ROOT = path.join(PROJECT_ROOT, 'packages', 'gql-executor') const DOTENV = path.join(PROJECT_ROOT, 'scripts/webpack/utils/dotenv.js') const distPath = path.join(PROJECT_ROOT, 'dist') +const INIT_PUBLIC_PATH = path.join(SERVER_ROOT, 'initPublicPath.ts') const COMMIT_HASH = cp.execSync('git rev-parse HEAD').toString().trim() @@ -29,6 +30,7 @@ module.exports = (config) => { chronos: [DOTENV, path.join(PROJECT_ROOT, 'packages/chronos/chronos.ts')], web: [ DOTENV, + INIT_PUBLIC_PATH, // each instance of web needs to generate its own index.html to use on startup path.join(PROJECT_ROOT, 'scripts/toolboxSrc/applyEnvVarsToClientAssets.ts'), path.join(SERVER_ROOT, 'server.ts') @@ -108,46 +110,10 @@ module.exports = (config) => { ...transformRules(PROJECT_ROOT, true), { test: /\.(png|jpg|jpeg|gif|svg)$/, - oneOf: [ - { - // Put templates in their own directory that will get pushed to the CDN. PG Migrations will reference that URL - test: /Template.png$/, - include: [path.resolve(PROJECT_ROOT, 'static/images/illustrations')], - use: [ - { - loader: 'file-loader', - options: { - name: 'templates/[name].[ext]', - publicPath: distPath - } - } - ] - }, - { - // manifest.json icons just need the file name, we'll prefix them with the CDN in preDeploy - test: /mark-cropped-\d+.png$/, - include: [path.resolve(PROJECT_ROOT, 'static/images/brand')], - use: [ - { - loader: 'file-loader', - options: { - name: '[name].[ext]' - } - } - ] - }, - { - use: [ - { - loader: 'file-loader', - options: { - publicPath: distPath, - name: '[name].[ext]' - } - } - ] - } - ] + type: 'asset/resource', + generator: { + filename: 'images/[name][ext]' + } }, { include: [/node_modules/], From 17517318502b5aaa0849c8d03c4e068f5da92e82 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 5 Mar 2024 15:47:56 -0800 Subject: [PATCH 046/529] fix: upgrade oy-vey from 0.11.2 to 0.12.1 (#9497) Snyk has created this PR to upgrade oy-vey from 0.11.2 to 0.12.1. See this package in npm: https://www.npmjs.com/package/oy-vey See this project in Snyk: https://app.snyk.io/org/mattkrick/project/261c96ef-7af4-4e13-a731-451e0158293e?utm_source=github&utm_medium=referral&page=upgrade-pr Co-authored-by: snyk-bot --- packages/server/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/package.json b/packages/server/package.json index 776adbfb414..e27f11a0f4f 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -123,7 +123,7 @@ "nodemailer": "^6.9.9", "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", - "oy-vey": "^0.11.0", + "oy-vey": "^0.12.1", "parabol-client": "7.20.0", "pg": "^8.5.1", "react": "^17.0.2", From 9fff93397e35e2bfeb8f4fbc34c8d535284551eb Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 5 Mar 2024 15:48:52 -0800 Subject: [PATCH 047/529] fix: upgrade sharp from 0.32.6 to 0.33.2 (#9493) Snyk has created this PR to upgrade sharp from 0.32.6 to 0.33.2. See this package in npm: https://www.npmjs.com/package/sharp See this project in Snyk: https://app.snyk.io/org/mattkrick/project/261c96ef-7af4-4e13-a731-451e0158293e?utm_source=github&utm_medium=referral&page=upgrade-pr Co-authored-by: snyk-bot --- packages/server/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/package.json b/packages/server/package.json index e27f11a0f4f..fdb0c1d0a2e 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -132,7 +132,7 @@ "rethinkdb-ts": "2.6.0", "rrule": "^2.7.2", "samlify": "^2.8.2", - "sharp": "^0.32.6", + "sharp": "^0.33.2", "string-similarity": "^3.0.0", "stripe": "^9.13.0", "tslib": "^2.4.0", From fe1ad434a990ffbfb5c52863988e7f4406d2ac84 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 5 Mar 2024 18:17:57 -0800 Subject: [PATCH 048/529] fix: upgrade graphql-jit from 0.7.4 to 0.8.4 (#9495) Snyk has created this PR to upgrade graphql-jit from 0.7.4 to 0.8.4. See this package in npm: https://www.npmjs.com/package/graphql-jit See this project in Snyk: https://app.snyk.io/org/mattkrick/project/261c96ef-7af4-4e13-a731-451e0158293e?utm_source=github&utm_medium=referral&page=upgrade-pr Co-authored-by: snyk-bot --- packages/server/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/package.json b/packages/server/package.json index fdb0c1d0a2e..b98145e0cb6 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -103,7 +103,7 @@ "fast-xml-parser": "^4.2.7", "googleapis": "^118.0.0", "graphql": "15.7.2", - "graphql-jit": "^0.7.4", + "graphql-jit": "^0.8.4", "graphql-middleware": "^6.1.18", "graphql-relay": "^0.10.0", "graphql-shield": "^7.5.0", From 5dfe26b29753f881dc54c35d6dfa15e894f3726a Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 5 Mar 2024 18:18:08 -0800 Subject: [PATCH 049/529] chore: remove pg-typed part 1 (#9508) * chore: refactor archive team Signed-off-by: Matt Krick * remove archivedTeam sql Signed-off-by: Matt Krick * refactor addNewFeature sql Signed-off-by: Matt Krick * chore: refactor add/remove reactjis Signed-off-by: Matt Krick * chore: refactor add/remove feature flags Signed-off-by: Matt Krick * chore: refactor appendUserTmsQuery Signed-off-by: Matt Krick * fix: revert sharp back to v0.32.6 (#9509) Signed-off-by: Matt Krick --------- Signed-off-by: Matt Krick --- .../server/billing/helpers/teamLimitsCheck.ts | 20 ++++++----- .../private/mutations/addNewFeature.ts | 6 ++-- .../public/mutations/addReactjiToReactable.ts | 34 ++++++++++--------- .../public/mutations/updateFeatureFlag.ts | 14 ++++---- packages/server/package.json | 2 +- .../postgres/queries/archiveTeamsByTeamIds.ts | 17 ---------- .../queries/src/addUserNewFeatureQuery.sql | 6 ---- .../src/appendTeamResponseReactjiQuery.sql | 7 ---- .../src/appendUserFeatureFlagsQuery.sql | 7 ---- .../queries/src/appendUserTmsQuery.sql | 6 ---- .../src/archiveTeamsByTeamIdsQuery.sql | 10 ------ .../src/removeTeamResponseReactjiQuery.sql | 7 ---- .../src/removeUserFeatureFlagsQuery.sql | 12 ------- .../server/safeMutations/addTeamIdToTMS.ts | 11 ++++-- .../server/safeMutations/safeArchiveTeam.ts | 12 +++++-- yarn.lock | 15 ++++---- 16 files changed, 66 insertions(+), 120 deletions(-) delete mode 100644 packages/server/postgres/queries/archiveTeamsByTeamIds.ts delete mode 100644 packages/server/postgres/queries/src/addUserNewFeatureQuery.sql delete mode 100644 packages/server/postgres/queries/src/appendTeamResponseReactjiQuery.sql delete mode 100644 packages/server/postgres/queries/src/appendUserFeatureFlagsQuery.sql delete mode 100644 packages/server/postgres/queries/src/appendUserTmsQuery.sql delete mode 100644 packages/server/postgres/queries/src/archiveTeamsByTeamIdsQuery.sql delete mode 100644 packages/server/postgres/queries/src/removeTeamResponseReactjiQuery.sql delete mode 100644 packages/server/postgres/queries/src/removeUserFeatureFlagsQuery.sql diff --git a/packages/server/billing/helpers/teamLimitsCheck.ts b/packages/server/billing/helpers/teamLimitsCheck.ts index b9326c20e7c..31103db959b 100644 --- a/packages/server/billing/helpers/teamLimitsCheck.ts +++ b/packages/server/billing/helpers/teamLimitsCheck.ts @@ -2,32 +2,36 @@ import ms from 'ms' import {Threshold} from 'parabol-client/types/constEnums' // Uncomment for easier testing // import { ThresholdTest as Threshold } from "~/types/constEnums"; +import {sql} from 'kysely' import {r} from 'rethinkdb-ts' import NotificationTeamsLimitExceeded from '../../database/types/NotificationTeamsLimitExceeded' import Organization from '../../database/types/Organization' import scheduleTeamLimitsJobs from '../../database/types/scheduleTeamLimitsJobs' import {DataLoaderWorker} from '../../graphql/graphql' import publishNotification from '../../graphql/public/mutations/helpers/publishNotification' +import getActiveTeamCountByTeamIds from '../../graphql/public/types/helpers/getActiveTeamCountByTeamIds' +import {getFeatureTier} from '../../graphql/types/helpers/getFeatureTier' import {domainHasActiveDeals} from '../../hubSpot/hubSpotApi' -import getPg from '../../postgres/getPg' -import {appendUserFeatureFlagsQuery} from '../../postgres/queries/generated/appendUserFeatureFlagsQuery' +import getKysely from '../../postgres/getKysely' +import getTeamIdsByOrgIds from '../../postgres/queries/getTeamIdsByOrgIds' +import {getBillingLeadersByOrgId} from '../../utils/getBillingLeadersByOrgId' import sendToSentry from '../../utils/sendToSentry' import removeTeamsLimitObjects from './removeTeamsLimitObjects' import sendTeamsLimitEmail from './sendTeamsLimitEmail' -import getTeamIdsByOrgIds from '../../postgres/queries/getTeamIdsByOrgIds' -import getActiveTeamCountByTeamIds from '../../graphql/public/types/helpers/getActiveTeamCountByTeamIds' -import {getBillingLeadersByOrgId} from '../../utils/getBillingLeadersByOrgId' -import {getFeatureTier} from '../../graphql/types/helpers/getFeatureTier' const enableUsageStats = async (userIds: string[], orgId: string) => { + const pg = getKysely() await r .table('OrganizationUser') .getAll(r.args(userIds), {index: 'userId'}) .filter({orgId}) .update({suggestedTier: 'team'}) .run() - - await appendUserFeatureFlagsQuery.run({ids: userIds, flag: 'insights'}, getPg()) + await pg + .updateTable('User') + .set({featureFlags: sql`arr_append_uniq("featureFlags", 'insights')`}) + .where('id', 'in', userIds) + .execute() } const sendWebsiteNotifications = async ( diff --git a/packages/server/graphql/private/mutations/addNewFeature.ts b/packages/server/graphql/private/mutations/addNewFeature.ts index 3a5c8fb73d1..b612061ee9d 100644 --- a/packages/server/graphql/private/mutations/addNewFeature.ts +++ b/packages/server/graphql/private/mutations/addNewFeature.ts @@ -1,8 +1,7 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../../database/rethinkDriver' import generateUID from '../../../generateUID' -import getPg from '../../../postgres/getPg' -import {addUserNewFeatureQuery} from '../../../postgres/queries/generated/addUserNewFeatureQuery' +import getKysely from '../../../postgres/getKysely' import getRedis from '../../../utils/getRedis' import publish from '../../../utils/publish' import sendToSentry from '../../../utils/sendToSentry' @@ -15,6 +14,7 @@ const addNewFeature: MutationResolvers['addNewFeature'] = async ( ) => { const r = await getRethink() const redis = getRedis() + const pg = getKysely() // AUTH const operationId = dataLoader.share() @@ -30,7 +30,7 @@ const addNewFeature: MutationResolvers['addNewFeature'] = async ( } await Promise.all([ r.table('NewFeature').insert(newFeature).run(), - addUserNewFeatureQuery.run({newFeatureId}, getPg()) + pg.updateTable('User').set({newFeatureId}).execute() ]) const onlineUserIds = new Set() diff --git a/packages/server/graphql/public/mutations/addReactjiToReactable.ts b/packages/server/graphql/public/mutations/addReactjiToReactable.ts index 1b4161f0335..0a343f921f4 100644 --- a/packages/server/graphql/public/mutations/addReactjiToReactable.ts +++ b/packages/server/graphql/public/mutations/addReactjiToReactable.ts @@ -7,9 +7,6 @@ import {RDatum} from '../../../database/stricterR' import Comment from '../../../database/types/Comment' import {Reactable} from '../../../database/types/Reactable' import Reflection from '../../../database/types/Reflection' -import getPg from '../../../postgres/getPg' -import {appendTeamResponseReactji} from '../../../postgres/queries/generated/appendTeamResponseReactjiQuery' -import {removeTeamResponseReactji} from '../../../postgres/queries/generated/removeTeamResponseReactjiQuery' import {analytics} from '../../../utils/analytics/analytics' import {getUserId} from '../../../utils/authorization' import emojiIds from '../../../utils/emojiIds' @@ -17,14 +14,15 @@ import getGroupedReactjis from '../../../utils/getGroupedReactjis' import publish from '../../../utils/publish' import {GQLContext} from '../../graphql' -import getReactableType from '../../types/getReactableType' -import {ReactableEnumType} from '../../types/ReactableEnum' -import getKysely from '../../../postgres/getKysely' -import {AnyMeeting} from '../../../postgres/types/Meeting' +import {sql} from 'kysely' import MeetingRetrospective from '../../../database/types/MeetingRetrospective' +import NotificationKudosReceived from '../../../database/types/NotificationKudosReceived' +import getKysely from '../../../postgres/getKysely' import {TeamPromptResponse} from '../../../postgres/queries/getTeamPromptResponsesByIds' +import {AnyMeeting} from '../../../postgres/types/Meeting' +import {ReactableEnumType} from '../../types/ReactableEnum' +import getReactableType from '../../types/getReactableType' import {MutationResolvers} from '../resolverTypes' -import NotificationKudosReceived from '../../../database/types/NotificationKudosReceived' import publishNotification from './helpers/publishNotification' const rethinkTableLookup = { @@ -129,15 +127,19 @@ const addReactjiToReactable: MutationResolvers['addReactjiToReactable'] = async if (pgLoaderName) { const numberReactableId = TeamPromptResponseId.split(reactableId) if (isRemove) { - await removeTeamResponseReactji.run( - {id: numberReactableId, reactji: {shortname: reactji, userid: viewerId}}, - getPg() - ) + await pg + .updateTable('TeamPromptResponse') + .set({reactjis: sql`array_remove("reactjis", (${reactji},${viewerId})::"Reactji")`}) + .where('id', '=', numberReactableId) + .execute() } else { - await appendTeamResponseReactji.run( - {id: numberReactableId, reactji: {shortname: reactji, userid: viewerId}}, - getPg() - ) + await pg + .updateTable('TeamPromptResponse') + .set({ + reactjis: sql`arr_append_uniq("reactjis", (${reactji},${viewerId})::"Reactji")` + }) + .where('id', '=', numberReactableId) + .execute() } dataLoader.get(pgLoaderName).clear(reactableId) diff --git a/packages/server/graphql/public/mutations/updateFeatureFlag.ts b/packages/server/graphql/public/mutations/updateFeatureFlag.ts index a8b336c6db9..bf7a19cef69 100644 --- a/packages/server/graphql/public/mutations/updateFeatureFlag.ts +++ b/packages/server/graphql/public/mutations/updateFeatureFlag.ts @@ -1,7 +1,7 @@ +import {ValueExpression, sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getPg from '../../../postgres/getPg' -import {appendUserFeatureFlagsQuery} from '../../../postgres/queries/generated/appendUserFeatureFlagsQuery' -import {removeUserFeatureFlagsQuery} from '../../../postgres/queries/generated/removeUserFeatureFlagsQuery' +import getKysely from '../../../postgres/getKysely' +import {DB} from '../../../postgres/pg' import getUsersByDomain from '../../../postgres/queries/getUsersByDomain' import {getUsersByEmails} from '../../../postgres/queries/getUsersByEmails' import IUser from '../../../postgres/types/IUser' @@ -17,6 +17,7 @@ const updateFeatureFlag: MutationResolvers['updateFeatureFlag'] = async ( ) => { const operationId = dataLoader.share() const subOptions = {operationId} + const pg = getKysely() // AUTH const viewerId = getUserId(authToken) @@ -43,9 +44,10 @@ const updateFeatureFlag: MutationResolvers['updateFeatureFlag'] = async ( } const userIds = isUpdatingViewerFlag ? [viewerId] : users.map(({id}) => id) - addFlag - ? await appendUserFeatureFlagsQuery.run({ids: userIds, flag}, getPg()) - : await removeUserFeatureFlagsQuery.run({ids: userIds, flag}, getPg()) + const featureFlags: ValueExpression = addFlag + ? sql`arr_append_uniq("featureFlags", ${flag})` + : sql`array_remove("featureFlags", ${flag})` + await pg.updateTable('User').set({featureFlags}).where('id', 'in', userIds).execute() userIds.forEach((userId) => { const data = {userId} publish(SubscriptionChannel.NOTIFICATION, userId, 'UpdateFeatureFlagPayload', data, subOptions) diff --git a/packages/server/package.json b/packages/server/package.json index b98145e0cb6..6c187954e77 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -132,7 +132,7 @@ "rethinkdb-ts": "2.6.0", "rrule": "^2.7.2", "samlify": "^2.8.2", - "sharp": "^0.33.2", + "sharp": "^0.32.6", "string-similarity": "^3.0.0", "stripe": "^9.13.0", "tslib": "^2.4.0", diff --git a/packages/server/postgres/queries/archiveTeamsByTeamIds.ts b/packages/server/postgres/queries/archiveTeamsByTeamIds.ts deleted file mode 100644 index 7ba702c37a2..00000000000 --- a/packages/server/postgres/queries/archiveTeamsByTeamIds.ts +++ /dev/null @@ -1,17 +0,0 @@ -import getPg from '../../postgres/getPg' -import { - archiveTeamsByTeamIdsQuery, - IArchiveTeamsByTeamIdsQueryParams -} from '../../postgres/queries/generated/archiveTeamsByTeamIdsQuery' - -const archiveTeamsByTeamIds = async (teamIds: string | string[]) => { - teamIds = typeof teamIds === 'string' ? [teamIds] : teamIds - return archiveTeamsByTeamIdsQuery.run( - { - ids: teamIds - } as IArchiveTeamsByTeamIdsQueryParams, - getPg() - ) -} - -export default archiveTeamsByTeamIds diff --git a/packages/server/postgres/queries/src/addUserNewFeatureQuery.sql b/packages/server/postgres/queries/src/addUserNewFeatureQuery.sql deleted file mode 100644 index b91f680f7b5..00000000000 --- a/packages/server/postgres/queries/src/addUserNewFeatureQuery.sql +++ /dev/null @@ -1,6 +0,0 @@ -/* - @name addUserNewFeatureQuery -*/ -UPDATE "User" SET - "newFeatureId" = :newFeatureId -; diff --git a/packages/server/postgres/queries/src/appendTeamResponseReactjiQuery.sql b/packages/server/postgres/queries/src/appendTeamResponseReactjiQuery.sql deleted file mode 100644 index 7e0875c3857..00000000000 --- a/packages/server/postgres/queries/src/appendTeamResponseReactjiQuery.sql +++ /dev/null @@ -1,7 +0,0 @@ -/* - @name appendTeamResponseReactji - @param reactji -> (shortname, userid) -*/ -UPDATE "TeamPromptResponse" SET - "reactjis" = arr_append_uniq("reactjis", (:reactji)::"Reactji") -WHERE id = :id; diff --git a/packages/server/postgres/queries/src/appendUserFeatureFlagsQuery.sql b/packages/server/postgres/queries/src/appendUserFeatureFlagsQuery.sql deleted file mode 100644 index 4b155286fe4..00000000000 --- a/packages/server/postgres/queries/src/appendUserFeatureFlagsQuery.sql +++ /dev/null @@ -1,7 +0,0 @@ -/* - @name appendUserFeatureFlagsQuery - @param ids -> (...) -*/ -UPDATE "User" SET - "featureFlags" = arr_append_uniq("featureFlags", :flag) -WHERE id IN :ids; diff --git a/packages/server/postgres/queries/src/appendUserTmsQuery.sql b/packages/server/postgres/queries/src/appendUserTmsQuery.sql deleted file mode 100644 index 9bca7883e64..00000000000 --- a/packages/server/postgres/queries/src/appendUserTmsQuery.sql +++ /dev/null @@ -1,6 +0,0 @@ -/* - @name appendUserTmsQuery -*/ -UPDATE "User" SET - tms = arr_append_uniq(tms, :teamId) -WHERE id = :id; diff --git a/packages/server/postgres/queries/src/archiveTeamsByTeamIdsQuery.sql b/packages/server/postgres/queries/src/archiveTeamsByTeamIdsQuery.sql deleted file mode 100644 index aa2d38a8270..00000000000 --- a/packages/server/postgres/queries/src/archiveTeamsByTeamIdsQuery.sql +++ /dev/null @@ -1,10 +0,0 @@ -/* - @name archiveTeamsByTeamIdsQuery - @param ids -> (...) -*/ -UPDATE "Team" SET - "isArchived" = true -WHERE - id IN :ids AND - "isArchived" = false -RETURNING *; diff --git a/packages/server/postgres/queries/src/removeTeamResponseReactjiQuery.sql b/packages/server/postgres/queries/src/removeTeamResponseReactjiQuery.sql deleted file mode 100644 index d86f4d852b8..00000000000 --- a/packages/server/postgres/queries/src/removeTeamResponseReactjiQuery.sql +++ /dev/null @@ -1,7 +0,0 @@ -/* - @name removeTeamResponseReactji - @param reactji -> (shortname, userid) -*/ -UPDATE "TeamPromptResponse" SET - "reactjis" = array_remove("reactjis", (:reactji)::"Reactji") -WHERE id = :id; diff --git a/packages/server/postgres/queries/src/removeUserFeatureFlagsQuery.sql b/packages/server/postgres/queries/src/removeUserFeatureFlagsQuery.sql deleted file mode 100644 index 8db6a6fe0bd..00000000000 --- a/packages/server/postgres/queries/src/removeUserFeatureFlagsQuery.sql +++ /dev/null @@ -1,12 +0,0 @@ -/* - @name removeUserFeatureFlagsQuery - @param ids -> (...) -*/ -UPDATE "User" -SET "featureFlags" = array_remove("featureFlags", x) -FROM ( -SELECT id, unnest("featureFlags") as x -FROM "User" -WHERE id IN :ids -) sub -WHERE "User".id = sub.id AND x = :flag; diff --git a/packages/server/safeMutations/addTeamIdToTMS.ts b/packages/server/safeMutations/addTeamIdToTMS.ts index 0da65f0e66e..faa223723d3 100644 --- a/packages/server/safeMutations/addTeamIdToTMS.ts +++ b/packages/server/safeMutations/addTeamIdToTMS.ts @@ -1,8 +1,13 @@ -import getPg from '../postgres/getPg' -import {appendUserTmsQuery} from '../postgres/queries/generated/appendUserTmsQuery' +import {sql} from 'kysely' +import getKysely from '../postgres/getKysely' const addTeamIdToTMS = async (userId: string, teamId: string) => { - return appendUserTmsQuery.run({id: userId, teamId}, getPg()) + const pg = getKysely() + return pg + .updateTable('User') + .set({tms: sql`arr_append_uniq("tms", ${teamId})`}) + .where('id', '=', userId) + .execute() } export default addTeamIdToTMS diff --git a/packages/server/safeMutations/safeArchiveTeam.ts b/packages/server/safeMutations/safeArchiveTeam.ts index 9ed81c4ce7b..5283349838d 100644 --- a/packages/server/safeMutations/safeArchiveTeam.ts +++ b/packages/server/safeMutations/safeArchiveTeam.ts @@ -1,11 +1,12 @@ import getRethink from '../database/rethinkDriver' import {RDatum} from '../database/stricterR' import {DataLoaderWorker} from '../graphql/graphql' -import archiveTeamsByTeamIds from '../postgres/queries/archiveTeamsByTeamIds' +import getKysely from '../postgres/getKysely' import removeUserTms from '../postgres/queries/removeUserTms' const safeArchiveTeam = async (teamId: string, dataLoader: DataLoaderWorker) => { const r = await getRethink() + const pg = getKysely() const now = new Date() const userIds = await r .table('TeamMember') @@ -34,10 +35,15 @@ const safeArchiveTeam = async (teamId: string, dataLoader: DataLoaderWorker) => )('changes')('new_val')('id') .default([]) as unknown as string[] }).run(), - archiveTeamsByTeamIds(teamId) + pg + .updateTable('Team') + .set({isArchived: true}) + .where('id', '=', teamId) + .returningAll() + .executeTakeFirst() ]) - return {...rethinkResult, team: pgResult[0] ?? null, users} + return {...rethinkResult, team: pgResult ?? null, users} } export default safeArchiveTeam diff --git a/yarn.lock b/yarn.lock index 0678b62b736..976027c679f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9627,9 +9627,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001580, caniuse-lite@~1.0.0: - version "1.0.30001591" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001591.tgz#16745e50263edc9f395895a7cd468b9f3767cf33" - integrity sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ== + version "1.0.30001594" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001594.tgz#bea552414cd52c2d0c985ed9206314a696e685f5" + integrity sha512-VblSX6nYqyJVs8DKFMldE2IVCJjZ225LW00ydtUWwh5hk9IfkTOffO6r8gJNsH0qqqeAF8KrbMYA2VEwTlGW5g== capital-case@^1.0.4: version "1.0.4" @@ -11223,7 +11223,6 @@ draft-js-utils@^1.4.0: "draft-js@https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6": version "0.10.5" - uid "025fddba56f21aaf3383aee778e0b17025c9a7bc" resolved "https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6#025fddba56f21aaf3383aee778e0b17025c9a7bc" dependencies: fbjs "^0.8.15" @@ -16844,10 +16843,10 @@ osenv@^0.1.4: os-homedir "^1.0.0" os-tmpdir "^1.0.0" -oy-vey@^0.11.0: - version "0.11.2" - resolved "https://registry.yarnpkg.com/oy-vey/-/oy-vey-0.11.2.tgz#3bbc36a4064993a2ff52f985b066d44dcb4500d9" - integrity sha512-06prDST4MicbAWie4eXcouJbGhAu0r7j3Yta1KFtgs7v2t7goHmY06/GWFjT6lpIsGKJC+7vZtwdecRSYnFtPQ== +oy-vey@^0.12.1: + version "0.12.1" + resolved "https://registry.yarnpkg.com/oy-vey/-/oy-vey-0.12.1.tgz#a3bdda93bdb3a9f483fc01432b7b8d5abf838405" + integrity sha512-fFpS8mRoXqMTPYXUoDTO6S5S8WXvqBE+AOPbDwxFu2n3ZNns6x3+Ml/49lAomyJV6RzdFBVA5LxCUEHNaLlhjA== dependencies: clean-css "^4.0.12" object-assign "^4.1.1" From 6762ebc4068f9062091db7f8d467fecf60388d63 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Wed, 6 Mar 2024 09:05:45 +0000 Subject: [PATCH 050/529] feat: make all templates free (#9503) * feat: make all templates free * set orgid to aghostorg * update template ids and down migration org id check * make down migration noop * remove unused var --- .../1709551949071_makeAllTemplatesFree.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 packages/server/postgres/migrations/1709551949071_makeAllTemplatesFree.ts diff --git a/packages/server/postgres/migrations/1709551949071_makeAllTemplatesFree.ts b/packages/server/postgres/migrations/1709551949071_makeAllTemplatesFree.ts new file mode 100644 index 00000000000..25bc6553255 --- /dev/null +++ b/packages/server/postgres/migrations/1709551949071_makeAllTemplatesFree.ts @@ -0,0 +1,20 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query( + ` + UPDATE "MeetingTemplate" + SET "isFree" = true + WHERE "orgId" = 'aGhostOrg' + ` + ) + + await client.end() +} + +export async function down() { + // noop +} From 4ce391e53a11cbd174bf67364db29128afe72092 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 6 Mar 2024 05:37:05 -0800 Subject: [PATCH 051/529] feat: saml login no email, auth design fixups (#9507) * feat: saml login no email, auth design fixups Signed-off-by: Matt Krick * fixup: remove console log Signed-off-by: Matt Krick --------- Signed-off-by: Matt Krick --- .../components/AuthenticationDialog.tsx | 3 +- .../client/components/AuthenticationPage.tsx | 4 ++- .../components/EmailPasswordAuthForm.tsx | 21 ++++++------ .../components/GenericAuthentication.tsx | 23 +++++++++---- .../components/GoogleOAuthButtonBlock.tsx | 12 ++++--- .../components/MicrosoftOAuthButtonBlock.tsx | 6 ++-- .../TeamInvitationEmailCreateAccount.tsx | 5 +-- .../components/TeamInvitationEmailSignin.tsx | 3 +- .../TeamInvitationGoogleCreateAccount.tsx | 3 +- .../components/TeamInvitationWrapper.tsx | 29 ++-------------- .../OrgAuthenticationMetadata.tsx | 2 +- packages/client/utils/GoogleClientManager.ts | 9 +++-- .../client/utils/MicrosoftClientManager.ts | 9 +++-- .../client/utils/getOAuthPopupFeatures.ts | 7 ++-- packages/client/utils/getTokenFromSSO.ts | 4 +-- .../graphql/private/mutations/loginSAML.ts | 6 ++-- packages/server/graphql/public/permissions.ts | 3 +- .../server/graphql/public/queries/SAMLIdP.ts | 13 ++++++++ .../graphql/public/typeDefs/Query.graphql | 4 +-- packages/server/graphql/queries/SAMLIdP.ts | 33 ------------------- packages/server/graphql/rootQuery.ts | 4 +-- packages/server/utils/getSAMLURLFromEmail.ts | 8 +++-- 22 files changed, 97 insertions(+), 114 deletions(-) create mode 100644 packages/server/graphql/public/queries/SAMLIdP.ts delete mode 100644 packages/server/graphql/queries/SAMLIdP.ts diff --git a/packages/client/components/AuthenticationDialog.tsx b/packages/client/components/AuthenticationDialog.tsx index 3ed5ad9f103..a53e250c3e1 100644 --- a/packages/client/components/AuthenticationDialog.tsx +++ b/packages/client/components/AuthenticationDialog.tsx @@ -1,11 +1,12 @@ import styled from '@emotion/styled' import InviteDialog from './InviteDialog' +export const AUTH_DIALOG_WIDTH = 356 const AuthenticationDialog = styled(InviteDialog)({ alignItems: 'center', paddingTop: 24, paddingBottom: 24, - width: 356 + width: AUTH_DIALOG_WIDTH }) export default AuthenticationDialog diff --git a/packages/client/components/AuthenticationPage.tsx b/packages/client/components/AuthenticationPage.tsx index dd3f6808e82..c89eb2c9be2 100644 --- a/packages/client/components/AuthenticationPage.tsx +++ b/packages/client/components/AuthenticationPage.tsx @@ -5,13 +5,15 @@ import useAtmosphere from '../hooks/useAtmosphere' import useDocumentTitle from '../hooks/useDocumentTitle' import useRouter from '../hooks/useRouter' import getValidRedirectParam from '../utils/getValidRedirectParam' +import {AUTH_DIALOG_WIDTH} from './AuthenticationDialog' import GenericAuthentication, {AuthPageSlug, GotoAuthPage} from './GenericAuthentication' import TeamInvitationWrapper from './TeamInvitationWrapper' const CopyBlock = styled('div')({ marginBottom: 48, width: 'calc(100vw - 16px)', - maxWidth: 500, + // must be no wider than the auth popup width to keep it looking clean + maxWidth: AUTH_DIALOG_WIDTH, textAlign: 'center' }) diff --git a/packages/client/components/EmailPasswordAuthForm.tsx b/packages/client/components/EmailPasswordAuthForm.tsx index ed195b49b16..d124dd8428c 100644 --- a/packages/client/components/EmailPasswordAuthForm.tsx +++ b/packages/client/components/EmailPasswordAuthForm.tsx @@ -30,6 +30,8 @@ import RaisedButton from './RaisedButton' import StyledTip from './StyledTip' interface Props { + // used to determine the coordinates of the auth popup + getOffsetTop?: () => number email: string invitationToken: string | undefined | null // is the primary login action (not secondary to Google Oauth) @@ -38,9 +40,6 @@ interface Props { goToPage?: (page: AuthPageSlug, params: string) => void } -const FieldGroup = styled('div')({ - margin: '16px 0' -}) const FieldBlock = styled('div')<{isSSO?: boolean}>(({isSSO}) => ({ margin: '0 0 1.25rem', visibility: isSSO ? 'hidden' : undefined @@ -90,7 +89,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => { const isInternalAuthEnabled = window.__ACTION__.AUTH_INTERNAL_ENABLED const isSSOAuthEnabled = window.__ACTION__.AUTH_SSO_ENABLED - const {isPrimary, isSignin, invitationToken, email, goToPage} = props + const {getOffsetTop, isPrimary, isSignin, invitationToken, email, goToPage} = props const {location} = useRouter() const params = new URLSearchParams(location.search) const isSSODefault = isSSOAuthEnabled && Boolean(params.get('sso')) @@ -105,7 +104,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => { const {fields, onChange, setDirtyField, validateField} = useForm({ email: { getDefault: () => email, - validate: validateEmail + validate: signInWithSSOOnly ? undefined : validateEmail }, password: { getDefault: () => '', @@ -150,6 +149,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => { const domain = getSSODomainFromEmail(email)! const validSSOURL = domain === ssoDomain && ssoURL const isProbablySSO = isSSO || !fields.password.value || validSSOURL + const top = getOffsetTop?.() || 56 let optimisticPopup if (isProbablySSO) { // Safari blocks all calls to window.open that are not triggered SYNCHRONOUSLY from an event @@ -164,7 +164,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => { optimisticPopup = window.open( '', 'SSO', - getOAuthPopupFeatures({width: 385, height: 550, top: 64}) + getOAuthPopupFeatures({width: 385, height: 576, top}) ) } const url = validSSOURL || (await getSSOUrl(atmosphere, email)) @@ -173,7 +173,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => { return false } submitMutation() - const response = await getTokenFromSSO(url) + const response = await getTokenFromSSO(url, top) if ('error' in response) { onError(new Error(response.error || 'Error logging in')) return true @@ -198,6 +198,7 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => { const onSubmit = async (e: React.FormEvent) => { e.preventDefault() if (submitting) return + onCompleted() setDirtyField() const {email: emailRes, password: passwordRes} = validateField() if (emailRes.error) return @@ -244,8 +245,8 @@ const EmailPasswordAuthForm = forwardRef((props: Props, ref: any) => {
{error && } {isSSO && submitting && Continue through the login popup} - - +
+ { /> )} - +
+

{team.name}

+
+
+ +
+
+
+
{teamMembers.length} Active
+
+
+ {teamMembers.map((teamMember) => ( + + ))} +
+ + {menuPortal( + + )} + + {isDeleteTeamDialogOpened ? ( + + ) : null} + + ) +} diff --git a/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersMenu.tsx b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersMenu.tsx new file mode 100644 index 00000000000..bc53031bfc8 --- /dev/null +++ b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersMenu.tsx @@ -0,0 +1,26 @@ +import React from 'react' +import {MenuProps} from '../../../../hooks/useMenu' +import Menu from '../../../../components/Menu' +import MenuItem from '../../../../components/MenuItem' + +interface OrgTeamMembersMenuProps { + menuProps: MenuProps + openDeleteTeamModal: () => void +} + +export const OrgTeamMembersMenu = (props: OrgTeamMembersMenuProps) => { + const {menuProps, openDeleteTeamModal} = props + const {closePortal} = menuProps + + return ( + + { + closePortal() + openDeleteTeamModal() + }} + /> + + ) +} diff --git a/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersRoot.tsx b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersRoot.tsx new file mode 100644 index 00000000000..c0c959d084c --- /dev/null +++ b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersRoot.tsx @@ -0,0 +1,25 @@ +import React, {Suspense} from 'react' +import orgTeamMembersQuery, {OrgTeamMembersQuery} from '~/__generated__/OrgTeamMembersQuery.graphql' +import useQueryLoaderNow from '../../../../hooks/useQueryLoaderNow' +import {LoaderSize} from '../../../../types/constEnums' +import {Loader} from '../../../../utils/relay/renderLoader' +import {OrgTeamMembers} from './OrgTeamMembers' +import useRouter from '../../../../hooks/useRouter' + +const OrgTeamMembersRoot = () => { + const {match} = useRouter<{teamId: string}>() + const { + params: {teamId} + } = match + const queryRef = useQueryLoaderNow(orgTeamMembersQuery, { + teamId + }) + + return ( + }> + {queryRef && } + + ) +} + +export default OrgTeamMembersRoot diff --git a/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersRow.tsx b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersRow.tsx new file mode 100644 index 00000000000..02b643f744f --- /dev/null +++ b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembersRow.tsx @@ -0,0 +1,117 @@ +import React from 'react' +import graphql from 'babel-plugin-relay/macro' +import {useFragment} from 'react-relay' +import {OrgTeamMembersRow_teamMember$key} from '../../../../__generated__/OrgTeamMembersRow_teamMember.graphql' +import {Avatar} from '../../../../ui/Avatar/Avatar' +import {AvatarFallback} from '../../../../ui/Avatar/AvatarFallback' +import {AvatarImage} from '../../../../ui/Avatar/AvatarImage' +import {Button} from '../../../../ui/Button/Button' +import {MoreVert} from '@mui/icons-material' +import {OrgTeamMemberMenu} from './OrgTeamMemberMenu' +import {MenuPosition} from '../../../../hooks/useCoords' +import useMenu from '../../../../hooks/useMenu' +import PromoteTeamMemberModal from '../../../teamDashboard/components/PromoteTeamMemberModal/PromoteTeamMemberModal' +import RemoveTeamMemberModal from '../../../teamDashboard/components/RemoveTeamMemberModal/RemoveTeamMemberModal' +import useModal from '../../../../hooks/useModal' +import useAtmosphere from '../../../../hooks/useAtmosphere' + +type Props = { + teamMemberRef: OrgTeamMembersRow_teamMember$key + isViewerLead: boolean + isViewerOrgAdmin: boolean +} + +export const OrgTeamMembersRow = (props: Props) => { + const {teamMemberRef} = props + const teamMember = useFragment( + graphql` + fragment OrgTeamMembersRow_teamMember on TeamMember { + ...OrgTeamMemberMenu_teamMember + userId + picture + preferredName + isLead + isOrgAdmin + isSelf + email + ...PromoteTeamMemberModal_teamMember + ...RemoveTeamMemberModal_teamMember + } + `, + teamMemberRef + ) + + const {isViewerLead, isViewerOrgAdmin} = props + const {isLead, isOrgAdmin, userId} = teamMember + + const atmosphere = useAtmosphere() + const {viewerId} = atmosphere + const isSelf = userId === viewerId + + const showMenuButton = (isViewerOrgAdmin && !isLead) || (isViewerLead && !isSelf && !isOrgAdmin) + + const {togglePortal, originRef, menuPortal, menuProps} = useMenu(MenuPosition.UPPER_RIGHT) + const { + closePortal: closePromote, + togglePortal: togglePromote, + modalPortal: portalPromote + } = useModal() + const { + closePortal: closeRemove, + togglePortal: toggleRemove, + modalPortal: portalRemove + } = useModal() + + return ( +
+
+ + + {teamMember.preferredName.substring(0, 2)} + +
+
+
+ {teamMember.preferredName}{' '} + {teamMember.isLead ? ( + + Team Lead + + ) : null} +
+ +
+
+ {showMenuButton && ( + + )} + {menuPortal( + + )} +
+ + {portalPromote()} + {portalRemove()} +
+ ) +} diff --git a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx index e9bd39f71d7..3f544d7dd27 100644 --- a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx +++ b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx @@ -1,16 +1,12 @@ import React from 'react' import graphql from 'babel-plugin-relay/macro' -import styled from '@emotion/styled' -import Row from '../../../../components/Row/Row' -import Panel from '../../../../components/Panel/Panel' -import {ElementWidth} from '../../../../types/constEnums' import {useFragment} from 'react-relay' import OrgTeamsRow from './OrgTeamsRow' import {OrgTeams_organization$key} from '../../../../__generated__/OrgTeams_organization.graphql' - -const StyledPanel = styled(Panel)({ - maxWidth: ElementWidth.PANEL_WIDTH -}) +import AddTeamDialogRoot from '../../../../components/AddTeamDialogRoot' +import {Button} from '../../../../ui/Button/Button' +import {useDialogState} from '../../../../ui/Dialog/useDialogState' +import plural from '../../../../utils/plural' type Props = { organizationRef: OrgTeams_organization$key @@ -31,23 +27,48 @@ const OrgTeams = (props: Props) => { `, organizationRef ) + const { + open: openAddTeamDialog, + close: closeAddTeamDialog, + isOpen: isAddTeamDialogOpened + } = useDialogState() + const {allTeams, isBillingLeader} = organization if (!isBillingLeader) return null + return ( - <> -

{'Teams'}

- - -
-
Team Name
-
Lead
+
+
+

Teams

+
+ +
+
+ +
+
+
+
+ {allTeams.length} {plural(allTeams.length, 'Team')} +
- +
{allTeams.map((team) => ( ))} - - +
+ + {isAddTeamDialogOpened ? ( + + ) : null} +
) } diff --git a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeamsRow.tsx b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeamsRow.tsx index 00123a43b5d..d6da2e5e6c6 100644 --- a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeamsRow.tsx +++ b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeamsRow.tsx @@ -1,8 +1,9 @@ import React from 'react' import {Link} from 'react-router-dom' import graphql from 'babel-plugin-relay/macro' -import Row from '../../../../components/Row/Row' import {useFragment} from 'react-relay' +import {ChevronRight} from '@mui/icons-material' + import plural from '../../../../utils/plural' import {OrgTeamsRow_team$key} from '../../../../__generated__/OrgTeamsRow_team.graphql' @@ -30,42 +31,26 @@ const OrgTeamsRow = (props: Props) => { ) const {id: teamId, teamMembers, name} = team const teamMembersCount = teamMembers.length - const teamLeadEmail = teamMembers.find((member) => member.isLead)?.email ?? '' - const isViewerTeamLead = teamMembers.some( - (member) => member.isSelf && (member.isLead || member.isOrgAdmin) - ) + return ( - -
-
{name}
-
-
- {`${teamMembersCount} ${plural(teamMembersCount, 'member')}`} - {isViewerTeamLead && ( - <> - - - {'Manage Team'} - - - )} + +
+
+
{name}
+
+
+ {`${teamMembersCount} ${plural(teamMembersCount, 'member')}`} +
- - - {`${teamLeadEmail} ${isViewerTeamLead ? '(You)' : ''}`} - +
+
+
- + ) } diff --git a/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx b/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx index eda83c7b6dc..8a15e046096 100644 --- a/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx +++ b/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx @@ -18,13 +18,18 @@ import RoleTag from '../../../../components/Tag/RoleTag' import {MenuPosition} from '../../../../hooks/useCoords' import useMenu from '../../../../hooks/useMenu' import useModal from '../../../../hooks/useModal' -import useTooltip from '../../../../hooks/useTooltip' import defaultUserAvatar from '../../../../styles/theme/images/avatar-user.svg' import {Breakpoint} from '../../../../types/constEnums' import lazyPreload from '../../../../utils/lazyPreload' import withMutationProps, {WithMutationProps} from '../../../../utils/relay/withMutationProps' -import {OrgMemberRow_organization$key} from '../../../../__generated__/OrgMemberRow_organization.graphql' -import {OrgMemberRow_organizationUser$key} from '../../../../__generated__/OrgMemberRow_organizationUser.graphql' +import { + OrgMemberRow_organization$key, + OrgMemberRow_organization$data +} from '../../../../__generated__/OrgMemberRow_organization.graphql' +import { + OrgMemberRow_organizationUser$key, + OrgMemberRow_organizationUser$data +} from '../../../../__generated__/OrgMemberRow_organizationUser.graphql' import BaseTag from '../../../../components/Tag/BaseTag' const AvatarBlock = styled('div')({ @@ -59,6 +64,7 @@ const MenuToggleBlock = styled('div')({ interface Props extends WithMutationProps { billingLeaderCount: number + orgAdminCount: number organizationUser: OrgMemberRow_organizationUser$key organization: OrgMemberRow_organization$key } @@ -90,29 +96,157 @@ const BillingLeaderActionMenu = lazyPreload( /* webpackChunkName: 'BillingLeaderActionMenu' */ '../../../../components/BillingLeaderActionMenu' ) ) +const OrgAdminActionMenu = lazyPreload( + () => + import(/* webpackChunkName: 'OrgAdminActionMenu' */ '../../../../components/OrgAdminActionMenu') +) const RemoveFromOrgModal = lazyPreload( () => import(/* webpackChunkName: 'RemoveFromOrgModal' */ '../RemoveFromOrgModal/RemoveFromOrgModal') ) +interface UserAvatarProps { + picture?: string +} + +const UserAvatar: React.FC = ({picture}) => ( + + {picture ? ( + + ) : ( + default avatar + )} + +) + +interface UserInfoProps { + preferredName: string + email: string + isBillingLeader: boolean + isOrgAdmin: boolean + inactive: boolean | null + newUserUntil: string +} + +const UserInfo: React.FC = ({ + preferredName, + email, + isBillingLeader, + isOrgAdmin, + inactive, + newUserUntil +}) => ( + + + {preferredName} + {isBillingLeader && Billing Leader} + {isOrgAdmin && Org Admin} + {inactive && !isBillingLeader && !isOrgAdmin && Inactive} + {new Date(newUserUntil) > new Date() && New} + + + {email} + + +) + +interface UserActionsProps { + isViewerOrgAdmin: boolean + isViewerBillingLeader: boolean + isViewerLastOrgAdmin: boolean + isViewerLastBillingLeader: boolean + organization: OrgMemberRow_organization$data + organizationUser: OrgMemberRow_organizationUser$data + preferredName: string + viewerId: string +} + +const UserActions: React.FC = ({ + isViewerOrgAdmin, + isViewerBillingLeader, + isViewerLastOrgAdmin, + isViewerLastBillingLeader, + organizationUser, + organization, + preferredName, + viewerId +}) => { + const {orgId} = organization + const { + user: {userId} + } = organizationUser + const {togglePortal, originRef, menuPortal, menuProps} = useMenu(MenuPosition.UPPER_RIGHT) + const {togglePortal: toggleLeave, modalPortal: leaveModal} = useModal() + const {togglePortal: toggleRemove, modalPortal: removeModal} = useModal() + const actionMenuProps = { + menuProps, + originRef, + togglePortal, + toggleLeave, + toggleRemove, + isViewerLastOrgAdmin, + isViewerLastBillingLeader, + organization, + organizationUser + } + + const showLeaveButton = !isViewerOrgAdmin && !isViewerBillingLeader && viewerId === userId + + return ( + + + {showLeaveButton && ( + + Leave Organization + + )} + {(isViewerOrgAdmin || (isViewerBillingLeader && !isViewerLastBillingLeader)) && ( + + + + )} + {isViewerOrgAdmin && menuPortal()} + {!isViewerOrgAdmin && + isViewerBillingLeader && + menuPortal()} + {leaveModal()} + {removeModal( + + )} + + + ) +} + const OrgMemberRow = (props: Props) => { const atmosphere = useAtmosphere() const { billingLeaderCount, + orgAdminCount, organizationUser: organizationUserRef, organization: organizationRef } = props + const organization = useFragment( graphql` fragment OrgMemberRow_organization on Organization { isViewerBillingLeader: isBillingLeader + isViewerOrgAdmin: isOrgAdmin orgId: id ...BillingLeaderActionMenu_organization + ...OrgAdminActionMenu_organization } `, organizationRef ) + const organizationUser = useFragment( graphql` fragment OrgMemberRow_organizationUser on OrganizationUser { @@ -126,103 +260,49 @@ const OrgMemberRow = (props: Props) => { role newUserUntil ...BillingLeaderActionMenu_organizationUser + ...OrgAdminActionMenu_organizationUser } `, organizationUserRef ) - const {orgId, isViewerBillingLeader} = organization - const {newUserUntil, user, role} = organizationUser + + const {isViewerBillingLeader, isViewerOrgAdmin} = organization + + const { + newUserUntil, + user: {email, inactive, picture, preferredName}, + role + } = organizationUser + + const {viewerId} = atmosphere + const isBillingLeader = role === 'BILLING_LEADER' const isOrgAdmin = role === 'ORG_ADMIN' - const {email, inactive, picture, preferredName, userId} = user const isViewerLastBillingLeader = isViewerBillingLeader && isBillingLeader && billingLeaderCount === 1 - const {viewerId} = atmosphere - const {togglePortal, originRef, menuPortal, menuProps} = useMenu(MenuPosition.UPPER_RIGHT) - const {togglePortal: toggleLeave, modalPortal: leaveModal} = useModal() - const {togglePortal: toggleRemove, modalPortal: removeModal} = useModal() - const { - tooltipPortal, - openTooltip, - closeTooltip, - originRef: tooltipRef - } = useTooltip(MenuPosition.LOWER_RIGHT) - const canViewMenu = !isViewerLastBillingLeader && organizationUser.role !== 'ORG_ADMIN' + const isViewerLastOrgAdmin = isViewerOrgAdmin && isOrgAdmin && orgAdminCount === 1 return ( - - {picture ? ( - - ) : ( - - )} - - - - {preferredName} - {isBillingLeader && {'Billing Leader'}} - {isOrgAdmin && {'Org Admin'}} - {inactive && !isBillingLeader && !isOrgAdmin && {'Inactive'}} - {new Date(newUserUntil) > new Date() && {'New'}} - - - {email} - - - - - {!isBillingLeader && !isOrgAdmin && viewerId === userId && ( - - Leave Organization - - )} - {!canViewMenu && ( - - {tooltipPortal( - isViewerLastBillingLeader ? ( -
- {'You need to promote another Billing Leader'} -
- {'before you can remove this role.'} -
- ) : ( -
Contact support (love@parabol.co) to remove the Org Admin role
- ) - )} - -
- )} - {isViewerBillingLeader && canViewMenu && ( - - - - )} - {menuPortal( - - )} - {leaveModal()} - {removeModal( - - )} -
-
+ + +
) } diff --git a/packages/client/modules/userDashboard/containers/OrgMembers/OrgMembersRoot.tsx b/packages/client/modules/userDashboard/containers/OrgMembers/OrgMembersRoot.tsx index b0a7ab43373..8242ebf2b8e 100644 --- a/packages/client/modules/userDashboard/containers/OrgMembers/OrgMembersRoot.tsx +++ b/packages/client/modules/userDashboard/containers/OrgMembers/OrgMembersRoot.tsx @@ -15,6 +15,7 @@ const OrgMembersRoot = (props: Props) => { orgId, first: 10000 }) + return ( }> {queryRef && } diff --git a/packages/client/mutations/ArchiveTeamMutation.ts b/packages/client/mutations/ArchiveTeamMutation.ts index 15afd828d4b..218fdd0c7c1 100644 --- a/packages/client/mutations/ArchiveTeamMutation.ts +++ b/packages/client/mutations/ArchiveTeamMutation.ts @@ -30,6 +30,14 @@ graphql` activeMeetings { id } + organization { + allTeams { + id + } + viewerTeams { + id + } + } } teamTemplateIds } diff --git a/packages/client/package.json b/packages/client/package.json index 364c5dbd367..81691d805d0 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -82,6 +82,8 @@ "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-slot": "^1.0.2", + "@radix-ui/react-avatar": "^1.0.4", + "@radix-ui/react-alert-dialog": "1.0.5", "@sentry/browser": "^5.8.0", "@stripe/react-stripe-js": "^1.16.5", "@stripe/stripe-js": "^1.47.0", diff --git a/packages/client/ui/AlertDialog/AlertDialog.tsx b/packages/client/ui/AlertDialog/AlertDialog.tsx new file mode 100644 index 00000000000..8b4fdf6a396 --- /dev/null +++ b/packages/client/ui/AlertDialog/AlertDialog.tsx @@ -0,0 +1,5 @@ +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' + +const AlertDialog = AlertDialogPrimitive.Root + +export {AlertDialog} diff --git a/packages/client/ui/AlertDialog/AlertDialogAction.tsx b/packages/client/ui/AlertDialog/AlertDialogAction.tsx new file mode 100644 index 00000000000..75b93fb02a9 --- /dev/null +++ b/packages/client/ui/AlertDialog/AlertDialogAction.tsx @@ -0,0 +1,18 @@ +import * as React from 'react' +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' +import clsx from 'clsx' + +const AlertDialogAction = React.forwardRef< + HTMLButtonElement, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName diff --git a/packages/client/ui/AlertDialog/AlertDialogCancel.tsx b/packages/client/ui/AlertDialog/AlertDialogCancel.tsx new file mode 100644 index 00000000000..201ca648679 --- /dev/null +++ b/packages/client/ui/AlertDialog/AlertDialogCancel.tsx @@ -0,0 +1,18 @@ +import * as React from 'react' +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' +import clsx from 'clsx' + +const AlertDialogCancel = React.forwardRef< + HTMLButtonElement, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName diff --git a/packages/client/ui/AlertDialog/AlertDialogContent.tsx b/packages/client/ui/AlertDialog/AlertDialogContent.tsx new file mode 100644 index 00000000000..dbcd0de0582 --- /dev/null +++ b/packages/client/ui/AlertDialog/AlertDialogContent.tsx @@ -0,0 +1,23 @@ +import * as React from 'react' +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' +import clsx from 'clsx' +import {AlertDialogOverlay} from './AlertDialogOverlay' +import {AlertDialogPortal} from './AlertDialog' + +const AlertDialogContent = React.forwardRef< + HTMLDivElement, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName diff --git a/packages/client/ui/AlertDialog/AlertDialogDescription.tsx b/packages/client/ui/AlertDialog/AlertDialogDescription.tsx new file mode 100644 index 00000000000..7c940b02fec --- /dev/null +++ b/packages/client/ui/AlertDialog/AlertDialogDescription.tsx @@ -0,0 +1,15 @@ +import * as React from 'react' +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' +import clsx from 'clsx' + +const AlertDialogDescription = React.forwardRef< + HTMLParagraphElement, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)) +AlertDialogDescription.displayName = AlertDialogPrimitive.Description.displayName diff --git a/packages/client/ui/AlertDialog/AlertDialogFooter.tsx b/packages/client/ui/AlertDialog/AlertDialogFooter.tsx new file mode 100644 index 00000000000..0148d9a8c39 --- /dev/null +++ b/packages/client/ui/AlertDialog/AlertDialogFooter.tsx @@ -0,0 +1,10 @@ +import * as React from 'react' +import clsx from 'clsx' + +const AlertDialogFooter = ({className, ...props}: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = 'AlertDialogFooter' diff --git a/packages/client/ui/AlertDialog/AlertDialogHeader.tsx b/packages/client/ui/AlertDialog/AlertDialogHeader.tsx new file mode 100644 index 00000000000..869b09ccfc3 --- /dev/null +++ b/packages/client/ui/AlertDialog/AlertDialogHeader.tsx @@ -0,0 +1,7 @@ +import * as React from 'react' +import clsx from 'clsx' + +const AlertDialogHeader = ({className, ...props}: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = 'AlertDialogHeader' diff --git a/packages/client/ui/AlertDialog/AlertDialogOverlay.tsx b/packages/client/ui/AlertDialog/AlertDialogOverlay.tsx new file mode 100644 index 00000000000..e867c7ed363 --- /dev/null +++ b/packages/client/ui/AlertDialog/AlertDialogOverlay.tsx @@ -0,0 +1,18 @@ +import * as React from 'react' +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' +import clsx from 'clsx' + +export const AlertDialogOverlay = React.forwardRef< + HTMLDivElement, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName diff --git a/packages/client/ui/AlertDialog/AlertDialogPortal.tsx b/packages/client/ui/AlertDialog/AlertDialogPortal.tsx new file mode 100644 index 00000000000..c4e8d56916f --- /dev/null +++ b/packages/client/ui/AlertDialog/AlertDialogPortal.tsx @@ -0,0 +1,3 @@ +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' + +const AlertDialogPortal = AlertDialogPrimitive.Portal diff --git a/packages/client/ui/AlertDialog/AlertDialogTitle.tsx b/packages/client/ui/AlertDialog/AlertDialogTitle.tsx new file mode 100644 index 00000000000..c7c14e011c5 --- /dev/null +++ b/packages/client/ui/AlertDialog/AlertDialogTitle.tsx @@ -0,0 +1,15 @@ +import * as React from 'react' +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' +import clsx from 'clsx' + +const AlertDialogTitle = React.forwardRef< + HTMLHeadingElement, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName diff --git a/packages/client/ui/AlertDialog/AlertDialogTrigger.tsx b/packages/client/ui/AlertDialog/AlertDialogTrigger.tsx new file mode 100644 index 00000000000..0a20feddfc1 --- /dev/null +++ b/packages/client/ui/AlertDialog/AlertDialogTrigger.tsx @@ -0,0 +1,3 @@ +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger diff --git a/packages/client/ui/Avatar/Avatar.tsx b/packages/client/ui/Avatar/Avatar.tsx new file mode 100644 index 00000000000..23bbecd22a6 --- /dev/null +++ b/packages/client/ui/Avatar/Avatar.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import * as AvatarPrimitive from '@radix-ui/react-avatar' +import clsx from 'clsx' + +export const Avatar = React.forwardRef< + HTMLSpanElement, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)) + +Avatar.displayName = AvatarPrimitive.Root.displayName diff --git a/packages/client/ui/Avatar/AvatarFallback.tsx b/packages/client/ui/Avatar/AvatarFallback.tsx new file mode 100644 index 00000000000..482648102a8 --- /dev/null +++ b/packages/client/ui/Avatar/AvatarFallback.tsx @@ -0,0 +1,19 @@ +import React from 'react' +import * as AvatarPrimitive from '@radix-ui/react-avatar' +import clsx from 'clsx' + +export const AvatarFallback = React.forwardRef< + HTMLSpanElement, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)) + +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName diff --git a/packages/client/ui/Avatar/AvatarImage.tsx b/packages/client/ui/Avatar/AvatarImage.tsx new file mode 100644 index 00000000000..66f41abad5d --- /dev/null +++ b/packages/client/ui/Avatar/AvatarImage.tsx @@ -0,0 +1,16 @@ +import React from 'react' +import * as AvatarPrimitive from '@radix-ui/react-avatar' +import clsx from 'clsx' + +export const AvatarImage = React.forwardRef< + HTMLImageElement, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)) + +AvatarImage.displayName = AvatarPrimitive.Image.displayName diff --git a/packages/client/ui/Button/Button.tsx b/packages/client/ui/Button/Button.tsx index a60c67700d7..90554dc662e 100644 --- a/packages/client/ui/Button/Button.tsx +++ b/packages/client/ui/Button/Button.tsx @@ -7,20 +7,21 @@ type Size = 'sm' | 'md' | 'lg' | 'default' type Shape = 'pill' | 'circle' | 'default' const BASE_STYLES = - 'cursor-pointer inline-flex items-center justify-center whitespace-nowrap font-semibold transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50' + 'cursor-pointer inline-flex items-center justify-center whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50' // TODO: make sure the styles match the designs const VARIANT_STYLES: Record = { - primary: 'bg-primary text-white hover:bg-primary/90', - destructive: 'bg-tomato-500 text-white hover:bg-tomato-500/90', - outline: 'text-slate-900 border border-slate-400 hover:bg-slate-200 px-2.5 py-1 bg-transparent', - secondary: 'bg-sky-500 text-white hover:bg-sky-500/80', - ghost: 'hover:bg-accent', + primary: 'bg-gradient-to-r from-tomato-600 to-rose-500 text-white font-semibold hover:opacity-90', + destructive: 'bg-tomato-500 text-white font-semibold hover:bg-tomato-500/90', + outline: + 'text-slate-900 border border-slate-400 hover:bg-slate-200 px-2.5 py-1 bg-transparent font-semibold', + secondary: 'bg-sky-500 text-white hover:bg-sky-500/80 font-semibold', + ghost: 'hover:opacity-80 bg-transparent font-semibold', link: 'text-primary underline-offset-4 hover:underline' } const SIZE_STYLES: Record = { - default: 'px-4 py-2 text-xs', + default: '', sm: 'h-7 px-3 text-xs', md: 'h-9 px-4 text-sm', lg: 'h-11 px-8 text-base' @@ -29,18 +30,18 @@ const SIZE_STYLES: Record = { const SHAPE_STYLES: Record = { pill: 'rounded-full', circle: 'rounded-full aspect-square', - default: 'rounded-md' + default: '' } export interface ButtonProps extends React.ButtonHTMLAttributes { asChild?: boolean variant: Variant size?: Size - shape: Shape + shape?: Shape } const Button = React.forwardRef( - ({className, variant, size, shape, asChild = false, ...props}, ref) => { + ({className, variant, size = 'default', shape = 'default', asChild = false, ...props}, ref) => { const Comp = asChild ? Slot : 'button' return ( = new GraphQLObjectType { const viewerId = getUserId(authToken) return isUserBillingLeader(viewerId, orgId, dataLoader) } }, + isOrgAdmin: { + type: new GraphQLNonNull(GraphQLBoolean), + description: 'true if the viewer holds the the org admin role on the org', + resolve: async ({id: orgId}, _args: unknown, {authToken, dataLoader}) => { + const viewerId = getUserId(authToken) + return isUserOrgAdmin(viewerId, orgId, dataLoader) + } + }, name: { type: new GraphQLNonNull(GraphQLString), description: 'The name of the organization' diff --git a/packages/server/utils/authorization.ts b/packages/server/utils/authorization.ts index 4537f03e77c..dd873e6c885 100644 --- a/packages/server/utils/authorization.ts +++ b/packages/server/utils/authorization.ts @@ -4,6 +4,7 @@ import AuthToken from '../database/types/AuthToken' import OrganizationUser from '../database/types/OrganizationUser' import {DataLoaderWorker} from '../graphql/graphql' import {RDatum} from '../database/stricterR' +import {OrgUserRole} from '../database/types/OrganizationUser' export const getUserId = (authToken: any) => { return authToken && typeof authToken === 'object' ? (authToken.sub as string) : '' @@ -51,10 +52,12 @@ export const isTeamLead = async (userId: string, teamId: string, dataLoader: Dat interface Options { clearCache?: boolean } -export const isUserBillingLeader = async ( + +const isUserAnyRoleIn = async ( userId: string, orgId: string, dataLoader: DataLoaderWorker, + roles: OrgUserRole[], options?: Options ) => { const organizationUser = await dataLoader @@ -63,9 +66,23 @@ export const isUserBillingLeader = async ( if (options && options.clearCache) { dataLoader.get('organizationUsersByUserId').clear(userId) } - return organizationUser - ? organizationUser.role === 'BILLING_LEADER' || organizationUser.role === 'ORG_ADMIN' - : false + return organizationUser && organizationUser.role ? roles.includes(organizationUser.role) : false +} +export const isUserBillingLeader = async ( + userId: string, + orgId: string, + dataLoader: DataLoaderWorker, + options?: Options +) => { + return isUserAnyRoleIn(userId, orgId, dataLoader, ['BILLING_LEADER', 'ORG_ADMIN'], options) +} +export const isUserOrgAdmin = async ( + userId: string, + orgId: string, + dataLoader: DataLoaderWorker, + options?: Options +) => { + return isUserAnyRoleIn(userId, orgId, dataLoader, ['ORG_ADMIN'], options) } export const isUserInOrg = async (userId: string, orgId: string, dataLoader: DataLoaderWorker) => { diff --git a/yarn.lock b/yarn.lock index af959f0896f..ffa47f0a625 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5704,6 +5704,19 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-alert-dialog@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.5.tgz#70dd529cbf1e4bff386814d3776901fcaa131b8c" + integrity sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-dialog" "1.0.5" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-arrow@1.0.3": version "1.0.3" resolved "https://registry.yarnpkg.com/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz#c24f7968996ed934d57fe6cde5d6ec7266e1d25d" @@ -5712,6 +5725,17 @@ "@babel/runtime" "^7.13.10" "@radix-ui/react-primitive" "1.0.3" +"@radix-ui/react-avatar@^1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-avatar/-/react-avatar-1.0.4.tgz#de9a5349d9e3de7bbe990334c4d2011acbbb9623" + integrity sha512-kVK2K7ZD3wwj3qhle0ElXhOjbezIgyl2hVvgwfIdexL3rN6zJmy5AqqIf+D31lxVppdzV8CjAfZ6PklkmInZLw== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-collection@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.2.tgz#d50da00bfa2ac14585319efdbbb081d4c5a29a97" @@ -5762,6 +5786,27 @@ dependencies: "@babel/runtime" "^7.13.10" +"@radix-ui/react-dialog@1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz#71657b1b116de6c7a0b03242d7d43e01062c7300" + integrity sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-dismissable-layer" "1.0.5" + "@radix-ui/react-focus-guards" "1.0.1" + "@radix-ui/react-focus-scope" "1.0.4" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-portal" "1.0.4" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-use-controllable-state" "1.0.1" + aria-hidden "^1.1.1" + react-remove-scroll "2.5.5" + "@radix-ui/react-dialog@^1.0.4": version "1.0.4" resolved "https://registry.yarnpkg.com/@radix-ui/react-dialog/-/react-dialog-1.0.4.tgz#06bce6c16bb93eb36d7a8589e665a20f4c1c52c1" @@ -5852,6 +5897,16 @@ "@radix-ui/react-primitive" "1.0.3" "@radix-ui/react-use-callback-ref" "1.0.1" +"@radix-ui/react-focus-scope@1.0.4": + version "1.0.4" + resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz#2ac45fce8c5bb33eb18419cdc1905ef4f1906525" + integrity sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-callback-ref" "1.0.1" + "@radix-ui/react-id@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@radix-ui/react-id/-/react-id-1.0.0.tgz#8d43224910741870a45a8c9d092f25887bb6d11e" @@ -11230,6 +11285,7 @@ draft-js-utils@^1.4.0: "draft-js@https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6": version "0.10.5" + uid "025fddba56f21aaf3383aee778e0b17025c9a7bc" resolved "https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6#025fddba56f21aaf3383aee778e0b17025c9a7bc" dependencies: fbjs "^0.8.15" From 88bf97f6ff3e820d49a24e9a8a8cf4dbab46b22c Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Mon, 11 Mar 2024 21:17:58 +0000 Subject: [PATCH 057/529] chore(ci): add capability to manually generate Docker Images (#9524) --- .github/workflows/build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f340977a97..a19c552ac06 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,6 +7,7 @@ on: push: branches: - "release-please--**" + workflow_dispatch: concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: true From 9350b93b7a2a6f48e0af712cc0a6edbb8395004c Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 12 Mar 2024 12:31:03 +0100 Subject: [PATCH 058/529] fix: Make hasGCalError optional (#9526) --- packages/server/graphql/public/typeDefs/_legacy.graphql | 2 +- packages/server/graphql/public/typeDefs/startCheckIn.graphql | 2 +- .../server/graphql/public/typeDefs/startRetrospective.graphql | 2 +- .../server/graphql/public/typeDefs/startTeamPrompt.graphql | 2 +- .../server/graphql/public/types/StartRetrospectiveSuccess.ts | 3 +-- 5 files changed, 5 insertions(+), 6 deletions(-) diff --git a/packages/server/graphql/public/typeDefs/_legacy.graphql b/packages/server/graphql/public/typeDefs/_legacy.graphql index 79e8797a2a3..75ba9203d1f 100644 --- a/packages/server/graphql/public/typeDefs/_legacy.graphql +++ b/packages/server/graphql/public/typeDefs/_legacy.graphql @@ -7863,7 +7863,7 @@ type StartSprintPokerSuccess { meeting: PokerMeeting! team: Team! teamId: ID! - hasGcalError: Boolean! + hasGcalError: Boolean } """ diff --git a/packages/server/graphql/public/typeDefs/startCheckIn.graphql b/packages/server/graphql/public/typeDefs/startCheckIn.graphql index b21e52d3eb4..8fde46f9b80 100644 --- a/packages/server/graphql/public/typeDefs/startCheckIn.graphql +++ b/packages/server/graphql/public/typeDefs/startCheckIn.graphql @@ -7,7 +7,7 @@ type StartCheckInSuccess { meeting: ActionMeeting! meetingId: ID! team: Team! - hasGcalError: Boolean! + hasGcalError: Boolean } extend type Mutation { diff --git a/packages/server/graphql/public/typeDefs/startRetrospective.graphql b/packages/server/graphql/public/typeDefs/startRetrospective.graphql index 01f0a269fc1..91bb03261c2 100644 --- a/packages/server/graphql/public/typeDefs/startRetrospective.graphql +++ b/packages/server/graphql/public/typeDefs/startRetrospective.graphql @@ -27,7 +27,7 @@ type StartRetrospectiveSuccess { meeting: RetrospectiveMeeting! meetingId: ID! team: Team! - hasGcalError: Boolean! + hasGcalError: Boolean } input CreateGcalEventInput { diff --git a/packages/server/graphql/public/typeDefs/startTeamPrompt.graphql b/packages/server/graphql/public/typeDefs/startTeamPrompt.graphql index 42b2730b109..01bd98483b5 100644 --- a/packages/server/graphql/public/typeDefs/startTeamPrompt.graphql +++ b/packages/server/graphql/public/typeDefs/startTeamPrompt.graphql @@ -35,7 +35,7 @@ type StartTeamPromptSuccess { """ True if there was an error creating the Google Calendar event. False if there was no error or no gcalInput was provided. """ - hasGcalError: Boolean! + hasGcalError: Boolean } input CreateGcalEventInput { diff --git a/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts b/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts index 5005dd506c9..4ba4baef9be 100644 --- a/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts +++ b/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts @@ -13,8 +13,7 @@ const StartRetrospectiveSuccess: StartRetrospectiveSuccessResolvers = { }, team: ({teamId}, _args: unknown, {dataLoader}) => { return dataLoader.get('teams').loadNonNull(teamId) - }, - hasGcalError: ({hasGcalError}) => !!hasGcalError + } } export default StartRetrospectiveSuccess From 37bd20cf8e073d353e3b3dffb5f3037c199adf67 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 12 Mar 2024 12:36:50 +0100 Subject: [PATCH 059/529] chore: Remove Add Activity button from discussions (#9528) --- .../client/components/AddActivityButton.tsx | 43 ------------------- .../components/DiscussionThreadInput.tsx | 19 +------- packages/client/modules/demo/initDB.ts | 1 - .../typeDefs/updateOrgFeatureFlag.graphql | 1 - .../public/typeDefs/Organization.graphql | 1 - .../public/types/OrganizationFeatureFlags.ts | 1 - 6 files changed, 2 insertions(+), 64 deletions(-) delete mode 100644 packages/client/components/AddActivityButton.tsx diff --git a/packages/client/components/AddActivityButton.tsx b/packages/client/components/AddActivityButton.tsx deleted file mode 100644 index 53899b38267..00000000000 --- a/packages/client/components/AddActivityButton.tsx +++ /dev/null @@ -1,43 +0,0 @@ -import styled from '@emotion/styled' -import {Add} from '@mui/icons-material' -import React from 'react' -import {PALETTE} from '~/styles/paletteV3' -import PlainButton from './PlainButton/PlainButton' - -const StyledPlainButton = styled(PlainButton)({ - display: 'flex', - alignItems: 'center', - justifyContent: 'center', - color: PALETTE.SKY_500, - fontWeight: 600, - fontSize: 14, - margin: '0 8px', - ':hover, :focus, :active': { - color: PALETTE.SKY_600 - }, - transition: 'color 0.1s ease' -}) - -const Icon = styled(Add)({ - width: 20, - height: 20, - margin: '0 4px 0 0' -}) - -interface Props { - onClick: () => void - disabled?: boolean -} - -const AddActivityButton = (props: Props) => { - const {onClick, disabled} = props - - return ( - - -
Add an activity
-
- ) -} - -export default AddActivityButton diff --git a/packages/client/components/DiscussionThreadInput.tsx b/packages/client/components/DiscussionThreadInput.tsx index 06f8a1a2fd5..ba7cd2f3778 100644 --- a/packages/client/components/DiscussionThreadInput.tsx +++ b/packages/client/components/DiscussionThreadInput.tsx @@ -29,8 +29,6 @@ import {createLocalPoll} from './Poll/local/newPoll' import SendCommentButton from './SendCommentButton' import CommentEditor from './TaskEditor/CommentEditor' import {ReplyMention, SetReplyMention} from './ThreadedItem' -import AddActivityButton from '~/components/AddActivityButton' -import SendClientSideEvent from '../utils/SendClientSideEvent' const Wrapper = styled('div')<{isReply: boolean; isDisabled: boolean}>(({isDisabled, isReply}) => ({ display: 'flex', @@ -106,9 +104,6 @@ const DiscussionThreadInput = forwardRef((props: Props, ref: any) => { graphql` fragment DiscussionThreadInput_viewer on User { picture - featureFlags { - retrosInDisguise - } } `, viewerRef @@ -127,7 +122,7 @@ const DiscussionThreadInput = forwardRef((props: Props, ref: any) => { `, discussionRef ) - const {picture, featureFlags} = viewer + const {picture} = viewer const isReply = !!props.isReply const isDisabled = !!props.isDisabled const {id: discussionId, meetingId, isAnonymousComment, team, discussionTopicType} = discussion @@ -298,8 +293,7 @@ const DiscussionThreadInput = forwardRef((props: Props, ref: any) => { } }, []) - const allowAddActivity = featureFlags.retrosInDisguise - const isActionsContainerVisible = allowTasks || allowPolls || allowAddActivity + const isActionsContainerVisible = allowTasks || allowPolls const isActionsContainerDisabled = isCreatingTask || isCreatingPoll const avatar = isAnonymousComment ? anonymousAvatar : picture @@ -345,15 +339,6 @@ const DiscussionThreadInput = forwardRef((props: Props, ref: any) => { disabled={isActionsContainerDisabled} /> )} - {allowAddActivity && ( - { - window.open(`/activity-library/category/recommended`, '_blank', 'noreferrer') - SendClientSideEvent(atmosphere, 'Add Activity Button Clicked') - }} - disabled={isActionsContainerDisabled} - /> - )} )} diff --git a/packages/client/modules/demo/initDB.ts b/packages/client/modules/demo/initDB.ts index 73089695412..6e7954ef5a4 100644 --- a/packages/client/modules/demo/initDB.ts +++ b/packages/client/modules/demo/initDB.ts @@ -304,7 +304,6 @@ const initDemoOrg = () => { teamsLimit: false, noPromptToJoinOrg: false, AIGeneratedDiscussionPrompt: false, - meetingInception: false, publicTeams: false }, showConversionModal: false diff --git a/packages/server/graphql/private/typeDefs/updateOrgFeatureFlag.graphql b/packages/server/graphql/private/typeDefs/updateOrgFeatureFlag.graphql index fffa8ee086e..211113f9903 100644 --- a/packages/server/graphql/private/typeDefs/updateOrgFeatureFlag.graphql +++ b/packages/server/graphql/private/typeDefs/updateOrgFeatureFlag.graphql @@ -14,7 +14,6 @@ enum OrganizationFeatureFlagsEnum { oneOnOne singleColumnStandups publicTeams - meetingInception kudos aiIcebreakers aiTemplate diff --git a/packages/server/graphql/public/typeDefs/Organization.graphql b/packages/server/graphql/public/typeDefs/Organization.graphql index 005ced3a3ab..485adcac9f6 100644 --- a/packages/server/graphql/public/typeDefs/Organization.graphql +++ b/packages/server/graphql/public/typeDefs/Organization.graphql @@ -194,7 +194,6 @@ type OrganizationFeatureFlags { oneOnOne: Boolean! singleColumnStandups: Boolean! publicTeams: Boolean! - meetingInception: Boolean! kudos: Boolean! aiIcebreakers: Boolean! aiTemplate: Boolean! diff --git a/packages/server/graphql/public/types/OrganizationFeatureFlags.ts b/packages/server/graphql/public/types/OrganizationFeatureFlags.ts index efca8fb9406..14f4d59e6a4 100644 --- a/packages/server/graphql/public/types/OrganizationFeatureFlags.ts +++ b/packages/server/graphql/public/types/OrganizationFeatureFlags.ts @@ -13,7 +13,6 @@ const OrganizationFeatureFlags: OrganizationFeatureFlagsResolvers = { oneOnOne: ({oneOnOne}) => !!oneOnOne, publicTeams: ({publicTeams}) => !!publicTeams, singleColumnStandups: ({singleColumnStandups}) => !!singleColumnStandups, - meetingInception: ({meetingInception}) => !!meetingInception, kudos: ({kudos}) => !!kudos, aiIcebreakers: ({aiIcebreakers}) => !!aiIcebreakers, aiTemplate: ({aiTemplate}) => !!aiTemplate, From 10c6f6932008fcca434d1b6a73c288aea88768d5 Mon Sep 17 00:00:00 2001 From: Jordan Husney Date: Tue, 12 Mar 2024 10:37:11 -0700 Subject: [PATCH 060/529] fix: Korean greeting corrected (#9525) eat: added additional greetings feat: added additinal check-in questions --- packages/client/utils/makeCheckinGreeting.ts | 44 +++++++++++++++++++- 1 file changed, 42 insertions(+), 2 deletions(-) diff --git a/packages/client/utils/makeCheckinGreeting.ts b/packages/client/utils/makeCheckinGreeting.ts index c7618d32e3b..09203e4595f 100644 --- a/packages/client/utils/makeCheckinGreeting.ts +++ b/packages/client/utils/makeCheckinGreeting.ts @@ -40,7 +40,7 @@ const greetings = [ language: 'Chinese' }, { - content: 'Yeoboseyo', + content: 'Annyeong', language: 'Korean' }, { @@ -70,6 +70,42 @@ const greetings = [ { content: 'Lei Ho', language: 'Cantonese' + }, + { + content: 'Salaam', + language: 'Arabic' + }, + { + content: 'Filipino', + language: 'Kumusta' + }, + { + content: 'Pryvit', + language: 'Ukranian' + }, + { + content: 'Habari', + language: 'Swahili' + }, + { + content: 'Vanakkam', + language: 'Tamil' + }, + { + content: 'Chào', + language: 'Vietnamese' + }, + { + content: 'Gamardjoba', + language: 'Georgian' + }, + { + content: 'Selam', + language: 'Amharic' + }, + { + content: 'Iska warran', + language: 'Somalian' } ] @@ -294,7 +330,11 @@ const questions = [ 'If buying groceries were a game, what would be one of the loading screen tips?', 'What’s one of your recent pet peeves?', 'How would your best friend describe you?', - 'Congratulations! You’ve been chosen to represent your country in a global competition. What sport or activity are you doing?' + 'Congratulations! You’ve been chosen to represent your country in a global competition. What sport or activity are you doing?', + 'What’s a small victory you had this week that might seem trivial but was important to you?', + 'If you could have any author, living or dead, write the story of your life, who would it be and why?', + 'Imagine you could teleport to any place in the world for your next meal. Where would you go and what would you eat?', + 'What’s one song that always boosts your mood, no matter how many times you hear it?' ] export const makeCheckinGreeting = (meetingCount: number, seedId = '') => { From fc4429c85dd9610d3fdadf83882c2dbdd88f424f Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Wed, 13 Mar 2024 09:22:27 +0100 Subject: [PATCH 061/529] feat: Recurring GCal event dialog (#9506) --- .../ActivityDetailsRecurrenceSettings.tsx | 42 ---- .../ActivityDetailsSidebar.tsx | 68 +++--- .../ActivityLibrary/ScheduleMeetingButton.tsx | 70 +++--- .../NewMeetingRecurrenceSettings.tsx | 32 ++- .../Recurrence/RecurrenceSettings.tsx | 90 ++------ .../UpdateRecurrenceSettingsModal.tsx | 85 ++++--- packages/client/components/ScheduleDialog.tsx | 217 ++++++++++++++++++ .../components/GcalModal/GcalSettings.tsx | 164 +++++++++++++ packages/client/package.json | 3 +- yarn.lock | 16 +- 10 files changed, 577 insertions(+), 210 deletions(-) delete mode 100644 packages/client/components/ActivityLibrary/ActivityDetailsRecurrenceSettings.tsx create mode 100644 packages/client/components/ScheduleDialog.tsx create mode 100644 packages/client/modules/userDashboard/components/GcalModal/GcalSettings.tsx diff --git a/packages/client/components/ActivityLibrary/ActivityDetailsRecurrenceSettings.tsx b/packages/client/components/ActivityLibrary/ActivityDetailsRecurrenceSettings.tsx deleted file mode 100644 index 7fe8efeeb21..00000000000 --- a/packages/client/components/ActivityLibrary/ActivityDetailsRecurrenceSettings.tsx +++ /dev/null @@ -1,42 +0,0 @@ -import React from 'react' -import {RecurrenceSettings} from '../Recurrence/RecurrenceSettings' -import NewMeetingDropdown from '../NewMeetingDropdown' -import {toHumanReadable} from '../Recurrence/HumanReadableRecurrenceRule' -import useModal from '../../hooks/useModal' -import DialogContainer from '../DialogContainer' - -interface Props { - onRecurrenceSettingsUpdated: (recurrenceSettings: RecurrenceSettings) => void - recurrenceSettings: RecurrenceSettings - placeholder: string -} - -export const ActivityDetailsRecurrenceSettings = (props: Props) => { - const {onRecurrenceSettingsUpdated, recurrenceSettings, placeholder} = props - const {togglePortal, modalPortal} = useModal({ - id: 'activityDetailsRecurrenceSettings' - }) - - return ( - <> - - {modalPortal( - - - - )} - - ) -} diff --git a/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx b/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx index 645fd319fab..a686ca89c36 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx @@ -20,10 +20,12 @@ import SendClientSideEvent from '../../utils/SendClientSideEvent' import StartCheckInMutation from '../../mutations/StartCheckInMutation' import StartTeamPromptMutation from '../../mutations/StartTeamPromptMutation' import {PALETTE} from '../../styles/paletteV3' -import {CreateGcalEventInput} from '../../__generated__/StartRetrospectiveMutation.graphql' +import { + CreateGcalEventInput, + RecurrenceSettingsInput +} from '../../__generated__/StartRetrospectiveMutation.graphql' import sortByTier from '../../utils/sortByTier' import {MeetingTypeEnum} from '../../__generated__/ActivityDetailsQuery.graphql' -import {RecurrenceSettings} from '../Recurrence/RecurrenceSettings' import NewMeetingSettingsToggleAnonymity from '../NewMeetingSettingsToggleAnonymity' import NewMeetingSettingsToggleTeamHealth from '../NewMeetingSettingsToggleTeamHealth' import NewMeetingSettingsToggleCheckIn from '../NewMeetingSettingsToggleCheckIn' @@ -32,7 +34,6 @@ import FlatPrimaryButton from '../FlatPrimaryButton' import NewMeetingActionsCurrentMeetings from '../NewMeetingActionsCurrentMeetings' import RaisedButton from '../RaisedButton' import NewMeetingTeamPicker from '../NewMeetingTeamPicker' -import {ActivityDetailsRecurrenceSettings} from './ActivityDetailsRecurrenceSettings' import {AdhocTeamMultiSelect, Option} from '../AdhocTeamMultiSelect/AdhocTeamMultiSelect' import {Select} from '../../ui/Select/Select' import {SelectTrigger} from '../../ui/Select/SelectTrigger' @@ -119,17 +120,13 @@ const ActivityDetailsSidebar = (props: Props) => { ...NewMeetingTeamPicker_teams ...NewMeetingActionsCurrentMeetings_team ...ScheduleMeetingButton_team + ...ScheduleDialog_team } `, teamsRef ) const atmosphere = useAtmosphere() - const [recurrenceSettings, setRecurrenceSettings] = useState({ - name: '', - rrule: null - }) - const templateTeam = teams.find((team) => team.id === selectedTemplate.teamId) const availableTeams = @@ -193,7 +190,10 @@ const ActivityDetailsSidebar = (props: Props) => { } : null - const handleStartActivity = (gcalInput?: CreateGcalEventInput) => { + const handleStartActivity = ( + gcalInput?: CreateGcalEventInput, + recurrenceSettings?: RecurrenceSettingsInput + ) => { if (submitting) return submitMutation() if (type === 'teamPrompt') { @@ -201,10 +201,12 @@ const ActivityDetailsSidebar = (props: Props) => { atmosphere, { teamId: selectedTeam.id, - recurrenceSettings: { - rrule: recurrenceSettings.rrule?.toString(), - name: recurrenceSettings.name - }, + recurrenceSettings: recurrenceSettings + ? { + rrule: recurrenceSettings.rrule?.toString(), + name: recurrenceSettings.name + } + : undefined, gcalInput }, {history, onError, onCompleted} @@ -238,10 +240,12 @@ const ActivityDetailsSidebar = (props: Props) => { atmosphere, { teamId: selectedTeam.id, - recurrenceSettings: { - rrule: recurrenceSettings.rrule?.toString(), - name: recurrenceSettings.name - }, + recurrenceSettings: recurrenceSettings + ? { + rrule: recurrenceSettings.rrule?.toString(), + name: recurrenceSettings.name + } + : undefined, gcalInput }, {history, onError, onCompleted} @@ -299,6 +303,20 @@ const ActivityDetailsSidebar = (props: Props) => { history.push(`/me/organizations/${selectedTeam.orgId}/billing`) } + const meetingNamePlaceholder = + type === 'retrospective' + ? 'Retro' + : type === 'teamPrompt' + ? 'Standup' + : type === 'poker' + ? 'Poker' + : type === 'action' + ? 'Check-in' + : 'Meeting' + const withRecurrence = + type === 'teamPrompt' || + (selectedTeam.organization.featureFlags.recurringRetros && type === 'retrospective') + return ( <> {isOpen &&
} @@ -404,13 +422,6 @@ const ActivityDetailsSidebar = (props: Props) => { teamRef={selectedTeam} /> - {selectedTeam.organization.featureFlags.recurringRetros && ( - - )} )} {type === 'poker' && ( @@ -419,13 +430,6 @@ const ActivityDetailsSidebar = (props: Props) => { {type === 'action' && ( )} - {type === 'teamPrompt' && ( - - )} )}
@@ -449,6 +453,8 @@ const ActivityDetailsSidebar = (props: Props) => { handleStartActivity={handleStartActivity} mutationProps={mutationProps} teamRef={selectedTeam} + placeholder={meetingNamePlaceholder} + withRecurrence={withRecurrence} /> )} diff --git a/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx b/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx index a756aa9bccc..319187dd742 100644 --- a/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx +++ b/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx @@ -1,26 +1,35 @@ -import React, {useEffect, useState} from 'react' +import React from 'react' import graphql from 'babel-plugin-relay/macro' import SecondaryButton from '../SecondaryButton' -import GcalModal from '../../modules/userDashboard/components/GcalModal/GcalModal' -import {CreateGcalEventInput} from '../../__generated__/StartRetrospectiveMutation.graphql' -import GcalClientManager from '../../utils/GcalClientManager' -import useAtmosphere from '../../hooks/useAtmosphere' +import { + CreateGcalEventInput, + RecurrenceSettingsInput +} from '../../__generated__/StartRetrospectiveMutation.graphql' import {useFragment} from 'react-relay' import {ScheduleMeetingButton_team$key} from '~/__generated__/ScheduleMeetingButton_team.graphql' import {MenuMutationProps} from '../../hooks/useMutationProps' import useModal from '../../hooks/useModal' +import {ScheduleDialog} from '../ScheduleDialog' +import DialogContainer from '../DialogContainer' type Props = { mutationProps: MenuMutationProps - handleStartActivity: (gcalInput?: CreateGcalEventInput) => void + handleStartActivity: ( + gcalInput?: CreateGcalEventInput, + recurrenceInput?: RecurrenceSettingsInput + ) => void teamRef: ScheduleMeetingButton_team$key + placeholder: string + withRecurrence?: boolean } const ScheduleMeetingButton = (props: Props) => { - const {mutationProps, handleStartActivity, teamRef} = props - const atmosphere = useAtmosphere() - const [hasStartedGcalAuthTeamId, setHasStartedGcalAuthTeamId] = useState(null) - const {togglePortal: toggleModal, modalPortal} = useModal({ + const {mutationProps, handleStartActivity, teamRef, placeholder, withRecurrence} = props + const { + togglePortal: toggleModal, + closePortal: closeModal, + modalPortal + } = useModal({ id: 'createGcalEventModal' }) const {submitting} = mutationProps @@ -43,32 +52,26 @@ const ScheduleMeetingButton = (props: Props) => { } } } - ...GcalModal_team + ...ScheduleDialog_team } `, teamRef ) - const {id: teamId, viewerTeamMember} = team - const hasStartedGcalAuth = hasStartedGcalAuthTeamId === teamId + const {viewerTeamMember} = team const viewerGcalIntegration = viewerTeamMember?.integrations.gcal const cloudProvider = viewerGcalIntegration?.cloudProvider const handleClick = () => { - if (viewerGcalIntegration?.auth) { - toggleModal() - } else if (cloudProvider) { - const {clientId, id: providerId} = cloudProvider - GcalClientManager.openOAuth(atmosphere, providerId, clientId, teamId, mutationProps) - setHasStartedGcalAuthTeamId(teamId) - } + toggleModal() + } + const onStartActivity = ( + gcalInput?: CreateGcalEventInput, + recurrenceInput?: RecurrenceSettingsInput + ) => { + handleStartActivity(gcalInput, recurrenceInput) + closeModal() } - - useEffect(() => { - if (hasStartedGcalAuth && viewerGcalIntegration?.auth) { - toggleModal() - } - }, [hasStartedGcalAuth, viewerGcalIntegration]) if (!cloudProvider) return null return ( @@ -77,11 +80,16 @@ const ScheduleMeetingButton = (props: Props) => {
Schedule
{modalPortal( - + + + )} ) diff --git a/packages/client/components/NewMeetingRecurrenceSettings.tsx b/packages/client/components/NewMeetingRecurrenceSettings.tsx index 6b85c650a51..51206d0f3d0 100644 --- a/packages/client/components/NewMeetingRecurrenceSettings.tsx +++ b/packages/client/components/NewMeetingRecurrenceSettings.tsx @@ -1,4 +1,5 @@ -import React from 'react' +import React, {ChangeEvent} from 'react' +import {RRule} from 'rrule' import {MenuPosition} from '../hooks/useCoords' import useMenu from '../hooks/useMenu' import {PortalStatus} from '../hooks/usePortal' @@ -14,6 +15,17 @@ interface Props { export const NewMeetingRecurrenceSettings = (props: Props) => { const {onRecurrenceSettingsUpdated, recurrenceSettings, placeholder} = props + const {rrule, name} = recurrenceSettings + + const onNameChange = (e: ChangeEvent) => { + const title = e.target.value || placeholder + onRecurrenceSettingsUpdated({...recurrenceSettings, name: title}) + } + + const onRruleChange = (rrule: RRule | null) => { + onRecurrenceSettingsUpdated({...recurrenceSettings, rrule}) + } + const {togglePortal, menuPortal, originRef, portalStatus} = useMenu( MenuPosition.LOWER_RIGHT, { @@ -39,11 +51,19 @@ export const NewMeetingRecurrenceSettings = (props: Props) => { ref={originRef} /> {menuPortal( - +
+ + +
)} ) diff --git a/packages/client/components/Recurrence/RecurrenceSettings.tsx b/packages/client/components/Recurrence/RecurrenceSettings.tsx index f90f734500a..8015c6a7772 100644 --- a/packages/client/components/Recurrence/RecurrenceSettings.tsx +++ b/packages/client/components/Recurrence/RecurrenceSettings.tsx @@ -10,8 +10,6 @@ import DropdownMenuToggle from '../DropdownMenuToggle' import {toHumanReadable} from './HumanReadableRecurrenceRule' import {Day, RecurrenceDayCheckbox} from './RecurrenceDayCheckbox' import {RecurrenceTimePicker} from './RecurrenceTimePicker' -import Legitity from '../../validation/Legitity' -import {isNotNull} from '../../utils/predicates' import {getJSDateFromRRuleDate, getRRuleDateFromJSDate} from '../../shared/rruleUtil' dayjs.extend(utcPlugin) @@ -133,10 +131,7 @@ const Description = ({ ...rest }: PropsWithChildren>) => { return ( -
+
{children}
) @@ -157,46 +152,31 @@ const validateInterval = (interval: number) => { return undefined } -const validateMeetingSeriesName = (name: string) => { - const legitity = new Legitity(name) - legitity.trim().max(50, 'Meeting series name must be less than 50 characters') - - return legitity.error -} - export interface RecurrenceSettings { name: string rrule: RRule | null } interface Props { - onRecurrenceSettingsUpdated: ( - recurrenceSettings: RecurrenceSettings, - validationErrors: string[] | undefined - ) => void - recurrenceSettings: RecurrenceSettings - placeholder: string + onRruleUpdated: (rrule: RRule | null) => void + rrule: RRule | null + title: string } export const RecurrenceSettings = (props: Props) => { - const {onRecurrenceSettingsUpdated, recurrenceSettings, placeholder} = props - const {name: meetingSeriesName, rrule: recurrenceRule} = recurrenceSettings - const [name, setName] = React.useState(meetingSeriesName) - const [nameError, setNameError] = React.useState() + const {onRruleUpdated, rrule, title} = props const [recurrenceInterval, setRecurrenceInterval] = React.useState( - recurrenceRule ? recurrenceRule.options.interval : 1 + rrule ? rrule.options.interval : 1 ) const [intervalError, setIntervalError] = React.useState() const [recurrenceDays, setRecurrenceDays] = React.useState( - recurrenceRule - ? recurrenceRule.options.byweekday.map( - (weekday) => ALL_DAYS.find((day) => day.intVal === weekday)! - ) + rrule + ? rrule.options.byweekday.map((weekday) => ALL_DAYS.find((day) => day.intVal === weekday)!) : [] ) const [recurrenceStartTime, setRecurrenceStartTime] = React.useState( - recurrenceRule - ? getJSDateFromRRuleDate(recurrenceRule.options.dtstart) + rrule + ? getJSDateFromRRuleDate(rrule.options.dtstart) : dayjs() .add(1, 'day') .set('hour', 6) @@ -235,14 +215,6 @@ export const RecurrenceSettings = (props: Props) => { } } - const handleNameChange = (e: React.ChangeEvent) => { - const name = e.target.value - const res = validateMeetingSeriesName(name) - - setName(e.target.value) - setNameError(res) - } - useEffect(() => { const rrule = recurrenceDays.length > 0 && !intervalError @@ -255,41 +227,21 @@ export const RecurrenceSettings = (props: Props) => { }) : null - onRecurrenceSettingsUpdated({name, rrule}, [nameError, intervalError].filter(isNotNull)) - }, [recurrenceDays, recurrenceInterval, recurrenceStartTime, name]) - const hasErrors = !!nameError || !!intervalError + onRruleUpdated(rrule) + }, [recurrenceDays, recurrenceInterval, recurrenceStartTime]) return (
-
Recurrence
- - Series title - - } - /> + - Restarts every - - } onChange={handleIntervalChange} value={recurrenceInterval} min={1} @@ -297,15 +249,13 @@ export const RecurrenceSettings = (props: Props) => { />
{plural(recurrenceInterval, 'week')}
- {hasErrors ? ( - [nameError, intervalError] - .filter(isNotNull) - .map((error) => {error}) + {intervalError ? ( + {intervalError} ) : ( The next meeting in this series will be called{' '} - "{meetingSeriesName || placeholder} - {dayjs(recurrenceStartTime).format('MMM DD')}" + "{title} - {dayjs(recurrenceStartTime).format('MMM DD')}" )} @@ -332,8 +282,8 @@ export const RecurrenceSettings = (props: Props) => { Your meeting{' '} - {recurrenceRule - ? `will restart ${toHumanReadable(recurrenceRule, {isPartOfSentence: true})}` + {rrule + ? `will restart ${toHumanReadable(rrule, {isPartOfSentence: true})}` : 'will not restart'} diff --git a/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx b/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx index 227fc8667fe..e3e82f88859 100644 --- a/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx +++ b/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx @@ -1,18 +1,21 @@ import styled from '@emotion/styled' import {Close} from '@mui/icons-material' import graphql from 'babel-plugin-relay/macro' -import React, {useMemo, useState} from 'react' +import React, {ChangeEvent, useMemo, useState} from 'react' import {useFragment} from 'react-relay' import {RRule} from 'rrule' import UpdateRecurrenceSettingsMutation from '~/mutations/UpdateRecurrenceSettingsMutation' import {UpdateRecurrenceSettingsModal_meeting$key} from '~/__generated__/UpdateRecurrenceSettingsModal_meeting.graphql' import useAtmosphere from '../../hooks/useAtmosphere' +import useForm from '../../hooks/useForm' import useMutationProps, {getOnCompletedError} from '../../hooks/useMutationProps' import {PALETTE} from '../../styles/paletteV3' import {CompletedHandler} from '../../types/relayMutations' +import Legitity from '../../validation/Legitity' import {UpdateRecurrenceSettingsMutation as TUpdateRecurrenceSettingsMutation} from '../../__generated__/UpdateRecurrenceSettingsMutation.graphql' import DialogContainer from '../DialogContainer' import PlainButton from '../PlainButton/PlainButton' +import StyledError from '../StyledError' import {RecurrenceSettings} from './RecurrenceSettings' const UpdateRecurrenceSettingsModalRoot = styled(DialogContainer)({ @@ -80,6 +83,9 @@ const ErrorContainer = styled('div')({ padding: '0px 16px 16px 16px' }) +const validateTitle = (title: string) => + new Legitity(title).trim().min(2, `C’mon, you call that a title?`) + interface Props { meeting: UpdateRecurrenceSettingsModal_meeting$key closeModal: () => void @@ -114,14 +120,10 @@ export const UpdateRecurrenceSettingsModal = (props: Props) => { const currentRecurrenceRule = meeting.meetingSeries?.recurrenceRule const atmosphere = useAtmosphere() const isMeetingSeriesActive = meeting.meetingSeries?.cancelledAt === null - const [newRecurrenceSettings, setNewRecurrenceSettings] = useState(() => ({ - name: meeting.meetingSeries?.title || '', - rrule: - isMeetingSeriesActive && currentRecurrenceRule - ? RRule.fromString(currentRecurrenceRule) - : null - })) - const [validationErrors, setValidationErrors] = useState(undefined) + + const [rrule, setRrule] = useState( + isMeetingSeriesActive && currentRecurrenceRule ? RRule.fromString(currentRecurrenceRule) : null + ) const {submitting, onError, onCompleted, submitMutation, error} = useMutationProps() const onRecurrenceSettingsUpdated: CompletedHandler< @@ -142,8 +144,31 @@ export const UpdateRecurrenceSettingsModal = (props: Props) => { closeModal() } + const {fields, onChange} = useForm({ + title: { + getDefault: () => meeting.meetingSeries?.title || '' + } + }) + const title = fields.title.value + const titleErr = fields.title.error + + const onNameChange = (event: ChangeEvent) => { + if (titleErr) { + fields.title.setError('') + } + onChange(event) + } + const onUpdateRecurrenceClicked = () => { if (submitting) return + + const title = fields.title.value || placeholder + const titleRes = validateTitle(title) + if (titleRes.error) { + fields.title.setError(titleRes.error) + return + } + submitMutation() UpdateRecurrenceSettingsMutation( @@ -151,8 +176,8 @@ export const UpdateRecurrenceSettingsModal = (props: Props) => { { meetingId: meeting.id, recurrenceSettings: { - rrule: newRecurrenceSettings.rrule?.toString(), - name: newRecurrenceSettings.name + rrule: rrule?.toString(), + name: title } }, {onError, onCompleted: onRecurrenceSettingsUpdated} @@ -170,37 +195,41 @@ export const UpdateRecurrenceSettingsModal = (props: Props) => { ) } - const handleNewRecurrenceSettings = ( - newRecurrenceSettings: RecurrenceSettings, - errors: string[] | undefined - ) => { - setNewRecurrenceSettings(newRecurrenceSettings) - setValidationErrors(errors) - } - const canUpdate = useMemo(() => { - if (validationErrors?.length) return false - const isRecurrenceReenabled = !isMeetingSeriesActive && newRecurrenceSettings.rrule + const title = fields.title.value || placeholder + const titleRes = validateTitle(title) + if (titleRes.error) { + fields.title.setError(titleRes.error) + return + } + + const isRecurrenceReenabled = !isMeetingSeriesActive && rrule if (isRecurrenceReenabled) return true const hasRecurrenceSettingsChanged = - isMeetingSeriesActive && currentRecurrenceRule !== newRecurrenceSettings.rrule?.toString() + isMeetingSeriesActive && currentRecurrenceRule !== rrule?.toString() if (hasRecurrenceSettingsChanged) return true - const hasNameChanged = - isMeetingSeriesActive && meeting.meetingSeries?.title !== newRecurrenceSettings.name + const hasNameChanged = isMeetingSeriesActive && meeting.meetingSeries?.title !== title if (hasNameChanged) return true return false - }, [meeting, newRecurrenceSettings, currentRecurrenceRule, isMeetingSeriesActive]) + }, [meeting, title, rrule, currentRecurrenceRule, isMeetingSeriesActive]) return ( - + {titleErr && {titleErr}} + diff --git a/packages/client/components/ScheduleDialog.tsx b/packages/client/components/ScheduleDialog.tsx new file mode 100644 index 00000000000..39f484dbff1 --- /dev/null +++ b/packages/client/components/ScheduleDialog.tsx @@ -0,0 +1,217 @@ +import React, {ChangeEvent, useState} from 'react' +import {RecurrenceSettings} from './Recurrence/RecurrenceSettings' +import * as Collapsible from '@radix-ui/react-collapsible' +import {EventRepeat, ExpandMore} from '@mui/icons-material' +import PrimaryButton from './PrimaryButton' +import {DialogActions} from '../ui/Dialog/DialogActions' +import SecondaryButton from './SecondaryButton' +import GcalSettings, { + GcalEventInput +} from '../modules/userDashboard/components/GcalModal/GcalSettings' +import logo from '../styles/theme/images/graphics/google.svg' +import gcalLogo from '../styles/theme/images/graphics/google-calendar.svg' +import useForm from '../hooks/useForm' +import StyledError from './StyledError' +import GcalClientManager from '../utils/GcalClientManager' +import SendClientSideEvent from '../utils/SendClientSideEvent' +import graphql from 'babel-plugin-relay/macro' +import {useFragment} from 'react-relay' +import {ScheduleDialog_team$key} from '~/__generated__/ScheduleDialog_team.graphql' +import useAtmosphere from '../hooks/useAtmosphere' +import {MenuMutationProps} from '../hooks/useMutationProps' +import Legitity from '../validation/Legitity' +import { + CreateGcalEventInput, + RecurrenceSettingsInput +} from '../__generated__/StartRetrospectiveMutation.graphql' +import {RRule} from 'rrule' +import dayjs from 'dayjs' +import {toHumanReadable} from './Recurrence/HumanReadableRecurrenceRule' +import clsx from 'clsx' +import plural from '../utils/plural' + +const validateTitle = (title: string) => + new Legitity(title).trim().min(2, `C’mon, you call that a title?`) + +interface Props { + onStartActivity: (gcalInput?: CreateGcalEventInput, recurrence?: RecurrenceSettingsInput) => void + placeholder: string + teamRef: ScheduleDialog_team$key + onCancel: () => void + mutationProps: MenuMutationProps + withRecurrence?: boolean +} + +export const ScheduleDialog = (props: Props) => { + const {placeholder, teamRef, onCancel, mutationProps, withRecurrence} = props + const [rrule, setRrule] = useState(null) + const [openRecurrence, setOpenRecurrence] = React.useState(!!rrule) + const [openGcalEvent, setOpenGcalEvent] = React.useState(true) + const [addedInvite, setAddedInvite] = React.useState(false) + + const [gcalInput, setGcalInput] = useState({ + start: dayjs().add(1, 'hour').startOf('hour'), + end: dayjs().add(2, 'hour').startOf('hour'), + invitees: [], + videoType: null + }) + + const team = useFragment( + graphql` + fragment ScheduleDialog_team on Team { + id + viewerTeamMember { + isSelf + integrations { + gcal { + auth { + id + } + cloudProvider { + id + clientId + } + } + } + } + ...GcalModal_team + ...GcalSettings_team + } + `, + teamRef + ) + + const {id: teamId, viewerTeamMember} = team + const {gcal} = viewerTeamMember?.integrations ?? {} + + const atmosphere = useAtmosphere() + const {fields, onChange} = useForm({ + title: { + getDefault: () => '' + } + }) + const title = fields.title.value + const titleErr = fields.title.error + + const onNameChange = (event: ChangeEvent) => { + if (titleErr) { + fields.title.setError('') + } + onChange(event) + } + + const handleSubmit = () => { + const title = fields.title.value || placeholder + const titleRes = validateTitle(title) + if (titleRes.error) { + fields.title.setError(titleRes.error) + return + } + + const gcalEventInput = addedInvite + ? { + title, + startTimestamp: gcalInput.start.unix(), + endTimestamp: gcalInput.end.unix(), + timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, + invitees: gcalInput.invitees, + videoType: gcalInput.videoType ?? undefined + } + : undefined + props.onStartActivity(gcalEventInput, rrule ? {rrule} : undefined) + } + + const onAddInvite = () => { + if (!gcal?.cloudProvider) { + return + } + if (!gcal?.auth) { + const {clientId, id: providerId} = gcal.cloudProvider + GcalClientManager.openOAuth(atmosphere, providerId, clientId, teamId, mutationProps) + + SendClientSideEvent(atmosphere, 'Schedule meeting add gcal clicked', { + teamId: teamId, + service: 'gcal' + }) + setAddedInvite(true) + } else { + setAddedInvite(true) + } + } + + return ( +
+
Schedule Your Meeting
+
+ Create a recurring meeting series or add the meeting to your calendar. +
+
+ + {titleErr && {titleErr}} +
+ {gcal?.cloudProvider && + (gcal?.auth && addedInvite ? ( + + + +
+ {gcalInput.start.format('MMM D, h:mm A')} - {gcalInput.end.format('h:mm A')} + {gcalInput.invitees.length > 0 && + `, ${gcalInput.invitees.length} ${plural(gcalInput.invitees.length, 'invitee')}`} +
+ +
+ + + +
+ ) : ( +
+ + + {gcal?.auth ? 'Add Calendar Event' : 'Connect to Google Calendar'} + +
+ ))} + {withRecurrence && ( + + + +
+ {rrule + ? toHumanReadable(rrule, {useShortNames: true, shortDayNameAfter: 1}) + : 'Does not restart'} +
+ +
+ + + +
+ )} + + Cancel + + Create Meeting + + +
+ ) +} diff --git a/packages/client/modules/userDashboard/components/GcalModal/GcalSettings.tsx b/packages/client/modules/userDashboard/components/GcalModal/GcalSettings.tsx new file mode 100644 index 00000000000..de4c3c70be1 --- /dev/null +++ b/packages/client/modules/userDashboard/components/GcalModal/GcalSettings.tsx @@ -0,0 +1,164 @@ +import styled from '@emotion/styled' +import graphql from 'babel-plugin-relay/macro' +import dayjs from 'dayjs' +import React, {useEffect, useState} from 'react' +import DateTimePickers from './DateTimePickers' +import Checkbox from '../../../../components/Checkbox' +import {GcalModal_team$key} from '../../../../__generated__/GcalModal_team.graphql' +import BasicTextArea from '../../../../components/InputField/BasicTextArea' +import parseEmailAddressList from '../../../../utils/parseEmailAddressList' +import {useFragment} from 'react-relay' +import StyledError from '../../../../components/StyledError' +import VideoConferencing from './VideoConferencing' +import {GcalVideoTypeEnum} from '../../../../__generated__/StartTeamPromptMutation.graphql' + +const ErrorMessage = styled(StyledError)({ + textAlign: 'left', + paddingBottom: 8 +}) + +export interface GcalEventInput { + start: dayjs.Dayjs + end: dayjs.Dayjs + invitees: string[] + videoType: GcalVideoTypeEnum | null +} + +interface Props { + teamRef: GcalModal_team$key + onSettingsChanged: (input: GcalEventInput) => void + settings: GcalEventInput +} + +const GcalSettings = (props: Props) => { + const {teamRef, settings, onSettingsChanged} = props + const {invitees, start, end, videoType} = settings + const [rawInvitees, setRawInvitees] = useState(invitees.join(', ')) + const [inviteAll, setInviteAll] = useState(true) + + const [inviteError, setInviteError] = useState(null) + + const setInvitees = (invitees: string[]) => { + onSettingsChanged({...settings, invitees}) + } + const setVideoType = (videoType: GcalVideoTypeEnum | null) => { + onSettingsChanged({...settings, videoType}) + } + + const setStart = (start: dayjs.Dayjs) => { + onSettingsChanged({...settings, start}) + } + const setEnd = (end: dayjs.Dayjs) => { + onSettingsChanged({...settings, end}) + } + + const team = useFragment( + graphql` + fragment GcalSettings_team on Team { + name + teamMembers { + email + isSelf + } + } + `, + teamRef + ) + const {teamMembers, name: teamName} = team ?? {} + const teamMemberEmails = teamMembers?.filter(({isSelf}) => !isSelf).map(({email}) => email) + const hasTeamMemberEmails = teamMemberEmails?.length > 0 + + const onInvitesChange = (e: React.ChangeEvent) => { + const nextValue = e.target.value + if (rawInvitees === nextValue) return + const {parsedInvitees, invalidEmailExists} = parseEmailAddressList(nextValue) + const allInvitees = parsedInvitees + ? (parsedInvitees.map((invitee: any) => invitee.address) as string[]) + : [] + const uniqueInvitees = Array.from(new Set(allInvitees)) + if (invalidEmailExists) { + const lastValidEmail = uniqueInvitees[uniqueInvitees.length - 1] + lastValidEmail + ? setInviteError(`Invalid email(s) after ${lastValidEmail}`) + : setInviteError(`Invalid email(s)`) + } else { + setInviteError(null) + } + setRawInvitees(nextValue) + setInvitees(uniqueInvitees) + } + + const addAllTeamMembers = () => { + const {parsedInvitees} = parseEmailAddressList(rawInvitees) + const currentInvitees = parsedInvitees + ? (parsedInvitees as emailAddresses.ParsedMailbox[]).map((invitee) => invitee.address) + : [] + const emailsToAdd = teamMemberEmails.filter((email) => !currentInvitees.includes(email)) + const lastInvitee = currentInvitees[currentInvitees.length - 1] + const formattedCurrentInvitees = + currentInvitees.length && lastInvitee && !lastInvitee.endsWith(',') + ? `${currentInvitees.join(', ')}, ` + : currentInvitees.join(', ') + setRawInvitees(`${formattedCurrentInvitees}${emailsToAdd.join(', ')}`) + setInvitees([...currentInvitees, ...emailsToAdd]) + } + + useEffect(() => { + if (hasTeamMemberEmails) { + addAllTeamMembers() + } + }, [hasTeamMemberEmails]) + + const removeAllTeamMembers = () => { + const {parsedInvitees} = parseEmailAddressList(rawInvitees) + const currentInvitees = parsedInvitees + ? (parsedInvitees.map((invitee: any) => invitee.address) as string[]) + : [] + const remainingInvitees = currentInvitees.filter((email) => !teamMemberEmails.includes(email)) + setRawInvitees(remainingInvitees.join(', ')) + setInvitees(remainingInvitees) + } + + const handleToggleInviteAll = () => { + if (!inviteAll) { + addAllTeamMembers() + } else { + removeAllTeamMembers() + } + setInviteAll((inviteAll) => !inviteAll) + } + + const handleChangeVideoType = (option: GcalVideoTypeEnum | null) => { + setVideoType(option) + } + + return ( +
+
+ +
+ +

{'Invite others to your Google Calendar event'}

+ + {hasTeamMemberEmails && ( +
+ + +
+ )} + {inviteError && {inviteError}} +
+ ) +} + +export default GcalSettings diff --git a/packages/client/package.json b/packages/client/package.json index 81691d805d0..757226203fe 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -75,15 +75,16 @@ "@mui/icons-material": "^5.8.4", "@mui/material": "^5.9.2", "@mui/x-date-pickers": "^6.3.1", + "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dropdown-menu": "^2.0.4", "@radix-ui/react-radio-group": "^1.1.2", "@radix-ui/react-scroll-area": "^1.0.3", "@radix-ui/react-select": "^1.2.2", - "@radix-ui/react-tooltip": "^1.0.7", "@radix-ui/react-slot": "^1.0.2", "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-alert-dialog": "1.0.5", + "@radix-ui/react-tooltip": "^1.0.7", "@sentry/browser": "^5.8.0", "@stripe/react-stripe-js": "^1.16.5", "@stripe/stripe-js": "^1.47.0", diff --git a/yarn.lock b/yarn.lock index ffa47f0a625..38410b42173 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5736,6 +5736,21 @@ "@radix-ui/react-use-callback-ref" "1.0.1" "@radix-ui/react-use-layout-effect" "1.0.1" +"@radix-ui/react-collapsible@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@radix-ui/react-collapsible/-/react-collapsible-1.0.3.tgz#df0e22e7a025439f13f62d4e4a9e92c4a0df5b81" + integrity sha512-UBmVDkmR6IvDsloHVN+3rtx4Mi5TFvylYXpluuv0f37dtaz3H99bp8No0LGXRigVpl3UAT4l9j6bIchh42S/Gg== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-use-layout-effect" "1.0.1" + "@radix-ui/react-collection@1.0.2": version "1.0.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-collection/-/react-collection-1.0.2.tgz#d50da00bfa2ac14585319efdbbb081d4c5a29a97" @@ -11285,7 +11300,6 @@ draft-js-utils@^1.4.0: "draft-js@https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6": version "0.10.5" - uid "025fddba56f21aaf3383aee778e0b17025c9a7bc" resolved "https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6#025fddba56f21aaf3383aee778e0b17025c9a7bc" dependencies: fbjs "^0.8.15" From 1ed279673fdaa7a21a995677c7e2b0e6a7c41f96 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Wed, 13 Mar 2024 13:20:17 +0100 Subject: [PATCH 062/529] feat: Release MS Teams integration (#9527) --- packages/client/modules/demo/DemoUser.ts | 3 +-- .../teamDashboard/components/ProviderList/ProviderList.tsx | 6 ++---- .../graphql/public/typeDefs/updateFeatureFlag.graphql | 2 -- packages/server/graphql/public/types/UserFeatureFlags.ts | 1 - packages/server/graphql/types/UserFlagEnum.ts | 1 - 5 files changed, 3 insertions(+), 10 deletions(-) diff --git a/packages/client/modules/demo/DemoUser.ts b/packages/client/modules/demo/DemoUser.ts index 65a6761e8cd..d875eb2bbcb 100644 --- a/packages/client/modules/demo/DemoUser.ts +++ b/packages/client/modules/demo/DemoUser.ts @@ -8,8 +8,7 @@ export default class DemoUser { createdAt = new Date().toJSON() email: string featureFlags = { - azureDevOps: false, - msTeams: false + azureDevOps: false } facilitatorUserId: string facilitatorName: string diff --git a/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx b/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx index a10e921a271..d8eb7a84485 100644 --- a/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx +++ b/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx @@ -109,7 +109,6 @@ const query = graphql` } featureFlags { azureDevOps - msTeams } } } @@ -120,7 +119,7 @@ const ProviderList = (props: Props) => { const data = usePreloadedQuery(query, queryRef) const {viewer} = data const { - featureFlags: {azureDevOps: allowAzureDevOps, msTeams: allowMSTeams} + featureFlags: {azureDevOps: allowAzureDevOps} } = viewer const integrations = viewer.teamMember?.integrations @@ -166,8 +165,7 @@ const ProviderList = (props: Props) => { { name: 'MS Teams', connected: !!integrations?.msTeams.auth, - component: , - hidden: !allowMSTeams + component: }, { name: 'Gcal Integration', diff --git a/packages/server/graphql/public/typeDefs/updateFeatureFlag.graphql b/packages/server/graphql/public/typeDefs/updateFeatureFlag.graphql index fe47e6f40f4..ba9506cf4cd 100644 --- a/packages/server/graphql/public/typeDefs/updateFeatureFlag.graphql +++ b/packages/server/graphql/public/typeDefs/updateFeatureFlag.graphql @@ -4,7 +4,6 @@ A flag to give an individual user super powers enum UserFlagEnum { standups azureDevOps - msTeams insights recurrence noAISummary @@ -21,7 +20,6 @@ The types of flags that give an individual user super powers type UserFeatureFlags { standups: Boolean! azureDevOps: Boolean! - msTeams: Boolean! insights: Boolean! recurrence: Boolean! noAISummary: Boolean! diff --git a/packages/server/graphql/public/types/UserFeatureFlags.ts b/packages/server/graphql/public/types/UserFeatureFlags.ts index 84bdbd8f5ec..806a33b7c8a 100644 --- a/packages/server/graphql/public/types/UserFeatureFlags.ts +++ b/packages/server/graphql/public/types/UserFeatureFlags.ts @@ -2,7 +2,6 @@ import {UserFeatureFlagsResolvers} from '../resolverTypes' const UserFeatureFlags: UserFeatureFlagsResolvers = { azureDevOps: ({azureDevOps}) => !!azureDevOps, - msTeams: ({msTeams}) => !!msTeams, insights: ({insights}) => !!insights, noAISummary: ({noAISummary}) => !!noAISummary, noMeetingHistoryLimit: ({noMeetingHistoryLimit}) => !!noMeetingHistoryLimit, diff --git a/packages/server/graphql/types/UserFlagEnum.ts b/packages/server/graphql/types/UserFlagEnum.ts index 94827237ab3..09c6c59140a 100644 --- a/packages/server/graphql/types/UserFlagEnum.ts +++ b/packages/server/graphql/types/UserFlagEnum.ts @@ -5,7 +5,6 @@ const UserFlagEnum = new GraphQLEnumType({ description: 'A flag to give an individual user super powers', values: { azureDevOps: {}, - msTeams: {}, noAISummary: {}, noMeetingHistoryLimit: {}, adHocTeams: {}, From 470e0179a21f088eaaec8784f7d84f6833948a26 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 13 Mar 2024 18:33:59 +0100 Subject: [PATCH 063/529] chore(release): release v7.22.0 (#9513) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 25 +++++++++++++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 36 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3107bf4b240..16cdb1218de 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.21.0" + ".": "7.22.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index c613055fc36..9a8e8c93017 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.22.0](https://github.com/ParabolInc/parabol/compare/v7.21.0...v7.22.0) (2024-03-13) + + +### Added + +* Add team sections to the Custom category in activity library ([#9511](https://github.com/ParabolInc/parabol/issues/9511)) ([2338414](https://github.com/ParabolInc/parabol/commit/233841498bf997343f3d94e443104973078bf736)) +* added additinal check-in questions ([10c6f69](https://github.com/ParabolInc/parabol/commit/10c6f6932008fcca434d1b6a73c288aea88768d5)) +* managing teams ([#9285](https://github.com/ParabolInc/parabol/issues/9285)) ([f351cf9](https://github.com/ParabolInc/parabol/commit/f351cf9f5a894fe019f331cc0ec6f012a0779c42)) +* Recurring GCal event dialog ([#9506](https://github.com/ParabolInc/parabol/issues/9506)) ([fc4429c](https://github.com/ParabolInc/parabol/commit/fc4429c85dd9610d3fdadf83882c2dbdd88f424f)) +* Release MS Teams integration ([#9527](https://github.com/ParabolInc/parabol/issues/9527)) ([1ed2796](https://github.com/ParabolInc/parabol/commit/1ed279673fdaa7a21a995677c7e2b0e6a7c41f96)) + + +### Fixed + +* Korean greeting corrected ([#9525](https://github.com/ParabolInc/parabol/issues/9525)) ([10c6f69](https://github.com/ParabolInc/parabol/commit/10c6f6932008fcca434d1b6a73c288aea88768d5)) +* Make hasGCalError optional ([#9526](https://github.com/ParabolInc/parabol/issues/9526)) ([9350b93](https://github.com/ParabolInc/parabol/commit/9350b93b7a2a6f48e0af712cc0a6edbb8395004c)) +* recreate lockfile ([#9516](https://github.com/ParabolInc/parabol/issues/9516)) ([af47966](https://github.com/ParabolInc/parabol/commit/af47966d6c07b295536327a3ee4d6bac1fece57b)) + + +### Changed + +* **ci:** add capability to manually generate Docker Images ([#9524](https://github.com/ParabolInc/parabol/issues/9524)) ([88bf97f](https://github.com/ParabolInc/parabol/commit/88bf97f6ff3e820d49a24e9a8a8cf4dbab46b22c)) +* **gh-actions:** reporting status to Slack if test or build GH Actions fail ([#9512](https://github.com/ParabolInc/parabol/issues/9512)) ([e7539d1](https://github.com/ParabolInc/parabol/commit/e7539d152ccfb5fbe12bdcb9b5ce3cc64fd2955c)) +* Remove Add Activity button from discussions ([#9528](https://github.com/ParabolInc/parabol/issues/9528)) ([37bd20c](https://github.com/ParabolInc/parabol/commit/37bd20cf8e073d353e3b3dffb5f3037c199adf67)) + ## [7.21.0](https://github.com/ParabolInc/parabol/compare/v7.20.0...v7.21.0) (2024-03-06) diff --git a/package.json b/package.json index dc0564d2e8c..d9ee48e96e0 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.21.0", + "version": "7.22.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index ab2130ca1fe..1cffb8860a9 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.21.0", + "version": "7.22.0", "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.21.0" + "parabol-server": "7.22.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 757226203fe..407e0cf4d90 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.21.0", + "version": "7.22.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index db8f577b591..328c41ca21a 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.21.0", + "version": "7.22.0", "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.21.0", - "parabol-server": "7.21.0", + "parabol-client": "7.22.0", + "parabol-server": "7.22.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index b1a78c0c54a..60ffcae17c9 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.21.0", + "version": "7.22.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 8fcf641b27d..ca2e5ca1895 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.21.0", + "version": "7.22.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.21.0", + "parabol-client": "7.22.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From c417b453bc032a6bf813f477c9f1857205ffc767 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Thu, 14 Mar 2024 13:17:55 +0100 Subject: [PATCH 064/529] lint exceptions --- .../ActivityLibrary/ActivityDetails/ActivityDetails.tsx | 1 + .../client/modules/summary/components/NewMeetingSummary.tsx | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx b/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx index 9fcca27d12c..7f1706645c2 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx @@ -74,6 +74,7 @@ const ActivityDetails = (props: Props) => { if (!activity) { return } + // eslint-disable react-hooks/rules-of-hooks -- return above violates these rules, but is just a safeguard and not normal usage useEffect(() => { SendClientSideEvent(atmosphere, 'Viewed Template', { meetingType: activity.type, diff --git a/packages/client/modules/summary/components/NewMeetingSummary.tsx b/packages/client/modules/summary/components/NewMeetingSummary.tsx index 3e891548124..11e11406a67 100644 --- a/packages/client/modules/summary/components/NewMeetingSummary.tsx +++ b/packages/client/modules/summary/components/NewMeetingSummary.tsx @@ -60,10 +60,10 @@ const NewMeetingSummary = (props: Props) => { if (!newMeeting) { return null } + // eslint-disable react-hooks/rules-of-hooks -- return above violates these rules, but is just a safeguard and not normal usage const {id: meetingId, name: meetingName, team} = newMeeting const {id: teamId, name: teamName} = team const title = `${meetingName} ${MEETING_SUMMARY_LABEL} | ${teamName}` - // eslint-disable-next-line react-hooks/rules-of-hooks useDocumentTitle(title, 'Summary') const meetingUrl = makeHref(`/meet/${meetingId}`) const teamDashUrl = `/team/${teamId}/tasks` From efc0dc9d090f2bcd03d5abedc04a5507addb2f6e Mon Sep 17 00:00:00 2001 From: Jordan Husney Date: Thu, 14 Mar 2024 13:14:16 -0700 Subject: [PATCH 065/529] chore: migrate FailedAuthRequest to pg (#9500) --- packages/server/__tests__/globalSetup.ts | 2 +- .../database/types/FailedAuthRequest.ts | 7 +-- .../graphql/mutations/helpers/attemptLogin.ts | 52 ++++++++++++------- .../server/graphql/mutations/resetPassword.ts | 4 +- .../1709927369000_addFailedAuthRequest.ts | 28 ++++++++++ yarn.lock | 1 + 6 files changed, 66 insertions(+), 28 deletions(-) create mode 100644 packages/server/postgres/migrations/1709927369000_addFailedAuthRequest.ts diff --git a/packages/server/__tests__/globalSetup.ts b/packages/server/__tests__/globalSetup.ts index b4cba0d381f..69df2a89fdd 100644 --- a/packages/server/__tests__/globalSetup.ts +++ b/packages/server/__tests__/globalSetup.ts @@ -9,7 +9,7 @@ async function setup() { // so the safety checks will eventually fail if run too much await Promise.all([ - r.table('FailedAuthRequest').delete().run(), + pg.deleteFrom('FailedAuthRequest').execute(), r.table('PasswordResetRequest').delete().run(), pg.deleteFrom('SAMLDomain').where('domain', '=', 'example.com').execute() ]) diff --git a/packages/server/database/types/FailedAuthRequest.ts b/packages/server/database/types/FailedAuthRequest.ts index 7f6d165d9a2..fe30f30aafe 100644 --- a/packages/server/database/types/FailedAuthRequest.ts +++ b/packages/server/database/types/FailedAuthRequest.ts @@ -1,20 +1,15 @@ -import generateUID from '../../generateUID' - interface Input { - id?: string ip: string email: string time?: Date } export default class FailedAuthRequest { - id: string ip: string email: string time: Date constructor(input: Input) { - const {id, email, ip, time} = input - this.id = id ?? generateUID() + const {email, ip, time} = input this.email = email this.ip = ip this.time = time ?? new Date() diff --git a/packages/server/graphql/mutations/helpers/attemptLogin.ts b/packages/server/graphql/mutations/helpers/attemptLogin.ts index 0952c6bd26a..9afe4f20641 100644 --- a/packages/server/graphql/mutations/helpers/attemptLogin.ts +++ b/packages/server/graphql/mutations/helpers/attemptLogin.ts @@ -1,44 +1,56 @@ import bcrypt from 'bcryptjs' +import {sql} from 'kysely' import ms from 'ms' import {AuthenticationError, Threshold} from 'parabol-client/types/constEnums' import sleep from 'parabol-client/utils/sleep' import {AuthIdentityTypeEnum} from '../../../../client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' -import {RDatum} from '../../../database/stricterR' +import getKysely from '../../../postgres/getKysely' import AuthIdentityLocal from '../../../database/types/AuthIdentityLocal' import AuthToken from '../../../database/types/AuthToken' import FailedAuthRequest from '../../../database/types/FailedAuthRequest' import {getUserByEmail} from '../../../postgres/queries/getUsersByEmails' const logFailedLogin = async (ip: string, email: string) => { - const r = await getRethink() + const pg = getKysely() if (ip) { const failedAuthRequest = new FailedAuthRequest({ip, email}) - await r.table('FailedAuthRequest').insert(failedAuthRequest).run() + await pg.insertInto('FailedAuthRequest').values(failedAuthRequest).execute() } } const attemptLogin = async (denormEmail: string, password: string, ip = '') => { - const r = await getRethink() + const pg = getKysely() const yesterday = new Date(Date.now() - ms('1d')) const email = denormEmail.toLowerCase().trim() const existingUser = await getUserByEmail(email) - const {failOnAccount, failOnTime} = await r({ - failOnAccount: r - .table('FailedAuthRequest') - .getAll(ip, {index: 'ip'}) - .filter({email}) - .filter((row: RDatum) => row('time').ge(yesterday)) - .count() - .ge(Threshold.MAX_ACCOUNT_PASSWORD_ATTEMPTS) as unknown as boolean, - failOnTime: r - .table('FailedAuthRequest') - .getAll(ip, {index: 'ip'}) - .filter((row: RDatum) => row('time').ge(yesterday)) - .count() - .ge(Threshold.MAX_DAILY_PASSWORD_ATTEMPTS) as unknown as boolean - }).run() + const {failOnAccount, failOnTime} = (await pg + .with('byAccount', (qb) => + qb + .selectFrom('FailedAuthRequest') + .select((eb) => eb.fn.count('id').as('attempts')) + .where('ip', '=', ip) + .where('email', '=', email) + .where('time', '>=', yesterday) + ) + .with('byTime', (qb) => + qb + .selectFrom('FailedAuthRequest') + .select((eb) => eb.fn.count('id').as('attempts')) + .where('ip', '=', ip) + .where('time', '>=', yesterday) + ) + .selectFrom(['byAccount', 'byTime']) + .select(({ref}) => [ + sql`${ref('byAccount.attempts')} >= ${Threshold.MAX_ACCOUNT_PASSWORD_ATTEMPTS}`.as( + 'failOnAccount' + ), + sql`${ref('byTime.attempts')} >= ${Threshold.MAX_DAILY_PASSWORD_ATTEMPTS}`.as( + 'failOnTime' + ) + ]) + .executeTakeFirst()) as {failOnAccount: boolean; failOnTime: boolean} + if (failOnAccount || failOnTime) { await sleep(1000) // silently fail to trick security researchers diff --git a/packages/server/graphql/mutations/resetPassword.ts b/packages/server/graphql/mutations/resetPassword.ts index 8d4f86bc6ae..bc221f06279 100644 --- a/packages/server/graphql/mutations/resetPassword.ts +++ b/packages/server/graphql/mutations/resetPassword.ts @@ -2,6 +2,7 @@ import bcrypt from 'bcryptjs' import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {Security, Threshold} from 'parabol-client/types/constEnums' import {AuthIdentityTypeEnum} from '../../../client/types/constEnums' +import getKysely from '../../postgres/getKysely' import getRethink from '../../database/rethinkDriver' import AuthIdentityLocal from '../../database/types/AuthIdentityLocal' import AuthToken from '../../database/types/AuthToken' @@ -37,6 +38,7 @@ const resetPassword = { if (process.env.AUTH_INTERNAL_DISABLED === 'true') { return {error: {message: 'Resetting password is disabled'}} } + const pg = getKysely() const r = await getRethink() const resetRequest = (await r .table('PasswordResetRequest') @@ -73,7 +75,7 @@ const resetPassword = { localIdentity.isEmailVerified = true await Promise.all([ updateUser({identities}, userId), - r.table('FailedAuthRequest').getAll(email, {index: 'email'}).delete().run() + pg.deleteFrom('FailedAuthRequest').where('email', '=', email).execute() ]) context.authToken = new AuthToken({sub: userId, tms, rol}) await blacklistJWT(userId, context.authToken.iat, context.socketId) diff --git a/packages/server/postgres/migrations/1709927369000_addFailedAuthRequest.ts b/packages/server/postgres/migrations/1709927369000_addFailedAuthRequest.ts new file mode 100644 index 00000000000..b58461c7b94 --- /dev/null +++ b/packages/server/postgres/migrations/1709927369000_addFailedAuthRequest.ts @@ -0,0 +1,28 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + CREATE TABLE "FailedAuthRequest" ( + "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + "email" "citext" NOT NULL, + "ip" "inet" NOT NULL, + "time" TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL + ); + + CREATE INDEX IF NOT EXISTS "idx_FailedAuthRequest_email" ON "FailedAuthRequest"("email"); + CREATE INDEX IF NOT EXISTS "idx_FailedAuthRequest_ip" ON "FailedAuthRequest"("ip"); + `) + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE IF EXISTS "FailedAuthRequest"; + `) + await client.end() +} diff --git a/yarn.lock b/yarn.lock index 38410b42173..74d28b2fb23 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11300,6 +11300,7 @@ draft-js-utils@^1.4.0: "draft-js@https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6": version "0.10.5" + uid "025fddba56f21aaf3383aee778e0b17025c9a7bc" resolved "https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6#025fddba56f21aaf3383aee778e0b17025c9a7bc" dependencies: fbjs "^0.8.15" From 5c39fde04c6e8b5c31d70258d4ef7f548aa28298 Mon Sep 17 00:00:00 2001 From: Jordan Husney Date: Thu, 14 Mar 2024 13:14:41 -0700 Subject: [PATCH 066/529] chore: migrate ScheduledJob from rethinkdb to pg (#9490) --- .../helpers/removeTeamsLimitObjects.ts | 17 +++--- .../server/database/types/ScheduledJob.ts | 3 - .../database/types/scheduleTeamLimitsJobs.ts | 20 ++++--- .../mutations/helpers/removeScheduledJobs.ts | 19 ++++-- .../server/graphql/mutations/setStageTimer.ts | 10 ++-- .../private/mutations/runScheduledJobs.ts | 22 ++++--- .../1709927822000_addScheduledJob.ts | 38 ++++++++++++ .../1709927835000_moveScheduledJob.ts | 59 +++++++++++++++++++ 8 files changed, 151 insertions(+), 37 deletions(-) create mode 100644 packages/server/postgres/migrations/1709927822000_addScheduledJob.ts create mode 100644 packages/server/postgres/migrations/1709927835000_moveScheduledJob.ts diff --git a/packages/server/billing/helpers/removeTeamsLimitObjects.ts b/packages/server/billing/helpers/removeTeamsLimitObjects.ts index 2f38ee51122..2f112a11077 100644 --- a/packages/server/billing/helpers/removeTeamsLimitObjects.ts +++ b/packages/server/billing/helpers/removeTeamsLimitObjects.ts @@ -1,20 +1,21 @@ +import getKysely from '../../postgres/getKysely' import {r} from 'rethinkdb-ts' import {RValue} from '../../database/stricterR' import {DataLoaderWorker} from '../../graphql/graphql' import updateNotification from '../../graphql/public/mutations/helpers/updateNotification' const removeTeamsLimitObjects = async (orgId: string, dataLoader: DataLoaderWorker) => { - const removeJobTypes = ['LOCK_ORGANIZATION', 'WARN_ORGANIZATION'] - const removeNotificationTypes = ['TEAMS_LIMIT_EXCEEDED', 'TEAMS_LIMIT_REMINDER'] + const removeJobTypes = ['LOCK_ORGANIZATION', 'WARN_ORGANIZATION'] as const + const removeNotificationTypes = ['TEAMS_LIMIT_EXCEEDED', 'TEAMS_LIMIT_REMINDER'] as const + const pg = getKysely() // Remove team limits jobs and existing notifications const [, updateNotificationsChanges] = await Promise.all([ - r - .table('ScheduledJob') - .getAll(orgId, {index: 'orgId'}) - .filter((row: RValue) => r.expr(removeJobTypes).contains(row('type'))) - .delete() - .run(), + pg + .deleteFrom('ScheduledJob') + .where('orgId', '=', orgId) + .where('type', 'in', removeJobTypes) + .execute(), r .table('Notification') .getAll(orgId, {index: 'orgId'}) diff --git a/packages/server/database/types/ScheduledJob.ts b/packages/server/database/types/ScheduledJob.ts index 06071851a89..ad42dce95a6 100644 --- a/packages/server/database/types/ScheduledJob.ts +++ b/packages/server/database/types/ScheduledJob.ts @@ -1,11 +1,8 @@ -import generateUID from '../../generateUID' - export type ScheduledJobType = | 'MEETING_STAGE_TIME_LIMIT_END' | 'LOCK_ORGANIZATION' | 'WARN_ORGANIZATION' export default abstract class ScheduledJob { - id = generateUID() protected constructor(public type: ScheduledJobType, public runAt: Date) {} } diff --git a/packages/server/database/types/scheduleTeamLimitsJobs.ts b/packages/server/database/types/scheduleTeamLimitsJobs.ts index 368d75edc6a..427d328e608 100644 --- a/packages/server/database/types/scheduleTeamLimitsJobs.ts +++ b/packages/server/database/types/scheduleTeamLimitsJobs.ts @@ -1,21 +1,23 @@ import ms from 'ms' -import {r} from 'rethinkdb-ts' +import getKysely from '../../postgres/getKysely' import {Threshold} from '../../../client/types/constEnums' import ScheduledTeamLimitsJob from './ScheduledTeamLimitsJob' const scheduleTeamLimitsJobs = async (scheduledLockAt: Date, orgId: string) => { - const scheduledLock = r - .table('ScheduledJob') - .insert(new ScheduledTeamLimitsJob(scheduledLockAt, orgId, 'LOCK_ORGANIZATION')) - .run() + const pg = getKysely() + const scheduledLock = pg + .insertInto('ScheduledJob') + .values(new ScheduledTeamLimitsJob(scheduledLockAt, orgId, 'LOCK_ORGANIZATION')) + .execute() const oneWeekBeforeLock = new Date( scheduledLockAt.getTime() - ms(`${Threshold.FINAL_WARNING_DAYS_BEFORE_LOCK}d`) ) - const scheduledWarn = r - .table('ScheduledJob') - .insert(new ScheduledTeamLimitsJob(oneWeekBeforeLock, orgId, 'WARN_ORGANIZATION')) - .run() + + const scheduledWarn = pg + .insertInto('ScheduledJob') + .values(new ScheduledTeamLimitsJob(oneWeekBeforeLock, orgId, 'WARN_ORGANIZATION')) + .execute() await Promise.all([scheduledLock, scheduledWarn]) } diff --git a/packages/server/graphql/mutations/helpers/removeScheduledJobs.ts b/packages/server/graphql/mutations/helpers/removeScheduledJobs.ts index cd6a5eef813..b4931f9ca3b 100644 --- a/packages/server/graphql/mutations/helpers/removeScheduledJobs.ts +++ b/packages/server/graphql/mutations/helpers/removeScheduledJobs.ts @@ -1,8 +1,19 @@ -import getRethink from '../../../database/rethinkDriver' +import {Updateable} from 'kysely' +import {DB} from '../../../postgres/pg' +import getKysely from '../../../postgres/getKysely' -const removeScheduledJobs = async (runAt: Date, filter: {[key: string]: any}) => { - const r = await getRethink() - return r.table('ScheduledJob').getAll(runAt, {index: 'runAt'}).filter(filter).delete().run() +type FilterType = Omit, 'runAt'> + +const removeScheduledJobs = async (runAt: Date, filter?: FilterType) => { + const pg = getKysely() + let query = pg.deleteFrom('ScheduledJob').where('runAt', '=', runAt) + if (filter) { + Object.keys(filter).forEach((key) => { + const value = filter[key as keyof FilterType] + if (value) query = query.where(key as keyof FilterType, '=', value) + }) + } + return query.execute() } export default removeScheduledJobs diff --git a/packages/server/graphql/mutations/setStageTimer.ts b/packages/server/graphql/mutations/setStageTimer.ts index 81d262d220e..40901c6baa0 100644 --- a/packages/server/graphql/mutations/setStageTimer.ts +++ b/packages/server/graphql/mutations/setStageTimer.ts @@ -1,6 +1,7 @@ import {GraphQLFloat, GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import findStageById from 'parabol-client/utils/meetings/findStageById' +import getKysely from '../../postgres/getKysely' import getRethink from '../../database/rethinkDriver' import ScheduledJobMeetingStageTimeLimit from '../../database/types/ScheduledJobMetingStageTimeLimit' import {getUserId, isTeamMember} from '../../utils/authorization' @@ -90,12 +91,13 @@ export default { ? new Date(now.getTime() + timeRemaining - AVG_PING) : newScheduledEndTime } else { + const pg = getKysely() stage.isAsync = true stage.scheduledEndTime = newScheduledEndTime - await r - .table('ScheduledJob') - .insert(new ScheduledJobMeetingStageTimeLimit(newScheduledEndTime, meetingId)) - .run() + await pg + .insertInto('ScheduledJob') + .values(new ScheduledJobMeetingStageTimeLimit(newScheduledEndTime, meetingId)) + .execute() IntegrationNotifier.startTimeLimit(dataLoader, newScheduledEndTime, meetingId, teamId) } } else { diff --git a/packages/server/graphql/private/mutations/runScheduledJobs.ts b/packages/server/graphql/private/mutations/runScheduledJobs.ts index faee38517ec..aebb1e5eafe 100644 --- a/packages/server/graphql/private/mutations/runScheduledJobs.ts +++ b/packages/server/graphql/private/mutations/runScheduledJobs.ts @@ -1,4 +1,7 @@ +import {Selectable} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import getKysely from '../../../postgres/getKysely' +import {DB} from '../../../postgres/pg' import getRethink from '../../../database/rethinkDriver' import NotificationMeetingStageTimeLimitEnd from '../../../database/types/NotificationMeetingStageTimeLimitEnd' import processTeamsLimitsJob from '../../../database/types/processTeamsLimitsJob' @@ -39,11 +42,11 @@ const processMeetingStageTimeLimits = async ( export type ScheduledJobUnion = ScheduledJobMeetingStageTimeLimit | ScheduledTeamLimitsJob -const processJob = async (job: ScheduledJobUnion, dataLoader: DataLoaderWorker) => { - const r = await getRethink() - const res = await r.table('ScheduledJob').get(job.id).delete().run() +const processJob = async (job: Selectable, dataLoader: DataLoaderWorker) => { + const pg = getKysely() + const res = await pg.deleteFrom('ScheduledJob').where('id', '=', job.id).executeTakeFirst() // prevent duplicates. after this point, we assume the job finishes to completion (ignores server crashes, etc.) - if (res.deleted !== 1) return + if (res.numDeletedRows !== BigInt(1)) return if (job.type === 'MEETING_STAGE_TIME_LIMIT_END') { return processMeetingStageTimeLimits( @@ -60,15 +63,16 @@ const runScheduledJobs: MutationResolvers['runScheduledJobs'] = async ( {seconds}, {dataLoader} ) => { - const r = await getRethink() + const pg = getKysely() const now = new Date() // RESOLUTION const before = new Date(now.getTime() + seconds * 1000) - const upcomingJobs = (await r - .table('ScheduledJob') - .between(r.minval, before, {index: 'runAt'}) - .run()) as ScheduledJobUnion[] + const upcomingJobs = await pg + .selectFrom('ScheduledJob') + .selectAll() + .where('runAt', '<', before) + .execute() upcomingJobs.forEach((job) => { const {runAt} = job diff --git a/packages/server/postgres/migrations/1709927822000_addScheduledJob.ts b/packages/server/postgres/migrations/1709927822000_addScheduledJob.ts new file mode 100644 index 00000000000..dffd9217014 --- /dev/null +++ b/packages/server/postgres/migrations/1709927822000_addScheduledJob.ts @@ -0,0 +1,38 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'ScheduledJobTypeEnum') THEN + EXECUTE 'CREATE TYPE "ScheduledJobTypeEnum" AS ENUM (''MEETING_STAGE_TIME_LIMIT_END'', ''LOCK_ORGANIZATION'', ''WARN_ORGANIZATION'')'; + END IF; + END $$; + + CREATE TABLE "ScheduledJob" ( + "id" SERIAL PRIMARY KEY, + "runAt" TIMESTAMP WITH TIME ZONE DEFAULT NOW() NOT NULL, + "type" "ScheduledJobTypeEnum" NOT NULL, + "orgId" VARCHAR(100), + "meetingId" VARCHAR(100) + ); + + CREATE INDEX IF NOT EXISTS "idx_ScheduledJob_orgId" ON "ScheduledJob"("orgId"); + CREATE INDEX IF NOT EXISTS "idx_ScheduledJob_runAt" ON "ScheduledJob"("runAt"); + CREATE INDEX IF NOT EXISTS "idx_ScheduledJob_type" ON "ScheduledJob"("type"); + `) + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE IF EXISTS "ScheduledJob"; + DROP TYPE IF EXISTS "ScheduledJobTypeEnum"; + `) + await client.end() +} diff --git a/packages/server/postgres/migrations/1709927835000_moveScheduledJob.ts b/packages/server/postgres/migrations/1709927835000_moveScheduledJob.ts new file mode 100644 index 00000000000..1a1c6950d64 --- /dev/null +++ b/packages/server/postgres/migrations/1709927835000_moveScheduledJob.ts @@ -0,0 +1,59 @@ +import {FirstParam} from 'parabol-client/types/generics' +import {Client} from 'pg' +import {r} from 'rethinkdb-ts' +import getPgConfig from '../getPgConfig' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPgp from '../getPgp' + +export async function up() { + await connectRethinkDB() + const {pgp, pg} = getPgp() + const batchSize = 1000 + + const columnSet = new pgp.helpers.ColumnSet( + ['runAt', 'type', {name: 'orgId', def: null}, {name: 'meetingId', def: null}], + {table: 'ScheduledJob'} + ) + + const getNextData = async (leftBoundCursor: Date | undefined) => { + const startAt = leftBoundCursor || r.minval + const nextBatch = await r + .table('ScheduledJob') + .between(startAt, r.maxval, {index: 'runAt', leftBound: 'open'}) + .orderBy({index: 'runAt'}) + .limit(batchSize) + .run() + if (nextBatch.length === 0) return null + if (nextBatch.length < batchSize) return nextBatch + const lastItem = nextBatch.pop() + const lastMatchingRunAt = nextBatch.findLastIndex((item) => item.runAt !== lastItem!.runAt) + if (lastMatchingRunAt === -1) { + throw new Error( + 'batchSize is smaller than the number of items that share the same cursor. Increase batchSize' + ) + } + return nextBatch.slice(0, lastMatchingRunAt) + } + + await pg.tx('ScheduledJob', (task) => { + const fetchAndProcess: FirstParam = async ( + _index, + leftBoundCursor: undefined | Date + ) => { + const nextData = await getNextData(leftBoundCursor) + if (!nextData) return undefined + const insert = pgp.helpers.insert(nextData, columnSet) + await task.none(insert) + return nextData.at(-1)!.runAt + } + return task.sequence(fetchAndProcess) + }) + await r.getPoolMaster()?.drain() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(`DELETE FROM "ScheduledJob"`) + await client.end() +} From 1009edefba19b1caec0b8f9708aa468d565fc225 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 14 Mar 2024 16:40:04 -0700 Subject: [PATCH 067/529] fix: node-loader that ignores public path (#9537) Signed-off-by: Matt Krick --- scripts/webpack/prod.servers.config.js | 11 ++++-- .../webpack/utils/node-loader-private/cjs.js | 6 ++++ .../utils/node-loader-private/index.js | 36 +++++++++++++++++++ .../utils/node-loader-private/options.json | 20 +++++++++++ 4 files changed, 70 insertions(+), 3 deletions(-) create mode 100644 scripts/webpack/utils/node-loader-private/cjs.js create mode 100644 scripts/webpack/utils/node-loader-private/index.js create mode 100644 scripts/webpack/utils/node-loader-private/options.json diff --git a/scripts/webpack/prod.servers.config.js b/scripts/webpack/prod.servers.config.js index b1b914ba7c2..4f11b012db4 100644 --- a/scripts/webpack/prod.servers.config.js +++ b/scripts/webpack/prod.servers.config.js @@ -36,8 +36,12 @@ module.exports = (config) => { path.join(SERVER_ROOT, 'server.ts') ], embedder: [DOTENV, path.join(EMBEDDER_ROOT, 'embedder.ts')], - gqlExecutor: [DOTENV, path.join(GQL_ROOT, 'gqlExecutor.ts')], - preDeploy: [DOTENV, path.join(PROJECT_ROOT, 'scripts/toolboxSrc/preDeploy.ts')], + gqlExecutor: [DOTENV, INIT_PUBLIC_PATH, path.join(GQL_ROOT, 'gqlExecutor.ts')], + preDeploy: [ + DOTENV, + INIT_PUBLIC_PATH, + path.join(PROJECT_ROOT, 'scripts/toolboxSrc/preDeploy.ts') + ], pushToCDN: [DOTENV, path.join(PROJECT_ROOT, 'scripts/toolboxSrc/pushToCDN.ts')], migrate: [DOTENV, path.join(PROJECT_ROOT, 'scripts/toolboxSrc/standaloneMigrations.ts')], assignSURole: [DOTENV, path.join(PROJECT_ROOT, 'scripts/toolboxSrc/assignSURole.ts')] @@ -120,7 +124,8 @@ module.exports = (config) => { test: /\.node$/, use: [ { - loader: 'node-loader', + // use our fork of node-loader to exclude the public path from the script + loader: path.resolve(__dirname, './utils/node-loader-private/cjs.js'), options: { // sharp's bindings.gyp is hardcoded to look for libvips 2 directories up // rather than do a custom build, we just output it 2 directories down (/node/binaries) diff --git a/scripts/webpack/utils/node-loader-private/cjs.js b/scripts/webpack/utils/node-loader-private/cjs.js new file mode 100644 index 00000000000..9ad6efe69c2 --- /dev/null +++ b/scripts/webpack/utils/node-loader-private/cjs.js @@ -0,0 +1,6 @@ +"use strict"; + +const loader = require("./index"); + +module.exports = loader.default; +module.exports.raw = loader.raw; \ No newline at end of file diff --git a/scripts/webpack/utils/node-loader-private/index.js b/scripts/webpack/utils/node-loader-private/index.js new file mode 100644 index 00000000000..1bdea848355 --- /dev/null +++ b/scripts/webpack/utils/node-loader-private/index.js @@ -0,0 +1,36 @@ +"use strict"; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = loader; +exports.raw = void 0; + +var _loaderUtils = require("loader-utils"); + +var _options = _interopRequireDefault(require("./options.json")); + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +/* + MIT License http://www.opensource.org/licenses/mit-license.php + Author Tobias Koppers @sokra +*/ +function loader(content) { + const options = this.getOptions(_options.default); + const name = (0, _loaderUtils.interpolateName)(this, typeof options.name !== "undefined" ? options.name : "[contenthash].[ext]", { + context: this.rootContext, + content + }); + this.emitFile(name, content); + return ` +try { + process.dlopen(module, __dirname + require("path").sep + ${JSON.stringify(name)}${typeof options.flags !== "undefined" ? `, ${JSON.stringify(options.flags)}` : ""}); +} catch (error) { + throw new Error('node-loader:\\n' + error); +} +`; +} + +const raw = true; +exports.raw = raw; diff --git a/scripts/webpack/utils/node-loader-private/options.json b/scripts/webpack/utils/node-loader-private/options.json new file mode 100644 index 00000000000..d8322360ca9 --- /dev/null +++ b/scripts/webpack/utils/node-loader-private/options.json @@ -0,0 +1,20 @@ +{ + "title": "Node Loader options", + "type": "object", + "properties": { + "name": { + "anyOf": [ + { + "type": "string" + }, + { + "instanceof": "Function" + } + ] + }, + "flags": { + "type": "integer" + } + }, + "additionalProperties": false +} From 09302e65ce528987d4e61b48a5f903eb6f591d6d Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 14 Mar 2024 16:45:26 -0700 Subject: [PATCH 068/529] chore(release): release v7.22.1 (#9535) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 24 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 16cdb1218de..478dcbd2dfd 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.22.0" + ".": "7.22.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a8e8c93017..777d5c240fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.22.1](https://github.com/ParabolInc/parabol/compare/v7.22.0...v7.22.1) (2024-03-14) + + +### Fixed + +* node-loader that ignores public path ([#9537](https://github.com/ParabolInc/parabol/issues/9537)) ([1009ede](https://github.com/ParabolInc/parabol/commit/1009edefba19b1caec0b8f9708aa468d565fc225)) + + +### Changed + +* migrate FailedAuthRequest to pg ([#9500](https://github.com/ParabolInc/parabol/issues/9500)) ([efc0dc9](https://github.com/ParabolInc/parabol/commit/efc0dc9d090f2bcd03d5abedc04a5507addb2f6e)) +* migrate ScheduledJob from rethinkdb to pg ([#9490](https://github.com/ParabolInc/parabol/issues/9490)) ([5c39fde](https://github.com/ParabolInc/parabol/commit/5c39fde04c6e8b5c31d70258d4ef7f548aa28298)) + ## [7.22.0](https://github.com/ParabolInc/parabol/compare/v7.21.0...v7.22.0) (2024-03-13) diff --git a/package.json b/package.json index d9ee48e96e0..d9362d8031e 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.22.0", + "version": "7.22.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 1cffb8860a9..b20b12fd946 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.22.0", + "version": "7.22.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.22.0" + "parabol-server": "7.22.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index 407e0cf4d90..01aa07e3f41 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.22.0", + "version": "7.22.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 328c41ca21a..9b6a0a443f4 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.22.0", + "version": "7.22.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.22.0", - "parabol-server": "7.22.0", + "parabol-client": "7.22.1", + "parabol-server": "7.22.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 60ffcae17c9..0c43beca5f7 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.22.0", + "version": "7.22.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index ca2e5ca1895..243e73e2400 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.22.0", + "version": "7.22.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.22.0", + "parabol-client": "7.22.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From bd907a915a1848b17ff9385c70de072390f54cf5 Mon Sep 17 00:00:00 2001 From: Jordan Husney Date: Thu, 14 Mar 2024 17:02:50 -0700 Subject: [PATCH 069/529] chore: add GH Action, on Snyk PRs commit yarn.lock (#9534) --- .github/workflows/snyk-yarn-lock-commit.yml | 29 +++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/snyk-yarn-lock-commit.yml diff --git a/.github/workflows/snyk-yarn-lock-commit.yml b/.github/workflows/snyk-yarn-lock-commit.yml new file mode 100644 index 00000000000..02de198e665 --- /dev/null +++ b/.github/workflows/snyk-yarn-lock-commit.yml @@ -0,0 +1,29 @@ +name: Update Snyk PR to add yarn.lock + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + update-snyk-pr: + if: contains(github.event.pull_request.title.toLowerCase(), '[snyk]') + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + token: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, no need to create one + + - name: Install dependencies + run: yarn install + + - name: Commit yarn.lock to the PR branch + run: | + git config --global user.email "action@github.com" + git config --global user.name "GitHub Action" + git add yarn.lock + git commit -m "Update yarn.lock" || echo "No changes to commit" + git push + From 2c98ca1c71bb223d736a9d259f1d6314b8579c35 Mon Sep 17 00:00:00 2001 From: Rafael Romero Date: Mon, 18 Mar 2024 11:40:02 +0000 Subject: [PATCH 070/529] fix(snyk-ci): removed toLowerCase function as it does not exit --- .github/workflows/snyk-yarn-lock-commit.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/snyk-yarn-lock-commit.yml b/.github/workflows/snyk-yarn-lock-commit.yml index 02de198e665..f4705ec4b79 100644 --- a/.github/workflows/snyk-yarn-lock-commit.yml +++ b/.github/workflows/snyk-yarn-lock-commit.yml @@ -6,7 +6,7 @@ on: jobs: update-snyk-pr: - if: contains(github.event.pull_request.title.toLowerCase(), '[snyk]') + if: contains(github.event.pull_request.title, '[Snyk]') runs-on: ubuntu-latest steps: @@ -26,4 +26,3 @@ jobs: git add yarn.lock git commit -m "Update yarn.lock" || echo "No changes to commit" git push - From 0217e11147201759db85a1df010bc3d4d291b202 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Mon, 18 Mar 2024 15:42:01 +0100 Subject: [PATCH 071/529] fix: use base ref for migrition order check (#9542) --- .github/workflows/migration-order.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/migration-order.yml b/.github/workflows/migration-order.yml index a026ac8892f..9bce03582a5 100644 --- a/.github/workflows/migration-order.yml +++ b/.github/workflows/migration-order.yml @@ -10,7 +10,7 @@ jobs: - name: Checkout master uses: actions/checkout@v3 with: - ref: master + ref: ${{ github.base_ref }} - name: Get newest migration on master run: | From 081f7a09a94a3ce2d23816e801c745040737364f Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Mon, 18 Mar 2024 21:56:37 +0100 Subject: [PATCH 072/529] fix: Only read the first ip of the x-forwarded-for header (#9545) --- packages/server/utils/uwsGetIP.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/utils/uwsGetIP.ts b/packages/server/utils/uwsGetIP.ts index 0151cbd55f5..034b266a3c9 100644 --- a/packages/server/utils/uwsGetIP.ts +++ b/packages/server/utils/uwsGetIP.ts @@ -1,7 +1,7 @@ import {HttpRequest, HttpResponse} from 'uWebSockets.js' const uwsGetIP = (res: HttpResponse, req: HttpRequest) => { - const clientIp = req.getHeader('x-forwarded-for') + const clientIp = req.getHeader('x-forwarded-for')?.split(',')[0] if (clientIp) return clientIp // returns ipv6 e.g. '0000:0000:0000:0000:0000:ffff:ac11:0001' return Buffer.from(res.getRemoteAddressAsText()).toString() From 66b09600b9fd879696b039347bcfaf2306638bb1 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Mon, 18 Mar 2024 22:03:36 +0100 Subject: [PATCH 073/529] chore(release): release v7.22.2 (#9539) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 14 ++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 25 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 478dcbd2dfd..a5e4e701ea7 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.22.1" + ".": "7.22.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 777d5c240fa..e40f96936c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.22.2](https://github.com/ParabolInc/parabol/compare/v7.22.1...v7.22.2) (2024-03-18) + + +### Fixed + +* Only read the first ip of the x-forwarded-for header ([#9545](https://github.com/ParabolInc/parabol/issues/9545)) ([081f7a0](https://github.com/ParabolInc/parabol/commit/081f7a09a94a3ce2d23816e801c745040737364f)) +* **snyk-ci:** removed toLowerCase function as it does not exit ([2c98ca1](https://github.com/ParabolInc/parabol/commit/2c98ca1c71bb223d736a9d259f1d6314b8579c35)) +* use base ref for migrition order check ([#9542](https://github.com/ParabolInc/parabol/issues/9542)) ([0217e11](https://github.com/ParabolInc/parabol/commit/0217e11147201759db85a1df010bc3d4d291b202)) + + +### Changed + +* add GH Action, on Snyk PRs commit yarn.lock ([#9534](https://github.com/ParabolInc/parabol/issues/9534)) ([bd907a9](https://github.com/ParabolInc/parabol/commit/bd907a915a1848b17ff9385c70de072390f54cf5)) + ## [7.22.1](https://github.com/ParabolInc/parabol/compare/v7.22.0...v7.22.1) (2024-03-14) diff --git a/package.json b/package.json index d9362d8031e..d228664dd0c 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.22.1", + "version": "7.22.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index b20b12fd946..cc50069e63f 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.22.1", + "version": "7.22.2", "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.22.1" + "parabol-server": "7.22.2" } } diff --git a/packages/client/package.json b/packages/client/package.json index 01aa07e3f41..82271ce03b8 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.22.1", + "version": "7.22.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 9b6a0a443f4..9c8df6f3f95 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.22.1", + "version": "7.22.2", "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.22.1", - "parabol-server": "7.22.1", + "parabol-client": "7.22.2", + "parabol-server": "7.22.2", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 0c43beca5f7..fed0f57a2eb 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.22.1", + "version": "7.22.2", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 243e73e2400..4fa8018c480 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.22.1", + "version": "7.22.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.22.1", + "parabol-client": "7.22.2", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From fe128f017f01148ebd132fd532a771c6ab80ef16 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 19 Mar 2024 09:44:32 +0100 Subject: [PATCH 074/529] chore: Remove random team names (#9543) --- packages/client/utils/makeDefaultTeamName.ts | 54 ------------------- .../mutations/helpers/bootstrapNewUser.ts | 3 +- .../graphql/mutations/updateTeamName.ts | 9 +--- 3 files changed, 2 insertions(+), 64 deletions(-) delete mode 100644 packages/client/utils/makeDefaultTeamName.ts diff --git a/packages/client/utils/makeDefaultTeamName.ts b/packages/client/utils/makeDefaultTeamName.ts deleted file mode 100644 index a9dfbfa857f..00000000000 --- a/packages/client/utils/makeDefaultTeamName.ts +++ /dev/null @@ -1,54 +0,0 @@ -export const DEFAULT_TEAM_NAMES = [ - 'Bug Writers 🪲', - 'Long Meeting Lovers ❣️', - 'Work Procrastinators ⏱️', - 'Eat Lunch at 11AM 🥪', - 'Midday Nap 😴', - 'Show Us Your Cat 🐱', - 'Comb Your Hair for Zoom 💅', - 'Pajama Pants🌛', - 'Highly Caffeinated ☕', - 'Mute Slack & Chill 😎', - 'Friday Afternoon Meetings Should Be Illegal 🙈', - 'MacGuyvers of Fixing Bugs 🛠️', - 'Verified Swifties 🤠', - 'Excel is Hell 🔥', - 'Ice Cold Seltzer ✨', - '5 Minutes Late 😏', - 'Top Chefs 🧑‍🍳', - 'Clean Code or Bust 🧽', - 'Google It 👨‍💻', - 'AI-Generated Image 🦄', - 'Circling Back ⭕', - 'As Per My Last Email 😐', - 'Mute Button 🔕', - 'Comfy Socks 🧦', - 'Show Me the Data ‼️', - "Shakira's Strawberry Jam 🍓", - "5 O'clock Somewhere 🍻", - 'Trending on TikTok 🕺', - 'Sourdough Starter 🍞', - 'Collaboration Station 🤝', - 'Keyboard Warriors 🤺', - 'Sprinting Squirrels 🐿️', - 'Desk Jockeys 🎵', - 'Agenda Avengers 📝', - 'More Cheese 🧀', - 'Make it Work 💁‍♀️', - 'We ❤️ Dogs', - 'Extra Guac 🥑', - 'Copy/Paste 👬', - 'SEO Optimized 🔍', - 'Experience Architects 🏰', - 'User Interfacers 💡', - '404 Error 👾', - 'Commit and Push 🤓', - 'Crocs 4 Life 🐊', - 'Special Coffee Mug ☕' -] - -export const makeDefaultTeamName = (teamId: string) => { - const seed = [...teamId].reduce((prev, cur) => prev + cur.charCodeAt(0), 0) - const idx = seed % DEFAULT_TEAM_NAMES.length - return `Team ${DEFAULT_TEAM_NAMES[idx]!}` -} diff --git a/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts b/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts index a19f962661a..cb4fc7095f6 100644 --- a/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts +++ b/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts @@ -14,7 +14,6 @@ import createNewOrg from './createNewOrg' import createTeamAndLeader from './createTeamAndLeader' import getUsersbyDomain from '../../../postgres/queries/getUsersByDomain' import sendPromptToJoinOrg from '../../../utils/sendPromptToJoinOrg' -import {makeDefaultTeamName} from 'parabol-client/utils/makeDefaultTeamName' import {DataLoaderWorker} from '../../graphql' import acceptTeamInvitation from '../../../safeMutations/acceptTeamInvitation' import isValid from '../../isValid' @@ -137,7 +136,7 @@ const bootstrapNewUser = async ( const validNewTeam = { id: teamId, orgId, - name: makeDefaultTeamName(teamId), + name: `${preferredName}’s Team`, isOnboardTeam: true } const orgName = `${newUser.preferredName}’s Org` diff --git a/packages/server/graphql/mutations/updateTeamName.ts b/packages/server/graphql/mutations/updateTeamName.ts index 0788f2d9895..834c7d59318 100644 --- a/packages/server/graphql/mutations/updateTeamName.ts +++ b/packages/server/graphql/mutations/updateTeamName.ts @@ -10,7 +10,6 @@ import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' import UpdatedTeamInput, {UpdatedTeamInputType} from '../types/UpdatedTeamInput' import UpdateTeamNamePayload from '../types/UpdateTeamNamePayload' -import {makeDefaultTeamName} from 'parabol-client/utils/makeDefaultTeamName' export default { type: UpdateTeamNamePayload, @@ -60,13 +59,7 @@ export default { updatedAt: now } await updateTeamByTeamId(dbUpdate, teamId) - analytics.teamNameChanged( - viewer, - teamId, - oldName, - newName, - makeDefaultTeamName(teamId) === oldName - ) + analytics.teamNameChanged(viewer, teamId, oldName, newName, oldName.endsWith('’s Team')) const data = {teamId} publish(SubscriptionChannel.TEAM, teamId, 'UpdateTeamNamePayload', data, subOptions) From 6fca12c814f471ef33954381ee562cbbb4b93d67 Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Tue, 19 Mar 2024 11:23:32 +0000 Subject: [PATCH 075/529] chore(repo-structure): Docker images and stacks organized and clarified (#9530) --- .env.example | 12 +- .github/workflows/README.md | 2 +- .github/workflows/build.yml | 9 +- .github/workflows/test.yml | 2 +- docker-compose.yml | 53 ---- docker/Dockerfile.prod | 41 --- docker/README.md | 19 -- docker/docker-compose.selfHosted.yml | 6 - docker/entrypoint.prod.sh | 4 - .../parabol-ubi}/.gitignore | 0 .../parabol-ubi}/README.md | 8 +- .../parabol-ubi}/cloudbuild.yaml | 0 .../parabol-ubi}/docker-compose.yml | 0 .../parabol-ubi}/dockerfiles/basic.dockerfile | 4 +- .../dockerfiles/parabol.dockerfile | 0 .../entrypoints/docker-entrypoint.sh | 0 .../parabol-ubi}/environments/basic-env | 2 +- .../parabol-ubi}/environments/legacy-build | 0 .../parabol-ubi}/environments/pipeline | 2 +- ...rule_account_disable_post_pw_expiration.sh | 0 ....content_rule_accounts_logon_fail_delay.sh | 0 ..._accounts_max_concurrent_login_sessions.sh | 0 ...nt_rule_accounts_maximum_age_login_defs.sh | 0 ...nt_rule_accounts_minimum_age_login_defs.sh | 0 ...e_accounts_password_all_shadowed_sha512.sh | 0 ...ule_accounts_password_minlen_login_defs.sh | 0 ...tent_rule_accounts_password_pam_dcredit.sh | 0 ...nt_rule_accounts_password_pam_dictcheck.sh | 0 ...ontent_rule_accounts_password_pam_difok.sh | 0 ...tent_rule_accounts_password_pam_lcredit.sh | 0 ...le_accounts_password_pam_maxclassrepeat.sh | 0 ...nt_rule_accounts_password_pam_maxrepeat.sh | 0 ...ent_rule_accounts_password_pam_minclass.sh | 0 ...ntent_rule_accounts_password_pam_minlen.sh | 0 ...tent_rule_accounts_password_pam_ocredit.sh | 0 ...rd_pam_pwhistory_remember_password_auth.sh | 0 ...word_pam_pwhistory_remember_system_auth.sh | 0 ...tent_rule_accounts_password_pam_ucredit.sh | 0 ...ule_accounts_password_pam_unix_remember.sh | 0 ...nt_rule_accounts_passwords_pam_faillock.sh | 0 ...le_accounts_passwords_pam_faillock_deny.sh | 0 ...counts_passwords_pam_faillock_deny_root.sh | 0 ...ccounts_passwords_pam_faillock_interval.sh | 0 ...unts_passwords_pam_faillock_unlock_time.sh | 0 ....content_rule_accounts_umask_etc_bashrc.sh | 0 ...ntent_rule_accounts_umask_etc_csh_cshrc.sh | 0 ...tent_rule_accounts_umask_etc_login_defs.sh | 0 ...content_rule_accounts_umask_etc_profile.sh | 0 ...sgproject.content_rule_banner_etc_issue.sh | 0 ...ct.content_rule_configure_crypto_policy.sh | 0 ...t_rule_configure_kerberos_crypto_policy.sh | 0 ...nt_rule_configure_openssl_crypto_policy.sh | 0 ...nt_rule_configure_usbguard_auditbackend.sh | 0 ...ontent_rule_coredump_disable_backtraces.sh | 0 ...t.content_rule_coredump_disable_storage.sh | 0 ...ent_rule_disable_ctrlaltdel_burstaction.sh | 0 ...ct.content_rule_disable_users_coredumps.sh | 0 ...ect.content_rule_display_login_attempts.sh | 0 ...ent_rule_ensure_gpgcheck_local_packages.sh | 0 ...t_rule_file_groupowner_var_log_messages.sh | 0 ...ile_groupownership_system_commands_dirs.sh | 0 ...ontent_rule_file_owner_var_log_messages.sh | 0 ...content_rule_kernel_module_atm_disabled.sh | 0 ...content_rule_kernel_module_can_disabled.sh | 0 ...tent_rule_kernel_module_cramfs_disabled.sh | 0 ...le_kernel_module_firewire-core_disabled.sh | 0 ...ontent_rule_kernel_module_sctp_disabled.sh | 0 ...ontent_rule_kernel_module_tipc_disabled.sh | 0 ...project.content_rule_no_empty_passwords.sh | 0 ...content_rule_openssl_use_strong_entropy.sh | 0 ..._rule_package_crypto-policies_installed.sh | 0 ...content_rule_package_iptables_installed.sh | 0 ...ontent_rule_package_rng-tools_installed.sh | 0 ...ect.content_rule_package_sudo_installed.sh | 0 ...content_rule_package_usbguard_installed.sh | 0 ...tent_rule_sudo_require_reauthentication.sh | 0 ...ct.content_rule_sudoers_validate_passwd.sh | 0 .../tools/ip-to-server_id/index.js | 0 .../tools/ip-to-server_id/package.json | 0 .../images}/postgres/Dockerfile | 8 +- docker/images/postgres/extensions/install.sql | 1 + docker/stacks/development/README.md | 43 +++ .../datadog}/dd-conf.d/gqlExecutor.yml | 0 .../development/datadog}/dd-conf.d/web.yml | 0 .../development/docker-compose.yml} | 26 +- .../stacks/development/redis/certs}/README.md | 10 +- .../redis/certs}/gen-redis-certs.sh | 0 .../stacks/development/redis/certs}/redis.crt | 0 .../stacks/development/redis/certs}/redis.key | 0 .../development/redis/certs}/redisCA.crt | 0 .../single-tenant-host}/.env.example | 0 .../single-tenant-host}/README.md | 10 +- .../single-tenant-host}/docker-compose.yaml | 52 ++-- .../us-department-of-defense/README.md | 2 +- package.json | 4 +- packages/server/database/README.md | 1 - packages/server/postgres/README.md | 23 -- .../server/postgres/extensions/install.sql | 2 - .../extensions/postgres-json-schema/Makefile | 7 - .../postgres-json-schema--0.1.1.sql | 259 ------------------ .../postgres-json-schema.control | 3 - packages/server/postgres/postgres.conf | 1 - 102 files changed, 121 insertions(+), 495 deletions(-) delete mode 100644 docker-compose.yml delete mode 100644 docker/Dockerfile.prod delete mode 100644 docker/README.md delete mode 100644 docker/docker-compose.selfHosted.yml delete mode 100644 docker/entrypoint.prod.sh rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/.gitignore (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/README.md (95%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/cloudbuild.yaml (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/docker-compose.yml (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/dockerfiles/basic.dockerfile (75%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/dockerfiles/parabol.dockerfile (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/entrypoints/docker-entrypoint.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/environments/basic-env (81%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/environments/legacy-build (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/environments/pipeline (93%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_account_disable_post_pw_expiration.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_logon_fail_delay.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_max_concurrent_login_sessions.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_maximum_age_login_defs.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_minimum_age_login_defs.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_all_shadowed_sha512.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_minlen_login_defs.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_dcredit.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_dictcheck.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_difok.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_lcredit.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_maxclassrepeat.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_maxrepeat.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_minclass.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_minlen.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_ocredit.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_password_auth.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_system_auth.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_ucredit.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_unix_remember.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny_root.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_interval.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_unlock_time.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_bashrc.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_csh_cshrc.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_login_defs.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_profile.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_banner_etc_issue.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_configure_crypto_policy.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_configure_kerberos_crypto_policy.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_configure_openssl_crypto_policy.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_configure_usbguard_auditbackend.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_coredump_disable_backtraces.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_coredump_disable_storage.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_disable_ctrlaltdel_burstaction.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_disable_users_coredumps.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_display_login_attempts.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_ensure_gpgcheck_local_packages.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_file_groupowner_var_log_messages.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_file_groupownership_system_commands_dirs.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_file_owner_var_log_messages.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_kernel_module_atm_disabled.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_kernel_module_can_disabled.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_kernel_module_cramfs_disabled.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_kernel_module_firewire-core_disabled.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_kernel_module_sctp_disabled.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_kernel_module_tipc_disabled.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_no_empty_passwords.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_openssl_use_strong_entropy.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_package_crypto-policies_installed.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_package_iptables_installed.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_package_rng-tools_installed.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_package_sudo_installed.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_package_usbguard_installed.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_sudo_require_reauthentication.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/security/xccdf_org.ssgproject.content_rule_sudoers_validate_passwd.sh (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/tools/ip-to-server_id/index.js (100%) rename docker/{parabol-ubi/docker-build => images/parabol-ubi}/tools/ip-to-server_id/package.json (100%) rename {packages/server => docker/images}/postgres/Dockerfile (72%) create mode 100644 docker/images/postgres/extensions/install.sql create mode 100644 docker/stacks/development/README.md rename docker/{ => stacks/development/datadog}/dd-conf.d/gqlExecutor.yml (100%) rename docker/{ => stacks/development/datadog}/dd-conf.d/web.yml (100%) rename docker/{dev.yml => stacks/development/docker-compose.yml} (76%) rename {certs => docker/stacks/development/redis/certs}/README.md (74%) rename {certs => docker/stacks/development/redis/certs}/gen-redis-certs.sh (100%) rename {certs => docker/stacks/development/redis/certs}/redis.crt (100%) rename {certs => docker/stacks/development/redis/certs}/redis.key (100%) rename {certs => docker/stacks/development/redis/certs}/redisCA.crt (100%) rename docker/{parabol-ubi/docker-host-st => stacks/single-tenant-host}/.env.example (100%) rename docker/{parabol-ubi/docker-host-st => stacks/single-tenant-host}/README.md (88%) rename docker/{parabol-ubi/docker-host-st => stacks/single-tenant-host}/docker-compose.yaml (83%) delete mode 100644 packages/server/postgres/extensions/install.sql delete mode 100644 packages/server/postgres/extensions/postgres-json-schema/Makefile delete mode 100644 packages/server/postgres/extensions/postgres-json-schema/postgres-json-schema--0.1.1.sql delete mode 100644 packages/server/postgres/extensions/postgres-json-schema/postgres-json-schema.control delete mode 100644 packages/server/postgres/postgres.conf diff --git a/.env.example b/.env.example index 0d0a44b41f0..97dc499d644 100644 --- a/.env.example +++ b/.env.example @@ -74,9 +74,9 @@ POSTGRES_USE_PGVECTOR=true # POSTGRES_SSL_DIR='/var/lib/postgresql' REDIS_PASSWORD='' REDIS_URL='redis://localhost:6379' -# REDIS_TLS_CERT_FILE=./certs/redis.crt -# REDIS_TLS_KEY_FILE=./certs/redis.key -# REDIS_TLS_CA_FILE=./certs/redisCA.crt +# REDIS_TLS_CERT_FILE=./docker/stacks/development/redis/certs/redis.crt +# REDIS_TLS_KEY_FILE=./docker/stacks/development/redis/certs/redis.key +# REDIS_TLS_CA_FILE=./docker/stacks/development/redis/certs/redisCA.crt # REDIS_TLS_REJECT_UNAUTHORIZED='false' RETHINKDB_URL='rethinkdb://localhost:28015/actionDevelopment' RETHINKDB_SSL='false' @@ -119,9 +119,9 @@ RETHINKDB_SSL='false' # RECALL_AI_KEY='' # SLACK_CLIENT_ID='key_SLACK_CLIENT_ID' # SLACK_CLIENT_SECRET='key_SLACK_CLIENT_SECRET' -# STRIPE_SECRET_KEY='sk_test_4eC39HqLyjWDarjtT1zdp7dc' -# STRIPE_PUBLISHABLE_KEY='pk_test_TYooMQauvdEDq54NiTphI7jx' -# STRIPE_WEBHOOK_SECRET='sk_test_4eC39HqLyjWDarjtT1zdp7dc' +# STRIPE_SECRET_KEY='' +# STRIPE_PUBLISHABLE_KEY='' +# STRIPE_WEBHOOK_SECRET='' # MAIL # MAIL GLOBALS. PROVIDER: mailgun | google | debug | smtp diff --git a/.github/workflows/README.md b/.github/workflows/README.md index 244f4efb0da..0076af84bab 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -2,7 +2,7 @@ ## Runners -To run `docker-build.yml`, GitHub requires using a larger runner. +To run `build.yml`, GitHub requires using a larger runner. This is because we're webpackifying all the code in node_modules into a single `.js.`. Doing all that transpiling can take a LOT of memory. 8GB+. At this time, large GitHub-hosted runners are in beta. diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a19c552ac06..32dab47c748 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,8 +12,8 @@ concurrency: group: ${{ github.workflow }}-${{ github.event.pull_request.number }} cancel-in-progress: true env: - PARABOL_DOCKERFILE: ./docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile - PARABOL_BUILD_ENV_PATH: docker/parabol-ubi/docker-build/environments/pipeline + PARABOL_DOCKERFILE: ./docker/images/parabol-ubi/dockerfiles/basic.dockerfile + PARABOL_BUILD_ENV_PATH: docker/images/parabol-ubi/environments/pipeline jobs: build: runs-on: ubuntu-8cores @@ -22,8 +22,7 @@ jobs: id-token: "write" services: postgres: - # Image is pinned to v15, OK since it's just for testing - image: ankane/pgvector + image: pgvector/pgvector:pg15 # This env variables must be the same in the file PARABOL_BUILD_ENV_PATH env: POSTGRES_PASSWORD: "temppassword" @@ -106,7 +105,7 @@ jobs: username: "oauth2accesstoken" password: "${{ steps.auth.outputs.access_token }}" - name: Push build to dev - uses: docker/build-push-action@v4 + uses: docker/images-push-action@v4 with: network: host allow: network.host diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index cb6c94bd9a4..8378696b622 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -8,7 +8,7 @@ concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true env: - PARABOL_BUILD_ENV_PATH: docker/parabol-ubi/docker-build/environments/pipeline + PARABOL_BUILD_ENV_PATH: docker/images/parabol-ubi/environments/pipeline jobs: test: runs-on: ubuntu-8cores diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 941a0178a80..00000000000 --- a/docker-compose.yml +++ /dev/null @@ -1,53 +0,0 @@ -version: "3.7" - -services: - db: - image: rethinkdb:2.4.2 - ports: - - "8080:8080" - - "29015:29015" - - "28015:28015" - volumes: - - rethink-data:/data - networks: - - parabol-network - postgres: - image: postgres:15.4 - restart: always - env_file: .env - ports: - - "5432:5432" - volumes: - - "./packages/server/postgres/postgres.conf:/usr/local/etc/postgres/postgres.conf" - - "postgres-data:/data" - command: "postgres -c config_file=/usr/local/etc/postgres/postgres.conf" - networks: - - parabol-network - redis: - image: redis:7.0-alpine - ports: - - "6379:6379" - volumes: - - redis-data:/data - networks: - - parabol-network - app: - build: - context: . - dockerfile: ./docker/Dockerfile.prod - target: prod - env_file: .env - ports: - - "3000:3000" - depends_on: - - db - - redis - - postgres - networks: - - parabol-network -networks: - parabol-network: -volumes: - redis-data: {} - rethink-data: {} - postgres-data: {} diff --git a/docker/Dockerfile.prod b/docker/Dockerfile.prod deleted file mode 100644 index c070ef1e02d..00000000000 --- a/docker/Dockerfile.prod +++ /dev/null @@ -1,41 +0,0 @@ -# First, install all dependencies, including devDependencies, to run the build process -FROM node:20.11.0 as build - -WORKDIR /parabol - -# Only copy dependency-related files here (vs. COPY . .) to avoid breaking the cache and running -# the slow `yarn install` more than necessary -COPY package.json yarn.lock ./ -COPY packages/client/package.json ./packages/client/package.json -COPY packages/gql-executor/package.json packages/gql-executor/package.json -COPY packages/integration-tests/package.json packages/integration-tests/package.json -COPY packages/server/package.json packages/server/package.json -RUN yarn install --frozen-lockfile && \ - yarn cache clean - -COPY . . -RUN yarn build - -# Now, start over with a new stage so we don't pull over devDependencies -FROM node:20.11.0 as prod - -WORKDIR /parabol - -COPY package.json yarn.lock ./ -COPY packages/client/package.json ./packages/client/package.json -COPY packages/gql-executor/package.json packages/gql-executor/package.json -COPY packages/integration-tests/package.json packages/integration-tests/package.json -COPY packages/server/package.json packages/server/package.json -# Only install production dependencies -RUN yarn install --prod --frozen-lockfile && \ - yarn cache clean - -COPY . . -COPY --from=build /parabol/build ./build -COPY --from=build /parabol/dist ./dist - -RUN cp docker/entrypoint.prod.sh /bin/entrypoint && \ - chmod +x /bin/entrypoint -EXPOSE 80 -ENTRYPOINT [ "entrypoint" ] -CMD ["yarn", "start"] diff --git a/docker/README.md b/docker/README.md deleted file mode 100644 index b2ab1cefb56..00000000000 --- a/docker/README.md +++ /dev/null @@ -1,19 +0,0 @@ -## Usage of Docker in Different Envs - -### Development - -In development, docker handles all db services for us. This is done by calling `docker-compose` on the Compose file `./docker/dev.yml`. The web app itself is not containerized; it simply runs on the host machine by calling `yarn && yarn dev` - -### Production - -In production, dokku creates a Docker container using the [default Node.js heroku buildpack](https://dokku.com/docs~v0.5.1/deployment/buildpacks/). While it's possible to do so, we don't yet provide any [custom Dockerfile to dokku](https://dokku.com/docs~v0.5.1/deployment/dockerfiles/). - -### Self-Hosted - -Self-hosted instances may use the `docker-compose.yml` file in the root of the project directory. All services, including databases and the web app, will be containerized. - -If the owner of the self-hosted instance wants to use local file storage (instead of cloud storage such as AWS or GCP) for user uploaded images, make sure `FILE_STORE_PROVIDER='local'` in the root `.env` file. Additionally, the app must be deployed using the following command: - -`docker-compose -f docker-compose.yml -f ./docker/docker-compose.selfHosted.yml up -d` - -This ensures that the images will be persisted in a Docker volume. diff --git a/docker/docker-compose.selfHosted.yml b/docker/docker-compose.selfHosted.yml deleted file mode 100644 index 1c6598e42a3..00000000000 --- a/docker/docker-compose.selfHosted.yml +++ /dev/null @@ -1,6 +0,0 @@ -services: - app: - volumes: - - app-data:/parabol/self-hosted -volumes: - app-data: {} diff --git a/docker/entrypoint.prod.sh b/docker/entrypoint.prod.sh deleted file mode 100644 index 4ce89f9bce8..00000000000 --- a/docker/entrypoint.prod.sh +++ /dev/null @@ -1,4 +0,0 @@ -#!/bin/sh - -yarn predeploy -exec "$@" diff --git a/docker/parabol-ubi/docker-build/.gitignore b/docker/images/parabol-ubi/.gitignore similarity index 100% rename from docker/parabol-ubi/docker-build/.gitignore rename to docker/images/parabol-ubi/.gitignore diff --git a/docker/parabol-ubi/docker-build/README.md b/docker/images/parabol-ubi/README.md similarity index 95% rename from docker/parabol-ubi/docker-build/README.md rename to docker/images/parabol-ubi/README.md index 85c1a2d42b3..920c6d48fd8 100644 --- a/docker/parabol-ubi/docker-build/README.md +++ b/docker/images/parabol-ubi/README.md @@ -21,9 +21,9 @@ Recommended: | `postgresql_tag` | PostgreSQL version from the [Docker image](https://hub.docker.com/_/postgres) | `Any tag` | `15.4` | | `rethinkdb_tag` | RethinkDB version from the [Docker image](https://hub.docker.com/_/rethinkdb) | `Any tag` | `2.4.2` | | `redis_tag` | Redis version from the [Docker image](https://hub.docker.com/_/redis) | `Any tag` | `7.0-alpine` | -| `_BUILD_ENV_PATH` | File `.env` used by the application during the build process | `Relative path from the root level of the repository` | `docker/parabol-ubi/docker-build/environments/basic-env` | +| `_BUILD_ENV_PATH` | File `.env` used by the application during the build process | `Relative path from the root level of the repository` | `docker/parabol-ubi/environments/basic-env` | | `_NODE_VERSION` | Node version, used by Docker to use the Docker image node:\_NODE_VERSION as base image to build | `Same as in root package.json` | | -| `_DOCKERFILE` | Dockerfile used to build the image | `Relative path from the root level of the repository` | `./docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile` | +| `_DOCKERFILE` | Dockerfile used to build the image | `Relative path from the root level of the repository` | `./docker/parabol-ubi/dockerfiles/basic.dockerfile` | | `_DOCKER_REPOSITORY` | The destination repository | `String` | `parabol` | | `_DOCKER_TAG` | Tag for the produced image | `String` | | @@ -33,9 +33,9 @@ Example of variables: export postgresql_tag=15.4; \ export rethinkdb_tag=2.4.2; \ export redis_tag=7.0-alpine; \ -export _BUILD_ENV_PATH=docker/parabol-ubi/docker-build/environments/basic-env; \ +export _BUILD_ENV_PATH=docker/parabol-ubi/environments/basic-env; \ export _NODE_VERSION=$(jq -r -j '.engines.node|ltrimstr("^")' package.json); \ -export _DOCKERFILE=./docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile; \ +export _DOCKERFILE=./docker/parabol-ubi/dockerfiles/basic.dockerfile; \ export _DOCKER_REPOSITORY=parabol; \ export _DOCKER_TAG=test-image ``` diff --git a/docker/parabol-ubi/docker-build/cloudbuild.yaml b/docker/images/parabol-ubi/cloudbuild.yaml similarity index 100% rename from docker/parabol-ubi/docker-build/cloudbuild.yaml rename to docker/images/parabol-ubi/cloudbuild.yaml diff --git a/docker/parabol-ubi/docker-build/docker-compose.yml b/docker/images/parabol-ubi/docker-compose.yml similarity index 100% rename from docker/parabol-ubi/docker-build/docker-compose.yml rename to docker/images/parabol-ubi/docker-compose.yml diff --git a/docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile b/docker/images/parabol-ubi/dockerfiles/basic.dockerfile similarity index 75% rename from docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile rename to docker/images/parabol-ubi/dockerfiles/basic.dockerfile index 077d95a8fb6..a2bbbe8c1bb 100644 --- a/docker/parabol-ubi/docker-build/dockerfiles/basic.dockerfile +++ b/docker/images/parabol-ubi/dockerfiles/basic.dockerfile @@ -7,8 +7,8 @@ ENV HOME=/home/node \ ENV NPM_CONFIG_PREFIX=/home/node/.npm-global ENV PORT=3000 -COPY --chown=node --chmod=755 docker/parabol-ubi/docker-build/entrypoints/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh -COPY --chown=node docker/parabol-ubi/docker-build/tools/ip-to-server_id ${HOME}/tools/ip-to-server_id +COPY --chown=node --chmod=755 docker/parabol-ubi/entrypoints/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +COPY --chown=node docker/parabol-ubi/tools/ip-to-server_id ${HOME}/tools/ip-to-server_id # Required for pushToCDN to work with FILE_STORE_PROVIDER set to 'local' RUN mkdir -p ${HOME}/parabol/self-hosted && \ diff --git a/docker/parabol-ubi/docker-build/dockerfiles/parabol.dockerfile b/docker/images/parabol-ubi/dockerfiles/parabol.dockerfile similarity index 100% rename from docker/parabol-ubi/docker-build/dockerfiles/parabol.dockerfile rename to docker/images/parabol-ubi/dockerfiles/parabol.dockerfile diff --git a/docker/parabol-ubi/docker-build/entrypoints/docker-entrypoint.sh b/docker/images/parabol-ubi/entrypoints/docker-entrypoint.sh similarity index 100% rename from docker/parabol-ubi/docker-build/entrypoints/docker-entrypoint.sh rename to docker/images/parabol-ubi/entrypoints/docker-entrypoint.sh diff --git a/docker/parabol-ubi/docker-build/environments/basic-env b/docker/images/parabol-ubi/environments/basic-env similarity index 81% rename from docker/parabol-ubi/docker-build/environments/basic-env rename to docker/images/parabol-ubi/environments/basic-env index 86bbad9252d..7141dd3f371 100644 --- a/docker/parabol-ubi/docker-build/environments/basic-env +++ b/docker/images/parabol-ubi/environments/basic-env @@ -4,7 +4,7 @@ NODE_ENV='production' NODE_EXTRA_CA_CERTS='' PROTO='https' PORT='3000' -# Database configurations must be the same used in the docker-build.yml Github workflow +# Database configurations must be the same used in the build.yml Github workflow POSTGRES_PASSWORD='temppassword' POSTGRES_USER='tempuser' POSTGRES_DB='tempdb' diff --git a/docker/parabol-ubi/docker-build/environments/legacy-build b/docker/images/parabol-ubi/environments/legacy-build similarity index 100% rename from docker/parabol-ubi/docker-build/environments/legacy-build rename to docker/images/parabol-ubi/environments/legacy-build diff --git a/docker/parabol-ubi/docker-build/environments/pipeline b/docker/images/parabol-ubi/environments/pipeline similarity index 93% rename from docker/parabol-ubi/docker-build/environments/pipeline rename to docker/images/parabol-ubi/environments/pipeline index 5641d43725f..cfc707c746b 100644 --- a/docker/parabol-ubi/docker-build/environments/pipeline +++ b/docker/images/parabol-ubi/environments/pipeline @@ -36,7 +36,7 @@ PGADMIN_DEFAULT_EMAIL='' PGADMIN_DEFAULT_PASSWORD='' PGSSLMODE='' PORT='3000' -# Database configurations must be the same used in the docker-build.yml Github workflow +# Database configurations must be the same used in the build.yml Github workflow POSTGRES_PASSWORD='temppassword' POSTGRES_USER='tempuser' POSTGRES_DB='tempdb' diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_account_disable_post_pw_expiration.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_account_disable_post_pw_expiration.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_account_disable_post_pw_expiration.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_account_disable_post_pw_expiration.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_logon_fail_delay.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_logon_fail_delay.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_logon_fail_delay.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_logon_fail_delay.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_max_concurrent_login_sessions.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_max_concurrent_login_sessions.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_max_concurrent_login_sessions.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_max_concurrent_login_sessions.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_maximum_age_login_defs.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_maximum_age_login_defs.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_maximum_age_login_defs.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_maximum_age_login_defs.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_minimum_age_login_defs.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_minimum_age_login_defs.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_minimum_age_login_defs.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_minimum_age_login_defs.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_all_shadowed_sha512.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_all_shadowed_sha512.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_all_shadowed_sha512.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_all_shadowed_sha512.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_minlen_login_defs.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_minlen_login_defs.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_minlen_login_defs.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_minlen_login_defs.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_dcredit.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_dcredit.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_dcredit.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_dcredit.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_dictcheck.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_dictcheck.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_dictcheck.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_dictcheck.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_difok.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_difok.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_difok.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_difok.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_lcredit.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_lcredit.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_lcredit.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_lcredit.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_maxclassrepeat.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_maxclassrepeat.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_maxclassrepeat.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_maxclassrepeat.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_maxrepeat.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_maxrepeat.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_maxrepeat.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_maxrepeat.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_minclass.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_minclass.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_minclass.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_minclass.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_minlen.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_minlen.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_minlen.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_minlen.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_ocredit.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_ocredit.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_ocredit.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_ocredit.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_password_auth.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_password_auth.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_password_auth.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_password_auth.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_system_auth.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_system_auth.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_system_auth.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_pwhistory_remember_system_auth.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_ucredit.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_ucredit.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_ucredit.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_ucredit.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_unix_remember.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_unix_remember.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_unix_remember.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_password_pam_unix_remember.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny_root.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny_root.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny_root.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_deny_root.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_interval.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_interval.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_interval.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_interval.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_unlock_time.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_unlock_time.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_unlock_time.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_passwords_pam_faillock_unlock_time.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_bashrc.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_bashrc.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_bashrc.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_bashrc.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_csh_cshrc.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_csh_cshrc.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_csh_cshrc.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_csh_cshrc.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_login_defs.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_login_defs.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_login_defs.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_login_defs.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_profile.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_profile.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_profile.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_accounts_umask_etc_profile.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_banner_etc_issue.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_banner_etc_issue.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_banner_etc_issue.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_banner_etc_issue.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_configure_crypto_policy.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_configure_crypto_policy.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_configure_crypto_policy.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_configure_crypto_policy.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_configure_kerberos_crypto_policy.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_configure_kerberos_crypto_policy.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_configure_kerberos_crypto_policy.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_configure_kerberos_crypto_policy.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_configure_openssl_crypto_policy.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_configure_openssl_crypto_policy.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_configure_openssl_crypto_policy.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_configure_openssl_crypto_policy.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_configure_usbguard_auditbackend.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_configure_usbguard_auditbackend.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_configure_usbguard_auditbackend.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_configure_usbguard_auditbackend.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_coredump_disable_backtraces.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_coredump_disable_backtraces.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_coredump_disable_backtraces.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_coredump_disable_backtraces.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_coredump_disable_storage.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_coredump_disable_storage.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_coredump_disable_storage.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_coredump_disable_storage.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_disable_ctrlaltdel_burstaction.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_disable_ctrlaltdel_burstaction.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_disable_ctrlaltdel_burstaction.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_disable_ctrlaltdel_burstaction.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_disable_users_coredumps.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_disable_users_coredumps.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_disable_users_coredumps.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_disable_users_coredumps.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_display_login_attempts.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_display_login_attempts.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_display_login_attempts.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_display_login_attempts.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_ensure_gpgcheck_local_packages.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_ensure_gpgcheck_local_packages.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_ensure_gpgcheck_local_packages.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_ensure_gpgcheck_local_packages.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_file_groupowner_var_log_messages.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_file_groupowner_var_log_messages.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_file_groupowner_var_log_messages.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_file_groupowner_var_log_messages.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_file_groupownership_system_commands_dirs.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_file_groupownership_system_commands_dirs.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_file_groupownership_system_commands_dirs.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_file_groupownership_system_commands_dirs.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_file_owner_var_log_messages.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_file_owner_var_log_messages.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_file_owner_var_log_messages.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_file_owner_var_log_messages.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_kernel_module_atm_disabled.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_kernel_module_atm_disabled.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_kernel_module_atm_disabled.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_kernel_module_atm_disabled.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_kernel_module_can_disabled.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_kernel_module_can_disabled.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_kernel_module_can_disabled.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_kernel_module_can_disabled.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_kernel_module_cramfs_disabled.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_kernel_module_cramfs_disabled.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_kernel_module_cramfs_disabled.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_kernel_module_cramfs_disabled.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_kernel_module_firewire-core_disabled.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_kernel_module_firewire-core_disabled.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_kernel_module_firewire-core_disabled.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_kernel_module_firewire-core_disabled.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_kernel_module_sctp_disabled.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_kernel_module_sctp_disabled.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_kernel_module_sctp_disabled.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_kernel_module_sctp_disabled.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_kernel_module_tipc_disabled.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_kernel_module_tipc_disabled.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_kernel_module_tipc_disabled.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_kernel_module_tipc_disabled.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_no_empty_passwords.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_no_empty_passwords.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_no_empty_passwords.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_no_empty_passwords.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_openssl_use_strong_entropy.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_openssl_use_strong_entropy.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_openssl_use_strong_entropy.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_openssl_use_strong_entropy.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_package_crypto-policies_installed.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_package_crypto-policies_installed.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_package_crypto-policies_installed.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_package_crypto-policies_installed.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_package_iptables_installed.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_package_iptables_installed.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_package_iptables_installed.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_package_iptables_installed.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_package_rng-tools_installed.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_package_rng-tools_installed.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_package_rng-tools_installed.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_package_rng-tools_installed.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_package_sudo_installed.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_package_sudo_installed.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_package_sudo_installed.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_package_sudo_installed.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_package_usbguard_installed.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_package_usbguard_installed.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_package_usbguard_installed.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_package_usbguard_installed.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_sudo_require_reauthentication.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_sudo_require_reauthentication.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_sudo_require_reauthentication.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_sudo_require_reauthentication.sh diff --git a/docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_sudoers_validate_passwd.sh b/docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_sudoers_validate_passwd.sh similarity index 100% rename from docker/parabol-ubi/docker-build/security/xccdf_org.ssgproject.content_rule_sudoers_validate_passwd.sh rename to docker/images/parabol-ubi/security/xccdf_org.ssgproject.content_rule_sudoers_validate_passwd.sh diff --git a/docker/parabol-ubi/docker-build/tools/ip-to-server_id/index.js b/docker/images/parabol-ubi/tools/ip-to-server_id/index.js similarity index 100% rename from docker/parabol-ubi/docker-build/tools/ip-to-server_id/index.js rename to docker/images/parabol-ubi/tools/ip-to-server_id/index.js diff --git a/docker/parabol-ubi/docker-build/tools/ip-to-server_id/package.json b/docker/images/parabol-ubi/tools/ip-to-server_id/package.json similarity index 100% rename from docker/parabol-ubi/docker-build/tools/ip-to-server_id/package.json rename to docker/images/parabol-ubi/tools/ip-to-server_id/package.json diff --git a/packages/server/postgres/Dockerfile b/docker/images/postgres/Dockerfile similarity index 72% rename from packages/server/postgres/Dockerfile rename to docker/images/postgres/Dockerfile index e37b75a097e..3ce8d5d5185 100644 --- a/packages/server/postgres/Dockerfile +++ b/docker/images/postgres/Dockerfile @@ -1,15 +1,17 @@ FROM postgres:15.4 +ARG PGVECTOR_VERSION=v0.6.1 +ARG PSQL_MAJOR_VERSION=15 ADD extensions /extensions RUN apt-get update && apt-get install -y \ build-essential \ locales \ - postgresql-server-dev-15 \ + postgresql-server-dev-${PSQL_MAJOR_VERSION} \ git -RUN cd /extensions/postgres-json-schema && make install && make installcheck -RUN git clone --branch v0.5.0 \ +# PGVector +RUN git clone --branch ${PGVECTOR_VERSION} \ https://github.com/pgvector/pgvector.git /extensions/pgvector && \ cd extensions/pgvector && make clean && make && make install diff --git a/docker/images/postgres/extensions/install.sql b/docker/images/postgres/extensions/install.sql new file mode 100644 index 00000000000..5e2d0c13454 --- /dev/null +++ b/docker/images/postgres/extensions/install.sql @@ -0,0 +1 @@ +CREATE EXTENSION IF NOT EXISTS "vector"; diff --git a/docker/stacks/development/README.md b/docker/stacks/development/README.md new file mode 100644 index 00000000000..a14d00d59cb --- /dev/null +++ b/docker/stacks/development/README.md @@ -0,0 +1,43 @@ +# Development stack + +> ⚠️ **Parabol does not provide any support on this stack**. Use it under your own resposibility. + +## General notes + +- **This stack is not meant for production use.** It is our development stack and can change at any moment, have errors and incorporate and remove components we are testing without any notice. +- This stack is designed to be managed using `yarn db:start` and `yarn db:stop` to start the databases. The application can use it, starting with either `yarn dev` or building the application and using `yarn start`. + +## Components + +- **Datadog agent:** additional configuration can be added in the folder `datadog/dd-conf.d`. +- **RethinkDB:** ports 28015 and 8080 (console) exposed to communicate with the cluster. Data mounted in a volume rethinkdb-data. +- **Postgres:** container built from a Dockerfile in [docker/images/postgres](docker/images/postgres), that incorporates some extra extensions used by the application. Exposed through port 5432 and with the data mounted in a volume postgres-data. +- **PGAdmin:** available on 5050 with credentials on the `.env` file. Connect using the values of `PGADMIN_DEFAULT_EMAIL` and `PGADMIN_DEFAULT_PASSWORD` from the `.env`. Data mounted on a volume pgadmin-data. +- **Redis:** available on 6379 with the data mounted on a volume redis-data. +- **Redis Commander:** available on 8081. +- **Text Embedding Inference:** toolkit to deploy and serve open source text embeddings and sequence classification models. Exposed on 3040. More information in [their Github](https://github.com/huggingface/text-embeddings-inference). + +### Configue PGAdmin + +- pgadmin is at [http://localhost:5050](http://localhost:5050) +- Connect using the values of `PGADMIN_DEFAULT_EMAIL` and `PGADMIN_DEFAULT_PASSWORD` from your `.env` +- Click **Add New Server** and fill out the forms with your `.env` values + + - General.name = POSTGRES_DB + - Connection.host = 'postgres' (hardcoded from docker-compose dev.yml, not from .env!) + - Connection.username = POSTGRES_USER + - Connection.password = POSTGRES_PASSWORD + - Connection.maintenanceDatabase = POSTGRES_DB + - Connection.port = POSTGRES_PORT + +### Postgres + +#### Too many connections + +Sometimes pg pool will hit its connection limit. This should never happen in prod, but happens on occassion in dev. +You'll know it's happening because PG will say there are too many connections. +To fix, you can run the following SQL to remove all the connections except the one that is running the script + +```sql +select pg_terminate_backend(pid) from pg_stat_activity where datname='parabol-saas' AND pid <> pg_backend_pid(); +``` diff --git a/docker/dd-conf.d/gqlExecutor.yml b/docker/stacks/development/datadog/dd-conf.d/gqlExecutor.yml similarity index 100% rename from docker/dd-conf.d/gqlExecutor.yml rename to docker/stacks/development/datadog/dd-conf.d/gqlExecutor.yml diff --git a/docker/dd-conf.d/web.yml b/docker/stacks/development/datadog/dd-conf.d/web.yml similarity index 100% rename from docker/dd-conf.d/web.yml rename to docker/stacks/development/datadog/dd-conf.d/web.yml diff --git a/docker/dev.yml b/docker/stacks/development/docker-compose.yml similarity index 76% rename from docker/dev.yml rename to docker/stacks/development/docker-compose.yml index c45ba6b6f6b..91adbbb6578 100644 --- a/docker/dev.yml +++ b/docker/stacks/development/docker-compose.yml @@ -1,10 +1,11 @@ -version: "3.7" +version: "3.9" +name: parabol-dev services: datadog: image: gcr.io/datadoghq/agent:7 restart: unless-stopped - env_file: ../.env + env_file: ../../../.env ports: - "8126:8126" networks: @@ -13,7 +14,9 @@ services: - /var/run/docker.sock:/var/run/docker.sock - /proc/:/host/proc/:ro - /sys/fs/cgroup:/host/sys/fs/cgroup:ro - db: + - ".datadog/dd-conf.d:/etc/datadog-agent/conf.d/local.d/" + - "../../../dev/logs:/var/log/datadog/logs" + rethinkdb: image: rethinkdb:2.4.2 restart: unless-stopped ports: @@ -26,23 +29,20 @@ services: - parabol-network postgres: build: - context: "../packages/server/postgres" + context: "../../images/postgres" restart: unless-stopped - env_file: ../.env + env_file: ../../../.env ports: - "5432:5432" volumes: - - "../packages/server/postgres/postgres.conf:/usr/local/etc/postgres/postgres.conf" - "postgres-data:/var/lib/postgresql/data" - command: "postgres -c config_file=/usr/local/etc/postgres/postgres.conf" networks: - parabol-network pgadmin: - container_name: pgadmin_container - image: dpage/pgadmin4:latest + image: dpage/pgadmin4:8.3 depends_on: - postgres - env_file: ../.env + env_file: ../../../.env volumes: - "pgadmin-data:/var/lib/pgadmin" ports: @@ -60,18 +60,16 @@ services: networks: - parabol-network redis-commander: - container_name: redis_commander - image: ghcr.io/joeferner/redis-commander:latest + image: ghcr.io/joeferner/redis-commander:0.8.1 hostname: redis-commander restart: unless-stopped environment: - REDIS_HOSTS=local:redis:6379 ports: - - "8082:8081" + - "8081:8081" networks: parabol-network: text-embeddings-inference: - container_name: text-embeddings-inference image: ghcr.io/huggingface/text-embeddings-inference:cpu-0.6 command: - "--model-id=llmrails/ember-v1" diff --git a/certs/README.md b/docker/stacks/development/redis/certs/README.md similarity index 74% rename from certs/README.md rename to docker/stacks/development/redis/certs/README.md index 3059e90eead..0a2e2972912 100644 --- a/certs/README.md +++ b/docker/stacks/development/redis/certs/README.md @@ -8,8 +8,10 @@ The certs that are checked into version control are self-signed and safe to shar All env vars should correspond with the vars in the redis instance. In development, that means: -- In the `docker/dev.yml`, add a volume: `bitnami-redis-data: {}` -- In the `docker/dev.yml`, replace the Redis container sections with the following: + +- In the `docker/stacks/development/docker-compose.yml`, add a volume: `bitnami-redis-data: {}` +- In the `docker/stacks/development/docker-compose.yml`, replace the Redis container sections with the following: + ```yaml image: bitnami/redis:7.0-debian-11 environment: @@ -25,9 +27,9 @@ In development, that means: - ../certs:/opt/bitnami/redis/certs ``` -- Vars in .env should match the vars in dev.yml +- Vars in .env should match the vars in `docker/stacks/development/docker-compose.yml` -Any changes to dev.yml require running `yarn db:start` +Any changes to `docker/stacks/development/docker-compose.yml` require running `yarn db:start` REDIS_PASSWORD: Use this if you'd like our app to connect to redis using a password REDIS_TLS_CERT_FILE: The cert file used to authorize clients. Not available in GCP diff --git a/certs/gen-redis-certs.sh b/docker/stacks/development/redis/certs/gen-redis-certs.sh similarity index 100% rename from certs/gen-redis-certs.sh rename to docker/stacks/development/redis/certs/gen-redis-certs.sh diff --git a/certs/redis.crt b/docker/stacks/development/redis/certs/redis.crt similarity index 100% rename from certs/redis.crt rename to docker/stacks/development/redis/certs/redis.crt diff --git a/certs/redis.key b/docker/stacks/development/redis/certs/redis.key similarity index 100% rename from certs/redis.key rename to docker/stacks/development/redis/certs/redis.key diff --git a/certs/redisCA.crt b/docker/stacks/development/redis/certs/redisCA.crt similarity index 100% rename from certs/redisCA.crt rename to docker/stacks/development/redis/certs/redisCA.crt diff --git a/docker/parabol-ubi/docker-host-st/.env.example b/docker/stacks/single-tenant-host/.env.example similarity index 100% rename from docker/parabol-ubi/docker-host-st/.env.example rename to docker/stacks/single-tenant-host/.env.example diff --git a/docker/parabol-ubi/docker-host-st/README.md b/docker/stacks/single-tenant-host/README.md similarity index 88% rename from docker/parabol-ubi/docker-host-st/README.md rename to docker/stacks/single-tenant-host/README.md index af3a0b244ec..59a6a4a8a3e 100644 --- a/docker/parabol-ubi/docker-host-st/README.md +++ b/docker/stacks/single-tenant-host/README.md @@ -1,8 +1,8 @@ # Docker Host Single Tenant (ST) -To run Parabol in single tenant mode (e.g. simple docker-compose on a docker host). +To run Parabol in single tenant mode (e.g. simple docker-compose on a docker host): -1. Build your Parabol UBI using instructions in `docker/ubi/docker-build/README.md` +1. Build your Parabol UBI using instructions in `docker/images/parabol-ubi/README.md` 2. Create a working `.env` from `.env.example` 3. Update docker-compose.yaml `image: #image:tag` with your built image tag from `step (1.)` 4. Run `docker compose --profile databases --profile parabol up -d` to deploy the local stack. You can run `docker compose --profile databases --profile parabol down` to terminate the local stack @@ -31,12 +31,12 @@ This will run `pre-deploy` and thus it will recreate the `web-server` and the `g Some tools are available to debug the databases is needed: -- pgadmin -- redis-commander +- [pgadmin](https://www.pgadmin.org/) +- [redis-commander](https://github.com/joeferner/redis-commander) To operate them use `docker compose up --profile databases --profile database-debug`. ## Running the whole stack - Start the whole stack: `docker compose --profile databases --profile parabol --profile database-debug --profile chronos up -d`. -- Stop the stack: `docker compose --profile databases --profile parabol --profile database-debug --profile chronos down` +- Stop the stack: `docker compose --profile databases --profile parabol --profile database-debug --profile chronos down`. diff --git a/docker/parabol-ubi/docker-host-st/docker-compose.yaml b/docker/stacks/single-tenant-host/docker-compose.yaml similarity index 83% rename from docker/parabol-ubi/docker-host-st/docker-compose.yaml rename to docker/stacks/single-tenant-host/docker-compose.yaml index 52bb8c76a7d..e5f662ed74a 100644 --- a/docker/parabol-ubi/docker-host-st/docker-compose.yaml +++ b/docker/stacks/single-tenant-host/docker-compose.yaml @@ -1,6 +1,19 @@ -version: '3.9' +version: "3.9" services: + rethinkdb: + container_name: rethinkdb + profiles: ["databases"] + image: rethinkdb:2.4.2 + restart: always + ports: + - "8080:8080" + - "29015:29015" + - "28015:28015" + volumes: + - ./data/rethink:/data + networks: + - parabol-network postgres: container_name: postgres profiles: ["databases"] @@ -10,9 +23,9 @@ services: environment: - PGUSER=$POSTGRES_USER ports: - - '5432:5432' + - "5432:5432" volumes: - - './data/postgres/pgdata:/var/lib/postgresql/data' + - "./data/postgres/pgdata:/var/lib/postgresql/data" healthcheck: test: ["CMD-SHELL", "pg_isready", "-d", "$POSTGRES_DB", "-U", "$POSTGRES_USER"] interval: 10s @@ -32,19 +45,6 @@ services: - "5050:80" networks: - parabol-network - rethinkdb: - container_name: rethinkdb - profiles: ["databases"] - image: rethinkdb:2.4.2 - restart: always - ports: - - '8080:8080' - - '29015:29015' - - '28015:28015' - volumes: - - ./data/rethink:/data - networks: - - parabol-network redis: container_name: redis profiles: ["databases"] @@ -56,7 +56,7 @@ services: retries: 5 restart: always ports: - - '6379:6379' + - "6379:6379" volumes: - ./data/redis:/data networks: @@ -78,13 +78,13 @@ services: pre-deploy: container_name: pre-deploy profiles: ["parabol"] - image: us-central1-docker.pkg.dev/prbl-tooling/parabol-production/parabol:v7.15.2 + image: #image:tag command: bash -c "node dist/preDeploy.js" env_file: .env environment: - SERVER_ID=0 volumes: - - './.env:/parabol/.env' + - "./.env:/parabol/.env" depends_on: rethinkdb: condition: service_started @@ -97,14 +97,14 @@ services: chronos: container_name: chronos profiles: ["chronos"] - image: us-central1-docker.pkg.dev/prbl-tooling/parabol-production/parabol:v7.15.2 + image: #image:tag restart: always command: bash -c "node dist/chronos.js" env_file: .env environment: - SERVER_ID=1 volumes: - - './.env:/parabol/.env' + - "./.env:/parabol/.env" depends_on: pre-deploy: condition: service_completed_successfully @@ -119,16 +119,16 @@ services: web-server: container_name: web-server profiles: ["parabol"] - image: us-central1-docker.pkg.dev/prbl-tooling/parabol-production/parabol:v7.15.2 + image: #image:tag restart: always command: bash -c "node dist/web.js" env_file: .env environment: - SERVER_ID=5 ports: - - '3000:3000' + - "3000:3000" volumes: - - './.env:/parabol/.env' + - "./.env:/parabol/.env" depends_on: pre-deploy: condition: service_completed_successfully @@ -143,14 +143,14 @@ services: gql-executor: container_name: gql-executor profiles: ["parabol"] - image: us-central1-docker.pkg.dev/prbl-tooling/parabol-production/parabol:v7.15.2 + image: #image:tag restart: always command: bash -c "node dist/gqlExecutor.js" env_file: .env environment: - SERVER_ID=10 volumes: - - './.env:/parabol/.env' + - "./.env:/parabol/.env" depends_on: pre-deploy: condition: service_completed_successfully diff --git a/docs/alternative-licenses/us-department-of-defense/README.md b/docs/alternative-licenses/us-department-of-defense/README.md index efbdc1d9f1d..330f56931d5 100644 --- a/docs/alternative-licenses/us-department-of-defense/README.md +++ b/docs/alternative-licenses/us-department-of-defense/README.md @@ -82,7 +82,7 @@ $ yarn && yarn build && yarn start - Click "Add New Server" and fill out the forms with your `.env` values - General.name = POSTGRES_DB - - Connection.host = 'postgres' (hardcoded from docker-compose dev.yml, not from .env!) + - Connection.host = 'postgres' (hardcoded from docker-compose `docker/stacks/development/docker-compose.yml`, not from .env!) - Connection.username = POSTGRES_USER - Connection.password = POSTGRES_PASSWORD - Connection.maintenanceDatabase = POSTGRES_DB diff --git a/package.json b/package.json index d228664dd0c..e19d16f1941 100644 --- a/package.json +++ b/package.json @@ -30,8 +30,8 @@ "pg:migrate": "node-pg-migrate -f ./packages/server/postgres/pgmConfig.js", "pg:generate": "export $(grep ^POSTGRES_ .env | tr -d \"'\"); yarn kysely-codegen --exclude-pattern \"(PgMigrations|StripeQuantityMismatchLogging)\" --out-file ./packages/server/postgres/pg.d.ts --dialect postgres --url postgresql://$POSTGRES_USER:$POSTGRES_PASSWORD@$POSTGRES_HOST:$POSTGRES_PORT/$POSTGRES_DB && prettier --write ./packages/server/postgres/pg.d.ts", "pg:restore": "node ./scripts/toolbox/pgRestore.js", - "db:start": "docker compose -f docker/dev.yml up -d", - "db:stop": "docker compose -f docker/dev.yml down", + "db:start": "docker compose -f docker/stacks/development/docker-compose.yml up -d", + "db:stop": "docker compose -f docker/stacks/development/docker-compose.yml down", "db:migrate": "node scripts/migrate.js", "deduplicate": "yarn yarn-deduplicate yarn.lock", "predeploy": "node dist/preDeploy.js", diff --git a/packages/server/database/README.md b/packages/server/database/README.md index 43981c7b19b..13d527b63d7 100644 --- a/packages/server/database/README.md +++ b/packages/server/database/README.md @@ -10,4 +10,3 @@ Since that time, RethinkDB has occassionally been unstable for us under high loa Since our data is very relational, it made sense to move PostgresQL, which we are actively doing. - Migrations are stored in [`packages/server/database/migrations`](./migrations/) -- RethinkDB Dashboard is at [http://localhost:8080](http://localhost:8080) diff --git a/packages/server/postgres/README.md b/packages/server/postgres/README.md index 07047ec37c6..303ec662993 100644 --- a/packages/server/postgres/README.md +++ b/packages/server/postgres/README.md @@ -1,18 +1,5 @@ # PostgreSQL -## Setup - -- pgadmin is at [http://localhost:5050](http://localhost:5050) -- Connect using the values of `PGADMIN_DEFAULT_EMAIL` and `PGADMIN_DEFAULT_PASSWORD` from your `.env` -- Click "Add New Server" and fill out the forms with your `.env` values - - - General.name = POSTGRES_DB - - Connection.host = 'postgres' (hardcoded from docker-compose dev.yml, not from .env!) - - Connection.username = POSTGRES_USER - - Connection.password = POSTGRES_PASSWORD - - Connection.maintenanceDatabase = POSTGRES_DB - - Connection.port = POSTGRES_PORT - ## Migrations This folder contains all the postgres migrations that have been run on the database. @@ -56,13 +43,3 @@ Parameters are capped at 16-bit, so if you're doing a bulk insert, you'll need t In other words, if `# rows * columns per row > 65,535` you need to do it in batches. `pg-protocol` shows this here: Issue here: - -#### Too many connections - -Sometimes pg pool will hit its connection limit. This should never happen in prod, but happens on occassion in dev. -You'll know it's happening because PG will say there are too many connections. -To fix, you can run the following SQL to remove all the connections except the one that is running the script - -```sql -select pg_terminate_backend(pid) from pg_stat_activity where datname='parabol-saas' AND pid <> pg_backend_pid(); -``` diff --git a/packages/server/postgres/extensions/install.sql b/packages/server/postgres/extensions/install.sql deleted file mode 100644 index b2670548069..00000000000 --- a/packages/server/postgres/extensions/install.sql +++ /dev/null @@ -1,2 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS "postgres-json-schema"; -CREATE EXTENSION IF NOT EXISTS "vector"; diff --git a/packages/server/postgres/extensions/postgres-json-schema/Makefile b/packages/server/postgres/extensions/postgres-json-schema/Makefile deleted file mode 100644 index 52ea8ab56d5..00000000000 --- a/packages/server/postgres/extensions/postgres-json-schema/Makefile +++ /dev/null @@ -1,7 +0,0 @@ -EXTENSION = postgres-json-schema -DATA = postgres-json-schema--0.1.1.sql - -# postgres build stuff -PG_CONFIG = pg_config -PGXS := $(shell $(PG_CONFIG) --pgxs) -include $(PGXS) diff --git a/packages/server/postgres/extensions/postgres-json-schema/postgres-json-schema--0.1.1.sql b/packages/server/postgres/extensions/postgres-json-schema/postgres-json-schema--0.1.1.sql deleted file mode 100644 index 5c2ba846fff..00000000000 --- a/packages/server/postgres/extensions/postgres-json-schema/postgres-json-schema--0.1.1.sql +++ /dev/null @@ -1,259 +0,0 @@ -CREATE OR REPLACE FUNCTION _validate_json_schema_type(type text, data jsonb) RETURNS boolean AS $f$ -BEGIN - IF type = 'integer' THEN - IF jsonb_typeof(data) != 'number' THEN - RETURN false; - END IF; - IF trunc(data::text::numeric) != data::text::numeric THEN - RETURN false; - END IF; - ELSE - IF type != jsonb_typeof(data) THEN - RETURN false; - END IF; - END IF; - RETURN true; -END; -$f$ LANGUAGE 'plpgsql' IMMUTABLE; - - -CREATE OR REPLACE FUNCTION validate_json_schema(schema jsonb, data jsonb, root_schema jsonb DEFAULT NULL) RETURNS boolean AS $f$ -DECLARE - prop text; - item jsonb; - path text[]; - types text[]; - pattern text; - props text[]; -BEGIN - IF root_schema IS NULL THEN - root_schema = schema; - END IF; - - IF schema ? 'type' THEN - IF jsonb_typeof(schema->'type') = 'array' THEN - types = ARRAY(SELECT jsonb_array_elements_text(schema->'type')); - ELSE - types = ARRAY[schema->>'type']; - END IF; - IF (SELECT NOT bool_or(@extschema@._validate_json_schema_type(type, data)) FROM unnest(types) type) THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'properties' THEN - FOR prop IN SELECT jsonb_object_keys(schema->'properties') LOOP - IF data ? prop AND NOT @extschema@.validate_json_schema(schema->'properties'->prop, data->prop, root_schema) THEN - RETURN false; - END IF; - END LOOP; - END IF; - - IF schema ? 'required' AND jsonb_typeof(data) = 'object' THEN - IF NOT ARRAY(SELECT jsonb_object_keys(data)) @> - ARRAY(SELECT jsonb_array_elements_text(schema->'required')) THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'items' AND jsonb_typeof(data) = 'array' THEN - IF jsonb_typeof(schema->'items') = 'object' THEN - FOR item IN SELECT jsonb_array_elements(data) LOOP - IF NOT @extschema@.validate_json_schema(schema->'items', item, root_schema) THEN - RETURN false; - END IF; - END LOOP; - ELSE - IF NOT ( - SELECT bool_and(i > jsonb_array_length(schema->'items') OR @extschema@.validate_json_schema(schema->'items'->(i::int - 1), elem, root_schema)) - FROM jsonb_array_elements(data) WITH ORDINALITY AS t(elem, i) - ) THEN - RETURN false; - END IF; - END IF; - END IF; - - IF jsonb_typeof(schema->'additionalItems') = 'boolean' and NOT (schema->'additionalItems')::text::boolean AND jsonb_typeof(schema->'items') = 'array' THEN - IF jsonb_array_length(data) > jsonb_array_length(schema->'items') THEN - RETURN false; - END IF; - END IF; - - IF jsonb_typeof(schema->'additionalItems') = 'object' THEN - IF NOT ( - SELECT bool_and(@extschema@.validate_json_schema(schema->'additionalItems', elem, root_schema)) - FROM jsonb_array_elements(data) WITH ORDINALITY AS t(elem, i) - WHERE i > jsonb_array_length(schema->'items') - ) THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'minimum' AND jsonb_typeof(data) = 'number' THEN - IF data::text::numeric < (schema->>'minimum')::numeric THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'maximum' AND jsonb_typeof(data) = 'number' THEN - IF data::text::numeric > (schema->>'maximum')::numeric THEN - RETURN false; - END IF; - END IF; - - IF COALESCE((schema->'exclusiveMinimum')::text::bool, FALSE) THEN - IF data::text::numeric = (schema->>'minimum')::numeric THEN - RETURN false; - END IF; - END IF; - - IF COALESCE((schema->'exclusiveMaximum')::text::bool, FALSE) THEN - IF data::text::numeric = (schema->>'maximum')::numeric THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'anyOf' THEN - IF NOT (SELECT bool_or(@extschema@.validate_json_schema(sub_schema, data, root_schema)) FROM jsonb_array_elements(schema->'anyOf') sub_schema) THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'allOf' THEN - IF NOT (SELECT bool_and(@extschema@.validate_json_schema(sub_schema, data, root_schema)) FROM jsonb_array_elements(schema->'allOf') sub_schema) THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'oneOf' THEN - IF 1 != (SELECT COUNT(*) FROM jsonb_array_elements(schema->'oneOf') sub_schema WHERE @extschema@.validate_json_schema(sub_schema, data, root_schema)) THEN - RETURN false; - END IF; - END IF; - - IF COALESCE((schema->'uniqueItems')::text::boolean, false) THEN - IF (SELECT COUNT(*) FROM jsonb_array_elements(data)) != (SELECT count(DISTINCT val) FROM jsonb_array_elements(data) val) THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'additionalProperties' AND jsonb_typeof(data) = 'object' THEN - props := ARRAY( - SELECT key - FROM jsonb_object_keys(data) key - WHERE key NOT IN (SELECT jsonb_object_keys(schema->'properties')) - AND NOT EXISTS (SELECT * FROM jsonb_object_keys(schema->'patternProperties') pat WHERE key ~ pat) - ); - IF jsonb_typeof(schema->'additionalProperties') = 'boolean' THEN - IF NOT (schema->'additionalProperties')::text::boolean AND jsonb_typeof(data) = 'object' AND NOT props <@ ARRAY(SELECT jsonb_object_keys(schema->'properties')) THEN - RETURN false; - END IF; - ELSEIF NOT ( - SELECT bool_and(@extschema@.validate_json_schema(schema->'additionalProperties', data->key, root_schema)) - FROM unnest(props) key - ) THEN - RETURN false; - END IF; - END IF; - - IF schema ? '$ref' THEN - path := ARRAY( - SELECT regexp_replace(regexp_replace(path_part, '~1', '/'), '~0', '~') - FROM UNNEST(regexp_split_to_array(schema->>'$ref', '/')) path_part - ); - -- ASSERT path[1] = '#', 'only refs anchored at the root are supported'; - IF NOT @extschema@.validate_json_schema(root_schema #> path[2:array_length(path, 1)], data, root_schema) THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'enum' THEN - IF NOT EXISTS (SELECT * FROM jsonb_array_elements(schema->'enum') val WHERE val = data) THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'minLength' AND jsonb_typeof(data) = 'string' THEN - IF char_length(data #>> '{}') < (schema->>'minLength')::numeric THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'maxLength' AND jsonb_typeof(data) = 'string' THEN - IF char_length(data #>> '{}') > (schema->>'maxLength')::numeric THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'not' THEN - IF @extschema@.validate_json_schema(schema->'not', data, root_schema) THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'maxProperties' AND jsonb_typeof(data) = 'object' THEN - IF (SELECT count(*) FROM jsonb_object_keys(data)) > (schema->>'maxProperties')::numeric THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'minProperties' AND jsonb_typeof(data) = 'object' THEN - IF (SELECT count(*) FROM jsonb_object_keys(data)) < (schema->>'minProperties')::numeric THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'maxItems' AND jsonb_typeof(data) = 'array' THEN - IF (SELECT count(*) FROM jsonb_array_elements(data)) > (schema->>'maxItems')::numeric THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'minItems' AND jsonb_typeof(data) = 'array' THEN - IF (SELECT count(*) FROM jsonb_array_elements(data)) < (schema->>'minItems')::numeric THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'dependencies' THEN - FOR prop IN SELECT jsonb_object_keys(schema->'dependencies') LOOP - IF data ? prop THEN - IF jsonb_typeof(schema->'dependencies'->prop) = 'array' THEN - IF NOT (SELECT bool_and(data ? dep) FROM jsonb_array_elements_text(schema->'dependencies'->prop) dep) THEN - RETURN false; - END IF; - ELSE - IF NOT @extschema@.validate_json_schema(schema->'dependencies'->prop, data, root_schema) THEN - RETURN false; - END IF; - END IF; - END IF; - END LOOP; - END IF; - - IF schema ? 'pattern' AND jsonb_typeof(data) = 'string' THEN - IF (data #>> '{}') !~ (schema->>'pattern') THEN - RETURN false; - END IF; - END IF; - - IF schema ? 'patternProperties' AND jsonb_typeof(data) = 'object' THEN - FOR prop IN SELECT jsonb_object_keys(data) LOOP - FOR pattern IN SELECT jsonb_object_keys(schema->'patternProperties') LOOP - RAISE NOTICE 'prop %s, pattern %, schema %', prop, pattern, schema->'patternProperties'->pattern; - IF prop ~ pattern AND NOT @extschema@.validate_json_schema(schema->'patternProperties'->pattern, data->prop, root_schema) THEN - RETURN false; - END IF; - END LOOP; - END LOOP; - END IF; - - IF schema ? 'multipleOf' AND jsonb_typeof(data) = 'number' THEN - IF data::text::numeric % (schema->>'multipleOf')::numeric != 0 THEN - RETURN false; - END IF; - END IF; - - RETURN true; -END; -$f$ LANGUAGE 'plpgsql' IMMUTABLE; diff --git a/packages/server/postgres/extensions/postgres-json-schema/postgres-json-schema.control b/packages/server/postgres/extensions/postgres-json-schema/postgres-json-schema.control deleted file mode 100644 index eaaf496b08a..00000000000 --- a/packages/server/postgres/extensions/postgres-json-schema/postgres-json-schema.control +++ /dev/null @@ -1,3 +0,0 @@ -comment = 'Validate JSON schemas' -relocatable = false -default_version = '0.1.1' diff --git a/packages/server/postgres/postgres.conf b/packages/server/postgres/postgres.conf deleted file mode 100644 index 81a24496bf9..00000000000 --- a/packages/server/postgres/postgres.conf +++ /dev/null @@ -1 +0,0 @@ -listen_addresses='*' From f16c21ffcfd99f98b49846951e561c4afeebbdf2 Mon Sep 17 00:00:00 2001 From: Rafael Romero Date: Tue, 19 Mar 2024 11:28:52 +0000 Subject: [PATCH 076/529] fix(build-ci): docker-build-push action fixed --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 32dab47c748..709f0a2d818 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -105,7 +105,7 @@ jobs: username: "oauth2accesstoken" password: "${{ steps.auth.outputs.access_token }}" - name: Push build to dev - uses: docker/images-push-action@v4 + uses: docker/build-push-action@v4 with: network: host allow: network.host From 41f5654bc3046f770893c6840d4843ff58bce087 Mon Sep 17 00:00:00 2001 From: Rafael Romero Date: Tue, 19 Mar 2024 11:35:24 +0000 Subject: [PATCH 077/529] fix(parabol-ubi): references to local files corrected --- docker/images/parabol-ubi/dockerfiles/basic.dockerfile | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docker/images/parabol-ubi/dockerfiles/basic.dockerfile b/docker/images/parabol-ubi/dockerfiles/basic.dockerfile index a2bbbe8c1bb..f74101cf9cc 100644 --- a/docker/images/parabol-ubi/dockerfiles/basic.dockerfile +++ b/docker/images/parabol-ubi/dockerfiles/basic.dockerfile @@ -7,8 +7,8 @@ ENV HOME=/home/node \ ENV NPM_CONFIG_PREFIX=/home/node/.npm-global ENV PORT=3000 -COPY --chown=node --chmod=755 docker/parabol-ubi/entrypoints/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh -COPY --chown=node docker/parabol-ubi/tools/ip-to-server_id ${HOME}/tools/ip-to-server_id +COPY --chown=node --chmod=755 docker/images/parabol-ubi/entrypoints/docker-entrypoint.sh /usr/local/bin/docker-entrypoint.sh +COPY --chown=node docker/images/parabol-ubi/tools/ip-to-server_id ${HOME}/tools/ip-to-server_id # Required for pushToCDN to work with FILE_STORE_PROVIDER set to 'local' RUN mkdir -p ${HOME}/parabol/self-hosted && \ From 00a1ca2977cd1117b030aa538f526a24ca395ac9 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 19 Mar 2024 18:02:29 +0100 Subject: [PATCH 078/529] fix: Activity library illustrations in Firefox (#9549) --- packages/client/components/ActivityLibrary/ActivityCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/components/ActivityLibrary/ActivityCard.tsx b/packages/client/components/ActivityLibrary/ActivityCard.tsx index 5d671b49b3e..59a955d6249 100644 --- a/packages/client/components/ActivityLibrary/ActivityCard.tsx +++ b/packages/client/components/ActivityLibrary/ActivityCard.tsx @@ -35,8 +35,8 @@ export const ActivityCardImage = (props: PropsWithChildren + Date: Tue, 19 Mar 2024 18:02:45 +0100 Subject: [PATCH 079/529] fix: Configure trusted proxies (#9548) --- .env.example | 3 +++ packages/server/utils/uwsGetIP.ts | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/.env.example b/.env.example index 97dc499d644..2aa11780748 100644 --- a/.env.example +++ b/.env.example @@ -5,6 +5,9 @@ PROTO='http' SERVER_SECRET='key_SERVER_SECRET' # Cluster node number 0 - 1023. Must be unique per process. SERVER_ID='1' +# Used to read the client IP from the X-Forwarded-For header, if not set, it will use the first IP in the list. +# If configured, it must match the number of proxies in the stack, otherwise it might rate limit all traffic coming from the proxy. +# TRUSTED_PROXY_COUNT='1' # Websocket port for the websocket server, only used in development (yarn dev) SOCKET_PORT='3001' diff --git a/packages/server/utils/uwsGetIP.ts b/packages/server/utils/uwsGetIP.ts index 034b266a3c9..b41765b100d 100644 --- a/packages/server/utils/uwsGetIP.ts +++ b/packages/server/utils/uwsGetIP.ts @@ -1,7 +1,11 @@ import {HttpRequest, HttpResponse} from 'uWebSockets.js' +const TRUSTED_PROXY_COUNT = Number(process.env.TRUSTED_PROXY_COUNT) +// if TRUSTED_PROXY_COUNT is not configured correctly we fall back to reading the first IP to avoid rate limiting our proxy +const CLIENT_IP_POS = isNaN(TRUSTED_PROXY_COUNT) ? 0 : -1 - TRUSTED_PROXY_COUNT + const uwsGetIP = (res: HttpResponse, req: HttpRequest) => { - const clientIp = req.getHeader('x-forwarded-for')?.split(',')[0] + const clientIp = req.getHeader('x-forwarded-for')?.split(',').at(CLIENT_IP_POS) if (clientIp) return clientIp // returns ipv6 e.g. '0000:0000:0000:0000:0000:ffff:ac11:0001' return Buffer.from(res.getRemoteAddressAsText()).toString() From b4ac8741f0f36cfb074cfa3b896407636785a276 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 19 Mar 2024 18:11:43 +0100 Subject: [PATCH 080/529] chore(release): release v7.22.3 (#9547) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 16 ++++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 27 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a5e4e701ea7..f9343c64067 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.22.2" + ".": "7.22.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index e40f96936c3..b4b60c13d98 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.22.3](https://github.com/ParabolInc/parabol/compare/v7.22.2...v7.22.3) (2024-03-19) + + +### Fixed + +* Activity library illustrations in Firefox ([#9549](https://github.com/ParabolInc/parabol/issues/9549)) ([00a1ca2](https://github.com/ParabolInc/parabol/commit/00a1ca2977cd1117b030aa538f526a24ca395ac9)) +* **build-ci:** docker-build-push action fixed ([f16c21f](https://github.com/ParabolInc/parabol/commit/f16c21ffcfd99f98b49846951e561c4afeebbdf2)) +* Configure trusted proxies ([#9548](https://github.com/ParabolInc/parabol/issues/9548)) ([24df17b](https://github.com/ParabolInc/parabol/commit/24df17bf3f0979ab65f785e95711ba53158ecb42)) +* **parabol-ubi:** references to local files corrected ([41f5654](https://github.com/ParabolInc/parabol/commit/41f5654bc3046f770893c6840d4843ff58bce087)) + + +### Changed + +* Remove random team names ([#9543](https://github.com/ParabolInc/parabol/issues/9543)) ([fe128f0](https://github.com/ParabolInc/parabol/commit/fe128f017f01148ebd132fd532a771c6ab80ef16)) +* **repo-structure:** Docker images and stacks organized and clarified ([#9530](https://github.com/ParabolInc/parabol/issues/9530)) ([6fca12c](https://github.com/ParabolInc/parabol/commit/6fca12c814f471ef33954381ee562cbbb4b93d67)) + ## [7.22.2](https://github.com/ParabolInc/parabol/compare/v7.22.1...v7.22.2) (2024-03-18) diff --git a/package.json b/package.json index e19d16f1941..27e1d48c0e2 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.22.2", + "version": "7.22.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index cc50069e63f..bd4099d0ee5 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.22.2", + "version": "7.22.3", "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.22.2" + "parabol-server": "7.22.3" } } diff --git a/packages/client/package.json b/packages/client/package.json index 82271ce03b8..2c2882a25a8 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.22.2", + "version": "7.22.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 9c8df6f3f95..51da1395eb3 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.22.2", + "version": "7.22.3", "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.22.2", - "parabol-server": "7.22.2", + "parabol-client": "7.22.3", + "parabol-server": "7.22.3", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index fed0f57a2eb..20fcc803893 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.22.2", + "version": "7.22.3", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 4fa8018c480..25a503e3254 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.22.2", + "version": "7.22.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.22.2", + "parabol-client": "7.22.3", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 4ba2c9eb059325aedfaf6a4b87fae9054245f83a Mon Sep 17 00:00:00 2001 From: Rafael Romero Date: Wed, 20 Mar 2024 10:26:01 +0000 Subject: [PATCH 081/529] chore(ci): Gitlab deployment access token changed --- .github/workflows/release-to-prod.yml | 4 ++-- .github/workflows/release-to-staging.yml | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/release-to-prod.yml b/.github/workflows/release-to-prod.yml index 9e964a20718..037676e4f62 100644 --- a/.github/workflows/release-to-prod.yml +++ b/.github/workflows/release-to-prod.yml @@ -31,14 +31,14 @@ jobs: command: | RES=$(curl "https://gitlab.com/api/v4/projects/${{ vars.GITLAB_PROJECT_ID }}/jobs/${{ env.JOB_ID }}/play" \ --request POST \ - --header 'PRIVATE-TOKEN: ${{ secrets.GITLAB_API_TOKEN }}') + --header 'PRIVATE-TOKEN: ${{ secrets.GITLAB_DEPLOYMENT_ACCESS_TOKEN }}') echo $RES JOB_ID_DONE=$(echo $RES | jq '.id // empty') [ -z "$JOB_ID_DONE" ] && exit 1 || exit 0 - name: Poll Production Release uses: artiz/poll-endpoint@1.0.2 with: - url: https://gitlab.com/api/v4/projects/${{ vars.GITLAB_PROJECT_ID }}/jobs/${{ env.JOB_ID }}?access_token=${{ secrets.GITLAB_API_TOKEN }} + url: https://gitlab.com/api/v4/projects/${{ vars.GITLAB_PROJECT_ID }}/jobs/${{ env.JOB_ID }}?access_token=${{ secrets.GITLAB_DEPLOYMENT_ACCESS_TOKEN }} method: GET expect-status: 200 expect-response-regex: '"status":"success"' diff --git a/.github/workflows/release-to-staging.yml b/.github/workflows/release-to-staging.yml index c6e3601f2ff..bc30de202b3 100644 --- a/.github/workflows/release-to-staging.yml +++ b/.github/workflows/release-to-staging.yml @@ -42,7 +42,7 @@ jobs: run: | COMMIT_ID=$(curl "https://gitlab.com/api/v4/projects/${{ vars.GITLAB_PROJECT_ID }}/repository/commits" \ --request POST \ - --header 'PRIVATE-TOKEN: ${{ secrets.GITLAB_API_TOKEN }}' \ + --header 'PRIVATE-TOKEN: ${{ secrets.GITLAB_DEPLOYMENT_ACCESS_TOKEN }}' \ --form "branch=main" \ --form "commit_message=release v${{ env.ACTION_VERSION }}" \ --form "actions[][action]=update" \ @@ -67,11 +67,11 @@ jobs: command: | echo ${{ env.COMMIT_ID }} PIPELINES=$(curl "https://gitlab.com/api/v4/projects/${{ vars.GITLAB_PROJECT_ID }}/pipelines" \ - --header 'PRIVATE-TOKEN: ${{ secrets.GITLAB_API_TOKEN }}') + --header 'PRIVATE-TOKEN: ${{ secrets.GITLAB_DEPLOYMENT_ACCESS_TOKEN }}') PIPELINE_ID=$(echo $PIPELINES | jq ".[] | select(.sha == \"${{ env.COMMIT_ID }}\")" | jq .id) [ -z "$PIPELINE_ID" ] && exit 1 JOBS=$(curl "https://gitlab.com/api/v4/projects/${{ vars.GITLAB_PROJECT_ID }}/pipelines/$PIPELINE_ID/jobs" \ - --header 'PRIVATE-TOKEN: ${{ secrets.GITLAB_API_TOKEN }}') + --header 'PRIVATE-TOKEN: ${{ secrets.GITLAB_DEPLOYMENT_ACCESS_TOKEN }}') JOB_ID=$(echo $JOBS | jq '.[] | select(.name == "${{ env.STAGING_JOB }}")' | jq .id) PROD_JOB_ID=$(echo $JOBS | jq '.[] | select(.name == "${{ env.PRODUCTION_JOB}}")' | jq .id) echo "JOB_ID=${JOB_ID}" >> $GITHUB_ENV @@ -86,7 +86,7 @@ jobs: command: | RES=$(curl "https://gitlab.com/api/v4/projects/${{ vars.GITLAB_PROJECT_ID }}/jobs/${{ env.JOB_ID }}/play" \ --request POST \ - --header 'PRIVATE-TOKEN: ${{ secrets.GITLAB_API_TOKEN }}') + --header 'PRIVATE-TOKEN: ${{ secrets.GITLAB_DEPLOYMENT_ACCESS_TOKEN }}') echo $RES JOB_ID_DONE=$(echo $RES | jq '.id // empty') [ -z "$JOB_ID_DONE" ] && exit 1 || exit 0 @@ -114,7 +114,7 @@ jobs: - name: Poll Staging Release uses: artiz/poll-endpoint@1.0.2 with: - url: https://gitlab.com/api/v4/projects/${{ vars.GITLAB_PROJECT_ID }}/jobs/${{ env.JOB_ID }}?access_token=${{ secrets.GITLAB_API_TOKEN }} + url: https://gitlab.com/api/v4/projects/${{ vars.GITLAB_PROJECT_ID }}/jobs/${{ env.JOB_ID }}?access_token=${{ secrets.GITLAB_DEPLOYMENT_ACCESS_TOKEN }} method: GET expect-status: 200 expect-response-regex: '"status":"success"' From 94513aee044fe6f2e5eb21c62d82fb333193ac73 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 20 Mar 2024 10:33:17 +0000 Subject: [PATCH 082/529] chore(release): release v7.22.4 (#9552) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 18 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f9343c64067..4a2437d9945 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.22.3" + ".": "7.22.4" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b4b60c13d98..440fc75fbfd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.22.4](https://github.com/ParabolInc/parabol/compare/v7.22.3...v7.22.4) (2024-03-20) + + +### Changed + +* **ci:** Gitlab deployment access token changed ([4ba2c9e](https://github.com/ParabolInc/parabol/commit/4ba2c9eb059325aedfaf6a4b87fae9054245f83a)) + ## [7.22.3](https://github.com/ParabolInc/parabol/compare/v7.22.2...v7.22.3) (2024-03-19) diff --git a/package.json b/package.json index 27e1d48c0e2..034f26d340e 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.22.3", + "version": "7.22.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index bd4099d0ee5..66bfb9f4458 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.22.3", + "version": "7.22.4", "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.22.3" + "parabol-server": "7.22.4" } } diff --git a/packages/client/package.json b/packages/client/package.json index 2c2882a25a8..0a468113a2f 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.22.3", + "version": "7.22.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 51da1395eb3..c4470e61f05 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.22.3", + "version": "7.22.4", "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.22.3", - "parabol-server": "7.22.3", + "parabol-client": "7.22.4", + "parabol-server": "7.22.4", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 20fcc803893..a3d12e08429 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.22.3", + "version": "7.22.4", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 25a503e3254..9138ece5b9a 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.22.3", + "version": "7.22.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.22.3", + "parabol-client": "7.22.4", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 9be96eb206d367e550b97831621c8b2aee4fc355 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Thu, 21 Mar 2024 11:05:07 -0700 Subject: [PATCH 083/529] feat: make invoice row title more clear to understand (#9551) --- .../components/InvoiceRow/InvoiceRow.tsx | 21 ++++++++++++++++--- packages/client/utils/makeMonthDateString.ts | 10 +++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) create mode 100644 packages/client/utils/makeMonthDateString.ts diff --git a/packages/client/modules/userDashboard/components/InvoiceRow/InvoiceRow.tsx b/packages/client/modules/userDashboard/components/InvoiceRow/InvoiceRow.tsx index 479cca7a33d..6651c90663a 100644 --- a/packages/client/modules/userDashboard/components/InvoiceRow/InvoiceRow.tsx +++ b/packages/client/modules/userDashboard/components/InvoiceRow/InvoiceRow.tsx @@ -9,7 +9,7 @@ import RowInfo from '../../../../components/Row/RowInfo' import RowInfoHeading from '../../../../components/Row/RowInfoHeading' import {PALETTE} from '../../../../styles/paletteV3' import makeDateString from '../../../../utils/makeDateString' -import makeMonthString from '../../../../utils/makeMonthString' +import makeMonthDateString from '../../../../utils/makeMonthDateString' import invoiceLineFormat from '../../../invoice/helpers/invoiceLineFormat' const InvoiceAmount = styled('span')({ @@ -70,6 +70,9 @@ const InvoiceRow = (props: Props) => { brand } endAt + nextPeriodCharges { + nextPeriodEnd + } paidAt payUrl status @@ -77,7 +80,17 @@ const InvoiceRow = (props: Props) => { `, invoiceRef ) - const {id: invoiceId, amountDue, creditCard, endAt, paidAt, payUrl, status} = invoice + const { + id: invoiceId, + amountDue, + creditCard, + endAt, + nextPeriodCharges, + paidAt, + payUrl, + status + } = invoice + const {nextPeriodEnd} = nextPeriodCharges const isEstimate = status === 'UPCOMING' return ( @@ -91,7 +104,9 @@ const InvoiceRow = (props: Props) => { - {makeMonthString(endAt)} + + {makeMonthDateString(endAt)} to {makeMonthDateString(nextPeriodEnd)} + {isEstimate && '*'} diff --git a/packages/client/utils/makeMonthDateString.ts b/packages/client/utils/makeMonthDateString.ts new file mode 100644 index 00000000000..fd242ebffcc --- /dev/null +++ b/packages/client/utils/makeMonthDateString.ts @@ -0,0 +1,10 @@ +import ensureDate from './ensureDate' + +export default function makeMonthDateString(datetime: Date | string | null) { + const timestamp = ensureDate(datetime) + return timestamp.toLocaleDateString('en-US', { + year: 'numeric', + month: 'long', + day: 'numeric' + }) +} From 2352669ea516a3d764d63af77211fbb4c0a02563 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Fri, 22 Mar 2024 00:34:03 +0000 Subject: [PATCH 084/529] feat: allow 2 custom templates for every user (#9518) --- .../ActivityDetailsSidebar.tsx | 56 +++++-------------- .../CreateNewActivity/CreateNewActivity.tsx | 29 ++++++---- .../components/AddNewPokerTemplate.tsx | 10 ++-- .../components/AddNewReflectTemplate.tsx | 9 +-- .../mutations/AddPokerTemplateMutation.ts | 3 + .../mutations/AddReflectTemplateMutation.ts | 3 + .../graphql/mutations/addPokerTemplate.ts | 15 +++-- .../graphql/mutations/addReflectTemplate.ts | 15 +++-- .../graphql/public/typeDefs/User.graphql | 8 +++ .../graphql/types/AddPokerTemplatePayload.ts | 10 ++++ .../types/AddReflectTemplatePayload.ts | 10 ++++ ...3510511_addFreeCustomTemplatesRemaining.ts | 34 +++++++++++ .../decrementFreeTemplatesRemaining.ts | 18 ++++++ 13 files changed, 150 insertions(+), 70 deletions(-) create mode 100644 packages/server/postgres/migrations/1709933510511_addFreeCustomTemplatesRemaining.ts create mode 100644 packages/server/postgres/queries/decrementFreeTemplatesRemaining.ts diff --git a/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx b/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx index a686ca89c36..20574555b3b 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx @@ -32,7 +32,6 @@ import NewMeetingSettingsToggleCheckIn from '../NewMeetingSettingsToggleCheckIn' import StyledError from '../StyledError' import FlatPrimaryButton from '../FlatPrimaryButton' import NewMeetingActionsCurrentMeetings from '../NewMeetingActionsCurrentMeetings' -import RaisedButton from '../RaisedButton' import NewMeetingTeamPicker from '../NewMeetingTeamPicker' import {AdhocTeamMultiSelect, Option} from '../AdhocTeamMultiSelect/AdhocTeamMultiSelect' import {Select} from '../../ui/Select/Select' @@ -78,7 +77,6 @@ const ActivityDetailsSidebar = (props: Props) => { fragment ActivityDetailsSidebar_viewer on User { featureFlags { adHocTeams - noTemplateLimit } ...AdhocTeamMultiSelect_viewer organizations { @@ -295,14 +293,6 @@ const ActivityDetailsSidebar = (props: Props) => {
) - const handleUpgrade = () => { - SendClientSideEvent(atmosphere, 'Upgrade CTA Clicked', { - upgradeCTALocation: 'publicTemplate', - meetingType: type - }) - history.push(`/me/organizations/${selectedTeam.orgId}/billing`) - } - const meetingNamePlaceholder = type === 'retrospective' ? 'Retro' @@ -396,42 +386,22 @@ const ActivityDetailsSidebar = (props: Props) => { /> )} - {selectedTeam.tier === 'starter' && - !viewer.featureFlags.noTemplateLimit && - !selectedTemplate.isFree ? ( -
-
- Upgrade to the Team Plan to create custom activities unlocking your - team’s ideal workflow. -
- - Upgrade to Team Plan - -
- ) : ( + {type === 'retrospective' && ( <> - {type === 'retrospective' && ( - <> - - - - - )} - {type === 'poker' && ( - - )} - {type === 'action' && ( - - )} + + + )} + {type === 'poker' && ( + + )} + {type === 'action' && ( + + )}
diff --git a/packages/client/components/ActivityLibrary/CreateNewActivity/CreateNewActivity.tsx b/packages/client/components/ActivityLibrary/CreateNewActivity/CreateNewActivity.tsx index 8346b47a252..f105e0e1aab 100644 --- a/packages/client/components/ActivityLibrary/CreateNewActivity/CreateNewActivity.tsx +++ b/packages/client/components/ActivityLibrary/CreateNewActivity/CreateNewActivity.tsx @@ -95,9 +95,8 @@ const SUPPORTED_CUSTOM_ACTIVITIES: SupportedActivity[] = [ const query = graphql` query CreateNewActivityQuery { viewer { - featureFlags { - noTemplateLimit - } + freeCustomRetroTemplatesRemaining + freeCustomPokerTemplatesRemaining preferredTeamId teams { id @@ -135,12 +134,21 @@ export const CreateNewActivity = (props: Props) => { return selectedActivity }) const {viewer} = data - const {teams, preferredTeamId, featureFlags} = viewer + const { + teams, + preferredTeamId, + freeCustomRetroTemplatesRemaining, + freeCustomPokerTemplatesRemaining + } = viewer const [selectedTeam, setSelectedTeam] = useState( teams.find((team) => team.id === preferredTeamId) ?? sortByTier(teams)[0]! ) const {submitting, error, submitMutation, onError, onCompleted} = useMutationProps() const history = useHistory() + const freeCustomTemplatesRemaining = + selectedActivity.type === 'retrospective' + ? freeCustomRetroTemplatesRemaining + : freeCustomPokerTemplatesRemaining const handleCreateRetroTemplate = () => { if (submitting) { @@ -284,16 +292,15 @@ export const CreateNewActivity = (props: Props) => {
{error &&
{error.message}
}
- {selectedTeam.tier === 'starter' && !featureFlags.noTemplateLimit ? ( -
-
- Upgrade to the Team Plan to create custom activities unlocking your team’s - ideal workflow. -
+ {selectedTeam.tier === 'starter' && freeCustomTemplatesRemaining === 0 ? ( +
+ + Upgrade to the Team Plan to create more custom activities + Upgrade to Team Plan diff --git a/packages/client/modules/meeting/components/AddNewPokerTemplate.tsx b/packages/client/modules/meeting/components/AddNewPokerTemplate.tsx index 053684315a3..b1e4c9e5206 100644 --- a/packages/client/modules/meeting/components/AddNewPokerTemplate.tsx +++ b/packages/client/modules/meeting/components/AddNewPokerTemplate.tsx @@ -53,9 +53,7 @@ const AddNewPokerTemplate = (props: Props) => { id user { id - featureFlags { - noTemplateLimit - } + freeCustomPokerTemplatesRemaining } } } @@ -63,6 +61,9 @@ const AddNewPokerTemplate = (props: Props) => { teamRef ) const {id: teamId, tier, viewerTeamMember} = team + const {user} = viewerTeamMember || {} + const {freeCustomPokerTemplatesRemaining} = user || {} + const {onError, onCompleted, submitMutation, submitting, error} = useMutationProps() const errorTimerId = useRef() useEffect(() => { @@ -71,7 +72,8 @@ const AddNewPokerTemplate = (props: Props) => { } }, []) const canEditTemplates = - tier !== 'starter' || viewerTeamMember?.user?.featureFlags?.noTemplateLimit + tier !== 'starter' || + (freeCustomPokerTemplatesRemaining && freeCustomPokerTemplatesRemaining > 0) const addNewTemplate = () => { if (submitting) return if (!canEditTemplates) { diff --git a/packages/client/modules/meeting/components/AddNewReflectTemplate.tsx b/packages/client/modules/meeting/components/AddNewReflectTemplate.tsx index 61429332903..4e1bff87056 100644 --- a/packages/client/modules/meeting/components/AddNewReflectTemplate.tsx +++ b/packages/client/modules/meeting/components/AddNewReflectTemplate.tsx @@ -53,9 +53,7 @@ const AddNewReflectTemplate = (props: Props) => { id user { id - featureFlags { - noTemplateLimit - } + freeCustomRetroTemplatesRemaining } } } @@ -63,6 +61,8 @@ const AddNewReflectTemplate = (props: Props) => { teamRef ) const {id: teamId, tier, viewerTeamMember} = team + const {user} = viewerTeamMember || {} + const {freeCustomRetroTemplatesRemaining} = user || {} const {onError, onCompleted, submitMutation, submitting, error} = useMutationProps() const errorTimerId = useRef() useEffect(() => { @@ -71,7 +71,8 @@ const AddNewReflectTemplate = (props: Props) => { } }, []) const canEditTemplates = - tier !== 'starter' || viewerTeamMember?.user?.featureFlags?.noTemplateLimit + tier !== 'starter' || + (freeCustomRetroTemplatesRemaining && freeCustomRetroTemplatesRemaining > 0) const addNewTemplate = () => { if (submitting) return if (!canEditTemplates) { diff --git a/packages/client/mutations/AddPokerTemplateMutation.ts b/packages/client/mutations/AddPokerTemplateMutation.ts index f8fca48eda5..fa0b1a61769 100644 --- a/packages/client/mutations/AddPokerTemplateMutation.ts +++ b/packages/client/mutations/AddPokerTemplateMutation.ts @@ -10,6 +10,9 @@ import handleAddMeetingTemplate from './handlers/handleAddMeetingTemplate' graphql` fragment AddPokerTemplateMutation_team on AddPokerTemplatePayload { + user { + freeCustomPokerTemplatesRemaining + } pokerTemplate { ...TemplateSharing_template ...PokerTemplateDetailsTemplate diff --git a/packages/client/mutations/AddReflectTemplateMutation.ts b/packages/client/mutations/AddReflectTemplateMutation.ts index 4bea393ac0a..caf3000057e 100644 --- a/packages/client/mutations/AddReflectTemplateMutation.ts +++ b/packages/client/mutations/AddReflectTemplateMutation.ts @@ -9,6 +9,9 @@ import handleAddMeetingTemplate from './handlers/handleAddMeetingTemplate' graphql` fragment AddReflectTemplateMutation_team on AddReflectTemplatePayload { + user { + freeCustomRetroTemplatesRemaining + } reflectTemplate { ...TemplateSharing_template ...ReflectTemplateDetailsTemplate diff --git a/packages/server/graphql/mutations/addPokerTemplate.ts b/packages/server/graphql/mutations/addPokerTemplate.ts index 3b4e6dbccc0..cb6d7f390bb 100644 --- a/packages/server/graphql/mutations/addPokerTemplate.ts +++ b/packages/server/graphql/mutations/addPokerTemplate.ts @@ -12,6 +12,7 @@ import AddPokerTemplatePayload from '../types/AddPokerTemplatePayload' import getTemplateIllustrationUrl from './helpers/getTemplateIllustrationUrl' import {analytics} from '../../utils/analytics/analytics' import {getFeatureTier} from '../types/helpers/getFeatureTier' +import decrementFreeTemplatesRemaining from '../../postgres/queries/decrementFreeTemplatesRemaining' const addPokerTemplate = { description: 'Add a new poker template with a default dimension created', @@ -51,9 +52,11 @@ const addPokerTemplate = { } if ( getFeatureTier(viewerTeam) === 'starter' && - !viewer.featureFlags.includes('noTemplateLimit') + viewer.freeCustomPokerTemplatesRemaining === 0 ) { - return standardError(new Error('Creating templates is a premium feature'), {userId: viewerId}) + return standardError(new Error('You have reached the limit of free custom templates.'), { + userId: viewerId + }) } let data if (parentTemplateId) { @@ -102,8 +105,10 @@ const addPokerTemplate = { await Promise.all([ r.table('TemplateDimension').insert(newTemplateDimensions).run(), - insertMeetingTemplate(newTemplate) + insertMeetingTemplate(newTemplate), + decrementFreeTemplatesRemaining(viewerId, 'poker') ]) + viewer.freeCustomPokerTemplatesRemaining = viewer.freeCustomPokerTemplatesRemaining - 1 analytics.templateMetrics(viewer, newTemplate, 'Template Cloned') data = {templateId: newTemplate.id} } else { @@ -129,8 +134,10 @@ const addPokerTemplate = { await Promise.all([ r.table('TemplateDimension').insert(newDimension).run(), - insertMeetingTemplate(newTemplate) + insertMeetingTemplate(newTemplate), + decrementFreeTemplatesRemaining(viewerId, 'poker') ]) + viewer.freeCustomPokerTemplatesRemaining = viewer.freeCustomPokerTemplatesRemaining - 1 analytics.templateMetrics(viewer, newTemplate, 'Template Created') data = {templateId} } diff --git a/packages/server/graphql/mutations/addReflectTemplate.ts b/packages/server/graphql/mutations/addReflectTemplate.ts index d4c3825338a..88fd88cad46 100644 --- a/packages/server/graphql/mutations/addReflectTemplate.ts +++ b/packages/server/graphql/mutations/addReflectTemplate.ts @@ -13,6 +13,7 @@ import AddReflectTemplatePayload from '../types/AddReflectTemplatePayload' import makeRetroTemplates from './helpers/makeRetroTemplates' import {analytics} from '../../utils/analytics/analytics' import {getFeatureTier} from '../types/helpers/getFeatureTier' +import decrementFreeCustomTemplatesRemaining from '../../postgres/queries/decrementFreeTemplatesRemaining' const addReflectTemplate = { description: 'Add a new template full of prompts', @@ -52,9 +53,11 @@ const addReflectTemplate = { } if ( getFeatureTier(viewerTeam) === 'starter' && - !viewer.featureFlags.includes('noTemplateLimit') + viewer.freeCustomRetroTemplatesRemaining === 0 ) { - return standardError(new Error('Creating templates is a premium feature'), {userId: viewerId}) + return standardError(new Error('You have reached the limit of free custom templates.'), { + userId: viewerId + }) } let data if (parentTemplateId) { @@ -102,8 +105,10 @@ const addReflectTemplate = { await Promise.all([ r.table('ReflectPrompt').insert(newTemplatePrompts).run(), - insertMeetingTemplate(newTemplate) + insertMeetingTemplate(newTemplate), + decrementFreeCustomTemplatesRemaining(viewerId, 'retro') ]) + viewer.freeCustomRetroTemplatesRemaining = viewer.freeCustomRetroTemplatesRemaining - 1 analytics.templateMetrics(viewer, newTemplate, 'Template Cloned') data = {templateId: newTemplate.id} } else { @@ -129,8 +134,10 @@ const addReflectTemplate = { const {id: templateId} = newTemplate await Promise.all([ r.table('ReflectPrompt').insert(newTemplatePrompts).run(), - insertMeetingTemplate(newTemplate) + insertMeetingTemplate(newTemplate), + decrementFreeCustomTemplatesRemaining(viewerId, 'retro') ]) + viewer.freeCustomRetroTemplatesRemaining = viewer.freeCustomRetroTemplatesRemaining - 1 analytics.templateMetrics(viewer, newTemplate, 'Template Created') data = {templateId} } diff --git a/packages/server/graphql/public/typeDefs/User.graphql b/packages/server/graphql/public/typeDefs/User.graphql index 232c2027391..e204127f142 100644 --- a/packages/server/graphql/public/typeDefs/User.graphql +++ b/packages/server/graphql/public/typeDefs/User.graphql @@ -458,4 +458,12 @@ type User { """ domain: String! ): ParseSAMLMetadataPayload! + """ + The number of free custom retro templates remaining + """ + freeCustomRetroTemplatesRemaining: Int! + """ + The number of free custom poker templates remaining + """ + freeCustomPokerTemplatesRemaining: Int! } diff --git a/packages/server/graphql/types/AddPokerTemplatePayload.ts b/packages/server/graphql/types/AddPokerTemplatePayload.ts index 2616e83a8ff..0536e3203cf 100644 --- a/packages/server/graphql/types/AddPokerTemplatePayload.ts +++ b/packages/server/graphql/types/AddPokerTemplatePayload.ts @@ -2,6 +2,8 @@ import {GraphQLObjectType} from 'graphql' import {GQLContext} from '../graphql' import PokerTemplate from './PokerTemplate' import StandardMutationError from './StandardMutationError' +import {getUserId} from '../../utils/authorization' +import User from './User' const AddPokerTemplatePayload = new GraphQLObjectType({ name: 'AddPokerTemplatePayload', @@ -15,6 +17,14 @@ const AddPokerTemplatePayload = new GraphQLObjectType({ if (!templateId) return null return dataLoader.get('meetingTemplates').load(templateId) } + }, + user: { + type: User, + resolve: (_, _args: unknown, {dataLoader, authToken}) => { + const userId = getUserId(authToken) + if (!userId) return null + return dataLoader.get('users').load(userId) + } } }) }) diff --git a/packages/server/graphql/types/AddReflectTemplatePayload.ts b/packages/server/graphql/types/AddReflectTemplatePayload.ts index 3c6e4bcf5bf..07de89347a2 100644 --- a/packages/server/graphql/types/AddReflectTemplatePayload.ts +++ b/packages/server/graphql/types/AddReflectTemplatePayload.ts @@ -2,6 +2,8 @@ import {GraphQLObjectType} from 'graphql' import {GQLContext} from '../graphql' import ReflectTemplate from './ReflectTemplate' import StandardMutationError from './StandardMutationError' +import User from './User' +import {getUserId} from '../../utils/authorization' const AddReflectTemplatePayload = new GraphQLObjectType({ name: 'AddReflectTemplatePayload', @@ -15,6 +17,14 @@ const AddReflectTemplatePayload = new GraphQLObjectType({ if (!templateId) return null return dataLoader.get('meetingTemplates').load(templateId) } + }, + user: { + type: User, + resolve: (_, _args: unknown, {dataLoader, authToken}) => { + const userId = getUserId(authToken) + if (!userId) return null + return dataLoader.get('users').load(userId) + } } }) }) diff --git a/packages/server/postgres/migrations/1709933510511_addFreeCustomTemplatesRemaining.ts b/packages/server/postgres/migrations/1709933510511_addFreeCustomTemplatesRemaining.ts new file mode 100644 index 00000000000..cb378a1ad4b --- /dev/null +++ b/packages/server/postgres/migrations/1709933510511_addFreeCustomTemplatesRemaining.ts @@ -0,0 +1,34 @@ +import {Kysely, PostgresDialect} from 'kysely' +import getPg from '../getPg' + +export async function up() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + await pg.schema + .alterTable('User') + .addColumn('freeCustomRetroTemplatesRemaining', 'int2', (col) => col.defaultTo(2).notNull()) + .addColumn('freeCustomPokerTemplatesRemaining', 'int2', (col) => col.defaultTo(2).notNull()) + .execute() + + await pg.destroy() +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + await pg.schema + .alterTable('User') + .dropColumn('freeCustomRetroTemplatesRemaining') + .dropColumn('freeCustomPokerTemplatesRemaining') + .execute() + + await pg.destroy() +} diff --git a/packages/server/postgres/queries/decrementFreeTemplatesRemaining.ts b/packages/server/postgres/queries/decrementFreeTemplatesRemaining.ts new file mode 100644 index 00000000000..2eef866348b --- /dev/null +++ b/packages/server/postgres/queries/decrementFreeTemplatesRemaining.ts @@ -0,0 +1,18 @@ +import getKysely from '../getKysely' + +const decrementFreeTemplatesRemaining = async (userId: string, templateType: 'retro' | 'poker') => { + const pg = getKysely() + const customTemplateType = + templateType === 'retro' + ? 'freeCustomRetroTemplatesRemaining' + : 'freeCustomPokerTemplatesRemaining' + + await pg + .updateTable('User') + .set((eb) => ({[customTemplateType]: eb(customTemplateType, '-', 1)})) + .where('id', '=', userId) + .where(customTemplateType, '>', 0) + .executeTakeFirst() +} + +export default decrementFreeTemplatesRemaining From ef0fbc2da853e2248a16ff2a2ce37c1f85f07f1a Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Fri, 22 Mar 2024 11:11:43 -0700 Subject: [PATCH 085/529] fix(admin): fix an issue where ORG_ADMIN cannot see members from team they are not in (#9560) --- packages/server/graphql/queries/team.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/server/graphql/queries/team.ts b/packages/server/graphql/queries/team.ts index 40af63c637d..45c55e50764 100644 --- a/packages/server/graphql/queries/team.ts +++ b/packages/server/graphql/queries/team.ts @@ -22,7 +22,13 @@ export default { {authToken, dataLoader}: GQLContext, {operation}: GraphQLResolveInfo ) { - if (!isTeamMember(authToken, teamId) && !isSuperUser(authToken)) { + const team = await dataLoader.get('teams').loadNonNull(teamId) + const {orgId} = team + const viewerId = getUserId(authToken) + const {role} = + (await dataLoader.get('organizationUsersByUserIdOrgId').load({userId: viewerId, orgId})) ?? {} + const isOrgAdmin = role === 'ORG_ADMIN' + if (!isOrgAdmin && !isTeamMember(authToken, teamId) && !isSuperUser(authToken)) { const viewerId = getUserId(authToken) if (!HANDLED_OPS.includes(operation?.name?.value ?? '')) { standardError(new Error('Team not found'), {userId: viewerId}) From d18d75485116c883d72456ac51b817a044a38b4d Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Mon, 25 Mar 2024 10:05:02 +0000 Subject: [PATCH 086/529] chore(github): DevOps review if docker folder is modified or release-please-config is changed (#9562) --- .github/reviewers.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/reviewers.yml b/.github/reviewers.yml index 8d2732ba714..84b51a4d067 100644 --- a/.github/reviewers.yml +++ b/.github/reviewers.yml @@ -34,8 +34,12 @@ files: - data "**/analytics/**": - data + "docker/**": + - devops ".env.example": - devops + "release-please-config.json": + - devops options: ignore_draft: true ignored_keywords: From fe718413fa2d19afa660cc944d30a1284d6c2b18 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Mon, 25 Mar 2024 18:34:07 +0000 Subject: [PATCH 087/529] chore: refactor add template mutation to the new sdl pattern (#9533) --- codegen.json | 2 + .../mutations/AddPokerTemplateMutation.ts | 9 +- .../mutations/AddReflectTemplateMutation.ts | 9 +- .../client/subscriptions/TeamSubscription.ts | 11 +- .../graphql/mutations/addPokerTemplate.ts | 149 ------------------ .../graphql/mutations/addReflectTemplate.ts | 149 ------------------ .../public/mutations/addPokerTemplate.ts | 135 ++++++++++++++++ .../public/mutations/addReflectTemplate.ts | 128 +++++++++++++++ .../public/typeDefs/AddPokerTemplate.graphql | 31 ++++ .../typeDefs/AddReflectTemplate.graphql | 31 ++++ .../public/typeDefs/Subscriptions.graphql | 4 +- .../graphql/public/typeDefs/_legacy.graphql | 22 --- .../public/types/AddPokerTemplateSuccess.ts | 18 +++ .../public/types/AddReflectTemplateSuccess.ts | 18 +++ packages/server/graphql/rootMutation.ts | 4 - .../graphql/types/AddPokerTemplatePayload.ts | 32 ---- .../types/AddReflectTemplatePayload.ts | 32 ---- 17 files changed, 387 insertions(+), 397 deletions(-) delete mode 100644 packages/server/graphql/mutations/addPokerTemplate.ts delete mode 100644 packages/server/graphql/mutations/addReflectTemplate.ts create mode 100644 packages/server/graphql/public/mutations/addPokerTemplate.ts create mode 100644 packages/server/graphql/public/mutations/addReflectTemplate.ts create mode 100644 packages/server/graphql/public/typeDefs/AddPokerTemplate.graphql create mode 100644 packages/server/graphql/public/typeDefs/AddReflectTemplate.graphql create mode 100644 packages/server/graphql/public/types/AddPokerTemplateSuccess.ts create mode 100644 packages/server/graphql/public/types/AddReflectTemplateSuccess.ts delete mode 100644 packages/server/graphql/types/AddPokerTemplatePayload.ts delete mode 100644 packages/server/graphql/types/AddReflectTemplatePayload.ts diff --git a/codegen.json b/codegen.json index f47587c90c1..a0a38ad29f0 100644 --- a/codegen.json +++ b/codegen.json @@ -49,6 +49,8 @@ "ActionMeeting": "../../database/types/MeetingAction#default", "ActionMeetingMember": "../../database/types/ActionMeetingMember#default as ActionMeetingMemberDB", "AddApprovedOrganizationDomainsSuccess": "./types/AddApprovedOrganizationDomainsSuccess#AddApprovedOrganizationDomainsSuccessSource", + "AddReflectTemplateSuccess": "./types/AddReflectTemplateSuccess#AddReflectTemplateSuccessSource", + "AddPokerTemplateSuccess": "./types/AddPokerTemplateSuccess#AddPokerTemplateSuccessSource", "AddTranscriptionBotSuccess": "./types/AddTranscriptionBotSuccess#AddTranscriptionBotSuccessSource", "AddedNotification": "./types/AddedNotification#AddedNotificationSource", "AgendaItem": "../../database/types/AgendaItem#default as AgendaItemDB", diff --git a/packages/client/mutations/AddPokerTemplateMutation.ts b/packages/client/mutations/AddPokerTemplateMutation.ts index fa0b1a61769..7de55a43760 100644 --- a/packages/client/mutations/AddPokerTemplateMutation.ts +++ b/packages/client/mutations/AddPokerTemplateMutation.ts @@ -9,7 +9,7 @@ import {AddPokerTemplateMutation_team$data} from '../__generated__/AddPokerTempl import handleAddMeetingTemplate from './handlers/handleAddMeetingTemplate' graphql` - fragment AddPokerTemplateMutation_team on AddPokerTemplatePayload { + fragment AddPokerTemplateMutation_team on AddPokerTemplateSuccess { user { freeCustomPokerTemplatesRemaining } @@ -26,6 +26,11 @@ graphql` const mutation = graphql` mutation AddPokerTemplateMutation($teamId: ID!, $parentTemplateId: ID) { addPokerTemplate(teamId: $teamId, parentTemplateId: $parentTemplateId) { + ... on ErrorPayload { + error { + message + } + } ...AddPokerTemplateMutation_team @relay(mask: false) } } @@ -58,7 +63,7 @@ const AddPokerTemplateMutation: StandardMutation = ( updater: (store) => { const payload = store.getRootField('addPokerTemplate') if (!payload) return - addPokerTemplateTeamUpdater(payload, {atmosphere, store}) + addPokerTemplateTeamUpdater(payload as any, {atmosphere, store}) }, optimisticUpdater: (store) => { const {parentTemplateId, teamId} = variables diff --git a/packages/client/mutations/AddReflectTemplateMutation.ts b/packages/client/mutations/AddReflectTemplateMutation.ts index caf3000057e..0b74c28760a 100644 --- a/packages/client/mutations/AddReflectTemplateMutation.ts +++ b/packages/client/mutations/AddReflectTemplateMutation.ts @@ -8,7 +8,7 @@ import {AddReflectTemplateMutation_team$data} from '../__generated__/AddReflectT import handleAddMeetingTemplate from './handlers/handleAddMeetingTemplate' graphql` - fragment AddReflectTemplateMutation_team on AddReflectTemplatePayload { + fragment AddReflectTemplateMutation_team on AddReflectTemplateSuccess { user { freeCustomRetroTemplatesRemaining } @@ -25,6 +25,11 @@ graphql` const mutation = graphql` mutation AddReflectTemplateMutation($teamId: ID!, $parentTemplateId: ID) { addReflectTemplate(teamId: $teamId, parentTemplateId: $parentTemplateId) { + ... on ErrorPayload { + error { + message + } + } ...AddReflectTemplateMutation_team @relay(mask: false) } } @@ -57,7 +62,7 @@ const AddReflectTemplateMutation: StandardMutation updater: (store) => { const payload = store.getRootField('addReflectTemplate') if (!payload) return - addReflectTemplateTeamUpdater(payload, {atmosphere, store}) + addReflectTemplateTeamUpdater(payload as any, {atmosphere, store}) }, optimisticUpdater: (store) => { const {parentTemplateId, teamId} = variables diff --git a/packages/client/subscriptions/TeamSubscription.ts b/packages/client/subscriptions/TeamSubscription.ts index 6bce58cfa10..10e14740d4c 100644 --- a/packages/client/subscriptions/TeamSubscription.ts +++ b/packages/client/subscriptions/TeamSubscription.ts @@ -17,7 +17,6 @@ import { acceptTeamInvitationTeamUpdater } from '../mutations/AcceptTeamInvitationMutation' import {addAgendaItemUpdater} from '../mutations/AddAgendaItemMutation' -import {addReflectTemplateTeamUpdater} from '../mutations/AddReflectTemplateMutation' import {addReflectTemplatePromptTeamUpdater} from '../mutations/AddReflectTemplatePromptMutation' import {addTeamTeamUpdater} from '../mutations/AddTeamMutation' import {archiveTeamTeamOnNext, archiveTeamTeamUpdater} from '../mutations/ArchiveTeamMutation' @@ -40,6 +39,8 @@ import {updateAgendaItemUpdater} from '../mutations/UpdateAgendaItemMutation' import subscriptionOnNext from './subscriptionOnNext' import subscriptionUpdater from './subscriptionUpdater' import {batchArchiveTasksTaskUpdater} from '../mutations/BatchArchiveTasksMutation' +import {addReflectTemplateTeamUpdater} from '../mutations/AddReflectTemplateMutation' +import {addPokerTemplateTeamUpdater} from '../mutations/AddPokerTemplateMutation' const subscription = graphql` subscription TeamSubscription { @@ -66,9 +67,12 @@ const subscription = graphql` AddAtlassianAuthPayload { ...AddAtlassianAuthMutation_team @relay(mask: false) } - AddReflectTemplatePayload { + AddReflectTemplateSuccess { ...AddReflectTemplateMutation_team @relay(mask: false) } + AddPokerTemplateSuccess { + ...AddPokerTemplateMutation_team @relay(mask: false) + } AddReflectTemplatePromptPayload { ...AddReflectTemplatePromptMutation_team @relay(mask: false) } @@ -207,7 +211,8 @@ const updateHandlers = { RemoveAgendaItemPayload: removeAgendaItemUpdater, UpdateAgendaItemPayload: updateAgendaItemUpdater, AcceptTeamInvitationPayload: acceptTeamInvitationTeamUpdater, - AddReflectTemplatePayload: addReflectTemplateTeamUpdater, + AddReflectTemplateSuccess: addReflectTemplateTeamUpdater, + AddPokerTemplateSuccess: addPokerTemplateTeamUpdater, AddReflectTemplatePromptPayload: addReflectTemplatePromptTeamUpdater, AddTeamMutationPayload: addTeamTeamUpdater, ArchiveTeamPayload: archiveTeamTeamUpdater, diff --git a/packages/server/graphql/mutations/addPokerTemplate.ts b/packages/server/graphql/mutations/addPokerTemplate.ts deleted file mode 100644 index cb6d7f390bb..00000000000 --- a/packages/server/graphql/mutations/addPokerTemplate.ts +++ /dev/null @@ -1,149 +0,0 @@ -import {GraphQLID, GraphQLNonNull} from 'graphql' -import {SprintPokerDefaults, SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import PokerTemplate from '../../database/types/PokerTemplate' -import TemplateDimension from '../../database/types/TemplateDimension' -import insertMeetingTemplate from '../../postgres/queries/insertMeetingTemplate' -import {getUserId, isTeamMember, isUserInOrg} from '../../utils/authorization' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import AddPokerTemplatePayload from '../types/AddPokerTemplatePayload' -import getTemplateIllustrationUrl from './helpers/getTemplateIllustrationUrl' -import {analytics} from '../../utils/analytics/analytics' -import {getFeatureTier} from '../types/helpers/getFeatureTier' -import decrementFreeTemplatesRemaining from '../../postgres/queries/decrementFreeTemplatesRemaining' - -const addPokerTemplate = { - description: 'Add a new poker template with a default dimension created', - type: new GraphQLNonNull(AddPokerTemplatePayload), - args: { - parentTemplateId: { - type: GraphQLID - }, - teamId: { - type: new GraphQLNonNull(GraphQLID) - } - }, - async resolve( - _source: unknown, - {parentTemplateId, teamId}: {parentTemplateId?: string | null; teamId: string}, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) { - const r = await getRethink() - const operationId = dataLoader.share() - const subOptions = {operationId, mutatorId} - const viewerId = getUserId(authToken) - - // AUTH - if (!isTeamMember(authToken, teamId)) { - return standardError(new Error('Team not found'), {userId: viewerId}) - } - - // VALIDATION - const [allTemplates, viewerTeam, viewer] = await Promise.all([ - dataLoader.get('meetingTemplatesByType').load({meetingType: 'poker', teamId}), - dataLoader.get('teams').load(teamId), - dataLoader.get('users').loadNonNull(viewerId) - ]) - - if (!viewerTeam) { - return standardError(new Error('Team not found'), {userId: viewerId}) - } - if ( - getFeatureTier(viewerTeam) === 'starter' && - viewer.freeCustomPokerTemplatesRemaining === 0 - ) { - return standardError(new Error('You have reached the limit of free custom templates.'), { - userId: viewerId - }) - } - let data - if (parentTemplateId) { - const parentTemplate = await dataLoader.get('meetingTemplates').load(parentTemplateId) - if (!parentTemplate) { - return standardError(new Error('Parent template not found'), {userId: viewerId}) - } - const {name, scope} = parentTemplate - if (scope === 'TEAM') { - if (!isTeamMember(authToken, parentTemplate.teamId)) - return standardError(new Error('Template is scoped to team'), {userId: viewerId}) - } else if (scope === 'ORGANIZATION') { - const parentTemplateTeam = await dataLoader.get('teams').load(parentTemplate.teamId) - const isInOrg = - parentTemplateTeam && - (await isUserInOrg(getUserId(authToken), parentTemplateTeam?.orgId, dataLoader)) - if (!isInOrg) { - return standardError(new Error('Template is scoped to organization'), {userId: viewerId}) - } - } - const copyName = `${name} Copy` - const existingCopyCount = allTemplates.filter((template) => - template.name.startsWith(copyName) - ).length - const newName = existingCopyCount === 0 ? copyName : `${copyName} #${existingCopyCount + 1}` - const newTemplate = new PokerTemplate({ - name: newName, - teamId, - orgId: viewerTeam.orgId, - parentTemplateId, - mainCategory: parentTemplate.mainCategory, - illustrationUrl: parentTemplate.illustrationUrl - }) - - const dimensions = await dataLoader - .get('templateDimensionsByTemplateId') - .load(parentTemplate.id) - const activeDimensions = dimensions.filter(({removedAt}: TemplateDimension) => !removedAt) - const newTemplateDimensions = activeDimensions.map((dimension: TemplateDimension) => { - return new TemplateDimension({ - ...dimension, - teamId, - templateId: newTemplate.id - }) - }) - - await Promise.all([ - r.table('TemplateDimension').insert(newTemplateDimensions).run(), - insertMeetingTemplate(newTemplate), - decrementFreeTemplatesRemaining(viewerId, 'poker') - ]) - viewer.freeCustomPokerTemplatesRemaining = viewer.freeCustomPokerTemplatesRemaining - 1 - analytics.templateMetrics(viewer, newTemplate, 'Template Cloned') - data = {templateId: newTemplate.id} - } else { - const {orgId} = viewerTeam - - const templateCount = allTemplates.length - const newTemplate = new PokerTemplate({ - name: `*New Template #${templateCount + 1}`, - teamId, - orgId, - mainCategory: 'estimation', - illustrationUrl: getTemplateIllustrationUrl('estimatedEffortTemplate.png') - }) - const templateId = newTemplate.id - const newDimension = new TemplateDimension({ - scaleId: SprintPokerDefaults.DEFAULT_SCALE_ID, - description: '', - sortOrder: 0, - name: '*New Dimension', - teamId, - templateId - }) - - await Promise.all([ - r.table('TemplateDimension').insert(newDimension).run(), - insertMeetingTemplate(newTemplate), - decrementFreeTemplatesRemaining(viewerId, 'poker') - ]) - viewer.freeCustomPokerTemplatesRemaining = viewer.freeCustomPokerTemplatesRemaining - 1 - analytics.templateMetrics(viewer, newTemplate, 'Template Created') - data = {templateId} - } - publish(SubscriptionChannel.TEAM, teamId, 'AddPokerTemplatePayload', data, subOptions) - return data - } -} - -export default addPokerTemplate diff --git a/packages/server/graphql/mutations/addReflectTemplate.ts b/packages/server/graphql/mutations/addReflectTemplate.ts deleted file mode 100644 index 88fd88cad46..00000000000 --- a/packages/server/graphql/mutations/addReflectTemplate.ts +++ /dev/null @@ -1,149 +0,0 @@ -import {GraphQLID, GraphQLNonNull} from 'graphql' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import {PALETTE} from '../../../client/styles/paletteV3' -import getRethink from '../../database/rethinkDriver' -import ReflectTemplate from '../../database/types/ReflectTemplate' -import RetrospectivePrompt from '../../database/types/RetrospectivePrompt' -import insertMeetingTemplate from '../../postgres/queries/insertMeetingTemplate' -import {getUserId, isTeamMember, isUserInOrg} from '../../utils/authorization' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import AddReflectTemplatePayload from '../types/AddReflectTemplatePayload' -import makeRetroTemplates from './helpers/makeRetroTemplates' -import {analytics} from '../../utils/analytics/analytics' -import {getFeatureTier} from '../types/helpers/getFeatureTier' -import decrementFreeCustomTemplatesRemaining from '../../postgres/queries/decrementFreeTemplatesRemaining' - -const addReflectTemplate = { - description: 'Add a new template full of prompts', - type: AddReflectTemplatePayload, - args: { - parentTemplateId: { - type: GraphQLID - }, - teamId: { - type: new GraphQLNonNull(GraphQLID) - } - }, - async resolve( - _source: unknown, - {parentTemplateId, teamId}: {parentTemplateId?: string | null; teamId: string}, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) { - const r = await getRethink() - const operationId = dataLoader.share() - const subOptions = {operationId, mutatorId} - const viewerId = getUserId(authToken) - - // AUTH - if (!isTeamMember(authToken, teamId)) { - return standardError(new Error('Team not found'), {userId: viewerId}) - } - - // VALIDATION - const [allTemplates, viewerTeam, viewer] = await Promise.all([ - dataLoader.get('meetingTemplatesByType').load({meetingType: 'retrospective', teamId}), - dataLoader.get('teams').load(teamId), - dataLoader.get('users').loadNonNull(viewerId) - ]) - - if (!viewerTeam) { - return standardError(new Error('Team not found'), {userId: viewerId}) - } - if ( - getFeatureTier(viewerTeam) === 'starter' && - viewer.freeCustomRetroTemplatesRemaining === 0 - ) { - return standardError(new Error('You have reached the limit of free custom templates.'), { - userId: viewerId - }) - } - let data - if (parentTemplateId) { - const parentTemplate = await dataLoader.get('meetingTemplates').load(parentTemplateId) - if (!parentTemplate) { - return standardError(new Error('Parent template not found'), {userId: viewerId}) - } - const {name, scope} = parentTemplate - if (scope === 'TEAM') { - if (!isTeamMember(authToken, parentTemplate.teamId)) - return standardError(new Error('Template is scoped to team'), {userId: viewerId}) - } else if (scope === 'ORGANIZATION') { - const parentTemplateTeam = await dataLoader.get('teams').load(parentTemplate.teamId) - const isInOrg = - parentTemplateTeam && - (await isUserInOrg(getUserId(authToken), parentTemplateTeam?.orgId, dataLoader)) - if (!isInOrg) { - return standardError(new Error('Template is scoped to organization'), {userId: viewerId}) - } - } - const copyName = `${name} Copy` - const existingCopyCount = allTemplates.filter((template) => - template.name.startsWith(copyName) - ).length - const newName = existingCopyCount === 0 ? copyName : `${copyName} #${existingCopyCount + 1}` - const newTemplate = new ReflectTemplate({ - name: newName, - teamId, - orgId: viewerTeam.orgId, - parentTemplateId, - illustrationUrl: parentTemplate.illustrationUrl, - mainCategory: parentTemplate.mainCategory - }) - const prompts = await dataLoader.get('reflectPromptsByTemplateId').load(parentTemplate.id) - const activePrompts = prompts.filter(({removedAt}: RetrospectivePrompt) => !removedAt) - const newTemplatePrompts = activePrompts.map((prompt: RetrospectivePrompt) => { - return new RetrospectivePrompt({ - ...prompt, - teamId, - templateId: newTemplate.id, - parentPromptId: prompt.id, - removedAt: null - }) - }) - - await Promise.all([ - r.table('ReflectPrompt').insert(newTemplatePrompts).run(), - insertMeetingTemplate(newTemplate), - decrementFreeCustomTemplatesRemaining(viewerId, 'retro') - ]) - viewer.freeCustomRetroTemplatesRemaining = viewer.freeCustomRetroTemplatesRemaining - 1 - analytics.templateMetrics(viewer, newTemplate, 'Template Cloned') - data = {templateId: newTemplate.id} - } else { - const {orgId} = viewerTeam - // RESOLUTION - const templateCount = allTemplates.length - const base = { - [`*New Template #${templateCount + 1}`]: [ - { - question: 'New prompt', - description: '', - groupColor: PALETTE.JADE_400 - } - ] - } as const - const {reflectPrompts: newTemplatePrompts, templates} = makeRetroTemplates( - teamId, - orgId, - base - ) - // guaranteed since base has 1 key - const newTemplate = templates[0]! - const {id: templateId} = newTemplate - await Promise.all([ - r.table('ReflectPrompt').insert(newTemplatePrompts).run(), - insertMeetingTemplate(newTemplate), - decrementFreeCustomTemplatesRemaining(viewerId, 'retro') - ]) - viewer.freeCustomRetroTemplatesRemaining = viewer.freeCustomRetroTemplatesRemaining - 1 - analytics.templateMetrics(viewer, newTemplate, 'Template Created') - data = {templateId} - } - publish(SubscriptionChannel.TEAM, teamId, 'AddReflectTemplatePayload', data, subOptions) - return data - } -} - -export default addReflectTemplate diff --git a/packages/server/graphql/public/mutations/addPokerTemplate.ts b/packages/server/graphql/public/mutations/addPokerTemplate.ts new file mode 100644 index 00000000000..2141a7e5833 --- /dev/null +++ b/packages/server/graphql/public/mutations/addPokerTemplate.ts @@ -0,0 +1,135 @@ +import {SprintPokerDefaults, SubscriptionChannel} from 'parabol-client/types/constEnums' +import getRethink from '../../../database/rethinkDriver' +import PokerTemplate from '../../../database/types/PokerTemplate' +import TemplateDimension from '../../../database/types/TemplateDimension' +import insertMeetingTemplate from '../../../postgres/queries/insertMeetingTemplate' +import {getUserId, isTeamMember, isUserInOrg} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import getTemplateIllustrationUrl from '../../mutations/helpers/getTemplateIllustrationUrl' +import {analytics} from '../../../utils/analytics/analytics' +import {getFeatureTier} from '../../types/helpers/getFeatureTier' +import decrementFreeTemplatesRemaining from '../../../postgres/queries/decrementFreeTemplatesRemaining' +import {MutationResolvers} from '../resolverTypes' + +const addPokerTemplate: MutationResolvers['addPokerTemplate'] = async ( + _source, + {teamId, parentTemplateId}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const r = await getRethink() + const operationId = dataLoader.share() + const subOptions = {operationId, mutatorId} + const viewerId = getUserId(authToken) + + // AUTH + if (!isTeamMember(authToken, teamId)) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + + // VALIDATION + const [allTemplates, viewerTeam, viewer] = await Promise.all([ + dataLoader.get('meetingTemplatesByType').load({meetingType: 'poker', teamId}), + dataLoader.get('teams').load(teamId), + dataLoader.get('users').loadNonNull(viewerId) + ]) + + if (!viewerTeam) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + if (getFeatureTier(viewerTeam) === 'starter' && viewer.freeCustomPokerTemplatesRemaining === 0) { + return standardError(new Error('You have reached the limit of free custom templates.'), { + userId: viewerId + }) + } else { + decrementFreeTemplatesRemaining(viewerId, 'poker') + viewer.freeCustomPokerTemplatesRemaining = viewer.freeCustomPokerTemplatesRemaining - 1 + } + let data + if (parentTemplateId) { + const parentTemplate = await dataLoader.get('meetingTemplates').load(parentTemplateId) + if (!parentTemplate) { + return standardError(new Error('Parent template not found'), {userId: viewerId}) + } + const {name, scope} = parentTemplate + if (scope === 'TEAM') { + if (!isTeamMember(authToken, parentTemplate.teamId)) + return standardError(new Error('Template is scoped to team'), {userId: viewerId}) + } else if (scope === 'ORGANIZATION') { + const parentTemplateTeam = await dataLoader.get('teams').load(parentTemplate.teamId) + const isInOrg = + parentTemplateTeam && + (await isUserInOrg(getUserId(authToken), parentTemplateTeam?.orgId, dataLoader)) + if (!isInOrg) { + return standardError(new Error('Template is scoped to organization'), {userId: viewerId}) + } + } + const copyName = `${name} Copy` + const existingCopyCount = allTemplates.filter((template) => + template.name.startsWith(copyName) + ).length + const newName = existingCopyCount === 0 ? copyName : `${copyName} #${existingCopyCount + 1}` + const newTemplate = new PokerTemplate({ + name: newName, + teamId, + orgId: viewerTeam.orgId, + parentTemplateId, + mainCategory: parentTemplate.mainCategory, + illustrationUrl: parentTemplate.illustrationUrl + }) + + const dimensions = await dataLoader + .get('templateDimensionsByTemplateId') + .load(parentTemplate.id) + const activeDimensions = dimensions.filter(({removedAt}: TemplateDimension) => !removedAt) + const newTemplateDimensions = activeDimensions.map((dimension: TemplateDimension) => { + return new TemplateDimension({ + ...dimension, + teamId, + templateId: newTemplate.id + }) + }) + + await Promise.all([ + r.table('TemplateDimension').insert(newTemplateDimensions).run(), + insertMeetingTemplate(newTemplate), + decrementFreeTemplatesRemaining(viewerId, 'poker') + ]) + viewer.freeCustomPokerTemplatesRemaining = viewer.freeCustomPokerTemplatesRemaining - 1 + analytics.templateMetrics(viewer, newTemplate, 'Template Cloned') + data = {templateId: newTemplate.id} + } else { + const {orgId} = viewerTeam + + const templateCount = allTemplates.length + const newTemplate = new PokerTemplate({ + name: `*New Template #${templateCount + 1}`, + teamId, + orgId, + mainCategory: 'estimation', + illustrationUrl: getTemplateIllustrationUrl('estimatedEffortTemplate.png') + }) + const templateId = newTemplate.id + const newDimension = new TemplateDimension({ + scaleId: SprintPokerDefaults.DEFAULT_SCALE_ID, + description: '', + sortOrder: 0, + name: '*New Dimension', + teamId, + templateId + }) + + await Promise.all([ + r.table('TemplateDimension').insert(newDimension).run(), + insertMeetingTemplate(newTemplate), + decrementFreeTemplatesRemaining(viewerId, 'poker') + ]) + viewer.freeCustomPokerTemplatesRemaining = viewer.freeCustomPokerTemplatesRemaining - 1 + analytics.templateMetrics(viewer, newTemplate, 'Template Created') + data = {templateId} + } + publish(SubscriptionChannel.TEAM, teamId, 'AddPokerTemplateSuccess', data, subOptions) + return data +} + +export default addPokerTemplate diff --git a/packages/server/graphql/public/mutations/addReflectTemplate.ts b/packages/server/graphql/public/mutations/addReflectTemplate.ts new file mode 100644 index 00000000000..b2c5c7479a4 --- /dev/null +++ b/packages/server/graphql/public/mutations/addReflectTemplate.ts @@ -0,0 +1,128 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import getRethink from '../../../database/rethinkDriver' +import insertMeetingTemplate from '../../../postgres/queries/insertMeetingTemplate' +import {getUserId, isTeamMember, isUserInOrg} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import {analytics} from '../../../utils/analytics/analytics' +import {getFeatureTier} from '../../types/helpers/getFeatureTier' +import decrementFreeTemplatesRemaining from '../../../postgres/queries/decrementFreeTemplatesRemaining' +import {MutationResolvers} from '../resolverTypes' +import ReflectTemplate from '../../../database/types/ReflectTemplate' +import {PALETTE} from '../../../../client/styles/paletteV3' +import makeRetroTemplates from '../../mutations/helpers/makeRetroTemplates' +import RetrospectivePrompt from '../../../database/types/RetrospectivePrompt' + +const addPokerTemplate: MutationResolvers['addPokerTemplate'] = async ( + _source, + {teamId, parentTemplateId}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const r = await getRethink() + const operationId = dataLoader.share() + const subOptions = {operationId, mutatorId} + const viewerId = getUserId(authToken) + + // AUTH + if (!isTeamMember(authToken, teamId)) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + + // VALIDATION + const [allTemplates, viewerTeam, viewer] = await Promise.all([ + dataLoader.get('meetingTemplatesByType').load({meetingType: 'retrospective', teamId}), + dataLoader.get('teams').load(teamId), + dataLoader.get('users').loadNonNull(viewerId) + ]) + + if (!viewerTeam) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + if (getFeatureTier(viewerTeam) === 'starter' && viewer.freeCustomRetroTemplatesRemaining === 0) { + return standardError(new Error('You have reached the limit of free custom templates.'), { + userId: viewerId + }) + } + let data + if (parentTemplateId) { + const parentTemplate = await dataLoader.get('meetingTemplates').load(parentTemplateId) + if (!parentTemplate) { + return standardError(new Error('Parent template not found'), {userId: viewerId}) + } + const {name, scope} = parentTemplate + if (scope === 'TEAM') { + if (!isTeamMember(authToken, parentTemplate.teamId)) + return standardError(new Error('Template is scoped to team'), {userId: viewerId}) + } else if (scope === 'ORGANIZATION') { + const parentTemplateTeam = await dataLoader.get('teams').load(parentTemplate.teamId) + const isInOrg = + parentTemplateTeam && + (await isUserInOrg(getUserId(authToken), parentTemplateTeam?.orgId, dataLoader)) + if (!isInOrg) { + return standardError(new Error('Template is scoped to organization'), {userId: viewerId}) + } + } + const copyName = `${name} Copy` + const existingCopyCount = allTemplates.filter((template) => + template.name.startsWith(copyName) + ).length + const newName = existingCopyCount === 0 ? copyName : `${copyName} #${existingCopyCount + 1}` + const newTemplate = new ReflectTemplate({ + name: newName, + teamId, + orgId: viewerTeam.orgId, + parentTemplateId, + illustrationUrl: parentTemplate.illustrationUrl, + mainCategory: parentTemplate.mainCategory + }) + const prompts = await dataLoader.get('reflectPromptsByTemplateId').load(parentTemplate.id) + const activePrompts = prompts.filter(({removedAt}: RetrospectivePrompt) => !removedAt) + const newTemplatePrompts = activePrompts.map((prompt: RetrospectivePrompt) => { + return new RetrospectivePrompt({ + ...prompt, + teamId, + templateId: newTemplate.id, + parentPromptId: prompt.id, + removedAt: null + }) + }) + + await Promise.all([ + r.table('ReflectPrompt').insert(newTemplatePrompts).run(), + insertMeetingTemplate(newTemplate), + decrementFreeTemplatesRemaining(viewerId, 'retro') + ]) + viewer.freeCustomRetroTemplatesRemaining = viewer.freeCustomRetroTemplatesRemaining - 1 + analytics.templateMetrics(viewer, newTemplate, 'Template Cloned') + data = {templateId: newTemplate.id} + } else { + const {orgId} = viewerTeam + // RESOLUTION + const templateCount = allTemplates.length + const base = { + [`*New Template #${templateCount + 1}`]: [ + { + question: 'New prompt', + description: '', + groupColor: PALETTE.JADE_400 + } + ] + } as const + const {reflectPrompts: newTemplatePrompts, templates} = makeRetroTemplates(teamId, orgId, base) + // guaranteed since base has 1 key + const newTemplate = templates[0]! + const {id: templateId} = newTemplate + await Promise.all([ + r.table('ReflectPrompt').insert(newTemplatePrompts).run(), + insertMeetingTemplate(newTemplate), + decrementFreeTemplatesRemaining(viewerId, 'retro') + ]) + viewer.freeCustomRetroTemplatesRemaining = viewer.freeCustomRetroTemplatesRemaining - 1 + analytics.templateMetrics(viewer, newTemplate, 'Template Created') + data = {templateId} + } + publish(SubscriptionChannel.TEAM, teamId, 'AddReflectTemplateSuccess', data, subOptions) + return data +} + +export default addPokerTemplate diff --git a/packages/server/graphql/public/typeDefs/AddPokerTemplate.graphql b/packages/server/graphql/public/typeDefs/AddPokerTemplate.graphql new file mode 100644 index 00000000000..e9c6926d8eb --- /dev/null +++ b/packages/server/graphql/public/typeDefs/AddPokerTemplate.graphql @@ -0,0 +1,31 @@ +extend type Mutation { + """ + Adds a new poker template with a default dimension created. + """ + addPokerTemplate( + """ + The ID of the parent template, if this is a clone operation. + """ + parentTemplateId: ID + """ + The ID of the team for which the template is being created. + """ + teamId: ID! + ): AddPokerTemplatePayload! +} + +""" +Return value for addPokerTemplate, which could be an error +""" +union AddPokerTemplatePayload = ErrorPayload | AddPokerTemplateSuccess + +type AddPokerTemplateSuccess { + """ + The poker template that was created + """ + pokerTemplate: PokerTemplate! + """ + The user that created the template + """ + user: User! +} diff --git a/packages/server/graphql/public/typeDefs/AddReflectTemplate.graphql b/packages/server/graphql/public/typeDefs/AddReflectTemplate.graphql new file mode 100644 index 00000000000..ec7e1660e91 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/AddReflectTemplate.graphql @@ -0,0 +1,31 @@ +extend type Mutation { + """ + Adds a new reflect template with a default dimension created. + """ + addReflectTemplate( + """ + The ID of the parent template, if this is a clone operation. + """ + parentTemplateId: ID + """ + The ID of the team for which the template is being created. + """ + teamId: ID! + ): AddReflectTemplatePayload! +} + +""" +Return value for addReflectTemplate, which could be an error +""" +union AddReflectTemplatePayload = ErrorPayload | AddReflectTemplateSuccess + +type AddReflectTemplateSuccess { + """ + The reflect template that was created + """ + reflectTemplate: ReflectTemplate! + """ + The user that created the template + """ + user: User! +} diff --git a/packages/server/graphql/public/typeDefs/Subscriptions.graphql b/packages/server/graphql/public/typeDefs/Subscriptions.graphql index 8e48da50750..f3391104caf 100644 --- a/packages/server/graphql/public/typeDefs/Subscriptions.graphql +++ b/packages/server/graphql/public/typeDefs/Subscriptions.graphql @@ -151,8 +151,8 @@ type TeamSubscriptionPayload { UpdateTeamNamePayload: UpdateTeamNamePayload OldUpgradeToTeamTierPayload: OldUpgradeToTeamTierPayload UpgradeToTeamTierSuccess: UpgradeToTeamTierSuccess - AddReflectTemplatePayload: AddReflectTemplatePayload - AddPokerTemplatePayload: AddPokerTemplatePayload + AddReflectTemplateSuccess: AddReflectTemplateSuccess + AddPokerTemplateSuccess: AddPokerTemplateSuccess AddReflectTemplatePromptPayload: AddReflectTemplatePromptPayload AddPokerTemplateDimensionPayload: AddPokerTemplateDimensionPayload AddPokerTemplateScalePayload: AddPokerTemplateScalePayload diff --git a/packages/server/graphql/public/typeDefs/_legacy.graphql b/packages/server/graphql/public/typeDefs/_legacy.graphql index 75ba9203d1f..e0e09d93056 100644 --- a/packages/server/graphql/public/typeDefs/_legacy.graphql +++ b/packages/server/graphql/public/typeDefs/_legacy.graphql @@ -5168,11 +5168,6 @@ type Mutation { comment: AddCommentInput! ): AddCommentPayload! - """ - Add a new poker template with a default dimension created - """ - addPokerTemplate(parentTemplateId: ID, teamId: ID!): AddPokerTemplatePayload! - """ Add a new dimension for the poker template """ @@ -5191,11 +5186,6 @@ type Mutation { scaleValue: AddTemplateScaleInput! ): AddPokerTemplateScaleValuePayload! - """ - Add a new template full of prompts - """ - addReflectTemplate(parentTemplateId: ID, teamId: ID!): AddReflectTemplatePayload - """ Add a new template full of prompts """ @@ -6215,7 +6205,6 @@ type Mutation { pokerResetDimension(meetingId: ID!, stageId: ID!): PokerResetDimensionPayload! """ - """ pokerAnnounceDeckHover( meetingId: ID! @@ -6262,7 +6251,6 @@ type Mutation { ): SetPokerSpectatePayload! """ - """ persistGitHubSearchQuery( """ @@ -6497,11 +6485,6 @@ input AddCommentInput { threadParentId: ID } -type AddPokerTemplatePayload { - error: StandardMutationError - pokerTemplate: PokerTemplate -} - type AddPokerTemplateDimensionPayload { error: StandardMutationError dimension: TemplateDimension @@ -6546,11 +6529,6 @@ enum GcalVideoTypeEnum { zoom } -type AddReflectTemplatePayload { - error: StandardMutationError - reflectTemplate: ReflectTemplate -} - type AddReflectTemplatePromptPayload { error: StandardMutationError prompt: ReflectPrompt diff --git a/packages/server/graphql/public/types/AddPokerTemplateSuccess.ts b/packages/server/graphql/public/types/AddPokerTemplateSuccess.ts new file mode 100644 index 00000000000..f6d2b3d9146 --- /dev/null +++ b/packages/server/graphql/public/types/AddPokerTemplateSuccess.ts @@ -0,0 +1,18 @@ +import {getUserId} from '../../../utils/authorization' +import {AddPokerTemplateSuccessResolvers, PokerTemplate} from '../resolverTypes' + +export type AddPokerTemplateSuccessSource = { + templateId: string +} + +const AddPokerTemplateSuccess: AddPokerTemplateSuccessResolvers = { + pokerTemplate: async ({templateId}, _args, {dataLoader}) => { + return (await dataLoader.get('meetingTemplates').load(templateId)) as PokerTemplate + }, + user: async (_src, _args, {authToken, dataLoader}) => { + const viewerId = getUserId(authToken) + return dataLoader.get('users').loadNonNull(viewerId) + } +} + +export default AddPokerTemplateSuccess diff --git a/packages/server/graphql/public/types/AddReflectTemplateSuccess.ts b/packages/server/graphql/public/types/AddReflectTemplateSuccess.ts new file mode 100644 index 00000000000..eff41da3848 --- /dev/null +++ b/packages/server/graphql/public/types/AddReflectTemplateSuccess.ts @@ -0,0 +1,18 @@ +import {getUserId} from '../../../utils/authorization' +import {AddReflectTemplateSuccessResolvers, ReflectTemplate} from '../resolverTypes' + +export type AddPokerTemplateSuccessSource = { + templateId: string +} + +const AddPokerTemplateSuccess: AddReflectTemplateSuccessResolvers = { + reflectTemplate: async ({templateId}, _args, {dataLoader}) => { + return (await dataLoader.get('meetingTemplates').load(templateId)) as ReflectTemplate + }, + user: async (_src, _args, {authToken, dataLoader}) => { + const viewerId = getUserId(authToken) + return dataLoader.get('users').loadNonNull(viewerId) + } +} + +export default AddPokerTemplateSuccess diff --git a/packages/server/graphql/rootMutation.ts b/packages/server/graphql/rootMutation.ts index e82930a04ca..1fd42b72e43 100644 --- a/packages/server/graphql/rootMutation.ts +++ b/packages/server/graphql/rootMutation.ts @@ -6,11 +6,9 @@ import addComment from './mutations/addComment' import addGitHubAuth from './mutations/addGitHubAuth' import addIntegrationProvider from './mutations/addIntegrationProvider' import addOrg from './mutations/addOrg' -import addPokerTemplate from './mutations/addPokerTemplate' import addPokerTemplateDimension from './mutations/addPokerTemplateDimension' import addPokerTemplateScale from './mutations/addPokerTemplateScale' import addPokerTemplateScaleValue from './mutations/addPokerTemplateScaleValue' -import addReflectTemplate from './mutations/addReflectTemplate' import addReflectTemplatePrompt from './mutations/addReflectTemplatePrompt' import addSlackAuth from './mutations/addSlackAuth' import addTeam from './mutations/addTeam' @@ -129,11 +127,9 @@ export default new GraphQLObjectType({ addAgendaItem, addAtlassianAuth, addComment, - addPokerTemplate, addPokerTemplateDimension, addPokerTemplateScale, addPokerTemplateScaleValue, - addReflectTemplate, addReflectTemplatePrompt, addSlackAuth, addGitHubAuth, diff --git a/packages/server/graphql/types/AddPokerTemplatePayload.ts b/packages/server/graphql/types/AddPokerTemplatePayload.ts deleted file mode 100644 index 0536e3203cf..00000000000 --- a/packages/server/graphql/types/AddPokerTemplatePayload.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import PokerTemplate from './PokerTemplate' -import StandardMutationError from './StandardMutationError' -import {getUserId} from '../../utils/authorization' -import User from './User' - -const AddPokerTemplatePayload = new GraphQLObjectType({ - name: 'AddPokerTemplatePayload', - fields: () => ({ - error: { - type: StandardMutationError - }, - pokerTemplate: { - type: PokerTemplate, - resolve: ({templateId}, _args: unknown, {dataLoader}) => { - if (!templateId) return null - return dataLoader.get('meetingTemplates').load(templateId) - } - }, - user: { - type: User, - resolve: (_, _args: unknown, {dataLoader, authToken}) => { - const userId = getUserId(authToken) - if (!userId) return null - return dataLoader.get('users').load(userId) - } - } - }) -}) - -export default AddPokerTemplatePayload diff --git a/packages/server/graphql/types/AddReflectTemplatePayload.ts b/packages/server/graphql/types/AddReflectTemplatePayload.ts deleted file mode 100644 index 07de89347a2..00000000000 --- a/packages/server/graphql/types/AddReflectTemplatePayload.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import ReflectTemplate from './ReflectTemplate' -import StandardMutationError from './StandardMutationError' -import User from './User' -import {getUserId} from '../../utils/authorization' - -const AddReflectTemplatePayload = new GraphQLObjectType({ - name: 'AddReflectTemplatePayload', - fields: () => ({ - error: { - type: StandardMutationError - }, - reflectTemplate: { - type: ReflectTemplate, - resolve: ({templateId}, _args: unknown, {dataLoader}) => { - if (!templateId) return null - return dataLoader.get('meetingTemplates').load(templateId) - } - }, - user: { - type: User, - resolve: (_, _args: unknown, {dataLoader, authToken}) => { - const userId = getUserId(authToken) - if (!userId) return null - return dataLoader.get('users').load(userId) - } - } - }) -}) - -export default AddReflectTemplatePayload From 21710656b6d689b286759ea495ff334b7ce86adf Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Mon, 25 Mar 2024 19:05:50 +0000 Subject: [PATCH 088/529] feat: switch template UI (#9093) --- packages/client/components/MeetingOptions.tsx | 62 ++++++++++ packages/client/components/MeetingTopBar.tsx | 17 ++- packages/client/components/RetroDrawer.tsx | 108 ++++++++++++++++++ .../client/components/RetroDrawerRoot.tsx | 28 +++++ .../components/RetroDrawerTemplateCard.tsx | 55 +++++++++ .../RetroReflectPhase/RetroReflectPhase.tsx | 2 + .../TeamPrompt/TeamPromptDrawer.tsx | 2 +- .../TeamPrompt/TeamPromptOptions.tsx | 2 +- packages/client/ui/Menu/Menu.tsx | 32 ++++++ packages/client/ui/Menu/MenuItem.tsx | 28 +++++ packages/client/ui/Tooltip/TooltipContent.tsx | 2 +- packages/client/ui/Tooltip/TooltipTrigger.tsx | 2 +- .../{fordwardRadix.tsx => forwardRadix.tsx} | 0 .../graphql/public/typeDefs/User.graphql | 4 + packages/server/graphql/public/types/User.ts | 3 +- 15 files changed, 340 insertions(+), 7 deletions(-) create mode 100644 packages/client/components/MeetingOptions.tsx create mode 100644 packages/client/components/RetroDrawer.tsx create mode 100644 packages/client/components/RetroDrawerRoot.tsx create mode 100644 packages/client/components/RetroDrawerTemplateCard.tsx create mode 100644 packages/client/ui/Menu/Menu.tsx create mode 100644 packages/client/ui/Menu/MenuItem.tsx rename packages/client/ui/{fordwardRadix.tsx => forwardRadix.tsx} (100%) diff --git a/packages/client/components/MeetingOptions.tsx b/packages/client/components/MeetingOptions.tsx new file mode 100644 index 00000000000..d0fd3d97a7c --- /dev/null +++ b/packages/client/components/MeetingOptions.tsx @@ -0,0 +1,62 @@ +import React, {useState} from 'react' +import IconLabel from './IconLabel' +import {Menu} from '../ui/Menu/Menu' +import {MenuItem} from '../ui/Menu/MenuItem' +import SwapHorizIcon from '@mui/icons-material/SwapHoriz' +import {OptionsButton} from './TeamPrompt/TeamPromptOptions' +import {Tooltip} from '../ui/Tooltip/Tooltip' +import {TooltipTrigger} from '../ui/Tooltip/TooltipTrigger' +import {TooltipContent} from '../ui/Tooltip/TooltipContent' + +type Props = { + setShowDrawer: (showDrawer: boolean) => void + showDrawer: boolean + hasReflections: boolean +} + +const MeetingOptions = (props: Props) => { + const {setShowDrawer, showDrawer, hasReflections} = props + const [isOpen, setIsOpen] = useState(false) + + const handleClick = () => { + if (hasReflections) return + setShowDrawer(!showDrawer) + } + + const handleMouseEnter = () => { + if (hasReflections) { + setIsOpen(true) + } + } + + const handleMouseLeave = () => { + setIsOpen(false) + } + + return ( + + +
Options
+ + } + > + +
+ + +
{}
+ Change template +
+
+
+ + {'You can only change the template if no reflections have been added.'} + +
+
+ ) +} + +export default MeetingOptions diff --git a/packages/client/components/MeetingTopBar.tsx b/packages/client/components/MeetingTopBar.tsx index a9d37c7e096..287a53012b4 100644 --- a/packages/client/components/MeetingTopBar.tsx +++ b/packages/client/components/MeetingTopBar.tsx @@ -1,6 +1,6 @@ import styled from '@emotion/styled' import {Comment} from '@mui/icons-material' -import React, {ReactElement, ReactNode} from 'react' +import React, {ReactElement, ReactNode, useState} from 'react' import {PALETTE} from '~/styles/paletteV3' import {meetingAvatarMediaQueries} from '../styles/meeting' import hasToken from '../utils/hasToken' @@ -9,6 +9,7 @@ import makeMinWidthMediaQuery from '../utils/makeMinWidthMediaQuery' import DemoCreateAccountButton from './DemoCreateAccountButton' import PlainButton from './PlainButton/PlainButton' import SidebarToggle from './SidebarToggle' +import RetroDrawerRoot from './RetroDrawerRoot' const localHeaderBreakpoint = makeMinWidthMediaQuery(600) @@ -148,6 +149,7 @@ interface Props { isRightDrawerOpen?: boolean toggleSidebar: () => void toggleDrawer?: () => void + meetingId?: string } const MeetingTopBar = (props: Props) => { @@ -158,10 +160,14 @@ const MeetingTopBar = (props: Props) => { isMeetingSidebarCollapsed, isRightDrawerOpen, toggleDrawer, - toggleSidebar + toggleSidebar, + meetingId } = props const showButton = isDemoRoute() && !hasToken() const showDiscussionButton = toggleDrawer && !isRightDrawerOpen + const [showDrawer, setShowDrawer] = useState(false) + const isOptionsVisible = !!meetingId && !isDemoRoute() + return ( @@ -177,6 +183,13 @@ const MeetingTopBar = (props: Props) => { )} {avatarGroup} + {isOptionsVisible && ( + + )} {showDiscussionButton && toggleDrawer && ( diff --git a/packages/client/components/RetroDrawer.tsx b/packages/client/components/RetroDrawer.tsx new file mode 100644 index 00000000000..8d6404ada0e --- /dev/null +++ b/packages/client/components/RetroDrawer.tsx @@ -0,0 +1,108 @@ +import {Close} from '@mui/icons-material' +import graphql from 'babel-plugin-relay/macro' +import React, {useEffect} from 'react' +import {PreloadedQuery, usePreloadedQuery} from 'react-relay' +import {Breakpoint, DiscussionThreadEnum} from '../types/constEnums' +import ResponsiveDashSidebar from './ResponsiveDashSidebar' +import MeetingOptions from './MeetingOptions' +import RetroDrawerTemplateCard from './RetroDrawerTemplateCard' +import {Drawer} from './TeamPrompt/TeamPromptDrawer' +import {RetroDrawerQuery} from '../__generated__/RetroDrawerQuery.graphql' +import useBreakpoint from '../hooks/useBreakpoint' + +interface Props { + setShowDrawer: (showDrawer: boolean) => void + showDrawer: boolean + queryRef: PreloadedQuery +} + +const RetroDrawer = (props: Props) => { + const {queryRef, showDrawer, setShowDrawer} = props + const {viewer} = usePreloadedQuery( + graphql` + query RetroDrawerQuery($first: Int!, $type: MeetingTypeEnum!, $meetingId: ID!) { + viewer { + meeting(meetingId: $meetingId) { + ... on RetrospectiveMeeting { + reflectionGroups { + id + } + localPhase { + ... on ReflectPhase { + phaseType + } + } + } + } + availableTemplates(first: $first, type: $type) + @connection(key: "RetroDrawer_availableTemplates") { + edges { + node { + ...RetroDrawerTemplateCard_template + id + } + } + } + } + } + `, + queryRef + ) + + const templates = viewer.availableTemplates.edges + const meeting = viewer.meeting + const hasReflections = !!(meeting?.reflectionGroups && meeting.reflectionGroups.length > 0) + const phaseType = meeting?.localPhase?.phaseType + const isMobile = !useBreakpoint(Breakpoint.FUZZY_TABLET) + const isDesktop = useBreakpoint(Breakpoint.SIDEBAR_LEFT) + + const toggleDrawer = () => { + setShowDrawer(!showDrawer) + } + + useEffect(() => { + if (hasReflections && showDrawer) { + setShowDrawer(false) + } + }, [hasReflections]) + + if (!phaseType || phaseType !== 'reflect') return null + return ( + <> + + + +
+
+
Templates
+
+ +
+
+ {templates.map((template) => ( + + ))} +
+
+
+ + ) +} +export default RetroDrawer diff --git a/packages/client/components/RetroDrawerRoot.tsx b/packages/client/components/RetroDrawerRoot.tsx new file mode 100644 index 00000000000..58bf63544ff --- /dev/null +++ b/packages/client/components/RetroDrawerRoot.tsx @@ -0,0 +1,28 @@ +import React, {Suspense} from 'react' +import useQueryLoaderNow from '../hooks/useQueryLoaderNow' +import retroDrawerQuery, {RetroDrawerQuery} from '../__generated__/RetroDrawerQuery.graphql' +import RetroDrawer from './RetroDrawer' + +type Props = { + showDrawer: boolean + setShowDrawer: (showDrawer: boolean) => void + meetingId: string +} + +const RetroDrawerRoot = (props: Props) => { + const {showDrawer, setShowDrawer, meetingId} = props + const queryRef = useQueryLoaderNow(retroDrawerQuery, { + first: 200, + type: 'retrospective', + meetingId + }) + return ( + + {queryRef && ( + + )} + + ) +} + +export default RetroDrawerRoot diff --git a/packages/client/components/RetroDrawerTemplateCard.tsx b/packages/client/components/RetroDrawerTemplateCard.tsx new file mode 100644 index 00000000000..c126f6d0d6c --- /dev/null +++ b/packages/client/components/RetroDrawerTemplateCard.tsx @@ -0,0 +1,55 @@ +import {ActivityBadge} from './ActivityLibrary/ActivityBadge' +import {ActivityLibraryCardDescription} from './ActivityLibrary/ActivityLibraryCardDescription' +import graphql from 'babel-plugin-relay/macro' +import React from 'react' +import {useFragment} from 'react-relay' +import {ActivityLibraryCard} from './ActivityLibrary/ActivityLibraryCard' +import {ActivityCardImage} from './ActivityLibrary/ActivityCard' +import {RetroDrawerTemplateCard_template$key} from '~/__generated__/RetroDrawerTemplateCard_template.graphql' +import {CategoryID, CATEGORY_THEMES} from '././ActivityLibrary/Categories' + +interface Props { + templateRef: RetroDrawerTemplateCard_template$key +} + +const RetroDrawerTemplateCard = (props: Props) => { + const {templateRef} = props + const template = useFragment( + graphql` + fragment RetroDrawerTemplateCard_template on MeetingTemplate { + ...ActivityLibraryCardDescription_template + name + category + illustrationUrl + isFree + } + `, + templateRef + ) + + return ( +
+ Premium + ) : null + } + > + + + +
+ ) +} +export default RetroDrawerTemplateCard diff --git a/packages/client/components/RetroReflectPhase/RetroReflectPhase.tsx b/packages/client/components/RetroReflectPhase/RetroReflectPhase.tsx index 74a5f422cb3..493ae73247c 100644 --- a/packages/client/components/RetroReflectPhase/RetroReflectPhase.tsx +++ b/packages/client/components/RetroReflectPhase/RetroReflectPhase.tsx @@ -30,6 +30,7 @@ const RetroReflectPhase = (props: Props) => { ...StageTimerDisplay_meeting ...StageTimerControl_meeting ...PhaseItemColumn_meeting + id endedAt localPhase { ...RetroReflectPhase_phase @relay(mask: false) @@ -59,6 +60,7 @@ const RetroReflectPhase = (props: Props) => { ( +export const Drawer = styled('div')<{isDesktop: boolean; isMobile: boolean; isOpen: boolean}>( ({isDesktop, isMobile, isOpen}) => ({ boxShadow: isDesktop ? desktopSidebarShadow : undefined, backgroundColor: '#FFFFFF', diff --git a/packages/client/components/TeamPrompt/TeamPromptOptions.tsx b/packages/client/components/TeamPrompt/TeamPromptOptions.tsx index f34a7530830..bef32e09be0 100644 --- a/packages/client/components/TeamPrompt/TeamPromptOptions.tsx +++ b/packages/client/components/TeamPrompt/TeamPromptOptions.tsx @@ -14,7 +14,7 @@ import TeamPromptOptionsMenu from './TeamPromptOptionsMenu' const COPIED_TOOLTIP_DURATION_MS = 2000 -const OptionsButton = styled(BaseButton)({ +export const OptionsButton = styled(BaseButton)({ color: PALETTE.SKY_500, display: 'flex', flexDirection: 'column', diff --git a/packages/client/ui/Menu/Menu.tsx b/packages/client/ui/Menu/Menu.tsx new file mode 100644 index 00000000000..47afbbbf35b --- /dev/null +++ b/packages/client/ui/Menu/Menu.tsx @@ -0,0 +1,32 @@ +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' +import React from 'react' +import {twMerge} from 'tailwind-merge' + +interface MenuProps { + trigger: React.ReactNode + className?: string + children: React.ReactNode +} + +export const Menu = React.forwardRef( + ({trigger, className, children}, ref) => { + return ( + + {trigger} + + + {children} + + + + ) + } +) diff --git a/packages/client/ui/Menu/MenuItem.tsx b/packages/client/ui/Menu/MenuItem.tsx new file mode 100644 index 00000000000..fe117676eb0 --- /dev/null +++ b/packages/client/ui/Menu/MenuItem.tsx @@ -0,0 +1,28 @@ +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' +import React from 'react' +import {twMerge} from 'tailwind-merge' + +interface MenuItemProps { + onClick: (event: Event) => void + isDisabled?: boolean + className?: string + children: React.ReactNode +} + +export const MenuItem = React.forwardRef( + ({onClick, isDisabled, className, children}, ref) => { + return ( + + {children} + + ) + } +) diff --git a/packages/client/ui/Tooltip/TooltipContent.tsx b/packages/client/ui/Tooltip/TooltipContent.tsx index 03f77bea4ef..c119f4a2b70 100644 --- a/packages/client/ui/Tooltip/TooltipContent.tsx +++ b/packages/client/ui/Tooltip/TooltipContent.tsx @@ -1,7 +1,7 @@ import {Content, Portal} from '@radix-ui/react-tooltip' import * as React from 'react' import {twMerge} from 'tailwind-merge' -import {forwardRadix} from '../fordwardRadix' +import {forwardRadix} from '../forwardRadix' export const TooltipContent = forwardRadix( ({className, children, ...props}, ref) => ( diff --git a/packages/client/ui/Tooltip/TooltipTrigger.tsx b/packages/client/ui/Tooltip/TooltipTrigger.tsx index b364812034c..2a0b7b0ca36 100644 --- a/packages/client/ui/Tooltip/TooltipTrigger.tsx +++ b/packages/client/ui/Tooltip/TooltipTrigger.tsx @@ -1,6 +1,6 @@ import {Trigger} from '@radix-ui/react-tooltip' import * as React from 'react' -import {forwardRadix} from '../fordwardRadix' +import {forwardRadix} from '../forwardRadix' export const TooltipTrigger = forwardRadix( ({className, children, ...props}, ref) => ( diff --git a/packages/client/ui/fordwardRadix.tsx b/packages/client/ui/forwardRadix.tsx similarity index 100% rename from packages/client/ui/fordwardRadix.tsx rename to packages/client/ui/forwardRadix.tsx diff --git a/packages/server/graphql/public/typeDefs/User.graphql b/packages/server/graphql/public/typeDefs/User.graphql index e204127f142..bd29eaf1c02 100644 --- a/packages/server/graphql/public/typeDefs/User.graphql +++ b/packages/server/graphql/public/typeDefs/User.graphql @@ -433,6 +433,10 @@ type User { The cursor, which is the templateId """ after: ID + """ + An optional argument to filter by template type + """ + type: MeetingTypeEnum ): MeetingTemplateConnection! """ diff --git a/packages/server/graphql/public/types/User.ts b/packages/server/graphql/public/types/User.ts index cd396dd8963..f46afe30511 100644 --- a/packages/server/graphql/public/types/User.ts +++ b/packages/server/graphql/public/types/User.ts @@ -114,7 +114,7 @@ const User: UserResolvers = { const invoice = await manager.retrieveInvoice(invoiceId) return generateInvoice(invoice, stripeLineItems, orgId, invoiceId, dataLoader) }, - availableTemplates: async ({id: userId}, {first, after}, {authToken, dataLoader}) => { + availableTemplates: async ({id: userId}, {first, after, type}, {authToken, dataLoader}) => { const viewerId = getUserId(authToken) const user = await dataLoader.get('users').loadNonNull(userId) const teamIds = @@ -175,6 +175,7 @@ const User: UserResolvers = { ...activity, sortOrder: getScore(activity, teamIds) })) + .filter((activity) => !type || activity.type === type) .sort((a, b) => (a.sortOrder > b.sortOrder ? -1 : 1)) return connectionFromTemplateArray(allActivities, first, after) From e6434e181a864b2e61428f55a98994fb1137ac8f Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Mon, 25 Mar 2024 19:17:13 +0000 Subject: [PATCH 089/529] feat: add functionality to change templates during a retro (#9544) --- codegen.json | 11 ++-- packages/client/components/RetroDrawer.tsx | 14 ++++- .../components/RetroDrawerTemplateCard.tsx | 29 +++++++++-- .../UpdateMeetingTemplateMutation.ts | 51 +++++++++++++++++++ .../subscriptions/MeetingSubscription.ts | 3 ++ .../public/mutations/updateMeetingTemplate.ts | 49 ++++++++++++++++++ .../public/typeDefs/Subscriptions.graphql | 1 + .../typeDefs/updateMeetingTemplate.graphql | 27 ++++++++++ .../types/UpdateMeetingTemplateSuccess.ts | 14 +++++ 9 files changed, 188 insertions(+), 11 deletions(-) create mode 100644 packages/client/mutations/UpdateMeetingTemplateMutation.ts create mode 100644 packages/server/graphql/public/mutations/updateMeetingTemplate.ts create mode 100644 packages/server/graphql/public/typeDefs/updateMeetingTemplate.graphql create mode 100644 packages/server/graphql/public/types/UpdateMeetingTemplateSuccess.ts diff --git a/codegen.json b/codegen.json index a0a38ad29f0..83f9993f249 100644 --- a/codegen.json +++ b/codegen.json @@ -49,6 +49,7 @@ "ActionMeeting": "../../database/types/MeetingAction#default", "ActionMeetingMember": "../../database/types/ActionMeetingMember#default as ActionMeetingMemberDB", "AddApprovedOrganizationDomainsSuccess": "./types/AddApprovedOrganizationDomainsSuccess#AddApprovedOrganizationDomainsSuccessSource", + "AddReactjiToReactableSuccess": "./types/AddReactjiToReactableSuccess#AddReactjiToReactableSuccessSource", "AddReflectTemplateSuccess": "./types/AddReflectTemplateSuccess#AddReflectTemplateSuccessSource", "AddPokerTemplateSuccess": "./types/AddPokerTemplateSuccess#AddPokerTemplateSuccessSource", "AddTranscriptionBotSuccess": "./types/AddTranscriptionBotSuccess#AddTranscriptionBotSuccessSource", @@ -74,8 +75,8 @@ "InviteToTeamPayload": "./types/InviteToTeamPayload#InviteToTeamPayloadSource", "JiraIssue": "./types/JiraIssue#JiraIssueSource", "JiraRemoteProject": "../types/JiraRemoteProject#JiraRemoteProjectSource", - "MeetingSeries": "../../postgres/types/MeetingSeries#MeetingSeries", "Kudos": "../../postgres/types/Kudos#Kudos", + "MeetingSeries": "../../postgres/types/MeetingSeries#MeetingSeries", "MeetingTemplate": "../../database/types/MeetingTemplate#default", "NewMeeting": "../../postgres/types/Meeting#AnyMeeting", "NewMeetingPhase": "../../database/types/GenericMeetingPhase #default as GenericMeetingPhaseDB", @@ -83,11 +84,11 @@ "NotificationTeamInvitation": "../../database/types/NotificationTeamInvitation#default as NotificationTeamInvitationDB", "NotifyDiscussionMentioned": "../../database/types/NotificationDiscussionMentioned#default as NotificationDiscussionMentionedDB", "NotifyKickedOut": "../../database/types/NotificationKickedOut#default", + "NotifyMentioned": "../../database/types/NotificationMentioned#default as NotificationMentionedDB", "NotifyPaymentRejected": "../../database/types/NotificationPaymentRejected#default", "NotifyPromoteToOrgLeader": "../../database/types/NotificationPromoteToBillingLeader#default", "NotifyRequestToJoinOrg": "../../database/types/NotificationRequestToJoinOrg#default", "NotifyResponseMentioned": "../../database/types/NotificationResponseMentioned#default as NotificationResponseMentionedDB", - "NotifyMentioned": "../../database/types/NotificationMentioned#default as NotificationMentionedDB", "NotifyResponseReplied": "../../database/types/NotifyResponseReplied#default as NotifyResponseRepliedDB", "NotifyTaskInvolves": "../../database/types/NotificationTaskInvolves#default", "NotifyTeamArchived": "../../database/types/NotificationTeamArchived#default", @@ -96,6 +97,7 @@ "PokerMeeting": "../../database/types/MeetingPoker#default as MeetingPoker", "PokerMeetingMember": "../../database/types/MeetingPokerMeetingMember#default as PokerMeetingMemberDB", "RRule": "rrule#RRule", + "Reactable": "../../database/types/Reactable#Reactable", "ReflectPrompt": "../../database/types/RetrospectivePrompt#default", "ReflectTemplate": "../../database/types/ReflectTemplate#default", "RemoveApprovedOrganizationDomainsSuccess": "./types/RemoveApprovedOrganizationDomainsSuccess#RemoveApprovedOrganizationDomainsSuccessSource", @@ -103,11 +105,10 @@ "RemoveTeamMemberPayload": "./types/RemoveTeamMemberPayload#RemoveTeamMemberPayloadSource", "RequestToJoinDomainSuccess": "./types/RequestToJoinDomainSuccess#RequestToJoinDomainSuccessSource", "ResetReflectionGroupsSuccess": "./types/ResetReflectionGroupsSuccess#ResetReflectionGroupsSuccessSource", - "RetroReflectionGroup": "../../database/types/RetroReflectionGroup#default as RetroReflectionGroupDB", "RetroReflection": "../../database/types/RetroReflection#default as RetroReflectionDB", + "RetroReflectionGroup": "../../database/types/RetroReflectionGroup#default as RetroReflectionGroupDB", "RetrospectiveMeeting": "../../database/types/MeetingRetrospective#default", "RetrospectiveMeetingMember": "../../database/types/RetroMeetingMember#default", - "Reactable": "../../database/types/Reactable#Reactable", "RetrospectiveMeetingSettings": "../../database/types/MeetingSettingsRetrospective#default", "SAML": "./types/SAML#SAMLSource", "SetMeetingSettingsPayload": "../types/SetMeetingSettingsPayload#SetMeetingSettingsPayloadSource", @@ -141,13 +142,13 @@ "UpdateMeetingPromptSuccess": "./types/UpdateMeetingPromptSuccess#UpdateMeetingPromptSuccessSource", "UpdateOrgPayload": "./types/UpdateOrgPayload#UpdateOrgPayloadSource", "UpdateRecurrenceSettingsSuccess": "./types/UpdateRecurrenceSettingsSuccess#UpdateRecurrenceSettingsSuccessSource", + "UpdateMeetingTemplateSuccess": "./types/UpdateMeetingTemplateSuccess#UpdateMeetingTemplateSuccessSource", "UpdateTaskPayload": "./types/UpdateTaskPayload#UpdateTaskPayloadSource", "UpdateTemplateCategorySuccess": "./types/UpdateTemplateCategorySuccess#UpdateTemplateCategorySuccessSource", "UpdateUserProfilePayload": "./types/UpdateUserProfilePayload#UpdateUserProfilePayloadSource", "UpdatedNotification": "./types/AddedNotification#UpdatedNotificationSource", "UpgradeToTeamTierSuccess": "./types/UpgradeToTeamTierSuccess#UpgradeToTeamTierSuccessSource", "UpsertTeamPromptResponseSuccess": "./types/UpsertTeamPromptResponseSuccess#UpsertTeamPromptResponseSuccessSource", - "AddReactjiToReactableSuccess": "./types/AddReactjiToReactableSuccess#AddReactjiToReactableSuccessSource", "User": "../../postgres/types/IUser#default as IUser", "UserLogInPayload": "./types/UserLogInPayload#UserLogInPayloadSource" } diff --git a/packages/client/components/RetroDrawer.tsx b/packages/client/components/RetroDrawer.tsx index 8d6404ada0e..6249386cabf 100644 --- a/packages/client/components/RetroDrawer.tsx +++ b/packages/client/components/RetroDrawer.tsx @@ -24,6 +24,7 @@ const RetroDrawer = (props: Props) => { viewer { meeting(meetingId: $meetingId) { ... on RetrospectiveMeeting { + id reflectionGroups { id } @@ -60,9 +61,13 @@ const RetroDrawer = (props: Props) => { setShowDrawer(!showDrawer) } + const handleCloseDrawer = () => { + setShowDrawer(false) + } + useEffect(() => { if (hasReflections && showDrawer) { - setShowDrawer(false) + handleCloseDrawer() } }, [hasReflections]) @@ -97,7 +102,12 @@ const RetroDrawer = (props: Props) => {
{templates.map((template) => ( - + ))}
diff --git a/packages/client/components/RetroDrawerTemplateCard.tsx b/packages/client/components/RetroDrawerTemplateCard.tsx index c126f6d0d6c..73fa7c30e3e 100644 --- a/packages/client/components/RetroDrawerTemplateCard.tsx +++ b/packages/client/components/RetroDrawerTemplateCard.tsx @@ -7,17 +7,25 @@ import {ActivityLibraryCard} from './ActivityLibrary/ActivityLibraryCard' import {ActivityCardImage} from './ActivityLibrary/ActivityCard' import {RetroDrawerTemplateCard_template$key} from '~/__generated__/RetroDrawerTemplateCard_template.graphql' import {CategoryID, CATEGORY_THEMES} from '././ActivityLibrary/Categories' +import UpdateMeetingTemplateMutation from '../mutations/UpdateMeetingTemplateMutation' +import useMutationProps from '../hooks/useMutationProps' +import useAtmosphere from '../hooks/useAtmosphere' interface Props { templateRef: RetroDrawerTemplateCard_template$key + meetingId: string + handleCloseDrawer: () => void } const RetroDrawerTemplateCard = (props: Props) => { - const {templateRef} = props + const {templateRef, meetingId, handleCloseDrawer} = props + const {onError, onCompleted} = useMutationProps() + const atmosphere = useAtmosphere() const template = useFragment( graphql` fragment RetroDrawerTemplateCard_template on MeetingTemplate { ...ActivityLibraryCardDescription_template + id name category illustrationUrl @@ -27,12 +35,25 @@ const RetroDrawerTemplateCard = (props: Props) => { templateRef ) + const handleClick = () => { + UpdateMeetingTemplateMutation( + atmosphere, + { + meetingId: meetingId, + templateId: template.id + }, + {onError, onCompleted} + ) + handleCloseDrawer() + } + return ( -
+ Premium @@ -40,16 +61,16 @@ const RetroDrawerTemplateCard = (props: Props) => { } > -
+ ) } export default RetroDrawerTemplateCard diff --git a/packages/client/mutations/UpdateMeetingTemplateMutation.ts b/packages/client/mutations/UpdateMeetingTemplateMutation.ts new file mode 100644 index 00000000000..c4dd1d6951b --- /dev/null +++ b/packages/client/mutations/UpdateMeetingTemplateMutation.ts @@ -0,0 +1,51 @@ +import graphql from 'babel-plugin-relay/macro' +import {commitMutation} from 'react-relay' +import {StandardMutation} from '../types/relayMutations' +import {UpdateMeetingTemplateMutation as TUpdateMeetingTemplateMutation} from '../__generated__/UpdateMeetingTemplateMutation.graphql' + +graphql` + fragment UpdateMeetingTemplateMutation_meeting on UpdateMeetingTemplateSuccess { + meeting { + ... on RetrospectiveMeeting { + id + templateId + phases { + id + ... on ReflectPhase { + reflectPrompts { + id + } + } + } + } + } + } +` + +const mutation = graphql` + mutation UpdateMeetingTemplateMutation($meetingId: ID!, $templateId: ID!) { + updateMeetingTemplate(meetingId: $meetingId, templateId: $templateId) { + ... on ErrorPayload { + error { + message + } + } + ...UpdateMeetingTemplateMutation_meeting @relay(mask: false) + } + } +` + +const UpdateMeetingTemplateMutation: StandardMutation = ( + atmosphere, + variables, + {onError, onCompleted} +) => { + return commitMutation(atmosphere, { + mutation, + variables, + onCompleted, + onError + }) +} + +export default UpdateMeetingTemplateMutation diff --git a/packages/client/subscriptions/MeetingSubscription.ts b/packages/client/subscriptions/MeetingSubscription.ts index 65e3978b03c..d6f30e0a0f1 100644 --- a/packages/client/subscriptions/MeetingSubscription.ts +++ b/packages/client/subscriptions/MeetingSubscription.ts @@ -150,6 +150,9 @@ const subscription = graphql` UpdateRetroMaxVotesSuccess { ...UpdateRetroMaxVotesMutation_meeting @relay(mask: false) } + UpdateMeetingTemplateSuccess { + ...UpdateMeetingTemplateMutation_meeting @relay(mask: false) + } VoteForReflectionGroupPayload { ...VoteForReflectionGroupMutation_meeting @relay(mask: false) } diff --git a/packages/server/graphql/public/mutations/updateMeetingTemplate.ts b/packages/server/graphql/public/mutations/updateMeetingTemplate.ts new file mode 100644 index 00000000000..a0be265f8de --- /dev/null +++ b/packages/server/graphql/public/mutations/updateMeetingTemplate.ts @@ -0,0 +1,49 @@ +import {SubscriptionChannel} from '../../../../client/types/constEnums' +import getRethink from '../../../database/rethinkDriver' +import MeetingRetrospective from '../../../database/types/MeetingRetrospective' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import getPhase from '../../../utils/getPhase' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const updateMeetingTemplate: MutationResolvers['updateMeetingTemplate'] = async ( + _source, + {meetingId, templateId}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const viewerId = getUserId(authToken) + const r = await getRethink() + const operationId = dataLoader.share() + const subOptions = {mutatorId, operationId} + const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingRetrospective + if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) + if (!isTeamMember(authToken, meeting.teamId)) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + const reflections = await dataLoader.get('retroReflectionsByMeetingId').load(meetingId) + if (reflections.length > 0) { + return standardError(new Error('Cannot change template after reflections have been created'), { + userId: viewerId + }) + } + const reflectPhase = getPhase(meeting.phases, 'reflect') + const hasCompletedReflectPhase = reflectPhase.stages.every((stage) => stage.isComplete) + if (hasCompletedReflectPhase) { + return standardError( + new Error('Cannot change template after reflection phase has been completed'), + { + userId: viewerId + } + ) + } + + await r.table('NewMeeting').get(meetingId).update({templateId}).run() + meeting.templateId = templateId + + const data = {meetingId, templateId} + publish(SubscriptionChannel.MEETING, meetingId, 'UpdateMeetingTemplateSuccess', data, subOptions) + return data +} + +export default updateMeetingTemplate diff --git a/packages/server/graphql/public/typeDefs/Subscriptions.graphql b/packages/server/graphql/public/typeDefs/Subscriptions.graphql index f3391104caf..3f2e1a48de5 100644 --- a/packages/server/graphql/public/typeDefs/Subscriptions.graphql +++ b/packages/server/graphql/public/typeDefs/Subscriptions.graphql @@ -54,6 +54,7 @@ type MeetingSubscriptionPayload { SetPokerSpectateSuccess: SetPokerSpectateSuccess SetTaskEstimateSuccess: SetTaskEstimateSuccess UpsertTeamPromptResponseSuccess: UpsertTeamPromptResponseSuccess + UpdateMeetingTemplateSuccess: UpdateMeetingTemplateSuccess } type NotificationSubscriptionPayload { diff --git a/packages/server/graphql/public/typeDefs/updateMeetingTemplate.graphql b/packages/server/graphql/public/typeDefs/updateMeetingTemplate.graphql new file mode 100644 index 00000000000..1f378e42dbe --- /dev/null +++ b/packages/server/graphql/public/typeDefs/updateMeetingTemplate.graphql @@ -0,0 +1,27 @@ +extend type Mutation { + """ + Update a meeting template + """ + updateMeetingTemplate( + """ + The id of the meeting + """ + meetingId: ID! + """ + The id of the meeting template + """ + templateId: ID! + ): UpdateMeetingTemplatePayload! +} + +""" +Return value for updateMeetingTemplate, which could be an error +""" +union UpdateMeetingTemplatePayload = ErrorPayload | UpdateMeetingTemplateSuccess + +type UpdateMeetingTemplateSuccess { + """ + The updated meeting + """ + meeting: NewMeeting! +} diff --git a/packages/server/graphql/public/types/UpdateMeetingTemplateSuccess.ts b/packages/server/graphql/public/types/UpdateMeetingTemplateSuccess.ts new file mode 100644 index 00000000000..33a52658893 --- /dev/null +++ b/packages/server/graphql/public/types/UpdateMeetingTemplateSuccess.ts @@ -0,0 +1,14 @@ +import {UpdateMeetingTemplateSuccessResolvers} from '../resolverTypes' + +export type UpdateMeetingTemplateSuccessSource = { + meetingId: string +} + +const UpdateMeetingTemplateSuccess: UpdateMeetingTemplateSuccessResolvers = { + meeting: async ({meetingId}, _args, {dataLoader}) => { + const meeting = await dataLoader.get('newMeetings').load(meetingId) + return meeting + } +} + +export default UpdateMeetingTemplateSuccess From 87c84a2cca1a4d94629291d1325948b3c6cfb790 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Mon, 25 Mar 2024 20:17:47 +0100 Subject: [PATCH 090/529] feat: release AzureDevOps integration (#9531) --- packages/client/components/ScopePhaseArea.tsx | 9 +-------- .../components/ProviderList/ProviderList.tsx | 14 +++----------- .../client/utils/AzureDevOpsClientManager.ts | 4 ++-- .../server/__tests__/updateFeatureFlag.test.ts | 10 +++++----- .../public/typeDefs/updateFeatureFlag.graphql | 2 -- .../graphql/public/types/UserFeatureFlags.ts | 1 - .../CreateAzureDevOpsAuthorizeUrlPayload.ts | 18 ------------------ .../server/utils/AzureDevOpsServerManager.ts | 10 ++++------ 8 files changed, 15 insertions(+), 53 deletions(-) delete mode 100644 packages/server/graphql/types/CreateAzureDevOpsAuthorizeUrlPayload.ts diff --git a/packages/client/components/ScopePhaseArea.tsx b/packages/client/components/ScopePhaseArea.tsx index 651f86566a7..44702ccb239 100644 --- a/packages/client/components/ScopePhaseArea.tsx +++ b/packages/client/components/ScopePhaseArea.tsx @@ -130,11 +130,6 @@ const ScopePhaseArea = (props: Props) => { } } } - user { - featureFlags { - azureDevOps - } - } } } `, @@ -142,13 +137,11 @@ const ScopePhaseArea = (props: Props) => { ) const isDesktop = useBreakpoint(Breakpoint.SIDEBAR_LEFT) const {viewerMeetingMember} = meeting - const featureFlags = viewerMeetingMember?.user.featureFlags const gitlabIntegration = viewerMeetingMember?.teamMember.integrations.gitlab const jiraServerIntegration = viewerMeetingMember?.teamMember.integrations.jiraServer const azureDevOpsIntegration = viewerMeetingMember?.teamMember.integrations.azureDevOps const allowAzureDevOps = - (!!azureDevOpsIntegration?.sharedProviders.length || !!azureDevOpsIntegration?.cloudProvider) && - featureFlags?.azureDevOps + !!azureDevOpsIntegration?.sharedProviders.length || !!azureDevOpsIntegration?.cloudProvider const isGitLabProviderAvailable = !!( gitlabIntegration?.cloudProvider?.clientId || gitlabIntegration?.sharedProviders.length ) diff --git a/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx b/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx index d8eb7a84485..6f2360fad6b 100644 --- a/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx +++ b/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx @@ -107,9 +107,6 @@ const query = graphql` } } } - featureFlags { - azureDevOps - } } } ` @@ -118,10 +115,6 @@ const ProviderList = (props: Props) => { const {queryRef, retry, teamId} = props const data = usePreloadedQuery(query, queryRef) const {viewer} = data - const { - featureFlags: {azureDevOps: allowAzureDevOps} - } = viewer - const integrations = viewer.teamMember?.integrations const allIntegrations = [ @@ -159,8 +152,7 @@ const ProviderList = (props: Props) => { { name: 'Azure DevOps', connected: !!integrations?.azureDevOps.auth?.accessToken, - component: , - hidden: !allowAzureDevOps + component: }, { name: 'MS Teams', @@ -175,12 +167,12 @@ const ProviderList = (props: Props) => { ] const connectedIntegrations = allIntegrations - .filter((integration) => integration.connected && !integration.hidden) + .filter((integration) => integration.connected) .sort((a, b) => a.name.localeCompare(b.name)) .map((integration) => integration.component) const availableIntegrations = allIntegrations - .filter((integration) => !integration.connected && !integration.hidden) + .filter((integration) => !integration.connected) .sort((a, b) => a.name.localeCompare(b.name)) .map((integration) => integration.component) diff --git a/packages/client/utils/AzureDevOpsClientManager.ts b/packages/client/utils/AzureDevOpsClientManager.ts index 1ad3db655fb..09dc2e510bf 100644 --- a/packages/client/utils/AzureDevOpsClientManager.ts +++ b/packages/client/utils/AzureDevOpsClientManager.ts @@ -40,8 +40,8 @@ class AzureDevOpsClientManager { const providerState = Math.random().toString(36).substring(5) const verifier = AzureDevOpsClientManager.generateVerifier() const code = await AzureDevOpsClientManager.generateCodeChallenge(verifier) - const redirect = makeHref('/auth/ado') - const scope = '499b84ac-1321-427f-aa17-267ca6975798/.default' + const redirect = makeHref('/auth/ado2') + const scope = '499b84ac-1321-427f-aa17-267ca6975798/.default offline_access' const url = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirect}&response_mode=query&scope=${scope}&state=${providerState}&code_challenge=${code}&code_challenge_method=S256` // Open synchronously because of Safari diff --git a/packages/server/__tests__/updateFeatureFlag.test.ts b/packages/server/__tests__/updateFeatureFlag.test.ts index 5bafe858aa4..f252e553b55 100644 --- a/packages/server/__tests__/updateFeatureFlag.test.ts +++ b/packages/server/__tests__/updateFeatureFlag.test.ts @@ -12,7 +12,7 @@ const UPDATE_FEATURE_FLAG = ` users { id featureFlags { - azureDevOps + noAISummary } } } @@ -27,7 +27,7 @@ test('Add feature flag by email', async () => { query: UPDATE_FEATURE_FLAG, variables: { emails: [email], - flag: 'azureDevOps', + flag: 'noAISummary', addFlag: true }, authToken @@ -41,7 +41,7 @@ test('Add feature flag by email', async () => { { id: userId, featureFlags: { - azureDevOps: true + noAISummary: true } } ] @@ -58,7 +58,7 @@ test('Remove feature flag by email', async () => { query: UPDATE_FEATURE_FLAG, variables: { emails: [email], - flag: 'azureDevOps', + flag: 'noAISummary', addFlag: false }, authToken @@ -72,7 +72,7 @@ test('Remove feature flag by email', async () => { { id: userId, featureFlags: { - azureDevOps: false + noAISummary: false } } ] diff --git a/packages/server/graphql/public/typeDefs/updateFeatureFlag.graphql b/packages/server/graphql/public/typeDefs/updateFeatureFlag.graphql index ba9506cf4cd..411d2f484f6 100644 --- a/packages/server/graphql/public/typeDefs/updateFeatureFlag.graphql +++ b/packages/server/graphql/public/typeDefs/updateFeatureFlag.graphql @@ -3,7 +3,6 @@ A flag to give an individual user super powers """ enum UserFlagEnum { standups - azureDevOps insights recurrence noAISummary @@ -19,7 +18,6 @@ The types of flags that give an individual user super powers """ type UserFeatureFlags { standups: Boolean! - azureDevOps: Boolean! insights: Boolean! recurrence: Boolean! noAISummary: Boolean! diff --git a/packages/server/graphql/public/types/UserFeatureFlags.ts b/packages/server/graphql/public/types/UserFeatureFlags.ts index 806a33b7c8a..f8c72854653 100644 --- a/packages/server/graphql/public/types/UserFeatureFlags.ts +++ b/packages/server/graphql/public/types/UserFeatureFlags.ts @@ -1,7 +1,6 @@ import {UserFeatureFlagsResolvers} from '../resolverTypes' const UserFeatureFlags: UserFeatureFlagsResolvers = { - azureDevOps: ({azureDevOps}) => !!azureDevOps, insights: ({insights}) => !!insights, noAISummary: ({noAISummary}) => !!noAISummary, noMeetingHistoryLimit: ({noMeetingHistoryLimit}) => !!noMeetingHistoryLimit, diff --git a/packages/server/graphql/types/CreateAzureDevOpsAuthorizeUrlPayload.ts b/packages/server/graphql/types/CreateAzureDevOpsAuthorizeUrlPayload.ts deleted file mode 100644 index afd3caeb221..00000000000 --- a/packages/server/graphql/types/CreateAzureDevOpsAuthorizeUrlPayload.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {GraphQLObjectType, GraphQLString} from 'graphql' -import {GQLContext} from '../graphql' -import StandardMutationError from './StandardMutationError' - -export const CreateAzureDevOpsAuthorizeUrlPayload = new GraphQLObjectType({ - name: 'CreateAzureDevOpsAuthorizeUrlSuccess', - fields: () => ({ - error: { - type: StandardMutationError - }, - url: { - type: GraphQLString, - description: 'Authorization URL including oauth_token to start authorization flow' - } - }) -}) - -export default CreateAzureDevOpsAuthorizeUrlPayload diff --git a/packages/server/utils/AzureDevOpsServerManager.ts b/packages/server/utils/AzureDevOpsServerManager.ts index 7f495fe771b..ba3cf7f04f3 100644 --- a/packages/server/utils/AzureDevOpsServerManager.ts +++ b/packages/server/utils/AzureDevOpsServerManager.ts @@ -266,7 +266,7 @@ class AzureDevOpsServerManager implements TaskIntegrationManager { grant_type: 'authorization_code', code: code, code_verifier: codeVerifier, - redirect_uri: makeAppURL(appOrigin, 'auth/ado') + redirect_uri: makeAppURL(appOrigin, 'auth/ado2') }) } @@ -702,16 +702,14 @@ class AzureDevOpsServerManager implements TaskIntegrationManager { const body = { ...params, - client_id: this.provider.clientId + client_id: this.provider.clientId, + client_secret: this.provider.clientSecret } - const additonalHeaders = { - Origin: appOrigin - } const tenantId = this.provider.tenantId const authUrl = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/token` const contentType = 'application/x-www-form-urlencoded' - const oAuthRes = await authorizeOAuth2({authUrl, body, additonalHeaders, contentType}) + const oAuthRes = await authorizeOAuth2({authUrl, body, contentType}) if (!isError(oAuthRes)) { this.accessToken = oAuthRes.accessToken } From b8fa7088e610ab1a920d1b5522cd46ad28e2f715 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Mon, 25 Mar 2024 14:31:31 -0700 Subject: [PATCH 091/529] chore: Roll out AIGeneratedDiscussion to all users (#9554) --- packages/client/modules/demo/initDB.ts | 1 - .../SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx | 12 ++++++------ .../mutations/helpers/generateGroupSummaries.ts | 8 +------- .../private/typeDefs/updateOrgFeatureFlag.graphql | 1 - .../graphql/public/typeDefs/Organization.graphql | 1 - .../graphql/public/types/OrganizationFeatureFlags.ts | 1 - 6 files changed, 7 insertions(+), 17 deletions(-) diff --git a/packages/client/modules/demo/initDB.ts b/packages/client/modules/demo/initDB.ts index 6e7954ef5a4..7d1b673b94c 100644 --- a/packages/client/modules/demo/initDB.ts +++ b/packages/client/modules/demo/initDB.ts @@ -303,7 +303,6 @@ const initDemoOrg = () => { suggestGroups: false, teamsLimit: false, noPromptToJoinOrg: false, - AIGeneratedDiscussionPrompt: false, publicTeams: false }, showConversionModal: false diff --git a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx index f6befe9bb30..abbfc8a2e67 100644 --- a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx +++ b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx @@ -90,7 +90,7 @@ const RetroTopic = (props: Props) => { reflections { ...EmailReflectionCard_reflection } - topicSummary: summary + discussionPromptQuestion } discussion { commentCount @@ -121,7 +121,7 @@ const RetroTopic = (props: Props) => { const {reflectionGroup, discussion, id: stageId} = stage const {commentCount, discussionSummary} = discussion - const {reflections, title, voteCount, topicSummary} = reflectionGroup! + const {reflections, title, voteCount, discussionPromptQuestion} = reflectionGroup! const imageSource = isEmail ? 'static' : 'local' const icon = imageSource === 'local' ? 'thumb_up_18.svg' : 'thumb_up_18@3x.png' const src = `${ExternalLinks.EMAIL_CDN}${icon}` @@ -143,16 +143,16 @@ const RetroTopic = (props: Props) => { - {(topicSummary || discussionSummary) && ( + {(discussionPromptQuestion || discussionSummary) && ( - {topicSummary && ( + {discussionPromptQuestion && ( <> - {'🤖 Topic Summary'} + {'🤖 Discussion Question'} - {topicSummary} + {discussionPromptQuestion} )} diff --git a/packages/server/graphql/mutations/helpers/generateGroupSummaries.ts b/packages/server/graphql/mutations/helpers/generateGroupSummaries.ts index 86cd732709d..3a827ca20d1 100644 --- a/packages/server/graphql/mutations/helpers/generateGroupSummaries.ts +++ b/packages/server/graphql/mutations/helpers/generateGroupSummaries.ts @@ -15,7 +15,6 @@ const generateGroupSummaries = async ( dataLoader.get('users').loadNonNull(facilitatorUserId), dataLoader.get('teams').loadNonNull(teamId) ]) - const organization = await dataLoader.get('organizations').load(team.orgId) const isAISummaryAccessible = await canAccessAISummary( team, facilitator.featureFlags, @@ -35,9 +34,6 @@ const generateGroupSummaries = async ( sendToSentry(error, {userId: facilitator.id, tags: {meetingId}}) return } - const aiGeneratedDiscussionPromptEnabled = organization.featureFlags?.includes( - 'AIGeneratedDiscussionPrompt' - ) await Promise.all( reflectionGroups.map(async (group) => { const reflectionsByGroupId = reflections.filter( @@ -49,9 +45,7 @@ const generateGroupSummaries = async ( ) const [fullSummary, fullQuestion] = await Promise.all([ manager.getSummary(reflectionTextByGroupId), - aiGeneratedDiscussionPromptEnabled - ? manager.getDiscussionPromptQuestion(group.title ?? 'Unknown', reflectionsByGroupId) - : undefined + manager.getDiscussionPromptQuestion(group.title ?? 'Unknown', reflectionsByGroupId) ]) if (!fullSummary && !fullQuestion) return const summary = fullSummary?.slice(0, 2000) diff --git a/packages/server/graphql/private/typeDefs/updateOrgFeatureFlag.graphql b/packages/server/graphql/private/typeDefs/updateOrgFeatureFlag.graphql index 211113f9903..925a360df83 100644 --- a/packages/server/graphql/private/typeDefs/updateOrgFeatureFlag.graphql +++ b/packages/server/graphql/private/typeDefs/updateOrgFeatureFlag.graphql @@ -3,7 +3,6 @@ A flag to give an individual organization super powers """ enum OrganizationFeatureFlagsEnum { noAISummary - AIGeneratedDiscussionPrompt standupAISummary noPromptToJoinOrg suggestGroups diff --git a/packages/server/graphql/public/typeDefs/Organization.graphql b/packages/server/graphql/public/typeDefs/Organization.graphql index 485adcac9f6..40fe0800f8a 100644 --- a/packages/server/graphql/public/typeDefs/Organization.graphql +++ b/packages/server/graphql/public/typeDefs/Organization.graphql @@ -183,7 +183,6 @@ The types of flags that give an individual organization super powers """ type OrganizationFeatureFlags { noAISummary: Boolean! - AIGeneratedDiscussionPrompt: Boolean! standupAISummary: Boolean! noPromptToJoinOrg: Boolean! suggestGroups: Boolean! diff --git a/packages/server/graphql/public/types/OrganizationFeatureFlags.ts b/packages/server/graphql/public/types/OrganizationFeatureFlags.ts index 14f4d59e6a4..9903dc8dd4e 100644 --- a/packages/server/graphql/public/types/OrganizationFeatureFlags.ts +++ b/packages/server/graphql/public/types/OrganizationFeatureFlags.ts @@ -4,7 +4,6 @@ const OrganizationFeatureFlags: OrganizationFeatureFlagsResolvers = { noAISummary: ({noAISummary}) => !!noAISummary, standupAISummary: ({standupAISummary}) => !!standupAISummary, noPromptToJoinOrg: ({noPromptToJoinOrg}) => !!noPromptToJoinOrg, - AIGeneratedDiscussionPrompt: ({AIGeneratedDiscussionPrompt}) => !!AIGeneratedDiscussionPrompt, zoomTranscription: ({zoomTranscription}) => !!zoomTranscription, shareSummary: ({shareSummary}) => !!shareSummary, suggestGroups: ({suggestGroups}) => !!suggestGroups, From 1e22931c25c297e4697ea0e585d888c7b2738cfc Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 25 Mar 2024 18:02:55 -0700 Subject: [PATCH 092/529] chore: [Snyk] Upgrade dotenv from 8.0.0 to 8.6.0 (#9494) Co-authored-by: snyk-bot Co-authored-by: Jordan Husney --- packages/server/package.json | 2 +- yarn.lock | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/server/package.json b/packages/server/package.json index 9138ece5b9a..e65191586cc 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -94,7 +94,7 @@ "cheerio": "^1.0.0-rc.10", "dataloader": "^2.0.0", "dd-trace": "^4.2.0", - "dotenv": "8.0.0", + "dotenv": "8.6.0", "dotenv-expand": "5.1.0", "draft-js": "https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6", "draft-js-export-markdown": "^1.3.3", diff --git a/yarn.lock b/yarn.lock index 74d28b2fb23..afb8352abab 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11269,6 +11269,11 @@ dotenv@8.0.0: resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.0.0.tgz#ed310c165b4e8a97bb745b0a9d99c31bda566440" integrity sha512-30xVGqjLjiUOArT4+M5q9sYdvuR4riM6yK9wMcas9Vbp6zZa+ocC9dp6QoftuhTPhFAiLK/0C5Ni2nou/Bk8lg== +dotenv@8.6.0: + version "8.6.0" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.6.0.tgz#061af664d19f7f4d8fc6e4ff9b584ce237adcb8b" + integrity sha512-IrPdXQsk2BbzvCBGBOTmmSH5SodmqZNt4ERAZDmW4CT+tL8VtvinqywuANaFu4bOMWki16nqf0e4oC0QIaDr/g== + dotenv@^16.0.0, dotenv@^16.0.3: version "16.0.3" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.3.tgz#115aec42bac5053db3c456db30cc243a5a836a07" From 0ce1384b418f2a48971b732b548b9b93f8882e6c Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 25 Mar 2024 18:03:05 -0700 Subject: [PATCH 093/529] chore: [Snyk] Upgrade graphql-typed from 0.6.1 to 0.7.2 (#9522) Co-authored-by: snyk-bot Co-authored-by: Jordan Husney --- packages/client/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index 0a468113a2f..6fe6c57d163 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -113,7 +113,7 @@ "fbjs": "^3.0.4", "flatted": "^2.0.1", "graphiql": "^3.0.0", - "graphql-typed": "^0.6.1", + "graphql-typed": "^0.7.2", "hoist-non-react-statics": "^3.3.0", "humanize-duration": "3.29.0", "immutable": "3.8.2", diff --git a/yarn.lock b/yarn.lock index afb8352abab..4123e63d769 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13048,10 +13048,10 @@ graphql-typed@^0.4.1: resolved "https://registry.yarnpkg.com/graphql-typed/-/graphql-typed-0.4.1.tgz#d612f1a8215fc5b14b51d5a88b06c0174d6a6e47" integrity sha512-6a4BHG/uXMkxY+UeqmsTrwCvTy3BbFAYlKsVC2MzvAISXh+FcbBaJEfXsbcZKrydCFnVD3Xlg74Esip5O3LyLA== -graphql-typed@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/graphql-typed/-/graphql-typed-0.6.1.tgz#a305bad26cc1e6a5f7cf34e720d9cbd7bc57317d" - integrity sha512-41J/CLton6F6tt5AGmKbroU5RSyXOSH5My2D396yXcNuQynaehP8FFG9bx/XjLukJMgLHJ8pUSHOw+fAqcGklw== +graphql-typed@^0.7.2: + version "0.7.2" + resolved "https://registry.yarnpkg.com/graphql-typed/-/graphql-typed-0.7.2.tgz#c917eb79e58f340a842b204212e29506db481c2c" + integrity sha512-Np+YLzDCTY92ptXN5Rxi34fMJ4dy/vx0jZWnH0IVjpLW5SSoirve4ymIBy1UbkS//uEMCdNtvX4Y6loOJ3vyvA== graphql-ws@^5.14.0: version "5.14.0" From 3e42d9b155e171ec54f8d4b0cfddc1ace67cb754 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 25 Mar 2024 18:03:16 -0700 Subject: [PATCH 094/529] chore: [Snyk] Upgrade react-swipeable-views-core from 0.13.1 to 0.14.0 (#9521) Co-authored-by: snyk-bot Co-authored-by: Jordan Husney --- packages/client/package.json | 2 +- yarn.lock | 10 +--------- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index 6fe6c57d163..bcf23043650 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -134,7 +134,7 @@ "react-router": "^5.0.1", "react-router-dom": "^5.0.1", "react-swipeable-views": "https://github.com/mattkrick/react-swipeable-views/tarball/4f1d3062d6f8939e9889bc6241bb46aa7bc5332d", - "react-swipeable-views-core": "0.13.1", + "react-swipeable-views-core": "0.14.0", "react-swipeable-views-utils": "^0.14.0", "react-textarea-autosize": "^7.1.0", "react-transition-group": "^4.3.0", diff --git a/yarn.lock b/yarn.lock index 4123e63d769..7429558d3e6 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18456,15 +18456,7 @@ react-style-singleton@^2.2.1: invariant "^2.2.4" tslib "^2.0.0" -react-swipeable-views-core@0.13.1: - version "0.13.1" - resolved "https://registry.yarnpkg.com/react-swipeable-views-core/-/react-swipeable-views-core-0.13.1.tgz#8829a922462a8bdd701709cd1b385393d38f1527" - integrity sha512-EP8sCvvD7VDiZLglPt9icMuMNu8qLRLk0ab/fB1HXv7lX8ClnwF3UMCM0ZrN3sguSY7CsX3LevducGGsT1VcDg== - dependencies: - "@babel/runtime" "7.0.0" - warning "^4.0.1" - -react-swipeable-views-core@^0.14.0, react-swipeable-views-core@^0.14.0-alpha.0: +react-swipeable-views-core@0.14.0, react-swipeable-views-core@^0.14.0, react-swipeable-views-core@^0.14.0-alpha.0: version "0.14.0" resolved "https://registry.yarnpkg.com/react-swipeable-views-core/-/react-swipeable-views-core-0.14.0.tgz#6ac443a7cc7bc5ea022fbd549292bb5fff361cce" integrity sha512-0W/e9uPweNEOSPjmYtuKSC/SvKKg1sfo+WtPdnxeLF3t2L82h7jjszuOHz9C23fzkvLfdgkaOmcbAxE9w2GEjA== From ef6891569f468469c64041d0c97555d76c2657d3 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 25 Mar 2024 18:03:23 -0700 Subject: [PATCH 095/529] chore: [Snyk] Upgrade react-dom-confetti from 0.0.10 to 0.2.0 (#9520) Co-authored-by: snyk-bot Co-authored-by: Jordan Husney --- packages/client/package.json | 2 +- yarn.lock | 18 +++++++++--------- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index bcf23043650..d79a06254a8 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -128,7 +128,7 @@ "react-copy-to-clipboard": "^5.0.0", "react-day-picker": "^8.3.7", "react-dom": "^17.0.2", - "react-dom-confetti": "^0.0.10", + "react-dom-confetti": "^0.2.0", "react-ga4": "^1.4.1", "react-relay": "^14.1.0", "react-router": "^5.0.1", diff --git a/yarn.lock b/yarn.lock index 7429558d3e6..3efedb60a12 100644 --- a/yarn.lock +++ b/yarn.lock @@ -11168,10 +11168,10 @@ dogapi@2.8.4: minimist "^1.2.5" rc "^1.2.8" -dom-confetti@~0.0.11: - version "0.0.15" - resolved "https://registry.yarnpkg.com/dom-confetti/-/dom-confetti-0.0.15.tgz#fd7dd6888da3dc6b54fd53326c8fa826ef25106b" - integrity sha512-KJKrmHcydwoS6bTD0wVM/L7LZz1rQNJA+Y3Zw9ZVYNpx5efqoVwIY2rqY/F2sEpJuGGCRff/o8bAmfQ8KO4Grw== +dom-confetti@0.2.2: + version "0.2.2" + resolved "https://registry.yarnpkg.com/dom-confetti/-/dom-confetti-0.2.2.tgz#bdf2e7652d37b5cffb532c0a3263d108dd8a2363" + integrity sha512-+UVH9Y85qmpTnbmFURwLWjqLIykyIrsNSRkPX/eFlBuOURz9RDX8JoZHnajZHyFuCV0w/K3+tZK0ztfoTw6ejg== dom-converter@^0.2.0: version "0.2.0" @@ -18321,12 +18321,12 @@ react-day-picker@^8.3.7: resolved "https://registry.yarnpkg.com/react-day-picker/-/react-day-picker-8.3.7.tgz#35de1325376984e4dbaec1fed6888f45dc46ad1d" integrity sha512-sQvyJde6OKsXIB0ZFwUBmaDNb4IyKypv/uSyauQ6AUw4F6vmPijJsQNEVsxrxXQ4L3GZKIpcCD/7Pftz/sLegA== -react-dom-confetti@^0.0.10: - version "0.0.10" - resolved "https://registry.yarnpkg.com/react-dom-confetti/-/react-dom-confetti-0.0.10.tgz#6cde7af4af974ecf98ddf9da95c938144c68a4f0" - integrity sha512-EJMUmD9Z3/87wIjyQic5ZGd2beHE9K50vxouLdV+yPcBitlsVznBXKX900xEbzQ/DOMt+TycEg2CvB/V+pwJtQ== +react-dom-confetti@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/react-dom-confetti/-/react-dom-confetti-0.2.0.tgz#76df26762da532057d5b1fbe38a8096f9dc33d40" + integrity sha512-+XRTi+WlCrcRN2dTjdEopOaPFtS7hpaHRRQ0sHiVRGqpchKz4QVh3i+6eLEEpNHYpN2VgPmhjvJ/vnjmUYhlIQ== dependencies: - dom-confetti "~0.0.11" + dom-confetti "0.2.2" react-dom@^17.0.2: version "17.0.2" From ab47ce46c768f927c9d71d0a52a049df93b51ba4 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 25 Mar 2024 18:03:32 -0700 Subject: [PATCH 096/529] chore: [Snyk] Upgrade core-js from 3.8.1 to 3.36.0 (#9519) Co-authored-by: snyk-bot Co-authored-by: Jordan Husney --- packages/client/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index d79a06254a8..1cdda75b5d6 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -101,7 +101,7 @@ "chartjs-adapter-dayjs-3": "^1.2.3", "cleave.js": "^1.6.0", "clsx": "^1.2.1", - "core-js": "3.8.1", + "core-js": "3.36.0", "date-fns": "^2.29.3", "dayjs": "^1.11.3", "dompurify": "^2.4.1", diff --git a/yarn.lock b/yarn.lock index 3efedb60a12..dc34fd16fae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10505,10 +10505,10 @@ core-js-pure@^3.8.1: resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.20.3.tgz#6cc4f36da06c61d95254efc54024fe4797fd5d02" integrity sha512-Q2H6tQ5MtPtcC7f3HxJ48i4Q7T9ybPKgvWyuH7JXIoNa2pm0KuBnycsET/qw1SLLZYfbsbrZQNMeIOClb+6WIA== -core-js@3.8.1: - version "3.8.1" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.8.1.tgz#f51523668ac8a294d1285c3b9db44025fda66d47" - integrity sha512-9Id2xHY1W7m8hCl8NkhQn5CufmF/WuR30BTRewvCXc1aZd3kMECwNZ69ndLbekKfakw9Rf2Xyc+QR6E7Gg+obg== +core-js@3.36.0: + version "3.36.0" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.36.0.tgz#e752fa0b0b462a0787d56e9d73f80b0f7c0dde68" + integrity sha512-mt7+TUBbTFg5+GngsAxeKBTl5/VS0guFeJacYge9OmHb+m058UwwIm41SE9T4Den7ClatV57B6TYTuJ0CX1MAw== core-util-is@~1.0.0: version "1.0.3" From 092e5d95bf75ce2db772669971e28f22e6ee8679 Mon Sep 17 00:00:00 2001 From: Jordan Husney Date: Mon, 25 Mar 2024 18:07:49 -0700 Subject: [PATCH 097/529] chore: fix update snyk pr action (#9564) --- .github/workflows/snyk-yarn-lock-commit.yml | 28 ----------- .github/workflows/synk-yarn-lock-commit.yml | 52 +++++++++++++++++++++ 2 files changed, 52 insertions(+), 28 deletions(-) delete mode 100644 .github/workflows/snyk-yarn-lock-commit.yml create mode 100644 .github/workflows/synk-yarn-lock-commit.yml diff --git a/.github/workflows/snyk-yarn-lock-commit.yml b/.github/workflows/snyk-yarn-lock-commit.yml deleted file mode 100644 index f4705ec4b79..00000000000 --- a/.github/workflows/snyk-yarn-lock-commit.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Update Snyk PR to add yarn.lock - -on: - pull_request: - types: [opened, synchronize, reopened] - -jobs: - update-snyk-pr: - if: contains(github.event.pull_request.title, '[Snyk]') - runs-on: ubuntu-latest - - steps: - - name: Checkout the repository - uses: actions/checkout@v3 - with: - ref: ${{ github.head_ref }} - token: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, no need to create one - - - name: Install dependencies - run: yarn install - - - name: Commit yarn.lock to the PR branch - run: | - git config --global user.email "action@github.com" - git config --global user.name "GitHub Action" - git add yarn.lock - git commit -m "Update yarn.lock" || echo "No changes to commit" - git push diff --git a/.github/workflows/synk-yarn-lock-commit.yml b/.github/workflows/synk-yarn-lock-commit.yml new file mode 100644 index 00000000000..6ac67f37aa9 --- /dev/null +++ b/.github/workflows/synk-yarn-lock-commit.yml @@ -0,0 +1,52 @@ +name: Update Snyk PR to add yarn.lock + +on: + pull_request: + types: [opened, synchronize, reopened] + +jobs: + update-snyk-pr: + if: contains(github.event.pull_request.title, '[Snyk]') + runs-on: ubuntu-latest + + steps: + - name: Checkout the repository + uses: actions/checkout@v3 + with: + ref: ${{ github.head_ref }} + token: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, no need to create one + + - name: Setup environment variables + run: | + ACTION_VERSION=$(grep '"version":' package.json | cut -d\" -f4) + echo "ACTION_VERSION=${ACTION_VERSION}" >> $GITHUB_ENV + echo "NODE_VERSION=$(jq -r -j '.engines.node|ltrimstr("^")' package.json)" >> $GITHUB_ENV + + DOCKER_REPOSITORY_FOR_REF=${{ secrets.GCP_AR_PARABOL_DEV }} + echo "DOCKER_REPOSITORY_FOR_REF=${{ secrets.GCP_AR_PARABOL_DEV }}" >> $GITHUB_ENV + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version-file: package.json + # Caching yarn dir & running yarn install is too slow + # Instead, we aggressively cache node_modules below to avoid calling install + + - name: Get cached node modules + id: cache + uses: actions/cache@v3 + with: + path: | + **/node_modules + key: node_modules-${{ runner.arch }}-${{ env.NODE_VERSION }}-${{ hashFiles('yarn.lock') }} + + - name: Install node_modules + run: yarn install + + - name: Commit yarn.lock to the PR branch + run: | + git config --global user.email "action@github.com" + git config --global user.name "GitHub Action" + git add yarn.lock + git commit -m "Update yarn.lock" || echo "No changes to commit" + git push From 5e98234efca84e7ebcb653f3d71d229a88797a8d Mon Sep 17 00:00:00 2001 From: Marcus Wermuth Date: Tue, 26 Mar 2024 17:14:40 +0100 Subject: [PATCH 098/529] fix: Removed broken Rally links and fixed Youtube links (#9332) --- .../userDashboard/helpers/getRallyLink.tsx | 18 +++--------------- 1 file changed, 3 insertions(+), 15 deletions(-) diff --git a/packages/client/modules/userDashboard/helpers/getRallyLink.tsx b/packages/client/modules/userDashboard/helpers/getRallyLink.tsx index 961f17965c8..ca7288ca227 100644 --- a/packages/client/modules/userDashboard/helpers/getRallyLink.tsx +++ b/packages/client/modules/userDashboard/helpers/getRallyLink.tsx @@ -40,7 +40,7 @@ const rallyList = [ }, { phrase: 'Don’t Stop Believin’', - link: 'https://youtu.be/Pw3GTTYgEV8' + link: 'https://youtu.be/1k8craCGpgs' }, { phrase: 'Gimme Some New', @@ -72,7 +72,7 @@ const rallyList = [ }, { phrase: 'On With The Show', - link: 'https://youtu.be/4ADh8Fs3YdU' + link: 'https://youtu.be/NijPVAu42aI' }, { phrase: 'Rawr, Tiger', @@ -80,7 +80,7 @@ const rallyList = [ }, { phrase: 'Right Here, Right Now', - link: 'https://youtu.be/F7jSp2xmmEE' + link: 'https://youtu.be/ub747pprmJ8' }, { phrase: 'Ring That Bell', @@ -94,10 +94,6 @@ const rallyList = [ phrase: 'Serve It', link: 'https://youtu.be/0J2QdDbelmY' }, - { - phrase: 'Sharpness Without Effort', - link: 'https://youtu.be/hpeTLTj2tww' - }, { phrase: 'Stronger, Richer, Smarter', link: 'https://youtu.be/Wmc8bQoL-J0' @@ -126,10 +122,6 @@ const rallyList = [ phrase: 'You Bossy', link: 'https://youtu.be/SSgp-IIgr4I' }, - { - phrase: 'You Came To Win', - link: 'https://youtu.be/KZaz7OqyTHQ' - }, { phrase: 'You’re The Smart One', link: 'https://youtu.be/bKQYK7PYQpQ' @@ -138,10 +130,6 @@ const rallyList = [ phrase: 'You’ve Got A Bright Future', link: 'https://youtu.be/kZGvnI37mxk' }, - { - phrase: 'You Must Go Pro', - link: 'https://youtu.be/9mSMTXYj7pQ' - }, { phrase: 'Time To Begin', link: 'https://youtu.be/RYlCVwxoL_g' From d9afe937184cfd19424c0288879208d21d709afe Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 27 Mar 2024 09:43:58 +0000 Subject: [PATCH 099/529] chore(release): release v7.23.0 (#9556) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 30 +++++++++++++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 ++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 41 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4a2437d9945..c796b1344c4 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.22.4" + ".": "7.23.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 440fc75fbfd..72ecc601f8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,36 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.23.0](https://github.com/ParabolInc/parabol/compare/v7.22.4...v7.23.0) (2024-03-26) + + +### Added + +* add functionality to change templates during a retro ([#9544](https://github.com/ParabolInc/parabol/issues/9544)) ([e6434e1](https://github.com/ParabolInc/parabol/commit/e6434e181a864b2e61428f55a98994fb1137ac8f)) +* allow 2 custom templates for every user ([#9518](https://github.com/ParabolInc/parabol/issues/9518)) ([2352669](https://github.com/ParabolInc/parabol/commit/2352669ea516a3d764d63af77211fbb4c0a02563)) +* make invoice row title more clear to understand ([#9551](https://github.com/ParabolInc/parabol/issues/9551)) ([9be96eb](https://github.com/ParabolInc/parabol/commit/9be96eb206d367e550b97831621c8b2aee4fc355)) +* release AzureDevOps integration ([#9531](https://github.com/ParabolInc/parabol/issues/9531)) ([87c84a2](https://github.com/ParabolInc/parabol/commit/87c84a2cca1a4d94629291d1325948b3c6cfb790)) +* switch template UI ([#9093](https://github.com/ParabolInc/parabol/issues/9093)) ([2171065](https://github.com/ParabolInc/parabol/commit/21710656b6d689b286759ea495ff334b7ce86adf)) + + +### Fixed + +* **admin:** fix an issue where ORG_ADMIN cannot see members from team they are not in ([#9560](https://github.com/ParabolInc/parabol/issues/9560)) ([ef0fbc2](https://github.com/ParabolInc/parabol/commit/ef0fbc2da853e2248a16ff2a2ce37c1f85f07f1a)) +* Removed broken Rally links and fixed Youtube links ([#9332](https://github.com/ParabolInc/parabol/issues/9332)) ([5e98234](https://github.com/ParabolInc/parabol/commit/5e98234efca84e7ebcb653f3d71d229a88797a8d)) + + +### Changed + +* [Snyk] Upgrade core-js from 3.8.1 to 3.36.0 ([#9519](https://github.com/ParabolInc/parabol/issues/9519)) ([ab47ce4](https://github.com/ParabolInc/parabol/commit/ab47ce46c768f927c9d71d0a52a049df93b51ba4)) +* [Snyk] Upgrade dotenv from 8.0.0 to 8.6.0 ([#9494](https://github.com/ParabolInc/parabol/issues/9494)) ([1e22931](https://github.com/ParabolInc/parabol/commit/1e22931c25c297e4697ea0e585d888c7b2738cfc)) +* [Snyk] Upgrade graphql-typed from 0.6.1 to 0.7.2 ([#9522](https://github.com/ParabolInc/parabol/issues/9522)) ([0ce1384](https://github.com/ParabolInc/parabol/commit/0ce1384b418f2a48971b732b548b9b93f8882e6c)) +* [Snyk] Upgrade react-dom-confetti from 0.0.10 to 0.2.0 ([#9520](https://github.com/ParabolInc/parabol/issues/9520)) ([ef68915](https://github.com/ParabolInc/parabol/commit/ef6891569f468469c64041d0c97555d76c2657d3)) +* [Snyk] Upgrade react-swipeable-views-core from 0.13.1 to 0.14.0 ([#9521](https://github.com/ParabolInc/parabol/issues/9521)) ([3e42d9b](https://github.com/ParabolInc/parabol/commit/3e42d9b155e171ec54f8d4b0cfddc1ace67cb754)) +* fix update snyk pr action ([#9564](https://github.com/ParabolInc/parabol/issues/9564)) ([092e5d9](https://github.com/ParabolInc/parabol/commit/092e5d95bf75ce2db772669971e28f22e6ee8679)) +* **github:** DevOps review if docker folder is modified or release-please-config is changed ([#9562](https://github.com/ParabolInc/parabol/issues/9562)) ([d18d754](https://github.com/ParabolInc/parabol/commit/d18d75485116c883d72456ac51b817a044a38b4d)) +* refactor add template mutation to the new sdl pattern ([#9533](https://github.com/ParabolInc/parabol/issues/9533)) ([fe71841](https://github.com/ParabolInc/parabol/commit/fe718413fa2d19afa660cc944d30a1284d6c2b18)) +* Roll out AIGeneratedDiscussion to all users ([#9554](https://github.com/ParabolInc/parabol/issues/9554)) ([b8fa708](https://github.com/ParabolInc/parabol/commit/b8fa7088e610ab1a920d1b5522cd46ad28e2f715)) + ## [7.22.4](https://github.com/ParabolInc/parabol/compare/v7.22.3...v7.22.4) (2024-03-20) diff --git a/package.json b/package.json index 034f26d340e..84ab320d908 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.22.4", + "version": "7.23.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 66bfb9f4458..9d6b749e83c 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.22.4", + "version": "7.23.0", "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.22.4" + "parabol-server": "7.23.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 1cdda75b5d6..c0069eadabc 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.22.4", + "version": "7.23.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index c4470e61f05..75c98f09470 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.22.4", + "version": "7.23.0", "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.22.4", - "parabol-server": "7.22.4", + "parabol-client": "7.23.0", + "parabol-server": "7.23.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index a3d12e08429..d590b2077ec 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.22.4", + "version": "7.23.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index e65191586cc..9e8744fc8df 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.22.4", + "version": "7.23.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.22.4", + "parabol-client": "7.23.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 5baf3b7584f8f578284baa7e47eb5e264e99ad53 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Wed, 27 Mar 2024 14:08:06 +0000 Subject: [PATCH 100/529] fix: remove destroyAll from add custom templates migration --- ...ng.ts => 1709933510512_addFreeCustomTemplatesRemaining.ts} | 4 ---- 1 file changed, 4 deletions(-) rename packages/server/postgres/migrations/{1709933510511_addFreeCustomTemplatesRemaining.ts => 1709933510512_addFreeCustomTemplatesRemaining.ts} (94%) diff --git a/packages/server/postgres/migrations/1709933510511_addFreeCustomTemplatesRemaining.ts b/packages/server/postgres/migrations/1709933510512_addFreeCustomTemplatesRemaining.ts similarity index 94% rename from packages/server/postgres/migrations/1709933510511_addFreeCustomTemplatesRemaining.ts rename to packages/server/postgres/migrations/1709933510512_addFreeCustomTemplatesRemaining.ts index cb378a1ad4b..fd2ceae8931 100644 --- a/packages/server/postgres/migrations/1709933510511_addFreeCustomTemplatesRemaining.ts +++ b/packages/server/postgres/migrations/1709933510512_addFreeCustomTemplatesRemaining.ts @@ -13,8 +13,6 @@ export async function up() { .addColumn('freeCustomRetroTemplatesRemaining', 'int2', (col) => col.defaultTo(2).notNull()) .addColumn('freeCustomPokerTemplatesRemaining', 'int2', (col) => col.defaultTo(2).notNull()) .execute() - - await pg.destroy() } export async function down() { @@ -29,6 +27,4 @@ export async function down() { .dropColumn('freeCustomRetroTemplatesRemaining') .dropColumn('freeCustomPokerTemplatesRemaining') .execute() - - await pg.destroy() } From 79e67cdffb6083267d9eb150aad2b682d9d7e924 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Wed, 27 Mar 2024 14:08:50 +0000 Subject: [PATCH 101/529] Revert "fix: remove destroyAll from add custom templates migration" This reverts commit 5baf3b7584f8f578284baa7e47eb5e264e99ad53. --- ...ng.ts => 1709933510511_addFreeCustomTemplatesRemaining.ts} | 4 ++++ 1 file changed, 4 insertions(+) rename packages/server/postgres/migrations/{1709933510512_addFreeCustomTemplatesRemaining.ts => 1709933510511_addFreeCustomTemplatesRemaining.ts} (94%) diff --git a/packages/server/postgres/migrations/1709933510512_addFreeCustomTemplatesRemaining.ts b/packages/server/postgres/migrations/1709933510511_addFreeCustomTemplatesRemaining.ts similarity index 94% rename from packages/server/postgres/migrations/1709933510512_addFreeCustomTemplatesRemaining.ts rename to packages/server/postgres/migrations/1709933510511_addFreeCustomTemplatesRemaining.ts index fd2ceae8931..cb378a1ad4b 100644 --- a/packages/server/postgres/migrations/1709933510512_addFreeCustomTemplatesRemaining.ts +++ b/packages/server/postgres/migrations/1709933510511_addFreeCustomTemplatesRemaining.ts @@ -13,6 +13,8 @@ export async function up() { .addColumn('freeCustomRetroTemplatesRemaining', 'int2', (col) => col.defaultTo(2).notNull()) .addColumn('freeCustomPokerTemplatesRemaining', 'int2', (col) => col.defaultTo(2).notNull()) .execute() + + await pg.destroy() } export async function down() { @@ -27,4 +29,6 @@ export async function down() { .dropColumn('freeCustomRetroTemplatesRemaining') .dropColumn('freeCustomPokerTemplatesRemaining') .execute() + + await pg.destroy() } From 0d30206b154b60f11b23708c9315e9960d4825bb Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Thu, 28 Mar 2024 16:33:14 +0000 Subject: [PATCH 102/529] fix: ensure pool is callable after custom template migration (#9572) --- ...ng.ts => 1709933510512_addFreeCustomTemplatesRemaining.ts} | 4 ---- 1 file changed, 4 deletions(-) rename packages/server/postgres/migrations/{1709933510511_addFreeCustomTemplatesRemaining.ts => 1709933510512_addFreeCustomTemplatesRemaining.ts} (94%) diff --git a/packages/server/postgres/migrations/1709933510511_addFreeCustomTemplatesRemaining.ts b/packages/server/postgres/migrations/1709933510512_addFreeCustomTemplatesRemaining.ts similarity index 94% rename from packages/server/postgres/migrations/1709933510511_addFreeCustomTemplatesRemaining.ts rename to packages/server/postgres/migrations/1709933510512_addFreeCustomTemplatesRemaining.ts index cb378a1ad4b..fd2ceae8931 100644 --- a/packages/server/postgres/migrations/1709933510511_addFreeCustomTemplatesRemaining.ts +++ b/packages/server/postgres/migrations/1709933510512_addFreeCustomTemplatesRemaining.ts @@ -13,8 +13,6 @@ export async function up() { .addColumn('freeCustomRetroTemplatesRemaining', 'int2', (col) => col.defaultTo(2).notNull()) .addColumn('freeCustomPokerTemplatesRemaining', 'int2', (col) => col.defaultTo(2).notNull()) .execute() - - await pg.destroy() } export async function down() { @@ -29,6 +27,4 @@ export async function down() { .dropColumn('freeCustomRetroTemplatesRemaining') .dropColumn('freeCustomPokerTemplatesRemaining') .execute() - - await pg.destroy() } From cd229f7bba62a5a75f1efd0cbbc798f709069394 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 28 Mar 2024 09:39:58 -0700 Subject: [PATCH 103/529] chore(release): release v7.23.1 (#9571) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 8 files changed, 19 insertions(+), 11 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c796b1344c4..bae38c98366 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.23.0" + ".": "7.23.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 72ecc601f8f..294dbfc8830 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.23.1](https://github.com/ParabolInc/parabol/compare/v7.23.0...v7.23.1) (2024-03-28) + + +### Fixed + +* ensure pool is callable after custom template migration ([#9572](https://github.com/ParabolInc/parabol/issues/9572)) ([0d30206](https://github.com/ParabolInc/parabol/commit/0d30206b154b60f11b23708c9315e9960d4825bb)) +* remove destroyAll from add custom templates migration ([5baf3b7](https://github.com/ParabolInc/parabol/commit/5baf3b7584f8f578284baa7e47eb5e264e99ad53)) + ## [7.23.0](https://github.com/ParabolInc/parabol/compare/v7.22.4...v7.23.0) (2024-03-26) diff --git a/package.json b/package.json index 84ab320d908..82179da713f 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.23.0", + "version": "7.23.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 9d6b749e83c..b2e5061c6bc 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.23.0", + "version": "7.23.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.23.0" + "parabol-server": "7.23.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index c0069eadabc..3f105e730fb 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.23.0", + "version": "7.23.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 75c98f09470..bed8c3473eb 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.23.0", + "version": "7.23.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.23.0", - "parabol-server": "7.23.0", + "parabol-client": "7.23.1", + "parabol-server": "7.23.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index d590b2077ec..2cbc84391c1 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.23.0", + "version": "7.23.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 9e8744fc8df..68cfcfbb846 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.23.0", + "version": "7.23.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.23.0", + "parabol-client": "7.23.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From d1af0f164c629e8fc075278cd63475e8913f4295 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 28 Mar 2024 16:32:47 -0700 Subject: [PATCH 104/529] chore: fix tsconfig problems (#9579) Signed-off-by: Matt Krick --- packages/chronos/tsconfig.json | 11 +-- packages/client/components/AnimatedFade.tsx | 74 ------------------- .../AzureDevOpsScopingSelectAllIssues.tsx | 6 +- packages/client/hooks/useAtlassianSites.ts | 43 ----------- .../components/MeetingSummaryEmailRootSSR.tsx | 1 - .../NotificationSummaryEmailRoot.tsx | 2 +- .../MeetingSummaryEmail.tsx | 14 ---- .../handlers/handleUpdateTeamMembers.ts | 11 ++- packages/client/package.json | 6 +- packages/client/serviceWorker/tsconfig.json | 4 +- packages/client/tsconfig.json | 9 +-- packages/client/types/modules.d.ts | 1 + packages/client/types/reactHTML4.d.ts | 15 ++++ .../ui/AlertDialog/AlertDialogContent.tsx | 4 +- .../ui/AlertDialog/AlertDialogPortal.tsx | 2 +- .../ui/AlertDialog/AlertDialogTrigger.tsx | 2 +- .../__tests__/parseEmailAddressList.test.ts | 7 +- ...etBezierTimePercentGivenDistancePercent.ts | 11 ++- packages/client/utils/screenBugs/Bug.ts | 20 ++--- .../client/utils/screenBugs/BugController.ts | 22 +++--- .../embedder/ai_models/OpenAIGeneration.ts | 4 +- packages/embedder/embedder.ts | 22 +++--- .../indexing/createEmbeddingTextFrom.ts | 6 +- .../embedder/indexing/embeddingsTablesOps.ts | 25 ++++--- .../indexing/retrospectiveDiscussionTopic.ts | 8 +- packages/embedder/modules.d.ts | 2 + packages/embedder/tsconfig.json | 11 +-- packages/gql-executor/RedisStream.ts | 6 +- packages/gql-executor/gqlExecutor.ts | 7 +- packages/gql-executor/modules.d.ts | 2 + packages/gql-executor/tsconfig.json | 11 ++- packages/server/__tests__/autoJoin.test.ts | 4 +- packages/server/__tests__/common.ts | 2 +- .../server/__tests__/disableAnonymity.test.ts | 14 ++-- packages/server/__tests__/loginSAML.test.ts | 24 ------ .../__tests__/processRecurrence.test.ts | 11 ++- .../__tests__/startRetrospective.test.ts | 18 ++--- .../__tests__/isCompanyDomain.test.ts | 2 - .../__tests__/isOrgVerified.test.ts | 39 +++++----- .../__tests__/usersCustomRedisQueries.test.ts | 14 ++-- packages/server/email/inlineImages.ts | 6 +- packages/server/generateUID.ts | 2 +- packages/server/graphql/executeGraphQL.ts | 19 +---- packages/server/package.json | 1 + packages/server/tsconfig.json | 20 ++--- packages/server/types/custom.d.ts | 18 +++++ packages/server/types/modules.d.ts | 1 + .../isRequestToJoinDomainAllowed.test.ts | 65 ++++++++-------- .../rateLimiters/InMemoryRateLimiter.test.ts | 2 +- packages/server/utils/getGraphQLExecutor.ts | 2 +- packages/server/utils/uwsGetHeaders.ts | 2 +- scripts/webpack/utils/getProjectRoot.d.ts | 2 + yarn.lock | 34 +++++++-- 53 files changed, 280 insertions(+), 391 deletions(-) delete mode 100644 packages/client/components/AnimatedFade.tsx delete mode 100644 packages/client/hooks/useAtlassianSites.ts create mode 100644 packages/client/types/reactHTML4.d.ts create mode 100644 packages/embedder/modules.d.ts create mode 100644 packages/gql-executor/modules.d.ts create mode 100644 scripts/webpack/utils/getProjectRoot.d.ts diff --git a/packages/chronos/tsconfig.json b/packages/chronos/tsconfig.json index 00f66831a3b..87e7ef08687 100644 --- a/packages/chronos/tsconfig.json +++ b/packages/chronos/tsconfig.json @@ -9,13 +9,6 @@ "~/*": ["client/*"] }, "outDir": "lib", - "lib": ["esnext"], - "types": ["node"] - }, - "files": [ - "chronos.ts", - "../server/types/webpackEnv.ts", - "../server/types/modules.d.ts", - "../client/modules/email/components/SummaryEmail/MeetingSummaryEmail/MeetingSummaryEmail.tsx" - ] + "lib": ["esnext"] + } } diff --git a/packages/client/components/AnimatedFade.tsx b/packages/client/components/AnimatedFade.tsx deleted file mode 100644 index f1f612ec4ff..00000000000 --- a/packages/client/components/AnimatedFade.tsx +++ /dev/null @@ -1,74 +0,0 @@ -/* Deprecated. See internals of useMenuPortal */ - -import {ClassNames} from '@emotion/core' -import React, {Component, ReactNode} from 'react' -import {CSSTransition} from 'react-transition-group' - -interface Props extends CSSTransition { - appear?: boolean - in?: boolean - onExited?: () => void - exit?: boolean - unmountOnExit?: boolean - children: ReactNode - duration?: number - slide?: number -} - -// eslint-disable-next-line -class AnimatedFade extends Component { - render() { - const {children, duration = 100, slide = 32, ...props} = this.props - - const classNames = (css) => { - const enter = css({ - opacity: 0, - transform: `translate3d(0, ${slide}px, 0)` - }) - const enterActive = css({ - opacity: '1 !important' as any, - transform: 'translate3d(0, 0, 0) !important', - transition: `all ${duration}ms ease-in !important` - }) - - const exit = css({ - opacity: 1, - transform: 'translate3d(0, 0, 0)' - }) - - const exitActive = css({ - opacity: '0 !important' as any, - transform: `translate3d(0, ${-slide}px, 0) !important`, - transition: `all ${duration}ms ease-in !important` - }) - return { - appear: enter, - appearActive: enterActive, - enter, - enterActive, - exit, - exitActive - } - } - return ( - - {({css}) => { - return ( - - {children} - - ) - }} - - ) - } -} - -export default AnimatedFade diff --git a/packages/client/components/AzureDevOpsScopingSelectAllIssues.tsx b/packages/client/components/AzureDevOpsScopingSelectAllIssues.tsx index d4f80d54fe0..24c42792294 100644 --- a/packages/client/components/AzureDevOpsScopingSelectAllIssues.tsx +++ b/packages/client/components/AzureDevOpsScopingSelectAllIssues.tsx @@ -3,6 +3,7 @@ import graphql from 'babel-plugin-relay/macro' import React from 'react' import {useFragment} from 'react-relay' import useUnusedRecords from '~/hooks/useUnusedRecords' +import {AzureDevOpsScopingSelectAllIssues_workItems$key} from '../__generated__/AzureDevOpsScopingSelectAllIssues_workItems.graphql' import useAtmosphere from '../hooks/useAtmosphere' import useMutationProps from '../hooks/useMutationProps' import UpdatePokerScopeMutation from '../mutations/UpdatePokerScopeMutation' @@ -11,7 +12,6 @@ import {PALETTE} from '../styles/paletteV3' import {Threshold} from '../types/constEnums' import AzureDevOpsClientManager from '../utils/AzureDevOpsClientManager' import getSelectAllTitle from '../utils/getSelectAllTitle' -import {AzureDevOpsScopingSelectAllIssues_workItems$key} from '../__generated__/AzureDevOpsScopingSelectAllIssues_workItems.graphql' import Checkbox from './Checkbox' const Item = styled('div')({ @@ -42,7 +42,7 @@ interface Props { } const AzureDevOpsScopingSelectAllIssues = (props: Props) => { - const {meetingId, usedServiceTaskIds, workItems: workItemsRef, providerId} = props + const {meetingId, usedServiceTaskIds, workItems: workItemsRef} = props const workItems = useFragment( graphql` fragment AzureDevOpsScopingSelectAllIssues_workItems on AzureDevOpsWorkItemEdge @@ -57,7 +57,7 @@ const AzureDevOpsScopingSelectAllIssues = (props: Props) => { workItemsRef ) const atmosphere = useAtmosphere() - const {onCompleted, onError, submitMutation, submitting, error} = useMutationProps() + const {onCompleted, onError, submitMutation, error} = useMutationProps() const getProjectId = (url: URL) => { const firstIndex = url.pathname.indexOf('/', 1) const seconedIndex = url.pathname.indexOf('/', firstIndex + 1) diff --git a/packages/client/hooks/useAtlassianSites.ts b/packages/client/hooks/useAtlassianSites.ts deleted file mode 100644 index 8fdb3a66567..00000000000 --- a/packages/client/hooks/useAtlassianSites.ts +++ /dev/null @@ -1,43 +0,0 @@ -import {useEffect, useRef, useState} from 'react' -import {Unpromise} from '../types/generics' -import AtlassianClientManager from '../utils/AtlassianClientManager' -import {AccessibleResource} from '../utils/AtlassianManager' - -const useAtlassianSites = (accessToken?: string) => { - const isMountedRef = useRef(true) - const [sites, setSites] = useState([]) - const [status, setStatus] = useState(null) - useEffect(() => { - const manager = new AtlassianClientManager(accessToken || '') - const fetchSites = async () => { - let res: Unpromise> - try { - res = await manager.getAccessibleResources() - } catch (e) { - if (isMountedRef.current) { - setStatus('error') - } - return - } - if (isMountedRef.current) { - if (Array.isArray(res)) { - setStatus('loaded') - setSites(res) - } else { - setStatus('error') - } - } - } - - if (accessToken && isMountedRef.current) { - setStatus('loading') - fetchSites().catch() - } - return () => { - isMountedRef.current = false - } - }, [accessToken]) - return {sites, status} -} - -export default useAtlassianSites diff --git a/packages/client/modules/email/components/MeetingSummaryEmailRootSSR.tsx b/packages/client/modules/email/components/MeetingSummaryEmailRootSSR.tsx index 9ad3310cd7d..bd17389a344 100644 --- a/packages/client/modules/email/components/MeetingSummaryEmailRootSSR.tsx +++ b/packages/client/modules/email/components/MeetingSummaryEmailRootSSR.tsx @@ -2,7 +2,6 @@ import graphql from 'babel-plugin-relay/macro' import {MeetingSummaryEmailRootSSRQuery} from 'parabol-client/__generated__/MeetingSummaryEmailRootSSRQuery.graphql' import React from 'react' import {useLazyLoadQuery} from 'react-relay' -import {Environment} from 'relay-runtime' import {EMAIL_CORS_OPTIONS} from '../../../types/cors' import makeAppURL from '../../../utils/makeAppURL' import MeetingSummaryEmail from './SummaryEmail/MeetingSummaryEmail/MeetingSummaryEmail' diff --git a/packages/client/modules/email/components/NotificationSummaryEmailRoot.tsx b/packages/client/modules/email/components/NotificationSummaryEmailRoot.tsx index bcf16c49bb0..cd610f6758d 100644 --- a/packages/client/modules/email/components/NotificationSummaryEmailRoot.tsx +++ b/packages/client/modules/email/components/NotificationSummaryEmailRoot.tsx @@ -56,7 +56,7 @@ const NotificationSummaryEmailRoot = (props: NotificationSummaryRootProps) => { (edge) => edge.node.status === 'UNREAD' && new Date(edge.node.createdAt) > new Date(Date.now() - ms('1d')) && - NOTIFICATION_TEMPLATE_TYPE[edge.node.type] // Filter down to the notifications that have been implemented. + NOTIFICATION_TEMPLATE_TYPE[edge.node.type as keyof typeof NOTIFICATION_TEMPLATE_TYPE] // Filter down to the notifications that have been implemented. ) .map((edge) => edge.node) .slice(0, MAX_EMAIL_NOTIFICATIONS) diff --git a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/MeetingSummaryEmail.tsx b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/MeetingSummaryEmail.tsx index 5a2611456f8..b49644af0f5 100644 --- a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/MeetingSummaryEmail.tsx +++ b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/MeetingSummaryEmail.tsx @@ -36,20 +36,6 @@ const pagePadding = { paddingTop: 24 } -declare module 'react' { - interface TdHTMLAttributes { - height?: string | number - width?: string | number - bgcolor?: string - } - interface TableHTMLAttributes { - align?: 'center' | 'left' | 'right' - bgcolor?: string - height?: string | number - width?: string | number - } -} - const PagePadding = () => { return ( diff --git a/packages/client/mutations/handlers/handleUpdateTeamMembers.ts b/packages/client/mutations/handlers/handleUpdateTeamMembers.ts index f62a323f291..6ccecccea3c 100644 --- a/packages/client/mutations/handlers/handleUpdateTeamMembers.ts +++ b/packages/client/mutations/handlers/handleUpdateTeamMembers.ts @@ -1,8 +1,12 @@ +import {RecordProxy, RecordSourceSelectorProxy} from 'relay-runtime' import fromTeamMemberId from '../../utils/relay/fromTeamMemberId' import safeRemoveNodeFromArray from '../../utils/relay/safeRemoveNodeFromArray' import pluralizeHandler from './pluralizeHandler' -const handleUpdateTeamMember = (updatedTeamMember, store) => { +const handleUpdateTeamMember = ( + updatedTeamMember: RecordProxy<{id: string}>, + store: RecordSourceSelectorProxy +) => { if (!updatedTeamMember) return const {teamId} = fromTeamMemberId(updatedTeamMember.getValue('id')) const isNotRemoved = updatedTeamMember.getValue('isNotRemoved') @@ -11,7 +15,7 @@ const handleUpdateTeamMember = (updatedTeamMember, store) => { const sorts = ['preferredName'] if (isNotRemoved) { sorts.forEach((sortBy) => { - const teamMembers = team.getLinkedRecords('teamMembers', {sortBy}) + const teamMembers = team.getLinkedRecords<[]>('teamMembers', {sortBy}) if (!teamMembers) return teamMembers.sort((a, b) => (a.getValue(sortBy) > b.getValue(sortBy) ? 1 : -1)) team.setLinkedRecords(teamMembers, 'teamMembers', {sortBy}) @@ -19,8 +23,7 @@ const handleUpdateTeamMember = (updatedTeamMember, store) => { } else { const teamMemberId = updatedTeamMember.getValue('id') sorts.forEach((sortBy) => { - const teamMembers = team.getLinkedRecords('teamMembers', {sortBy}) - safeRemoveNodeFromArray(teamMemberId, teamMembers, 'teamMembers', { + safeRemoveNodeFromArray(teamMemberId, team, 'teamMembers', { storageKeyArgs: {sortBy} }) }) diff --git a/packages/client/package.json b/packages/client/package.json index 3f105e730fb..5775dc743ab 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -31,7 +31,7 @@ "@types/draft-js": "^0.10.24", "@types/fbjs": "^3.0.4", "@types/humanize-duration": "3.27.1", - "@types/jest": "^29.5.1", + "@types/jest": "^29.5.12", "@types/json2csv": "^4.4.0", "@types/jwt-decode": "^2.1.0", "@types/linkify-it": "^3.0.2", @@ -75,6 +75,8 @@ "@mui/icons-material": "^5.8.4", "@mui/material": "^5.9.2", "@mui/x-date-pickers": "^6.3.1", + "@radix-ui/react-alert-dialog": "1.0.5", + "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.4", "@radix-ui/react-dropdown-menu": "^2.0.4", @@ -82,8 +84,6 @@ "@radix-ui/react-scroll-area": "^1.0.3", "@radix-ui/react-select": "^1.2.2", "@radix-ui/react-slot": "^1.0.2", - "@radix-ui/react-avatar": "^1.0.4", - "@radix-ui/react-alert-dialog": "1.0.5", "@radix-ui/react-tooltip": "^1.0.7", "@sentry/browser": "^5.8.0", "@stripe/react-stripe-js": "^1.16.5", diff --git a/packages/client/serviceWorker/tsconfig.json b/packages/client/serviceWorker/tsconfig.json index ec0c5fb0b28..f393dcadb5f 100644 --- a/packages/client/serviceWorker/tsconfig.json +++ b/packages/client/serviceWorker/tsconfig.json @@ -1,7 +1,6 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { -// "composite": true, "lib": ["esnext", "webworker"], "target": "esnext", "module": "commonjs", @@ -11,6 +10,5 @@ "*" // resolve all absolute imports as imports relative to the client package ] } - }, -// "references": [{"path": "../"}] + } } diff --git a/packages/client/tsconfig.json b/packages/client/tsconfig.json index 58280d064ea..32bb2473e60 100644 --- a/packages/client/tsconfig.json +++ b/packages/client/tsconfig.json @@ -3,15 +3,10 @@ "compilerOptions": { "baseUrl": ".", "paths": { - "~/*": ["*"], - "static/*": ["../../static/*"] + "~/*": ["*"] }, "outDir": "lib", - "lib": ["esnext", "dom"], - "types": ["node"] + "lib": ["esnext", "dom"] }, - "files": [ - "client.tsx", - ], "exclude": ["serviceWorker", "**/node_modules"] } diff --git a/packages/client/types/modules.d.ts b/packages/client/types/modules.d.ts index 92293d0cbbd..28b06e27aa7 100644 --- a/packages/client/types/modules.d.ts +++ b/packages/client/types/modules.d.ts @@ -20,6 +20,7 @@ declare module 'emoji-mart/dist-modern/utils/data.js' declare module 'emoji-mart/dist-modern/components/picker/nimble-picker' declare module 'react-textarea-autosize' declare module 'react-copy-to-clipboard' +declare module 'tayden-clusterfck' declare let __webpack_public_path__: string declare const __PRODUCTION__: string diff --git a/packages/client/types/reactHTML4.d.ts b/packages/client/types/reactHTML4.d.ts new file mode 100644 index 00000000000..995d555df4a --- /dev/null +++ b/packages/client/types/reactHTML4.d.ts @@ -0,0 +1,15 @@ +import 'react' + +declare module 'react' { + export interface TdHTMLAttributes { + height?: string | number + width?: string | number + bgcolor?: string + } + export interface TableHTMLAttributes { + align?: 'center' | 'left' | 'right' + bgcolor?: string + height?: string | number + width?: string | number + } +} diff --git a/packages/client/ui/AlertDialog/AlertDialogContent.tsx b/packages/client/ui/AlertDialog/AlertDialogContent.tsx index dbcd0de0582..12d5a79355c 100644 --- a/packages/client/ui/AlertDialog/AlertDialogContent.tsx +++ b/packages/client/ui/AlertDialog/AlertDialogContent.tsx @@ -1,8 +1,8 @@ -import * as React from 'react' import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' import clsx from 'clsx' +import * as React from 'react' import {AlertDialogOverlay} from './AlertDialogOverlay' -import {AlertDialogPortal} from './AlertDialog' +import {AlertDialogPortal} from './AlertDialogPortal' const AlertDialogContent = React.forwardRef< HTMLDivElement, diff --git a/packages/client/ui/AlertDialog/AlertDialogPortal.tsx b/packages/client/ui/AlertDialog/AlertDialogPortal.tsx index c4e8d56916f..f572b2d9baa 100644 --- a/packages/client/ui/AlertDialog/AlertDialogPortal.tsx +++ b/packages/client/ui/AlertDialog/AlertDialogPortal.tsx @@ -1,3 +1,3 @@ import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' -const AlertDialogPortal = AlertDialogPrimitive.Portal +export const AlertDialogPortal = AlertDialogPrimitive.Portal diff --git a/packages/client/ui/AlertDialog/AlertDialogTrigger.tsx b/packages/client/ui/AlertDialog/AlertDialogTrigger.tsx index 0a20feddfc1..6b2d6149df4 100644 --- a/packages/client/ui/AlertDialog/AlertDialogTrigger.tsx +++ b/packages/client/ui/AlertDialog/AlertDialogTrigger.tsx @@ -1,3 +1,3 @@ import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog' -const AlertDialogTrigger = AlertDialogPrimitive.Trigger +export const AlertDialogTrigger = AlertDialogPrimitive.Trigger diff --git a/packages/client/utils/__tests__/parseEmailAddressList.test.ts b/packages/client/utils/__tests__/parseEmailAddressList.test.ts index a9778e7bebe..87c83ee894e 100644 --- a/packages/client/utils/__tests__/parseEmailAddressList.test.ts +++ b/packages/client/utils/__tests__/parseEmailAddressList.test.ts @@ -1,7 +1,12 @@ /* eslint-env jest */ import parseEmailAddressList from '../parseEmailAddressList' -const getAddressStr = (res) => res && res.parsedInvitees.map((val) => val.address).join(', ') +type Res = { + parsedInvitees: { + [other: string]: any + }[] +} +const getAddressStr = (res: Res) => res && res.parsedInvitees.map((val) => val.address).join(', ') describe('parseEmailAddressList', () => { it('validates a simple single email', () => { diff --git a/packages/client/utils/getBezierTimePercentGivenDistancePercent.ts b/packages/client/utils/getBezierTimePercentGivenDistancePercent.ts index af2b6ab346b..3b988aec450 100644 --- a/packages/client/utils/getBezierTimePercentGivenDistancePercent.ts +++ b/packages/client/utils/getBezierTimePercentGivenDistancePercent.ts @@ -10,7 +10,12 @@ const bezierLookup = [] as {x: number; y: number}[] const getBezierTimePercentGivenDistancePercent = (threshold: number, bezierCurve: string) => { if (bezierLookup.length === 0) { const re = /\(([^)]+)\)/ - const [x1, y1, x2, y2] = re.exec(bezierCurve)![1].split(',').map(Number) + const [x1, y1, x2, y2] = re.exec(bezierCurve)![1]!.split(',').map(Number) as [ + number, + number, + number, + number + ] // css bezier-curves imply a start of 0,0 and end of 1,1 const x0 = 0 const y0 = 0 @@ -32,9 +37,9 @@ const getBezierTimePercentGivenDistancePercent = (threshold: number, bezierCurve } let x for (let i = 1; i < bezierLookup.length; i++) { - const point = bezierLookup[i] + const point = bezierLookup[i]! if (point.y > threshold) { - x = bezierLookup[i - 1].x + x = bezierLookup[i - 1]!.x break } } diff --git a/packages/client/utils/screenBugs/Bug.ts b/packages/client/utils/screenBugs/Bug.ts index 536d0142887..0ab7411d40d 100644 --- a/packages/client/utils/screenBugs/Bug.ts +++ b/packages/client/utils/screenBugs/Bug.ts @@ -183,7 +183,7 @@ export default class Bug { this.bug.classList.remove('bug-dead') } - animate = (t) => { + animate = (t: any) => { if (!this.animating || !this.alive || !this.active) return this.going = requestAnimationFrame((t) => { this.animate(t) @@ -217,9 +217,9 @@ export default class Bug { this.angle_deg %= 360 if (this.angle_deg < 0) this.angle_deg += 360 - if (Math.abs(this.directions[this.near_edge] - this.angle_deg) > 15) { - const angle1 = this.directions[this.near_edge] - this.angle_deg - const angle2 = 360 - this.angle_deg + this.directions[this.near_edge] + if (Math.abs(this.directions[this.near_edge]! - this.angle_deg) > 15) { + const angle1 = this.directions[this.near_edge]! - this.angle_deg + const angle2 = 360 - this.angle_deg + this.directions[this.near_edge]! this.large_turn_angle_deg = Math.abs(angle1) < Math.abs(angle2) ? angle1 : angle2 this.edge_test_counter = 10 @@ -399,7 +399,7 @@ export default class Bug { let side = Math.round(Math.random() * 4 - 0.5) const d = document const e = d.documentElement - const g = d.getElementsByTagName('body')[0] + const g = d.getElementsByTagName('body')[0]! const windowX = window.innerWidth || e.clientWidth || g.clientWidth const windowY = window.innerHeight || e.clientHeight || g.clientHeight if (side > 3) side = 3 @@ -455,7 +455,7 @@ export default class Bug { let side = Math.round(Math.random() * 4 - 0.5) const d = document const e = d.documentElement - const g = d.getElementsByTagName('body')[0] + const g = d.getElementsByTagName('body')[0]! const windowX = window.innerWidth || e.clientWidth || g.clientWidth const windowY = window.innerHeight || e.clientHeight || g.clientHeight if (side > 3) side = 3 @@ -496,7 +496,7 @@ export default class Bug { const style = {} as Pos const d = document const e = d.documentElement - const g = d.getElementsByTagName('body')[0] + const g = d.getElementsByTagName('body')[0]! const windowX = window.innerWidth || e.clientWidth || g.clientWidth const windowY = window.innerHeight || e.clientHeight || g.clientHeight @@ -529,11 +529,11 @@ export default class Bug { this.drop(deathType) } - drop = (deathType) => { + drop = (deathType: any) => { const startPos = this.bug.top const d = document const e = d.documentElement - const g = d.getElementsByTagName('body')[0] + const g = d.getElementsByTagName('body')[0]! const pos = window.innerHeight || e.clientHeight || g.clientHeight const finalPos = pos - this.options.bugHeight const rotationRate = this.random(0, 20, true) @@ -545,7 +545,7 @@ export default class Bug { }) } - dropping = (t, startPos, finalPos, rotationRate, deathType) => { + dropping = (t: number, startPos: any, finalPos: any, rotationRate: any, deathType: any) => { const elapsedTime = t - this._lastTimestamp! const deltaPos = 0.002 * (elapsedTime * elapsedTime) let newPos = startPos + deltaPos diff --git a/packages/client/utils/screenBugs/BugController.ts b/packages/client/utils/screenBugs/BugController.ts index 132c1796103..330f729ad61 100644 --- a/packages/client/utils/screenBugs/BugController.ts +++ b/packages/client/utils/screenBugs/BugController.ts @@ -131,7 +131,7 @@ class BugDispatch { this.spawnDelay = [] for (let i = 0; i < numBugs; i++) { const delay = this.random(this.options.minDelay, this.options.maxDelay, true) - const thebug = this.bugs[i] + const thebug = this.bugs[i]! // fly the bug onto the page: this.spawnDelay[i] = window.setTimeout(() => { this.options.canFly ? thebug.flyIn() : thebug.walkIn() @@ -152,30 +152,30 @@ class BugDispatch { stop = () => { for (let i = 0; i < this.bugs.length; i++) { if (this.spawnDelay[i]) clearTimeout(this.spawnDelay[i]) - this.bugs[i].stop() + this.bugs[i]!.stop() } } end = () => { for (let i = 0; i < this.bugs.length; i++) { if (this.spawnDelay[i]) clearTimeout(this.spawnDelay[i]) - this.bugs[i].stop() - this.bugs[i].remove() + this.bugs[i]!.stop() + this.bugs[i]!.remove() } } reset = () => { this.stop() for (let i = 0; i < this.bugs.length; i++) { - this.bugs[i].reset() - this.bugs[i].walkIn() + this.bugs[i]!.reset() + this.bugs[i]!.walkIn() } } killAll = () => { for (let i = 0; i < this.bugs.length; i++) { if (this.spawnDelay[i]) clearTimeout(this.spawnDelay[i]) - this.bugs[i].die() + this.bugs[i]!.die() } } @@ -209,13 +209,13 @@ class BugDispatch { } const numBugs = this.bugs.length for (let i = 0; i < numBugs; i++) { - const pos = this.bugs[i].getPos() + const pos = this.bugs[i]!.getPos() if (pos) { if ( Math.abs(pos.top - posy) + Math.abs(pos.left - posx) < this.options.eventDistanceToBug && - !this.bugs[i].flyperiodical + !this.bugs[i]!.flyperiodical ) { - this.near_bug(this.bugs[i]) + this.near_bug(this.bugs[i]!) } } } @@ -233,7 +233,7 @@ class BugDispatch { let mode = this.options.mouseOver if (mode === 'random') { - mode = this.modes[this.random(0, this.modes.length - 1, true)] + mode = this.modes[this.random(0, this.modes.length - 1, true)]! } if (mode === 'fly') { diff --git a/packages/embedder/ai_models/OpenAIGeneration.ts b/packages/embedder/ai_models/OpenAIGeneration.ts index b5614b608c5..9818012edb4 100644 --- a/packages/embedder/ai_models/OpenAIGeneration.ts +++ b/packages/embedder/ai_models/OpenAIGeneration.ts @@ -6,8 +6,6 @@ import { GenerationOptions } from './AbstractModel' -const MAX_REQUEST_TIME_S = 3 * 60 - export type ModelId = 'gpt-3.5-turbo-0125' | 'gpt-4-turbo-preview' type OpenAIGenerationOptions = Omit @@ -27,7 +25,7 @@ function isValidModelId(object: any): object is ModelId { export class OpenAIGeneration extends AbstractGenerationModel { private openAIApi: OpenAI | null - private modelId: ModelId + private modelId!: ModelId constructor(config: GenerationModelConfig) { super(config) diff --git a/packages/embedder/embedder.ts b/packages/embedder/embedder.ts index f6762013d08..dccd9492a02 100644 --- a/packages/embedder/embedder.ts +++ b/packages/embedder/embedder.ts @@ -1,25 +1,25 @@ -import {Insertable} from 'kysely' import tracer from 'dd-trace' +import {Insertable} from 'kysely' import Redlock, {RedlockAbortSignal} from 'redlock' import 'parabol-server/initSentry' import getKysely from 'parabol-server/postgres/getKysely' import {DB} from 'parabol-server/postgres/pg' -import {refreshRetroDiscussionTopicsMeta as refreshRetroDiscussionTopicsMeta} from './indexing/retrospectiveDiscussionTopic' -import {orgIdsWithFeatureFlag} from './indexing/orgIdsWithFeatureFlag' import getModelManager, {ModelManager} from './ai_models/ModelManager' import {countWords} from './indexing/countWords' import {createEmbeddingTextFrom} from './indexing/createEmbeddingTextFrom' import { + completeJobTxn, + insertNewJobs, selectJobQueueItemById, + selectMetaToQueue, selectMetadataByJobQueueId, updateJobState } from './indexing/embeddingsTablesOps' -import {selectMetaToQueue} from './indexing/embeddingsTablesOps' -import {insertNewJobs} from './indexing/embeddingsTablesOps' -import {completeJobTxn} from './indexing/embeddingsTablesOps' -import {getRootDataLoader} from './indexing/getRootDataLoader' import {getRedisClient} from './indexing/getRedisClient' +import {getRootDataLoader} from './indexing/getRootDataLoader' +import {orgIdsWithFeatureFlag} from './indexing/orgIdsWithFeatureFlag' +import {refreshRetroDiscussionTopicsMeta} from './indexing/retrospectiveDiscussionTopic' /* * TODO List @@ -91,11 +91,11 @@ const maybeQueueMetadataItems = async (modelManager: ModelManager) => { model: item.model, state: 'queued' as const } - }) + }) as any[] const ejqRows = await insertNewJobs(ejqValues) - ejqRows.forEach((item) => { + ejqRows.forEach((item: any) => { const {refUpdatedAt} = ejqHash[makeKey(item)]! const score = new Date(refUpdatedAt).getTime() redisClient.zadd('embedder:queue', score, item.id) @@ -117,7 +117,7 @@ const dequeueAndEmbedUntilEmpty = async (modelManager: ModelManager) => { continue } const jobQueueId = parseInt(id, 10) - const jobQueueItem = await selectJobQueueItemById(jobQueueId) + const jobQueueItem = (await selectJobQueueItemById(jobQueueId)) as any if (!jobQueueItem) { console.log(`embedder: unable to fetch EmbeddingsJobQueue.id = ${id}`) continue @@ -131,7 +131,7 @@ const dequeueAndEmbedUntilEmpty = async (modelManager: ModelManager) => { continue } - let fullText = metadata?.fullText + let fullText = metadata?.fullText as string try { if (!fullText) { fullText = await createEmbeddingTextFrom(jobQueueItem, dataLoader) diff --git a/packages/embedder/indexing/createEmbeddingTextFrom.ts b/packages/embedder/indexing/createEmbeddingTextFrom.ts index 9d6e66b60e7..ce76c7fc380 100644 --- a/packages/embedder/indexing/createEmbeddingTextFrom.ts +++ b/packages/embedder/indexing/createEmbeddingTextFrom.ts @@ -1,14 +1,14 @@ import {Selectable} from 'kysely' -import {DB} from 'parabol-server/postgres/pg' import {DataLoaderWorker} from 'parabol-server/graphql/graphql' +import {DB} from 'parabol-server/postgres/pg' import {createText as createTextFromRetrospectiveDiscussionTopic} from './retrospectiveDiscussionTopic' export const createEmbeddingTextFrom = async ( item: Selectable, dataLoader: DataLoaderWorker -): Promise => { - switch (item.objectType) { +): Promise => { + switch ((item as any).objectType) { case 'retrospectiveDiscussionTopic': return createTextFromRetrospectiveDiscussionTopic(item, dataLoader) } diff --git a/packages/embedder/indexing/embeddingsTablesOps.ts b/packages/embedder/indexing/embeddingsTablesOps.ts index c74eb709708..4ce843248a9 100644 --- a/packages/embedder/indexing/embeddingsTablesOps.ts +++ b/packages/embedder/indexing/embeddingsTablesOps.ts @@ -1,8 +1,7 @@ -import {Insertable, Selectable, Updateable, sql} from 'kysely' +import {Insertable, RawBuilder, Selectable, Updateable, sql} from 'kysely' import getKysely from 'parabol-server/postgres/getKysely' import {DB} from 'parabol-server/postgres/pg' import {DBInsert} from '../embedder' -import {RawBuilder} from 'kysely' import numberVectorToString from './numberVectorToString' function unnestedArray(maybeArray: T[] | T): RawBuilder { @@ -24,7 +23,9 @@ export const selectMetadataByJobQueueId = async ( .selectFrom('EmbeddingsMetadata as em') .selectAll() .leftJoin('EmbeddingsJobQueue as ejq', (join) => - join.onRef('em.objectType', '=', 'ejq.objectType').onRef('em.refId', '=', 'ejq.refId') + join + .onRef('em.objectType', '=', 'ejq.objectType' as any) + .onRef('em.refId', '=', 'ejq.refId' as any) ) .where('ejq.id', '=', id) .executeTakeFirstOrThrow() @@ -52,16 +53,18 @@ export async function selectMetaToQueue( .where(({eb, not, or, and, exists, selectFrom}) => and([ or([ - not(eb('em.models', '@>', sql`ARRAY[${sql.ref('model')}]::varchar[]` as any) as any), + not( + eb('em.models' as any, '@>', sql`ARRAY[${sql.ref('model')}]::varchar[]` as any) as any + ), eb('em.models' as any, 'is', null) ]), not( exists( selectFrom('EmbeddingsJobQueue as ejq') .select('ejq.id') - .whereRef('em.objectType', '=', 'ejq.objectType') - .whereRef('em.refId', '=', 'ejq.refId') - .whereRef('ejq.model', '=', 'model' as any) + .whereRef('em.objectType', '=', 'ejq.objectType' as any) + .whereRef('em.refId', '=', 'ejq.refId' as any) + .whereRef('ejq.model' as any, '=', 'model' as any) ) ), eb('t.orgId', 'in', orgIds) @@ -104,7 +107,7 @@ export function insertNewJobs(ejqValues: Insertable[]) return pg .insertInto('EmbeddingsJobQueue') .values(ejqValues) - .returning(['id', 'objectType', 'refId']) + .returning(['id', 'objectType', 'refId'] as any) .execute() } @@ -123,11 +126,11 @@ export function completeJobTxn( const pg = getKysely() return pg.transaction().execute(async (trx) => { // get fields to update correct metadata row - const jobQueueItem = await trx + const jobQueueItem = (await trx .selectFrom('EmbeddingsJobQueue') - .select(['objectType', 'refId', 'model']) + .select(['objectType', 'refId', 'model'] as any) .where('id', '=', jobQueueId) - .executeTakeFirstOrThrow() + .executeTakeFirstOrThrow()) as any // (1) update metadata row const metadataColumnsToUpdate: { diff --git a/packages/embedder/indexing/retrospectiveDiscussionTopic.ts b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts index f31aab74cc2..eb28b1bc3b0 100644 --- a/packages/embedder/indexing/retrospectiveDiscussionTopic.ts +++ b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts @@ -1,4 +1,3 @@ -import {Selectable} from 'kysely' import prettier from 'prettier' import getRethink, {RethinkSchema} from 'parabol-server/database/rethinkDriver' @@ -12,8 +11,8 @@ import MeetingRetrospective, { isMeetingRetrospective } from 'parabol-server/database/types/MeetingRetrospective' -import {upsertEmbeddingsMetaRows} from './embeddingsTablesOps' import {AnyMeeting} from 'parabol-server/postgres/types/Meeting' +import {upsertEmbeddingsMetaRows} from './embeddingsTablesOps' const BATCH_SIZE = 1000 @@ -291,10 +290,7 @@ export const createTextFromNewMeetingDiscussionStage = async ( return markdown } -export const createText = async ( - item: Selectable, - dataLoader: DataLoaderWorker -): Promise => { +export const createText = async (item: any, dataLoader: DataLoaderWorker): Promise => { if (!item.refId) throw 'refId is undefined' const [newMeetingId, discussionId] = item.refId.split(':') if (!newMeetingId) throw new Error('newMeetingId cannot be undefined') diff --git a/packages/embedder/modules.d.ts b/packages/embedder/modules.d.ts new file mode 100644 index 00000000000..8a2be20ba0c --- /dev/null +++ b/packages/embedder/modules.d.ts @@ -0,0 +1,2 @@ +import '../server/types/modules' +import '../server/types/webpackEnv' diff --git a/packages/embedder/tsconfig.json b/packages/embedder/tsconfig.json index 1d179d08096..b75106d1a8a 100644 --- a/packages/embedder/tsconfig.json +++ b/packages/embedder/tsconfig.json @@ -3,13 +3,10 @@ "compilerOptions": { "baseUrl": "../", "paths": { - // when we import from lib, make goto-definition point to the src "parabol-server/*": ["server/*"], - "parabol-client/*": ["client/*"] + "parabol-client/*": ["client/*"], + "~/*": ["client/*"] }, - "outDir": "lib", - "lib": ["esnext"], - "types": ["node"] - }, - "files": ["../server/types/modules.d.ts"] + "lib": ["esnext"] + } } diff --git a/packages/gql-executor/RedisStream.ts b/packages/gql-executor/RedisStream.ts index 739480bd51d..173a493321a 100644 --- a/packages/gql-executor/RedisStream.ts +++ b/packages/gql-executor/RedisStream.ts @@ -2,8 +2,8 @@ import RedisInstance from 'parabol-server/utils/RedisInstance' type MessageValue = [prop: string, stringifiedData: string] type Message = [messageId: string, value: MessageValue] -type XReadGroupRes = [streamName: string, messages: Message[]] -export default class RedisStream implements AsyncIterableIterator { +type XReadGroupRes = [streamName: string, messages: [Message, ...Message[]]] +export default class RedisStream implements AsyncIterableIterator { private stream: string private consumerGroup: string // xreadgroup blocks until a response is received, so this needs its own connection @@ -19,7 +19,7 @@ export default class RedisStream implements AsyncIterableIterator { [Symbol.asyncIterator]() { return this } - async next() { + async next(): Promise> { const response = await this.redis.xreadgroup( 'GROUP', this.consumerGroup, diff --git a/packages/gql-executor/gqlExecutor.ts b/packages/gql-executor/gqlExecutor.ts index 2d4239b22a6..a3ce88d0853 100644 --- a/packages/gql-executor/gqlExecutor.ts +++ b/packages/gql-executor/gqlExecutor.ts @@ -2,9 +2,10 @@ import tracer from 'dd-trace' import {ServerChannel} from 'parabol-client/types/constEnums' import GQLExecutorChannelId from '../client/shared/gqlIds/GQLExecutorChannelId' import SocketServerChannelId from '../client/shared/gqlIds/SocketServerChannelId' -import executeGraphQL, {GQLRequest} from '../server/graphql/executeGraphQL' +import executeGraphQL from '../server/graphql/executeGraphQL' import '../server/initSentry' import '../server/monkeyPatchFetch' +import {GQLRequest} from '../server/types/custom' import RedisInstance from '../server/utils/RedisInstance' import RedisStream from './RedisStream' @@ -16,7 +17,7 @@ tracer.init({ }) tracer.use('ioredis').use('http').use('pg') -const {REDIS_URL, SERVER_ID} = process.env +const {SERVER_ID} = process.env interface PubSubPromiseMessage { jobId: string socketServerId: string @@ -26,7 +27,7 @@ interface PubSubPromiseMessage { const run = async () => { const publisher = new RedisInstance('gql_pub') const subscriber = new RedisInstance('gql_sub') - const executorChannel = GQLExecutorChannelId.join(SERVER_ID) + const executorChannel = GQLExecutorChannelId.join(SERVER_ID!) // on shutdown, remove consumer from the group process.on('SIGTERM', async () => { diff --git a/packages/gql-executor/modules.d.ts b/packages/gql-executor/modules.d.ts new file mode 100644 index 00000000000..8a2be20ba0c --- /dev/null +++ b/packages/gql-executor/modules.d.ts @@ -0,0 +1,2 @@ +import '../server/types/modules' +import '../server/types/webpackEnv' diff --git a/packages/gql-executor/tsconfig.json b/packages/gql-executor/tsconfig.json index 1d179d08096..5f1fdbf2d75 100644 --- a/packages/gql-executor/tsconfig.json +++ b/packages/gql-executor/tsconfig.json @@ -1,15 +1,14 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { + "esModuleInterop": true, "baseUrl": "../", "paths": { // when we import from lib, make goto-definition point to the src "parabol-server/*": ["server/*"], - "parabol-client/*": ["client/*"] + "parabol-client/*": ["client/*"], + "~/*": ["client/*"] }, - "outDir": "lib", - "lib": ["esnext"], - "types": ["node"] - }, - "files": ["../server/types/modules.d.ts"] + "lib": ["esnext"] + } } diff --git a/packages/server/__tests__/autoJoin.test.ts b/packages/server/__tests__/autoJoin.test.ts index 85bc9e3e222..3fe4564e6eb 100644 --- a/packages/server/__tests__/autoJoin.test.ts +++ b/packages/server/__tests__/autoJoin.test.ts @@ -1,11 +1,11 @@ import faker from 'faker' -import {getUserTeams, sendPublic, sendIntranet, signUp, signUpWithEmail} from './common' import getRethink from '../database/rethinkDriver' import createEmailVerification from '../email/createEmailVerification' +import {getUserTeams, sendIntranet, sendPublic} from './common' const signUpVerified = async (email: string) => { const password = faker.internet.password() - const signUp = await sendPublic({ + await sendPublic({ query: ` mutation SignUpWithPassword($email: ID!, $password: String!) { signUpWithPassword(email: $email, password: $password, params: "") { diff --git a/packages/server/__tests__/common.ts b/packages/server/__tests__/common.ts index f9ca34ab293..93c134b229b 100644 --- a/packages/server/__tests__/common.ts +++ b/packages/server/__tests__/common.ts @@ -199,5 +199,5 @@ export const getUserTeams = async (userId: string) => { } } }) - return user.data.user.teams + return user.data.user.teams as [{id: string}, ...{id: string}[]] } diff --git a/packages/server/__tests__/disableAnonymity.test.ts b/packages/server/__tests__/disableAnonymity.test.ts index 7143981aa36..1887c3357a0 100644 --- a/packages/server/__tests__/disableAnonymity.test.ts +++ b/packages/server/__tests__/disableAnonymity.test.ts @@ -89,8 +89,12 @@ const startRetro = async (teamId: string, authToken: string) => { }, authToken }) - - const meeting = startRetroQuery.data.startRetrospective.meeting + const meeting = startRetroQuery.data.startRetrospective.meeting as { + id: string + phases: { + reflectPrompts: [{id: string}] + }[] + } return meeting } @@ -134,7 +138,7 @@ test('By default all reflections are anonymous', async () => { expect(meetingSettings.disableAnonymity).toEqual(false) const meeting = await startRetro(teamId, authToken) - const reflectPrompts = meeting.phases.find(({reflectPrompts}) => !!reflectPrompts).reflectPrompts + const reflectPrompts = meeting.phases.find(({reflectPrompts}) => !!reflectPrompts)!.reflectPrompts const reflection = await addReflection(meeting.id, reflectPrompts[0].id, authToken) expect(reflection).toEqual({ @@ -153,7 +157,7 @@ test('Creator is visible when disableAnonymity is set', async () => { expect(updatedMeetingSettings.disableAnonymity).toEqual(true) const meeting = await startRetro(teamId, authToken) - const reflectPrompts = meeting.phases.find(({reflectPrompts}) => !!reflectPrompts).reflectPrompts + const reflectPrompts = meeting.phases.find(({reflectPrompts}) => !!reflectPrompts)!.reflectPrompts const reflection = await addReflection(meeting.id, reflectPrompts[0].id, authToken) expect(reflection).toEqual({ @@ -172,7 +176,7 @@ test('Super user can always read creatorId of a reflection', async () => { expect(meetingSettings.disableAnonymity).toEqual(false) const meeting = await startRetro(teamId, authToken) - const reflectPrompts = meeting.phases.find(({reflectPrompts}) => !!reflectPrompts).reflectPrompts + const reflectPrompts = meeting.phases.find(({reflectPrompts}) => !!reflectPrompts)!.reflectPrompts await addReflection(meeting.id, reflectPrompts[0].id, authToken) diff --git a/packages/server/__tests__/loginSAML.test.ts b/packages/server/__tests__/loginSAML.test.ts index 426f9606140..312bb3154e5 100644 --- a/packages/server/__tests__/loginSAML.test.ts +++ b/packages/server/__tests__/loginSAML.test.ts @@ -10,30 +10,6 @@ test.skip('SAML', async () => { const orgId = `${samlName}-orgId` const domain = 'example.com' - const _metadata = ` - - - - - - - MIICUDCCAbmgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBFMQswCQYDVQQGEwJ1czELMAkGA1UECAwCQ0ExEzARBgNVBAoMCkV4YW1wbGUgQ28xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4XDTIxMDkxMDA3NTkzMFoXDTI2MDkwOTA3NTkzMFowRTELMAkGA1UEBhMCdXMxCzAJBgNVBAgMAkNBMRMwEQYDVQQKDApFeGFtcGxlIENvMRQwEgYDVQQDDAtleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArg9DZwR9v7Vok1IW+hIpYin9llPBh1MV5CxjfK596EwuadyQuko3jGv8qDlx4tG6JiGTjQfCuzJVAhYi2OKuKBqyJewKoen1uF0dRyws9n6zZl0GsVJkObdrNo5P6eib3VOsXPJ10RjxWsWx5WRur2dYdkOJFxC6zN1IbXSXYYMCAwEAAaNQME4wHQYDVR0OBBYEFKr/1y4R+kamPz623HnHM7tz6C4XMB8GA1UdIwQYMBaAFKr/1y4R+kamPz623HnHM7tz6C4XMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQENBQADgYEALKBl6QPk9HMB5V+GYu50XNFmzyuuXt3zAKMSYcyhxVSBCe6SKw1iqvvPza4rGp7DpeJI/8R3qBTuZqfl0rX624wvHGc4N9WubMLPejAn7dMu3oGfm9KUX+Um1RG0U6zsi9t3X90rroea/5SQvw/uAWUxS59U2r8massI/WFJKh8= - - - - - - - MIICUDCCAbmgAwIBAgIBADANBgkqhkiG9w0BAQ0FADBFMQswCQYDVQQGEwJ1czELMAkGA1UECAwCQ0ExEzARBgNVBAoMCkV4YW1wbGUgQ28xFDASBgNVBAMMC2V4YW1wbGUuY29tMB4XDTIxMDkxMDA3NTkzMFoXDTI2MDkwOTA3NTkzMFowRTELMAkGA1UEBhMCdXMxCzAJBgNVBAgMAkNBMRMwEQYDVQQKDApFeGFtcGxlIENvMRQwEgYDVQQDDAtleGFtcGxlLmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEArg9DZwR9v7Vok1IW+hIpYin9llPBh1MV5CxjfK596EwuadyQuko3jGv8qDlx4tG6JiGTjQfCuzJVAhYi2OKuKBqyJewKoen1uF0dRyws9n6zZl0GsVJkObdrNo5P6eib3VOsXPJ10RjxWsWx5WRur2dYdkOJFxC6zN1IbXSXYYMCAwEAAaNQME4wHQYDVR0OBBYEFKr/1y4R+kamPz623HnHM7tz6C4XMB8GA1UdIwQYMBaAFKr/1y4R+kamPz623HnHM7tz6C4XMAwGA1UdEwQFMAMBAf8wDQYJKoZIhvcNAQENBQADgYEALKBl6QPk9HMB5V+GYu50XNFmzyuuXt3zAKMSYcyhxVSBCe6SKw1iqvvPza4rGp7DpeJI/8R3qBTuZqfl0rX624wvHGc4N9WubMLPejAn7dMu3oGfm9KUX+Um1RG0U6zsi9t3X90rroea/5SQvw/uAWUxS59U2r8massI/WFJKh8= - - - - urn:oasis:names:tc:SAML:1.1:nameid-format:unspecified - - - - ` - const verifyDomain = await sendIntranet({ query: ` mutation VerifyDomain($slug: ID!, $addDomains: [ID!], $orgId: ID!) { diff --git a/packages/server/__tests__/processRecurrence.test.ts b/packages/server/__tests__/processRecurrence.test.ts index f6e6c685715..fab6970bc9a 100644 --- a/packages/server/__tests__/processRecurrence.test.ts +++ b/packages/server/__tests__/processRecurrence.test.ts @@ -1,14 +1,13 @@ import ms from 'ms' import {RRule} from 'rrule' import getRethink from '../database/rethinkDriver' -import MeetingTeamPrompt from '../database/types/MeetingTeamPrompt' -import TeamPromptResponsesPhase from '../database/types/TeamPromptResponsesPhase' import MeetingRetrospective from '../database/types/MeetingRetrospective' +import MeetingTeamPrompt from '../database/types/MeetingTeamPrompt' import ReflectPhase from '../database/types/ReflectPhase' +import TeamPromptResponsesPhase from '../database/types/TeamPromptResponsesPhase' import generateUID from '../generateUID' import {insertMeetingSeries as insertMeetingSeriesQuery} from '../postgres/queries/insertMeetingSeries' import {getUserTeams, sendIntranet, signUp} from './common' -import createNewMeetingPhases from '../graphql/mutations/helpers/createNewMeetingPhases' const PROCESS_RECURRENCE = ` mutation { @@ -292,7 +291,11 @@ test('Should end the current retro meeting and start a new meeting', async () => facilitatorUserId: userId, scheduledEndTime: new Date(Date.now() - ms('5m')), meetingSeriesId, - templateId: 'startStopContinueTemplate' + templateId: 'startStopContinueTemplate', + disableAnonymity: false, + totalVotes: 5, + name: '', + maxVotesPerGroup: 5 }) // The last meeting in the series was created just over 24h ago, so the next one should start diff --git a/packages/server/__tests__/startRetrospective.test.ts b/packages/server/__tests__/startRetrospective.test.ts index 22ff21a7f12..17deae321e5 100644 --- a/packages/server/__tests__/startRetrospective.test.ts +++ b/packages/server/__tests__/startRetrospective.test.ts @@ -1,14 +1,8 @@ -import ms from 'ms' -import {RRule} from 'rrule' import getRethink from '../database/rethinkDriver' -import MeetingTeamPrompt from '../database/types/MeetingTeamPrompt' -import TeamPromptResponsesPhase from '../database/types/TeamPromptResponsesPhase' -import generateUID from '../generateUID' -import {insertMeetingSeries as insertMeetingSeriesQuery} from '../postgres/queries/insertMeetingSeries' -import {getUserTeams, sendIntranet, sendPublic, signUp} from './common' +import {getUserTeams, sendPublic, signUp} from './common' test('Retro is named Retro #1 by default', async () => { - const r = await getRethink() + await getRethink() const {userId, authToken} = await signUp() const {id: teamId} = (await getUserTeams(userId))[0] @@ -48,7 +42,7 @@ test('Retro is named Retro #1 by default', async () => { }) test('Recurring retro is named like RetroSeries Jan 1', async () => { - const r = await getRethink() + await getRethink() const {userId, authToken} = await signUp() const {id: teamId} = (await getUserTeams(userId))[0] @@ -81,7 +75,11 @@ test('Recurring retro is named like RetroSeries Jan 1', async () => { authToken }) - const formattedDate = now.toLocaleDateString('en-US', {month: 'short', day: 'numeric'}, 'UTC') + const formattedDate = now.toLocaleDateString('en-US', { + month: 'short', + day: 'numeric', + timeZone: 'UTC' + }) expect(newRetro).toMatchObject({ data: { startRetrospective: { diff --git a/packages/server/dataloader/__tests__/isCompanyDomain.test.ts b/packages/server/dataloader/__tests__/isCompanyDomain.test.ts index 172958a60d6..d364d2a7d61 100644 --- a/packages/server/dataloader/__tests__/isCompanyDomain.test.ts +++ b/packages/server/dataloader/__tests__/isCompanyDomain.test.ts @@ -1,7 +1,5 @@ -import faker from 'faker' import '../../../../scripts/webpack/utils/dotenv' import getDataLoader from '../../graphql/getDataLoader' -import getPg from '../../postgres/getPg' const dataloader = getDataLoader() diff --git a/packages/server/dataloader/__tests__/isOrgVerified.test.ts b/packages/server/dataloader/__tests__/isOrgVerified.test.ts index b12897d4b3a..58247c515ab 100644 --- a/packages/server/dataloader/__tests__/isOrgVerified.test.ts +++ b/packages/server/dataloader/__tests__/isOrgVerified.test.ts @@ -1,18 +1,22 @@ /* eslint-env jest */ -import {MasterPool, r} from 'rethinkdb-ts' -import getRedis from '../../utils/getRedis' +import {r} from 'rethinkdb-ts' import getRethinkConfig from '../../database/getRethinkConfig' import getRethink from '../../database/rethinkDriver' -import isUserVerified from '../../utils/isUserVerified' +import OrganizationUser from '../../database/types/OrganizationUser' import generateUID from '../../generateUID' +import {DataLoaderWorker} from '../../graphql/graphql' +import getRedis from '../../utils/getRedis' +import isUserVerified from '../../utils/isUserVerified' +import RootDataLoader from '../RootDataLoader' import {isOrgVerified} from '../customLoaderMakers' jest.mock('../../database/rethinkDriver') jest.mock('../../utils/isUserVerified') -getRethink.mockImplementation(() => { - return r +jest.mocked(getRethink).mockImplementation(() => { + return r as any }) -isUserVerified.mockImplementation(() => { + +jest.mocked(isUserVerified).mockImplementation(() => { return true }) @@ -24,7 +28,7 @@ const testConfig = { db: TEST_DB } -const createTables = async (...tables: string) => { +const createTables = async (...tables: string[]) => { for (const tableName of tables) { const structure = await r .db('rethinkdb') @@ -40,9 +44,10 @@ const createTables = async (...tables: string) => { } } -type TestOrganizationUser = Pick< - OrganizationUser, - 'inactive' | 'joinedAt' | 'removedAt' | 'role' | 'userId' +type TestOrganizationUser = Partial< + Pick & { + domain: string + } > const userLoader = { @@ -61,9 +66,9 @@ const dataLoader = { users: userLoader, isCompanyDomain: isCompanyDomainLoader } - return loaders[loader] + return loaders[loader as keyof typeof loaders] }) -} +} as any as DataLoaderWorker const addOrg = async ( activeDomain: string | null, @@ -101,10 +106,10 @@ const addOrg = async ( return orgId } -const isOrgVerifiedLoader = isOrgVerified(dataLoader) +const isOrgVerifiedLoader = isOrgVerified(dataLoader as any as RootDataLoader) beforeAll(async () => { - const conn = await r.connectPool(testConfig) + await r.connectPool(testConfig) try { await r.dbDrop(TEST_DB).run() } catch (e) { @@ -121,7 +126,7 @@ afterEach(async () => { }) afterAll(async () => { - await r.getPoolMaster().drain() + await r.getPoolMaster()?.drain() getRedis().quit() }) @@ -203,12 +208,12 @@ test('Empty org does not throw', async () => { }) test('Orgs with verified emails from different domains do not qualify', async () => { - const org1 = await addOrg('parabol.co', [ + await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), userId: 'founder1', domain: 'not-parabol.co' - } + } as any ]) const isVerified = await isOrgVerifiedLoader.load('parabol.co') diff --git a/packages/server/dataloader/__tests__/usersCustomRedisQueries.test.ts b/packages/server/dataloader/__tests__/usersCustomRedisQueries.test.ts index 0aa1d0a3162..50fcc7fa77f 100644 --- a/packages/server/dataloader/__tests__/usersCustomRedisQueries.test.ts +++ b/packages/server/dataloader/__tests__/usersCustomRedisQueries.test.ts @@ -1,6 +1,7 @@ import faker from 'faker' import '../../../../scripts/webpack/utils/dotenv' import getDataLoader from '../../graphql/getDataLoader' +import isValid from '../../graphql/isValid' import getPg from '../../postgres/getPg' afterAll(async () => { @@ -17,16 +18,19 @@ test('Result is mapped to correct id', async () => { const dataloader = getDataLoader() const expectedUsers = faker.helpers.shuffle( - (await pg.query('SELECT "id", "email" FROM "User" LIMIT 100')).rows + (await pg.query('SELECT "id", "email" FROM "User" LIMIT 100')).rows as { + id: string + email: string + }[] ) const userIds = expectedUsers.map(({id}) => id) - const actualUsers = (await (dataloader.get('users') as any).loadMany(userIds)).map( - ({id, email}) => ({ + const actualUsers = (await dataloader.get('users').loadMany(userIds)) + .filter(isValid) + .map(({id, email}) => ({ id, email - }) - ) + })) console.log('Ran with #users:', actualUsers.length) diff --git a/packages/server/email/inlineImages.ts b/packages/server/email/inlineImages.ts index d02c7bf6448..176a10d5798 100644 --- a/packages/server/email/inlineImages.ts +++ b/packages/server/email/inlineImages.ts @@ -20,7 +20,7 @@ const getFile = async (pathname: string) => { try { const res = await fetch(pathname) if (res.status !== 200) return null - data = await res.buffer() + data = await (res as any).buffer() } catch (e) { return null } @@ -42,7 +42,7 @@ const inlineImages = async (html: string) => { $('body') .find('img') .each((_i, img) => { - const pathname = $(img).attr('src') + const pathname = $(img).attr('src') as keyof typeof cidDict if (!pathname) return cidDict[pathname] = cidDict[pathname] || generateUID() + path.extname(pathname) $(img).attr('src', `cid:${cidDict[pathname]}`) @@ -51,7 +51,7 @@ const inlineImages = async (html: string) => { const files = await Promise.all(uniquePathnames.map(getFile)) const options = files.map((data, idx) => { if (!data) return null - const pathname = uniquePathnames[idx] + const pathname = uniquePathnames[idx] as keyof typeof cidDict const filename = cidDict[pathname] return {data, filename} }) diff --git a/packages/server/generateUID.ts b/packages/server/generateUID.ts index 15d3a616e70..822843fa52d 100644 --- a/packages/server/generateUID.ts +++ b/packages/server/generateUID.ts @@ -8,7 +8,7 @@ const SEQ_BIT_LEN = 12 const TS_OFFSET = BigInt(MACHINE_ID_BIT_LEN + SEQ_BIT_LEN) const MID_OFFSET = BigInt(SEQ_BIT_LEN) const BIG_ZERO = BigInt(0) -const MAX_SEQ = 2 ** SEQ_BIT_LEN - 1 +export const MAX_SEQ = 2 ** SEQ_BIT_LEN - 1 // if MID overflows, we will generate duplicate ids, throw instead if (MID < 0 || MID > 2 ** MACHINE_ID_BIT_LEN - 1) { diff --git a/packages/server/graphql/executeGraphQL.ts b/packages/server/graphql/executeGraphQL.ts index 6b6ad34ea72..8fea40bc2a5 100644 --- a/packages/server/graphql/executeGraphQL.ts +++ b/packages/server/graphql/executeGraphQL.ts @@ -7,30 +7,13 @@ import tracer from 'dd-trace' import {graphql} from 'graphql' import {FormattedExecutionResult} from 'graphql/execution/execute' -import AuthToken from '../database/types/AuthToken' +import type {GQLRequest} from '../types/custom' import CompiledQueryCache from './CompiledQueryCache' import getDataLoader from './getDataLoader' import getRateLimiter from './getRateLimiter' import privateSchema from './private/rootSchema' import publicSchema from './public/rootSchema' -export interface GQLRequest { - authToken: AuthToken - ip?: string - socketId?: string - variables?: {[key: string]: any} - docId?: string - query?: string - rootValue?: {[key: string]: any} - dataLoaderId?: string - // true if the query is on the private schema - isPrivate?: boolean - // true if the query is ad-hoc (e.g. GraphiQL, CLI) - isAdHoc?: boolean - // Datadog opentracing span of the calling server - carrier?: any -} - const queryCache = new CompiledQueryCache() const executeGraphQL = async (req: GQLRequest) => { diff --git a/packages/server/package.json b/packages/server/package.json index 68cfcfbb846..62295579ced 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -32,6 +32,7 @@ "@types/bcryptjs": "^2.4.2", "@types/cheerio": "^0.22.30", "@types/dotenv": "^6.1.1", + "@types/faker": "^5.5.9", "@types/graphql": "^14.5.0", "@types/html-minifier-terser": "5.1.2", "@types/jest": "^29.5.1", diff --git a/packages/server/tsconfig.json b/packages/server/tsconfig.json index a1a9c3130df..bc89d53cbbe 100644 --- a/packages/server/tsconfig.json +++ b/packages/server/tsconfig.json @@ -8,24 +8,16 @@ "parabol-client/*": ["client/*"], "~/*": ["client/*"] }, - "lib": ["esnext", "dom"], - "types": ["node", "jest", "jest-extended"] + "lib": ["esnext", "dom"] }, "exclude": [ "**/node_modules", "types/githubTypes.ts", "postgres/migrationTemplate.ts", - "graphql/intranetSchema/sdl/resolverTypes.ts" - ], - "files": [ - "types/webpackEnv.ts", - "types/modules.d.ts", - "server.ts", - "../client/modules/email/components/SummaryEmail/MeetingSummaryEmail/MeetingSummaryEmail.tsx" - ], - "include": ["graphql/**/*.ts"] - - // if "include" or "files" is added, even if they are empty arrays, then strictNullChecks breaks - // repro: https://www.typescriptlang.org/play?strictFunctionTypes=false&strictPropertyInitialization=false&strictBindCallApply=false&noImplicitThis=false&noImplicitReturns=false&alwaysStrict=false&declaration=false&experimentalDecorators=false&emitDecoratorMetadata=false&target=6&ts=3.5.1#code/C4TwDgpgBA8gRgKygXigbwFBSgWwIYhwQDKwATgIJlkD8AXFAM7kCWAdgOYDaAuhgL4ZQkKFTIpYiLgHJ8hEuTHS+AYwD2bZlDwS2AVwA2B7Y21sQJ0dQx4AdADM1ZAKJ4VACwAUngF4BKFAA+KH8MIA + "database/migrations/**/*", + "postgres/migrations/**/*", + "graphql/intranetSchema/sdl/resolverTypes.ts", + "billing/debug.ts" + ] } diff --git a/packages/server/types/custom.d.ts b/packages/server/types/custom.d.ts index 34587f968c4..2fc4dba2ce8 100644 --- a/packages/server/types/custom.d.ts +++ b/packages/server/types/custom.d.ts @@ -1,3 +1,5 @@ +import '../../client/types/reactHTML4' +import type AuthToken from '../database/types/AuthToken' export interface OAuth2Success { access_token: string token_type: string @@ -17,3 +19,19 @@ export interface OAuth2Error { error_description?: string error_uri?: string } +export interface GQLRequest { + authToken: AuthToken + ip?: string + socketId?: string + variables?: {[key: string]: any} + docId?: string + query?: string + rootValue?: {[key: string]: any} + dataLoaderId?: string + // true if the query is on the private schema + isPrivate?: boolean + // true if the query is ad-hoc (e.g. GraphiQL, CLI) + isAdHoc?: boolean + // Datadog opentracing span of the calling server + carrier?: any +} diff --git a/packages/server/types/modules.d.ts b/packages/server/types/modules.d.ts index c79e5a0b011..9b16b604830 100644 --- a/packages/server/types/modules.d.ts +++ b/packages/server/types/modules.d.ts @@ -21,6 +21,7 @@ declare module 'node-env-flag' declare module '*getProjectRoot' declare module 'tayden-clusterfck' declare module 'unicode-substring' +declare module 'jest-extended' declare module 'json2csv/lib/JSON2CSVParser' declare module 'object-hash' declare module 'string-score' diff --git a/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts b/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts index 3c65dff93cb..6858d2854cf 100644 --- a/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts +++ b/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts @@ -1,16 +1,16 @@ /* eslint-env jest */ -import {MasterPool, r} from 'rethinkdb-ts' -import getRedis from '../getRedis' -import RedisLockQueue from '../RedisLockQueue' -import sleep from 'parabol-client/utils/sleep' +import {r} from 'rethinkdb-ts' import getRethinkConfig from '../../database/getRethinkConfig' import getRethink from '../../database/rethinkDriver' -import {getEligibleOrgIdsByDomain} from '../isRequestToJoinDomainAllowed' +import OrganizationUser from '../../database/types/OrganizationUser' import generateUID from '../../generateUID' +import {DataLoaderWorker} from '../../graphql/graphql' +import getRedis from '../getRedis' +import {getEligibleOrgIdsByDomain} from '../isRequestToJoinDomainAllowed' jest.mock('../../database/rethinkDriver') -getRethink.mockImplementation(() => { - return r +jest.mocked(getRethink).mockImplementation(() => { + return r as any }) const TEST_DB = 'isRequestToJoinDomainAllowedTest' @@ -21,7 +21,7 @@ const testConfig = { db: TEST_DB } -const createTables = async (...tables: string) => { +const createTables = async (...tables: string[]) => { for (const tableName of tables) { const structure = await r .db('rethinkdb') @@ -37,9 +37,8 @@ const createTables = async (...tables: string) => { } } -type TestOrganizationUser = Pick< - OrganizationUser, - 'inactive' | 'joinedAt' | 'removedAt' | 'role' | 'userId' +type TestOrganizationUser = Partial< + Pick > const addOrg = async ( @@ -88,12 +87,12 @@ const dataLoader = { users: userLoader, isCompanyDomain: isCompanyDomainLoader } - return loaders[loader] + return loaders[loader as keyof typeof loaders] }) -} +} as any as DataLoaderWorker beforeAll(async () => { - const conn = await r.connectPool(testConfig) + await r.connectPool(testConfig) try { await r.dbDrop(TEST_DB).run() } catch (e) { @@ -109,7 +108,7 @@ afterEach(async () => { }) afterAll(async () => { - await r.getPoolMaster().drain() + await r.getPoolMaster()?.drain() getRedis().quit() }) @@ -126,7 +125,7 @@ test('Founder is billing lead', async () => { } ]) - const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) + await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) expect(userLoader.loadMany).toHaveBeenCalledTimes(1) expect(userLoader.loadMany).toHaveBeenCalledWith(['user1']) }) @@ -148,7 +147,7 @@ test('Org with noPromptToJoinOrg feature flag is ignored', async () => { {featureFlags: ['noPromptToJoinOrg']} ) - const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) + await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) expect(userLoader.loadMany).toHaveBeenCalledTimes(0) }) @@ -170,7 +169,7 @@ test('Inactive founder is ignored', async () => { } ]) - const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) + await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) // implementation detail, important is only that no user was loaded expect(userLoader.loadMany).toHaveBeenCalledTimes(1) expect(userLoader.loadMany).toHaveBeenCalledWith([]) @@ -195,7 +194,7 @@ test('Non-founder billing lead is checked', async () => { } ]) - const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) + await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) expect(userLoader.loadMany).toHaveBeenCalledTimes(1) expect(userLoader.loadMany).toHaveBeenCalledWith(['billing1']) }) @@ -212,7 +211,7 @@ test('Founder is checked even when not billing lead', async () => { } ]) - const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) + await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) expect(userLoader.loadMany).toHaveBeenCalledTimes(1) expect(userLoader.loadMany).toHaveBeenCalledWith(['user1']) }) @@ -239,7 +238,7 @@ test('All matching orgs are checked', async () => { } ]) - const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) + await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) // implementation detail, important is only that both users were loaded expect(userLoader.loadMany).toHaveBeenCalledTimes(2) expect(userLoader.loadMany).toHaveBeenCalledWith(['founder1']) @@ -249,12 +248,12 @@ test('All matching orgs are checked', async () => { test('Empty org does not throw', async () => { await addOrg('parabol.co', []) - const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) + await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) expect(userLoader.loadMany).toHaveBeenCalledTimes(0) }) test('No org does not throw', async () => { - const orgIds = await getEligibleOrgIdsByDomain('example.com', 'newUser', dataLoader) + await getEligibleOrgIdsByDomain('example.com', 'newUser', dataLoader) expect(userLoader.loadMany).toHaveBeenCalledTimes(0) }) @@ -267,7 +266,7 @@ test('1 person orgs are ignored', async () => { } ]) - const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) + await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) expect(userLoader.loadMany).toHaveBeenCalledTimes(0) }) @@ -283,12 +282,12 @@ test('Org matching the user are ignored', async () => { } ]) - const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) + await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) expect(userLoader.loadMany).toHaveBeenCalledTimes(0) }) test('Only the biggest org with verified emails qualify', async () => { - const org = await addOrg('parabol.co', [ + await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), userId: 'founder1' @@ -350,7 +349,7 @@ test('Only the biggest org with verified emails qualify', async () => { ] } } - return userIds.map((id) => ({ + return userIds.map((id: keyof typeof users) => ({ id, ...users[id] })) @@ -423,7 +422,7 @@ test('All the biggest orgs with verified emails qualify', async () => { ] } } - return userIds.map((id) => ({ + return userIds.map((id: keyof typeof users) => ({ id, ...users[id] })) @@ -452,7 +451,7 @@ test('Team trumps starter tier with more users org', async () => { ], {tier: 'team'} ) - const biggerStarterOrg = await addOrg('parabol.co', [ + await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), userId: 'founder2' @@ -504,7 +503,7 @@ test('Team trumps starter tier with more users org', async () => { ] } } - return userIds.map((id) => ({ + return userIds.map((id: keyof typeof users) => ({ id, ...users[id] })) @@ -533,7 +532,7 @@ test('Enterprise trumps team tier with more users org', async () => { ], {tier: 'enterprise'} ) - const starterOrg = await addOrg( + await addOrg( 'parabol.co', [ { @@ -589,7 +588,7 @@ test('Enterprise trumps team tier with more users org', async () => { ] } } - return userIds.map((id) => ({ + return userIds.map((id: keyof typeof users) => ({ id, ...users[id] })) @@ -604,7 +603,7 @@ test('Enterprise trumps team tier with more users org', async () => { }) test('Orgs with verified emails from different domains do not qualify', async () => { - const org1 = await addOrg('parabol.co', [ + await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), userId: 'founder1' diff --git a/packages/server/utils/__tests__/rateLimiters/InMemoryRateLimiter.test.ts b/packages/server/utils/__tests__/rateLimiters/InMemoryRateLimiter.test.ts index 01f4fa4394a..3b2478aa9d6 100644 --- a/packages/server/utils/__tests__/rateLimiters/InMemoryRateLimiter.test.ts +++ b/packages/server/utils/__tests__/rateLimiters/InMemoryRateLimiter.test.ts @@ -14,7 +14,7 @@ describe('InMemoryRateLimiter', () => { } beforeEach(() => { - jest.useFakeTimers('modern') + jest.useFakeTimers() }) afterEach(() => { diff --git a/packages/server/utils/getGraphQLExecutor.ts b/packages/server/utils/getGraphQLExecutor.ts index bc70b875316..759f21bade2 100644 --- a/packages/server/utils/getGraphQLExecutor.ts +++ b/packages/server/utils/getGraphQLExecutor.ts @@ -1,7 +1,7 @@ import {ExecutionResult} from 'graphql' import {ServerChannel} from 'parabol-client/types/constEnums' +import type {GQLRequest} from '../types/custom' import SocketServerChannelId from '../../client/shared/gqlIds/SocketServerChannelId' -import {GQLRequest} from '../graphql/executeGraphQL' import PubSubPromise from './PubSubPromise' let pubsub: PubSubPromise diff --git a/packages/server/utils/uwsGetHeaders.ts b/packages/server/utils/uwsGetHeaders.ts index 9fd7bdcdc02..f51fd27e78f 100644 --- a/packages/server/utils/uwsGetHeaders.ts +++ b/packages/server/utils/uwsGetHeaders.ts @@ -1,7 +1,7 @@ import {HttpRequest} from 'uWebSockets.js' const uwsGetHeaders = (req: HttpRequest) => { - const reqHeaders = {} + const reqHeaders: Record = {} req.forEach((key, value) => { reqHeaders[key] = value }) diff --git a/scripts/webpack/utils/getProjectRoot.d.ts b/scripts/webpack/utils/getProjectRoot.d.ts new file mode 100644 index 00000000000..436f1feb4c9 --- /dev/null +++ b/scripts/webpack/utils/getProjectRoot.d.ts @@ -0,0 +1,2 @@ +declare function getProjectRoot(): string +export default getProjectRoot diff --git a/yarn.lock b/yarn.lock index dc34fd16fae..817e730db40 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7565,6 +7565,11 @@ "@types/qs" "*" "@types/serve-static" "*" +"@types/faker@^5.5.9": + version "5.5.9" + resolved "https://registry.yarnpkg.com/@types/faker/-/faker-5.5.9.tgz#588ede92186dc557bff8341d294335d50d255f0c" + integrity sha512-uCx6mP3UY5SIO14XlspxsGjgaemrxpssJI0Ol+GfhxtcKpv9pgRZYsS4eeKeHVLje6Qtc8lGszuBI461+gVZBA== + "@types/fbjs@^3.0.4": version "3.0.4" resolved "https://registry.yarnpkg.com/@types/fbjs/-/fbjs-3.0.4.tgz#272faf44b6a24d24d6fdf36ed895b47436e6c125" @@ -7674,6 +7679,14 @@ expect "^29.0.0" pretty-format "^29.0.0" +"@types/jest@^29.5.12": + version "29.5.12" + resolved "https://registry.yarnpkg.com/@types/jest/-/jest-29.5.12.tgz#7f7dc6eb4cf246d2474ed78744b05d06ce025544" + integrity sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw== + dependencies: + expect "^29.0.0" + pretty-format "^29.0.0" + "@types/js-yaml@^4.0.0": version "4.0.5" resolved "https://registry.yarnpkg.com/@types/js-yaml/-/js-yaml-4.0.5.tgz#738dd390a6ecc5442f35e7f03fa1431353f7e138" @@ -9704,9 +9717,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001580, caniuse-lite@~1.0.0: - version "1.0.30001594" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001594.tgz#bea552414cd52c2d0c985ed9206314a696e685f5" - integrity sha512-VblSX6nYqyJVs8DKFMldE2IVCJjZ225LW00ydtUWwh5hk9IfkTOffO6r8gJNsH0qqqeAF8KrbMYA2VEwTlGW5g== + version "1.0.30001600" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz#93a3ee17a35aa6a9f0c6ef1b2ab49507d1ab9079" + integrity sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ== capital-case@^1.0.4: version "1.0.4" @@ -11305,7 +11318,6 @@ draft-js-utils@^1.4.0: "draft-js@https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6": version "0.10.5" - uid "025fddba56f21aaf3383aee778e0b17025c9a7bc" resolved "https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6#025fddba56f21aaf3383aee778e0b17025c9a7bc" dependencies: fbjs "^0.8.15" @@ -20384,7 +20396,19 @@ tar@^4.4.13: safe-buffer "^5.2.1" yallist "^3.1.1" -tar@^6.0.2, tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: +tar@^6.0.2: + version "6.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.1.tgz#717549c541bc3c2af15751bea94b1dd068d4b03a" + integrity sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A== + dependencies: + chownr "^2.0.0" + fs-minipass "^2.0.0" + minipass "^5.0.0" + minizlib "^2.1.1" + mkdirp "^1.0.3" + yallist "^4.0.0" + +tar@^6.1.0, tar@^6.1.11, tar@^6.1.2: version "6.2.0" resolved "https://registry.yarnpkg.com/tar/-/tar-6.2.0.tgz#b14ce49a79cb1cd23bc9b016302dea5474493f73" integrity sha512-/Wo7DcT0u5HUV486xg675HtjNd3BXZ6xDbzsCUZPt5iw8bTQ63bP0Raut3mvro9u+CUyq7YQd8Cx55fsZXxqLQ== From 538c95ce4dc7d4839b3e813006cb20e1b7d1d1c8 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 28 Mar 2024 17:19:41 -0700 Subject: [PATCH 105/529] feat: prepare embedder for Production (#9517) Signed-off-by: Matt Krick --- .env.example | 2 +- .github/workflows/build.yml | 4 +- .github/workflows/test.yml | 14 +- docker/images/parabol-ubi/README.md | 24 +- .../images/parabol-ubi/environments/pipeline | 4 + docker/stacks/development/docker-compose.yml | 2 +- ...docker-compose.yaml => docker-compose.yml} | 2 +- package.json | 2 +- .../client/shared/gqlIds/EmbedderChannelId.ts | 9 + packages/client/types/generics.ts | 3 + packages/embedder/EMBEDDER_JOB_PRIORITY.ts | 6 + packages/embedder/EmbeddingsJobQueueStream.ts | 72 ++ packages/embedder/README.md | 41 +- packages/embedder/addEmbeddingsMetadata.ts | 15 + ...MetadataForRetrospectiveDiscussionTopic.ts | 143 +++ .../ai_models/AbstractEmbeddingsModel.ts | 126 +++ .../ai_models/AbstractGenerationModel.ts | 26 + packages/embedder/ai_models/AbstractModel.ts | 57 +- packages/embedder/ai_models/ModelManager.ts | 51 +- .../embedder/ai_models/OpenAIGeneration.ts | 2 +- .../ai_models/TextEmbeddingsInference.ts | 85 +- .../ai_models/TextGenerationInference.ts | 3 +- packages/embedder/custom.d.ts | 11 + packages/embedder/embedder.ts | 310 ++----- packages/embedder/establishPrimaryEmbedder.ts | 17 + packages/embedder/importHistoricalMetadata.ts | 16 + ...tHistoricalRetrospectiveDiscussionTopic.ts | 37 + packages/embedder/indexing/countWords.ts | 17 - .../indexing/createEmbeddingTextFrom.ts | 16 +- .../embedder/indexing/embeddingsTablesOps.ts | 201 ---- packages/embedder/indexing/failJob.ts | 17 + packages/embedder/indexing/getRedisClient.ts | 11 - .../embedder/indexing/getRootDataLoader.ts | 10 - .../indexing/retrospectiveDiscussionTopic.ts | 216 +---- packages/embedder/iso6393To1.ts | 195 ++++ packages/embedder/logMemoryUse.ts | 10 + packages/embedder/mergeAsyncIterators.ts | 96 ++ packages/embedder/modules.d.ts | 2 - packages/embedder/package.json | 5 +- packages/embedder/processJob.ts | 13 + packages/embedder/processJobEmbed.ts | 102 +++ packages/embedder/resetStalledJobs.ts | 18 + packages/embedder/textEmbeddingsnterface.d.ts | 857 ++++++++++++++++++ packages/embedder/types/modules.d.ts | 4 + packages/embedder/types/shared.d.ts | 2 + packages/gql-executor/RedisStream.ts | 3 +- .../rethinkForeignKeyLoaderMakers.ts | 13 + .../mutations/helpers/publishToEmbedder.ts | 14 + .../mutations/helpers/safeEndRetrospective.ts | 16 +- .../1703031300000_addEmbeddingTables.ts | 6 +- .../1709934935000_embeddingsMetadataId.ts | 62 ++ release-please-config.json | 5 + yarn.lock | 64 +- 53 files changed, 2210 insertions(+), 849 deletions(-) rename docker/stacks/single-tenant-host/{docker-compose.yaml => docker-compose.yml} (98%) create mode 100644 packages/client/shared/gqlIds/EmbedderChannelId.ts create mode 100644 packages/embedder/EMBEDDER_JOB_PRIORITY.ts create mode 100644 packages/embedder/EmbeddingsJobQueueStream.ts create mode 100644 packages/embedder/addEmbeddingsMetadata.ts create mode 100644 packages/embedder/addEmbeddingsMetadataForRetrospectiveDiscussionTopic.ts create mode 100644 packages/embedder/ai_models/AbstractEmbeddingsModel.ts create mode 100644 packages/embedder/ai_models/AbstractGenerationModel.ts create mode 100644 packages/embedder/custom.d.ts create mode 100644 packages/embedder/establishPrimaryEmbedder.ts create mode 100644 packages/embedder/importHistoricalMetadata.ts create mode 100644 packages/embedder/importHistoricalRetrospectiveDiscussionTopic.ts delete mode 100644 packages/embedder/indexing/countWords.ts delete mode 100644 packages/embedder/indexing/embeddingsTablesOps.ts create mode 100644 packages/embedder/indexing/failJob.ts delete mode 100644 packages/embedder/indexing/getRedisClient.ts delete mode 100644 packages/embedder/indexing/getRootDataLoader.ts create mode 100644 packages/embedder/iso6393To1.ts create mode 100644 packages/embedder/logMemoryUse.ts create mode 100644 packages/embedder/mergeAsyncIterators.ts delete mode 100644 packages/embedder/modules.d.ts create mode 100644 packages/embedder/processJob.ts create mode 100644 packages/embedder/processJobEmbed.ts create mode 100644 packages/embedder/resetStalledJobs.ts create mode 100644 packages/embedder/textEmbeddingsnterface.d.ts create mode 100644 packages/embedder/types/modules.d.ts create mode 100644 packages/embedder/types/shared.d.ts create mode 100644 packages/server/graphql/mutations/helpers/publishToEmbedder.ts create mode 100644 packages/server/postgres/migrations/1709934935000_embeddingsMetadataId.ts diff --git a/.env.example b/.env.example index 2aa11780748..5f0285650a9 100644 --- a/.env.example +++ b/.env.example @@ -14,7 +14,7 @@ SOCKET_PORT='3001' # AI MODELS AI_EMBEDDING_MODELS='[{"model": "text-embeddings-inference:llmrails/ember-v1", "url": "http://localhost:3040/"}]' AI_GENERATION_MODELS='[{"model": "text-generation-inference:TheBloke/zephyr-7b-beta", "url": "http://localhost:3050/"}]' -AI_EMBEDDER_ENABLED='true' +AI_EMBEDDER_WORKERS='1' # APPLICATION # AMPLITUDE_WRITE_KEY='key_AMPLITUDE_WRITE_KEY' diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 709f0a2d818..4da61b03eac 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: id-token: "write" services: postgres: - image: pgvector/pgvector:pg15 + image: pgvector/pgvector:0.6.2-pg15 # This env variables must be the same in the file PARABOL_BUILD_ENV_PATH env: POSTGRES_PASSWORD: "temppassword" @@ -143,6 +143,6 @@ jobs: uses: ravsamhq/notify-slack-action@v2 with: status: ${{ job.status }} - notify_when: 'failure' + notify_when: "failure" env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_GH_ACTIONS_NOTIFICATIONS }} diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 8378696b622..171e080964f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: id-token: "write" services: postgres: - image: postgres:15.4 + image: pgvector/pgvector:0.6.2-pg15 # This env variables must be the same in the file PARABOL_BUILD_ENV_PATH env: POSTGRES_PASSWORD: "temppassword" @@ -78,7 +78,6 @@ jobs: yarn db:migrate yarn pg:migrate up yarn pg:build - yarn pg:generate - name: Build for testing run: yarn build @@ -86,9 +85,6 @@ jobs: - name: Verify source is clean run: git diff --quiet HEAD || (echo "Changes in generated files detected"; git diff; exit 1) - - name: Check Code Quality - run: yarn codecheck - - name: Run Predeploy for Testing run: yarn predeploy @@ -100,6 +96,12 @@ jobs: wait-on: | http://localhost:3000/graphql + - name: Kysely Codegen + run: yarn pg:generate + + - name: Check Code Quality + run: yarn codecheck + - name: Run server tests run: yarn test:server -- --reporters=default --reporters=jest-junit env: @@ -139,6 +141,6 @@ jobs: uses: ravsamhq/notify-slack-action@v2 with: status: ${{ job.status }} - notify_when: 'failure' + notify_when: "failure" env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_GH_ACTIONS_NOTIFICATIONS }} diff --git a/docker/images/parabol-ubi/README.md b/docker/images/parabol-ubi/README.md index 920c6d48fd8..518269140bc 100644 --- a/docker/images/parabol-ubi/README.md +++ b/docker/images/parabol-ubi/README.md @@ -16,21 +16,21 @@ Recommended: ## Variables -| Name | Description | Possible values | Recommended value | -| -------------------- | ----------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ------------------------------------------------------------------- | -| `postgresql_tag` | PostgreSQL version from the [Docker image](https://hub.docker.com/_/postgres) | `Any tag` | `15.4` | -| `rethinkdb_tag` | RethinkDB version from the [Docker image](https://hub.docker.com/_/rethinkdb) | `Any tag` | `2.4.2` | -| `redis_tag` | Redis version from the [Docker image](https://hub.docker.com/_/redis) | `Any tag` | `7.0-alpine` | -| `_BUILD_ENV_PATH` | File `.env` used by the application during the build process | `Relative path from the root level of the repository` | `docker/parabol-ubi/environments/basic-env` | -| `_NODE_VERSION` | Node version, used by Docker to use the Docker image node:\_NODE_VERSION as base image to build | `Same as in root package.json` | | -| `_DOCKERFILE` | Dockerfile used to build the image | `Relative path from the root level of the repository` | `./docker/parabol-ubi/dockerfiles/basic.dockerfile` | -| `_DOCKER_REPOSITORY` | The destination repository | `String` | `parabol` | -| `_DOCKER_TAG` | Tag for the produced image | `String` | | +| Name | Description | Possible values | Recommended value | +| -------------------- | ----------------------------------------------------------------------------------------------- | ----------------------------------------------------- | --------------------------------------------------- | +| `postgresql_tag` | PostgreSQL version from the [Docker image](https://hub.docker.com/r/pgvector/pgvector) | `Any tag` | `0.6.2-pg15` | +| `rethinkdb_tag` | RethinkDB version from the [Docker image](https://hub.docker.com/_/rethinkdb) | `Any tag` | `2.4.2` | +| `redis_tag` | Redis version from the [Docker image](https://hub.docker.com/_/redis) | `Any tag` | `7.0-alpine` | +| `_BUILD_ENV_PATH` | File `.env` used by the application during the build process | `Relative path from the root level of the repository` | `docker/parabol-ubi/environments/basic-env` | +| `_NODE_VERSION` | Node version, used by Docker to use the Docker image node:\_NODE_VERSION as base image to build | `Same as in root package.json` | | +| `_DOCKERFILE` | Dockerfile used to build the image | `Relative path from the root level of the repository` | `./docker/parabol-ubi/dockerfiles/basic.dockerfile` | +| `_DOCKER_REPOSITORY` | The destination repository | `String` | `parabol` | +| `_DOCKER_TAG` | Tag for the produced image | `String` | | Example of variables: ```commandLine -export postgresql_tag=15.4; \ +export postgresql_tag=0.6.2-pg15; \ export rethinkdb_tag=2.4.2; \ export redis_tag=7.0-alpine; \ export _BUILD_ENV_PATH=docker/parabol-ubi/environments/basic-env; \ @@ -61,7 +61,7 @@ cp $_BUILD_ENV_PATH ./.env > :warning: Stop all database containers you might have running before executing the following command. If other database containers are running, some ports might be already taken. ```commandLine -docker run --name temp-postgres -e POSTGRES_PASSWORD=temppassword -e POSTGRES_USER=tempuser -e POSTGRES_DB=tempdb -d -p 5432:5432 postgres:$postgresql_tag && \ +docker run --name temp-postgres -e POSTGRES_PASSWORD=temppassword -e POSTGRES_USER=tempuser -e POSTGRES_DB=tempdb -d -p 5432:5432 pgvector/pgvector:$postgresql_tag && \ docker run --name temp-rethinkdb -d -p 28015:28015 -p 29015:29015 -p 8080:8080 rethinkdb:$rethinkdb_tag && \ docker run --name temp-redis -d -p 6379:6379 redis:$redis_tag ``` diff --git a/docker/images/parabol-ubi/environments/pipeline b/docker/images/parabol-ubi/environments/pipeline index cfc707c746b..cd111fab88b 100644 --- a/docker/images/parabol-ubi/environments/pipeline +++ b/docker/images/parabol-ubi/environments/pipeline @@ -54,3 +54,7 @@ STRIPE_PUBLISHABLE_KEY='pk_test_MNoKbCzQX0lhktuxxI7M14wd' STRIPE_SECRET_KEY='' STRIPE_WEBHOOK_SECRET='' HUBSPOT_API_KEY='' +AI_EMBEDDING_MODELS='[{"model": "text-embeddings-inference:llmrails/ember-v1", "url": "http://localhost:3040/"}]' +AI_GENERATION_MODELS='[{"model": "text-generation-inference:TheBloke/zephyr-7b-beta", "url": "http://localhost:3050/"}]' +AI_EMBEDDER_WORKERS='1' +POSTGRES_USE_PGVECTOR='true' diff --git a/docker/stacks/development/docker-compose.yml b/docker/stacks/development/docker-compose.yml index 91adbbb6578..a4509d051b9 100644 --- a/docker/stacks/development/docker-compose.yml +++ b/docker/stacks/development/docker-compose.yml @@ -70,7 +70,7 @@ services: networks: parabol-network: text-embeddings-inference: - image: ghcr.io/huggingface/text-embeddings-inference:cpu-0.6 + image: ghcr.io/huggingface/text-embeddings-inference:cpu-1.2 command: - "--model-id=llmrails/ember-v1" platform: linux/x86_64 diff --git a/docker/stacks/single-tenant-host/docker-compose.yaml b/docker/stacks/single-tenant-host/docker-compose.yml similarity index 98% rename from docker/stacks/single-tenant-host/docker-compose.yaml rename to docker/stacks/single-tenant-host/docker-compose.yml index e5f662ed74a..a336201bfe3 100644 --- a/docker/stacks/single-tenant-host/docker-compose.yaml +++ b/docker/stacks/single-tenant-host/docker-compose.yml @@ -17,7 +17,7 @@ services: postgres: container_name: postgres profiles: ["databases"] - image: postgres:15.4 + image: pgvector/pgvector:0.6.2-pg15 restart: always env_file: .env environment: diff --git a/package.json b/package.json index 82179da713f..7312d1f47d3 100644 --- a/package.json +++ b/package.json @@ -103,7 +103,7 @@ "html-webpack-plugin": "^5.5.0", "husky": "^7.0.4", "jscodeshift": "^0.14.0", - "kysely": "^0.27.2", + "kysely": "^0.27.3", "kysely-codegen": "^0.11.0", "lerna": "^6.4.1", "mini-css-extract-plugin": "^2.7.2", diff --git a/packages/client/shared/gqlIds/EmbedderChannelId.ts b/packages/client/shared/gqlIds/EmbedderChannelId.ts new file mode 100644 index 00000000000..f1fb49341ef --- /dev/null +++ b/packages/client/shared/gqlIds/EmbedderChannelId.ts @@ -0,0 +1,9 @@ +export const EmbedderChannelId = { + join: (serverId: string) => `embedder:${serverId}`, + split: (id: string) => { + const [, serverId] = id.split(':') + return serverId + } +} + +export default EmbedderChannelId diff --git a/packages/client/types/generics.ts b/packages/client/types/generics.ts index 663bbd7ac52..7a1d76dafb0 100644 --- a/packages/client/types/generics.ts +++ b/packages/client/types/generics.ts @@ -100,6 +100,9 @@ export type WithFieldsAsType = { : TObj[K] } +export type Tuple = R['length'] extends N ? R : Tuple +export type ParseInt = T extends `${infer Digit extends number}` ? Digit : never + declare global { interface Array { findLastIndex(predicate: (value: T, index: number, obj: T[]) => unknown, thisArg?: any): number diff --git a/packages/embedder/EMBEDDER_JOB_PRIORITY.ts b/packages/embedder/EMBEDDER_JOB_PRIORITY.ts new file mode 100644 index 00000000000..a54e4b5c67d --- /dev/null +++ b/packages/embedder/EMBEDDER_JOB_PRIORITY.ts @@ -0,0 +1,6 @@ +export const EMBEDDER_JOB_PRIORITY = { + MEETING: 40, + DEFAULT: 50, + TOPIC_HISTORY: 80, + NEW_MODEL: 90 +} as const diff --git a/packages/embedder/EmbeddingsJobQueueStream.ts b/packages/embedder/EmbeddingsJobQueueStream.ts new file mode 100644 index 00000000000..7f5b1bde03d --- /dev/null +++ b/packages/embedder/EmbeddingsJobQueueStream.ts @@ -0,0 +1,72 @@ +import {Selectable, sql} from 'kysely' +import ms from 'ms' +import sleep from 'parabol-client/utils/sleep' +import 'parabol-server/initSentry' +import getKysely from 'parabol-server/postgres/getKysely' +import {DB} from 'parabol-server/postgres/pg' +import RootDataLoader from '../server/dataloader/RootDataLoader' +import {processJob} from './processJob' +import {Logger} from '../server/utils/Logger' + +export type DBJob = Selectable +export type EmbedJob = DBJob & { + jobType: 'embed' + jobData: { + embeddingsMetadataId: number + model: string + } +} +export type RerankJob = DBJob & {jobType: 'rerank'; jobData: {discussionIds: string[]}} +export type Job = EmbedJob | RerankJob + +export class EmbeddingsJobQueueStream implements AsyncIterableIterator { + [Symbol.asyncIterator]() { + return this + } + dataLoader = new RootDataLoader({maxBatchSize: 1000}) + async next(): Promise> { + const pg = getKysely() + const getJob = (isFailed: boolean) => { + return pg + .with( + (cte) => cte('ids').materialized(), + (db) => + db + .selectFrom('EmbeddingsJobQueue') + .select('id') + .orderBy(['priority']) + .$if(!isFailed, (db) => db.where('state', '=', 'queued')) + .$if(isFailed, (db) => + db.where('state', '=', 'failed').where('retryAfter', '<', new Date()) + ) + .limit(1) + .forUpdate() + .skipLocked() + ) + .updateTable('EmbeddingsJobQueue') + .set({state: 'running', startAt: new Date()}) + .where('id', '=', sql`ANY(SELECT id FROM ids)`) + .returningAll() + .executeTakeFirst() + } + const job = (await getJob(false)) || (await getJob(true)) + if (!job) { + Logger.log('JobQueueStream: no jobs found') + // queue is empty, so sleep for a while + await sleep(ms('1m')) + return this.next() + } + + const isSuccessful = await processJob(job as Job, this.dataLoader) + if (isSuccessful) { + await pg.deleteFrom('EmbeddingsJobQueue').where('id', '=', job.id).executeTakeFirstOrThrow() + } + return {done: false, value: job as Job} + } + return() { + return Promise.resolve({done: true as const, value: undefined}) + } + throw(error: any) { + return Promise.resolve({done: true, value: error}) + } +} diff --git a/packages/embedder/README.md b/packages/embedder/README.md index fc3fc68f335..36bb8e2ea50 100644 --- a/packages/embedder/README.md +++ b/packages/embedder/README.md @@ -3,27 +3,14 @@ This service builds embedding vectors for semantic search and for other AI/ML use cases. It does so by: -1. Updating a list of all possible items to create embedding vectors for and - storing that list in the `EmbeddingsMetadata` table -2. Adding these items in batches to the `EmbeddingsJobQueue` table and a redis - priority queue called `embedder:queue` -3. Allowing one or more parallel embedding services to calculate embedding - vectors (EmbeddingJobQueue states transistion from `queued` -> `embedding`, - then `embedding` -> [deleting the `EmbeddingJobQueue` row] - - In addition to deleteing the `EmbeddingJobQueue` row, when a job completes - successfully: - - - A row is added to the model table with the embedding vector; the - `EmbeddingMetadataId` field on this row points the appropriate - metadata row on `EmbeddingsMetadata` - - The `EmbeddingsMetadata.models` array is updated with the name of the - table that the embedding has been generated for - -4. This process repeats forever using a silly polling loop - -In the future, it would be wonderful to enhance this service such that it were -event driven. +1. Homogenizes different types of data into a single `EmbeddingsMetadata` table +2. Each new row in `EmbeddingsMetadata` creates a new row in `EmbeddingsJobQueue` for each model +3. Uses PG to pick a job from the queue and sets the job from `queued` -> `embedding`, + then `embedding` -> [deleting the `EmbeddingJobQueue` row] +4. Embedding involves creating a `fullText` from the work item and then a vector from that `fullText` +5. New jobs to add metadata are sent via redis streams from the GQL Executor +6. If embedding fails, the application increments the `retryCount` and increases the `retryAfter` if a retry is desired +7. If a job gets stalled, a process that runs every 5 minutes will look for jobs older than 5 minutes and reset them to `queued` ## Prerequisites @@ -37,10 +24,9 @@ The predeploy script checks for an environment variable The Embedder service takes no arguments and is controlled by the following environment variables, here given with example configuration: -- `AI_EMBEDDER_ENABLE`: enable/disable the embedder service from - performing work, or sleeping indefinitely +- `AI_EMBEDDER_WORKERS`: How many workers should simultaneously pick jobs from the queue. If less than 1, disabled. -`AI_EMBEDDER_ENABLED='true'` +`AI_EMBEDDER_WORKERS='1'` - `AI_EMBEDDING_MODELS`: JSON configuration for which embedding models are enabled. Each model in the array will be instantiated by @@ -69,3 +55,10 @@ environment variables, here given with example configuration: The Embedder service is stateless and takes no arguments. Multiple instances of the service may be started in order to match embedding load, or to catch up on history more quickly. + +## Resources + +### PG as a Job Queue + +- https://leontrolski.github.io/postgres-as-queue.html +- https://www.2ndquadrant.com/en/blog/what-is-select-skip-locked-for-in-postgresql-9-5/ diff --git a/packages/embedder/addEmbeddingsMetadata.ts b/packages/embedder/addEmbeddingsMetadata.ts new file mode 100644 index 00000000000..214fecc0409 --- /dev/null +++ b/packages/embedder/addEmbeddingsMetadata.ts @@ -0,0 +1,15 @@ +import {addEmbeddingsMetadataForRetrospectiveDiscussionTopic} from './addEmbeddingsMetadataForRetrospectiveDiscussionTopic' +import {MessageToEmbedder} from './custom' + +export const addEmbeddingsMetadata = async ({objectTypes, ...options}: MessageToEmbedder) => { + return Promise.all( + objectTypes.map((type) => { + switch (type) { + case 'retrospectiveDiscussionTopic': + return addEmbeddingsMetadataForRetrospectiveDiscussionTopic(options) + default: + throw new Error(`Invalid object type: ${type}`) + } + }) + ) +} diff --git a/packages/embedder/addEmbeddingsMetadataForRetrospectiveDiscussionTopic.ts b/packages/embedder/addEmbeddingsMetadataForRetrospectiveDiscussionTopic.ts new file mode 100644 index 00000000000..724aec6fb13 --- /dev/null +++ b/packages/embedder/addEmbeddingsMetadataForRetrospectiveDiscussionTopic.ts @@ -0,0 +1,143 @@ +import {ExpressionOrFactory, SqlBool, sql} from 'kysely' +import getRethink from 'parabol-server/database/rethinkDriver' +import {RDatum} from 'parabol-server/database/stricterR' +import getKysely from 'parabol-server/postgres/getKysely' +import {DB} from 'parabol-server/postgres/pg' +import {Logger} from 'parabol-server/utils/Logger' +import {EMBEDDER_JOB_PRIORITY} from './EMBEDDER_JOB_PRIORITY' +import getModelManager from './ai_models/ModelManager' +import {EmbedderOptions} from './custom' + +interface DiscussionMeta { + id: string + teamId: string + createdAt: Date +} + +const validateDiscussions = async (discussions: (DiscussionMeta & {meetingId: string})[]) => { + const r = await getRethink() + if (discussions.length === 0) return discussions + // Exclude discussions that belong to an unfinished meeting + const meetingIds = [...new Set(discussions.map(({meetingId}) => meetingId))] + const endedMeetingIds = await r + .table('NewMeeting') + .getAll(r.args(meetingIds), {index: 'id'}) + .filter((row: RDatum) => row('endedAt').default(null).ne(null))('id') + .distinct() + .run() + const endedMeetingIdsSet = new Set(endedMeetingIds) + return discussions.filter(({meetingId}) => endedMeetingIdsSet.has(meetingId)) +} + +const insertDiscussionsIntoMetadata = async (discussions: DiscussionMeta[], priority: number) => { + const pg = getKysely() + const metadataRows = discussions.map(({id, teamId, createdAt}) => ({ + refId: id, + objectType: 'retrospectiveDiscussionTopic' as const, + teamId, + // Not techincally updatedAt since discussions are be updated after they get created + refUpdatedAt: createdAt + })) + if (!metadataRows[0]) return + + const modelManager = getModelManager() + const models = modelManager.embeddingModels.map((m) => m.tableName) + return ( + pg + .with('Insert', (qc) => + qc + .insertInto('EmbeddingsMetadata') + .values(metadataRows) + .onConflict((oc) => oc.doNothing()) + .returning('id') + ) + // create n*m rows for n models & m discussions + .with('Metadata', (qc) => + qc + .selectFrom('Insert') + .fullJoin(sql<{model: string}>`UNNEST(ARRAY[${sql.join(models)}])`.as('model'), (join) => + join.onTrue() + ) + .select(['id', 'model']) + ) + .insertInto('EmbeddingsJobQueue') + .columns(['jobType', 'priority', 'jobData']) + .expression(({selectFrom}) => + selectFrom('Metadata').select(({lit, fn, ref}) => [ + sql.lit('embed').as('jobType'), + lit(priority).as('priority'), + fn('json_build_object', [ + sql.lit('embeddingsMetadataId'), + ref('Metadata.id'), + sql.lit('model'), + ref('Metadata.model') + ]).as('jobData') + ]) + ) + .execute() + ) +} + +export const addEmbeddingsMetadataForRetrospectiveDiscussionTopic = async ({ + startAt, + endAt, + meetingId +}: EmbedderOptions) => { + // load up the metadata table will all discussion topics that are a part of meetings ended within the given date range + const pg = getKysely() + if (meetingId) { + const discussions = await pg + .selectFrom('Discussion') + .select(['id', 'teamId', 'createdAt']) + .where('meetingId', '=', meetingId) + .execute() + await insertDiscussionsIntoMetadata(discussions, EMBEDDER_JOB_PRIORITY.MEETING) + return + } + // PG only accepts 65K parameters (inserted columns * number of rows + query params). Make the batches as big as possible + const PG_MAX_PARAMS = 65535 + const QUERY_PARAMS = 10 + const METADATA_COLS_PER_ROW = 4 + const BATCH_SIZE = Math.floor((PG_MAX_PARAMS - QUERY_PARAMS) / METADATA_COLS_PER_ROW) + const pgStartAt = startAt || new Date(0) + const pgEndAt = (endAt || new Date('4000-01-01')).getTime() / 1000 + + let curEndAt = pgEndAt + let curEndId = '' + for (let i = 0; i < 1e6; i++) { + // preserve microsecond resolution to keep timestamps equal + // so we can use the ID as a tiebreaker when count(createdAt) > BATCH_SIZE + const pgTime = sql`to_timestamp(${curEndAt})` + const lessThanTimeOrId: ExpressionOrFactory = curEndId + ? ({eb}) => + eb('createdAt', '<', pgTime).or(eb('createdAt', '=', pgTime).and('id', '>', curEndId)) + : ({eb}) => eb('createdAt', '<=', pgTime) + const discussions = await pg + .selectFrom('Discussion') + .select([ + 'id', + 'teamId', + 'createdAt', + 'meetingId', + sql`extract(epoch from "createdAt")`.as('createdAtEpoch') + ]) + .where('createdAt', '>', pgStartAt) + .where(lessThanTimeOrId) + .where('discussionTopicType', '=', 'reflectionGroup') + .orderBy('createdAt', 'desc') + .orderBy('id') + .limit(BATCH_SIZE) + .execute() + const earliestDiscussionInBatch = discussions.at(-1) + if (!earliestDiscussionInBatch) break + const {createdAtEpoch, id} = earliestDiscussionInBatch + curEndId = curEndAt === createdAtEpoch ? id : '' + curEndAt = createdAtEpoch + const validDiscussions = await validateDiscussions(discussions) + await insertDiscussionsIntoMetadata(validDiscussions, EMBEDDER_JOB_PRIORITY.TOPIC_HISTORY) + const jsTime = new Date(createdAtEpoch * 1000) + Logger.log( + `Inserted ${validDiscussions.length}/${discussions.length} discussions in metadata ending at ${jsTime}` + ) + } +} diff --git a/packages/embedder/ai_models/AbstractEmbeddingsModel.ts b/packages/embedder/ai_models/AbstractEmbeddingsModel.ts new file mode 100644 index 00000000000..9fd5831ea1f --- /dev/null +++ b/packages/embedder/ai_models/AbstractEmbeddingsModel.ts @@ -0,0 +1,126 @@ +import {sql} from 'kysely' +import getKysely from 'parabol-server/postgres/getKysely' +import {DB} from 'parabol-server/postgres/pg' +import {Logger} from '../../server/utils/Logger' +import {EMBEDDER_JOB_PRIORITY} from '../EMBEDDER_JOB_PRIORITY' +import {ISO6391} from '../iso6393To1' +import {AbstractModel, ModelConfig} from './AbstractModel' + +export interface EmbeddingModelParams { + embeddingDimensions: number + maxInputTokens: number + tableSuffix: string + languages: ISO6391[] +} +export type EmbeddingsTable = Extract +export interface EmbeddingModelConfig extends ModelConfig { + tableSuffix: string +} + +export abstract class AbstractEmbeddingsModel extends AbstractModel { + readonly embeddingDimensions: number + readonly maxInputTokens: number + readonly tableName: string + readonly languages: ISO6391[] + constructor(config: EmbeddingModelConfig) { + super(config) + const modelParams = this.constructModelParams(config) + this.embeddingDimensions = modelParams.embeddingDimensions + this.languages = modelParams.languages + this.maxInputTokens = modelParams.maxInputTokens + this.tableName = `Embeddings_${modelParams.tableSuffix}` + } + protected abstract constructModelParams(config: EmbeddingModelConfig): EmbeddingModelParams + abstract getEmbedding(content: string, retries?: number): Promise + + abstract getTokens(content: string): Promise + splitText(content: string) { + // it's actually 4 / 3, but don't want to chance a failed split + const TOKENS_PER_WORD = 5 / 3 + const WORD_LIMIT = Math.floor(this.maxInputTokens / TOKENS_PER_WORD) + const chunks: string[] = [] + const delimiters = ['\n\n', '\n', '.', ' '] + const countWords = (text: string) => text.trim().split(/\s+/).length + const splitOnDelimiter = (text: string, delimiter: string) => { + const sections = text.split(delimiter) + for (let i = 0; i < sections.length; i++) { + const section = sections[i]! + const sectionWordCount = countWords(section) + if (sectionWordCount < WORD_LIMIT) { + // try to merge this section with the last one + const previousSection = chunks.at(-1) + if (previousSection) { + const combinedChunks = `${previousSection}${delimiter}${section}` + const mergedWordCount = countWords(combinedChunks) + if (mergedWordCount < WORD_LIMIT) { + chunks[chunks.length - 1] = combinedChunks + continue + } + } + chunks.push(section) + } else { + const nextDelimiter = delimiters[delimiters.indexOf(delimiter) + 1]! + splitOnDelimiter(section, nextDelimiter) + } + } + } + splitOnDelimiter(content.trim(), delimiters[0]!) + return chunks + } + + async createEmbeddingsForModel() { + Logger.log(`Queueing EmbeddingsMetadata into EmbeddingsJobQueue for ${this.tableName}`) + const pg = getKysely() + await pg + .insertInto('EmbeddingsJobQueue') + .columns(['jobData', 'priority']) + .expression(({selectFrom}) => + selectFrom('EmbeddingsMetadata') + .select(({fn, lit}) => [ + fn('json_build_object', [ + sql.lit('model'), + sql.lit(this.tableName), + sql.lit('embeddingsMetadataId'), + 'id' + ]).as('jobData'), + lit(EMBEDDER_JOB_PRIORITY.NEW_MODEL).as('priority') + ]) + .where('language', 'in', this.languages) + ) + .onConflict((oc) => oc.doNothing()) + .execute() + } + async createTable() { + const pg = getKysely() + const hasTable = + ( + await sql`SELECT 1 FROM ${sql.id('pg_catalog', 'pg_tables')} WHERE ${sql.id( + 'tablename' + )} = ${this.tableName}`.execute(pg) + ).rows.length > 0 + if (hasTable) return undefined + const vectorDimensions = this.embeddingDimensions + Logger.log(`ModelManager: creating ${this.tableName} with ${vectorDimensions} dimensions`) + await sql` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS ${sql.id(this.tableName)} ( + "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + "embedText" TEXT, + "embedding" vector(${sql.raw(vectorDimensions.toString())}), + "embeddingsMetadataId" INTEGER UNIQUE NOT NULL, + "chunkNumber" SMALLINT, + UNIQUE("embeddingsMetadataId", "chunkNumber"), + FOREIGN KEY ("embeddingsMetadataId") + REFERENCES "EmbeddingsMetadata"("id") + ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS "idx_${sql.raw(this.tableName)}_embedding_vector_cosign_ops" + ON ${sql.id(this.tableName)} + USING hnsw ("embedding" vector_cosine_ops); + END + $$; + `.execute(pg) + await this.createEmbeddingsForModel() + } +} diff --git a/packages/embedder/ai_models/AbstractGenerationModel.ts b/packages/embedder/ai_models/AbstractGenerationModel.ts new file mode 100644 index 00000000000..c57d86c243a --- /dev/null +++ b/packages/embedder/ai_models/AbstractGenerationModel.ts @@ -0,0 +1,26 @@ +import {AbstractModel, ModelConfig} from './AbstractModel' + +export interface GenerationOptions { + maxNewTokens?: number + seed?: number + stop?: string + temperature?: number + topK?: number + topP?: number +} +export interface GenerationModelParams { + maxInputTokens: number +} +export interface GenerationModelConfig extends ModelConfig {} + +export abstract class AbstractGenerationModel extends AbstractModel { + readonly maxInputTokens: number + constructor(config: GenerationModelConfig) { + super(config) + const modelParams = this.constructModelParams(config) + this.maxInputTokens = modelParams.maxInputTokens + } + + protected abstract constructModelParams(config: GenerationModelConfig): GenerationModelParams + abstract summarize(content: string, options: GenerationOptions): Promise +} diff --git a/packages/embedder/ai_models/AbstractModel.ts b/packages/embedder/ai_models/AbstractModel.ts index b57d220cd35..3b114558539 100644 --- a/packages/embedder/ai_models/AbstractModel.ts +++ b/packages/embedder/ai_models/AbstractModel.ts @@ -3,71 +3,18 @@ export interface ModelConfig { url: string } -export interface EmbeddingModelConfig extends ModelConfig { - tableSuffix: string -} - -export interface GenerationModelConfig extends ModelConfig {} - export abstract class AbstractModel { - public readonly url?: string + public readonly url: string constructor(config: ModelConfig) { this.url = this.normalizeUrl(config.url) } // removes a trailing slash from the inputUrl - private normalizeUrl(inputUrl: string | undefined) { - if (!inputUrl) return undefined + private normalizeUrl(inputUrl: string) { const regex = /[/]+$/ return inputUrl.replace(regex, '') } } -export interface EmbeddingModelParams { - embeddingDimensions: number - maxInputTokens: number - tableSuffix: string -} - -export abstract class AbstractEmbeddingsModel extends AbstractModel { - readonly embeddingDimensions: number - readonly maxInputTokens: number - readonly tableName: string - constructor(config: EmbeddingModelConfig) { - super(config) - const modelParams = this.constructModelParams(config) - this.embeddingDimensions = modelParams.embeddingDimensions - this.maxInputTokens = modelParams.maxInputTokens - this.tableName = `Embeddings_${modelParams.tableSuffix}` - } - protected abstract constructModelParams(config: EmbeddingModelConfig): EmbeddingModelParams - abstract getEmbedding(content: string): Promise -} - -export interface GenerationModelParams { - maxInputTokens: number -} - -export interface GenerationOptions { - maxNewTokens?: number - seed?: number - stop?: string - temperature?: number - topK?: number - topP?: number -} - -export abstract class AbstractGenerationModel extends AbstractModel { - readonly maxInputTokens: number - constructor(config: GenerationModelConfig) { - super(config) - const modelParams = this.constructModelParams(config) - this.maxInputTokens = modelParams.maxInputTokens - } - - protected abstract constructModelParams(config: GenerationModelConfig): GenerationModelParams - abstract summarize(content: string, options: GenerationOptions): Promise -} - export default AbstractModel diff --git a/packages/embedder/ai_models/ModelManager.ts b/packages/embedder/ai_models/ModelManager.ts index bf6888378c8..55bf3feeadc 100644 --- a/packages/embedder/ai_models/ModelManager.ts +++ b/packages/embedder/ai_models/ModelManager.ts @@ -1,12 +1,6 @@ -import {Kysely, sql} from 'kysely' - -import { - AbstractEmbeddingsModel, - AbstractGenerationModel, - EmbeddingModelConfig, - GenerationModelConfig, - ModelConfig -} from './AbstractModel' +import {AbstractEmbeddingsModel, EmbeddingModelConfig} from './AbstractEmbeddingsModel' +import {AbstractGenerationModel, GenerationModelConfig} from './AbstractGenerationModel' +import {ModelConfig} from './AbstractModel' import OpenAIGeneration from './OpenAIGeneration' import TextEmbeddingsInference from './TextEmbeddingsInference' import TextGenerationInference from './TextGenerationInference' @@ -16,8 +10,8 @@ interface ModelManagerConfig { generationModels: GenerationModelConfig[] } -export type EmbeddingsModelType = 'text-embeddings-inference' -export type GenerationModelType = 'openai' | 'text-generation-inference' +type EmbeddingsModelType = 'text-embeddings-inference' +type GenerationModelType = 'openai' | 'text-generation-inference' export class ModelManager { embeddingModels: AbstractEmbeddingsModel[] @@ -93,39 +87,8 @@ export class ModelManager { }) } - async maybeCreateTables(pg: Kysely) { - const maybePromises = this.embeddingModels.map(async (embeddingsModel) => { - const tableName = embeddingsModel.tableName - const hasTable = - ( - await sql`SELECT 1 FROM ${sql.id('pg_catalog', 'pg_tables')} WHERE ${sql.id( - 'tablename' - )} = ${tableName}`.execute(pg) - ).rows.length > 0 - if (hasTable) return undefined - const vectorDimensions = embeddingsModel.embeddingDimensions - console.log(`ModelManager: creating ${tableName} with ${vectorDimensions} dimensions`) - const query = sql` - DO $$ - BEGIN - CREATE TABLE IF NOT EXISTS ${sql.id(tableName)} ( - "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, - "embedText" TEXT, - "embedding" vector(${sql.raw(vectorDimensions.toString())}), - "embeddingsMetadataId" INTEGER NOT NULL, - FOREIGN KEY ("embeddingsMetadataId") - REFERENCES "EmbeddingsMetadata"("id") - ON DELETE CASCADE - ); - CREATE INDEX IF NOT EXISTS "idx_${sql.raw(tableName)}_embedding_vector_cosign_ops" - ON ${sql.id(tableName)} - USING hnsw ("embedding" vector_cosine_ops); - END $$; - - ` - return query.execute(pg) - }) - Promise.all(maybePromises) + async maybeCreateTables() { + return Promise.all(this.embeddingModels.map((model) => model.createTable())) } } diff --git a/packages/embedder/ai_models/OpenAIGeneration.ts b/packages/embedder/ai_models/OpenAIGeneration.ts index 9818012edb4..697160513ae 100644 --- a/packages/embedder/ai_models/OpenAIGeneration.ts +++ b/packages/embedder/ai_models/OpenAIGeneration.ts @@ -4,7 +4,7 @@ import { GenerationModelConfig, GenerationModelParams, GenerationOptions -} from './AbstractModel' +} from './AbstractGenerationModel' export type ModelId = 'gpt-3.5-turbo-0125' | 'gpt-4-turbo-preview' diff --git a/packages/embedder/ai_models/TextEmbeddingsInference.ts b/packages/embedder/ai_models/TextEmbeddingsInference.ts index 549fadcd6fd..c30c59e8e8d 100644 --- a/packages/embedder/ai_models/TextEmbeddingsInference.ts +++ b/packages/embedder/ai_models/TextEmbeddingsInference.ts @@ -1,20 +1,25 @@ -import {AbstractEmbeddingsModel, EmbeddingModelConfig, EmbeddingModelParams} from './AbstractModel' -import fetchWithRetry from './helpers/fetchWithRetry' - -const MAX_REQUEST_TIME_S = 3 * 60 - +import createClient from 'openapi-fetch' +import sleep from 'parabol-client/utils/sleep' +import type {paths} from '../textEmbeddingsnterface' +import { + AbstractEmbeddingsModel, + EmbeddingModelConfig, + EmbeddingModelParams +} from './AbstractEmbeddingsModel' export type ModelId = 'BAAI/bge-large-en-v1.5' | 'llmrails/ember-v1' const modelIdDefinitions: Record = { 'BAAI/bge-large-en-v1.5': { embeddingDimensions: 1024, maxInputTokens: 512, - tableSuffix: 'bge_l_en_1p5' + tableSuffix: 'bge_l_en_1p5', + languages: ['en'] }, 'llmrails/ember-v1': { embeddingDimensions: 1024, maxInputTokens: 512, - tableSuffix: 'ember_1' + tableSuffix: 'ember_1', + languages: ['en'] } } @@ -23,34 +28,60 @@ function isValidModelId(object: any): object is ModelId { } export class TextEmbeddingsInference extends AbstractEmbeddingsModel { + client: ReturnType> constructor(config: EmbeddingModelConfig) { super(config) + this.client = createClient({baseUrl: this.url}) } - public async getEmbedding(content: string) { - const fetchOptions = { - body: JSON.stringify({inputs: content}), - deadline: new Date(new Date().getTime() + MAX_REQUEST_TIME_S * 1000), - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json; charset=utf-8' - }, - method: 'POST' + async getTokens(content: string) { + try { + const {data, error} = await this.client.POST('/tokenize', { + body: {inputs: content, add_special_tokens: true}, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json; charset=utf-8' + } + }) + if (error) return new Error(error.error) + return data[0]!.map(({id}) => id) + } catch (e) { + return e instanceof Error ? e : new Error(e as string) } + } + async decodeTokens(inputIds: number[]) { + try { + const {data, error} = await this.client.POST('/decode', { + body: {ids: inputIds}, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json; charset=utf-8' + } + }) + if (error) return new Error(error.error) + return data + } catch (e) { + return e instanceof Error ? e : new Error(e as string) + } + } + public async getEmbedding(content: string, retries = 5): Promise { try { - const res = await fetchWithRetry(`${this.url}/embed`, fetchOptions) - const listOfVectors = (await res.json()) as Array - if (!listOfVectors) - throw new Error('TextEmbeddingsInference.getEmbeddings(): listOfVectors is undefined') - if (listOfVectors.length !== 1 || !listOfVectors[0]) - throw new Error( - `TextEmbeddingsInference.getEmbeddings(): listOfVectors list length !== 1 (length: ${listOfVectors.length})` - ) - return listOfVectors[0] + const {data, error, response} = await this.client.POST('/embed', { + body: {inputs: content}, + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json; charset=utf-8' + } + }) + if (error) { + if (response.status !== 429 || retries < 1) return new Error(error.error) + await sleep(2000) + return this.getEmbedding(content, retries - 1) + } + return data[0]! } catch (e) { - console.log(`TextEmbeddingsInference.getEmbeddings() timeout: `, e) - throw e + return e instanceof Error ? e : new Error(e as string) } } diff --git a/packages/embedder/ai_models/TextGenerationInference.ts b/packages/embedder/ai_models/TextGenerationInference.ts index bcf1daa6303..8fa4ab7cd7b 100644 --- a/packages/embedder/ai_models/TextGenerationInference.ts +++ b/packages/embedder/ai_models/TextGenerationInference.ts @@ -3,7 +3,7 @@ import { GenerationModelConfig, GenerationModelParams, GenerationOptions -} from './AbstractModel' +} from './AbstractGenerationModel' import fetchWithRetry from './helpers/fetchWithRetry' const MAX_REQUEST_TIME_S = 3 * 60 @@ -51,7 +51,6 @@ export class TextGenerationInference extends AbstractGenerationModel { } try { - // console.log(`TextGenerationInference.summarize(): summarizing from ${this.url}/generate`) const res = await fetchWithRetry(`${this.url}/generate`, fetchOptions) const json = await res.json() if (!json || !json.generated_text) diff --git a/packages/embedder/custom.d.ts b/packages/embedder/custom.d.ts new file mode 100644 index 00000000000..6640974c6a9 --- /dev/null +++ b/packages/embedder/custom.d.ts @@ -0,0 +1,11 @@ +import type {DB} from '../server/postgres/pg' + +export type EmbeddingObjectType = DB['EmbeddingsMetadata']['objectType'] + +export interface MessageToEmbedder { + objectTypes: EmbeddingObjectType[] + startAt?: Date + endAt?: Date + meetingId?: string +} +export type EmbedderOptions = Omit diff --git a/packages/embedder/embedder.ts b/packages/embedder/embedder.ts index dccd9492a02..628d0d8decd 100644 --- a/packages/embedder/embedder.ts +++ b/packages/embedder/embedder.ts @@ -1,42 +1,18 @@ import tracer from 'dd-trace' -import {Insertable} from 'kysely' -import Redlock, {RedlockAbortSignal} from 'redlock' - +import EmbedderChannelId from 'parabol-client/shared/gqlIds/EmbedderChannelId' import 'parabol-server/initSentry' -import getKysely from 'parabol-server/postgres/getKysely' -import {DB} from 'parabol-server/postgres/pg' -import getModelManager, {ModelManager} from './ai_models/ModelManager' -import {countWords} from './indexing/countWords' -import {createEmbeddingTextFrom} from './indexing/createEmbeddingTextFrom' -import { - completeJobTxn, - insertNewJobs, - selectJobQueueItemById, - selectMetaToQueue, - selectMetadataByJobQueueId, - updateJobState -} from './indexing/embeddingsTablesOps' -import {getRedisClient} from './indexing/getRedisClient' -import {getRootDataLoader} from './indexing/getRootDataLoader' -import {orgIdsWithFeatureFlag} from './indexing/orgIdsWithFeatureFlag' -import {refreshRetroDiscussionTopicsMeta} from './indexing/retrospectiveDiscussionTopic' - -/* - * TODO List - * - [ ] implement a clean-up function that re-queues items that haven't transitioned - * to a completed state, or that failed - */ - -export type DBInsert = { - [K in keyof DB]: Insertable -} - -const POLLING_PERIOD_SEC = 60 // How often do we try to grab the lock and re-index? -const Q_MAX_LENGTH = 100 // How many EmbeddingIndex items do we batch in redis? -const WORD_COUNT_TO_TOKEN_RATIO = 3.0 / 2 // We multiple the word count by this to estimate token count - -const {AI_EMBEDDER_ENABLED} = process.env -const {SERVER_ID} = process.env +import {Logger} from 'parabol-server/utils/Logger' +import RedisInstance from 'parabol-server/utils/RedisInstance' +import {Tuple} from '../client/types/generics' +import RedisStream from '../gql-executor/RedisStream' +import {EmbeddingsJobQueueStream} from './EmbeddingsJobQueueStream' +import {addEmbeddingsMetadata} from './addEmbeddingsMetadata' +import getModelManager from './ai_models/ModelManager' +import {MessageToEmbedder} from './custom' +import {establishPrimaryEmbedder} from './establishPrimaryEmbedder' +import {importHistoricalMetadata} from './importHistoricalMetadata' +import {mergeAsyncIterators} from './mergeAsyncIterators' +import {resetStalledJobs} from './resetStalledJobs' tracer.init({ service: `embedder`, @@ -46,207 +22,89 @@ tracer.init({ }) tracer.use('pg') -const refreshMetadata = async () => { - const dataLoader = getRootDataLoader() - await refreshRetroDiscussionTopicsMeta(dataLoader) - // In the future, other sorts of objects to index could be added here... +const parseEmbedderMessage = (message: string): MessageToEmbedder => { + const {startAt, endAt, ...input} = JSON.parse(message) + return { + ...input, + startAt: startAt ? new Date(startAt) : undefined, + endAt: endAt ? new Date(endAt) : undefined + } } -const maybeQueueMetadataItems = async (modelManager: ModelManager) => { - const redisClient = getRedisClient() - const queueLength = await redisClient.zcard('embedder:queue') - if (queueLength >= Q_MAX_LENGTH) return - const itemCountToQueue = Q_MAX_LENGTH - queueLength - const modelTables = modelManager.embeddingModels.map((m) => m.tableName) - const orgIds = await orgIdsWithFeatureFlag() - - // For each configured embedding model, select rows from EmbeddingsMetadata - // that haven't been calculated nor exist in the EmbeddingsJobQueue yet - // - // Notes: - // * `em.models @> ARRAY[v.model]` is an indexed query - // * I don't love all overrides, I wish there was a better way - // see: https://github.com/kysely-org/kysely/issues/872 - - const batchToQueue = await selectMetaToQueue(modelTables, orgIds, itemCountToQueue) - if (!batchToQueue.length) { - console.log(`embedder: no new items to queue`) +const run = async () => { + const SERVER_ID = process.env.SERVER_ID + if (!SERVER_ID) throw new Error('env.SERVER_ID is required') + const embedderChannel = EmbedderChannelId.join(SERVER_ID) + const NUM_WORKERS = parseInt(process.env.AI_EMBEDDER_WORKERS!) + if (!(NUM_WORKERS > 0)) { + Logger.log('env.AI_EMBEDDER_WORKERS is < 0. Embedder will not run.') return } - const ejqHash: { - [key: string]: { - refUpdatedAt: Date - } - } = {} - const makeKey = (item: {objectType: string; refId: string}) => `${item.objectType}:${item.refId}` - - const ejqValues = batchToQueue.map((item) => { - ejqHash[makeKey(item)] = { - refUpdatedAt: item.refUpdatedAt - } - return { - objectType: item.objectType, - refId: item.refId as string, - model: item.model, - state: 'queued' as const - } - }) as any[] - - const ejqRows = await insertNewJobs(ejqValues) - - ejqRows.forEach((item: any) => { - const {refUpdatedAt} = ejqHash[makeKey(item)]! - const score = new Date(refUpdatedAt).getTime() - redisClient.zadd('embedder:queue', score, item.id) - }) - - console.log(`embedder: queued ${batchToQueue.length} items`) -} - -const dequeueAndEmbedUntilEmpty = async (modelManager: ModelManager) => { - const dataLoader = getRootDataLoader() - const redisClient = getRedisClient() - while (true) { - const maybeRedisQItem = await redisClient.zpopmax('embedder:queue', 1) - if (maybeRedisQItem.length < 2) return // Q is empty, all done! - - const [id, _] = maybeRedisQItem - if (!id) { - console.log(`embedder: de-queued undefined item from embedder:queue`) - continue - } - const jobQueueId = parseInt(id, 10) - const jobQueueItem = (await selectJobQueueItemById(jobQueueId)) as any - if (!jobQueueItem) { - console.log(`embedder: unable to fetch EmbeddingsJobQueue.id = ${id}`) - continue - } - - const metadata = await selectMetadataByJobQueueId(jobQueueId) - if (!metadata) { - await updateJobState(jobQueueId, 'failed', { - stateMessage: `unable to fetch metadata by EmbeddingsJobQueue.id = ${id}` - }) - continue - } - - let fullText = metadata?.fullText as string - try { - if (!fullText) { - fullText = await createEmbeddingTextFrom(jobQueueItem, dataLoader) - } - } catch (e) { - await updateJobState(jobQueueId, 'failed', { - stateMessage: `unable to create embedding text: ${e}` - }) - continue - } + const redis = new RedisInstance(`embedder_${SERVER_ID}`) + const primaryLock = await establishPrimaryEmbedder(redis) + const modelManager = getModelManager() + let streams: AsyncIterableIterator | undefined + const kill = () => { + primaryLock?.release() + streams?.return?.() + process.exit() + } + process.on('SIGTERM', kill) + process.on('SIGINT', kill) + if (primaryLock) { + // only 1 worker needs to perform these on startup + await modelManager.maybeCreateTables() + await importHistoricalMetadata() + resetStalledJobs() + } - const wordCount = countWords(fullText) + const onMessage = async (_channel: string, message: string) => { + const parsedMessage = parseEmbedderMessage(message) + await addEmbeddingsMetadata(parsedMessage) + } - const embeddingModel = modelManager.embeddingModelsMapByTable[jobQueueItem.model] - if (!embeddingModel) { - await updateJobState(jobQueueId, 'failed', { - stateMessage: `embedding model ${jobQueueItem.model} not available` - }) - continue - } - const itemKey = `${jobQueueItem.objectType}:${jobQueueItem.refId}` - const modelTable = embeddingModel.tableName + // subscribe to consumer group + try { + await redis.xgroup( + 'CREATE', + 'embedMetadataStream', + 'embedMetadataConsumerGroup', + '$', + 'MKSTREAM' + ) + } catch (e) { + // stream already exists + } - let embedText = fullText - const maxInputTokens = embeddingModel.maxInputTokens - // we're using word count as an appoximation of tokens - if (wordCount * WORD_COUNT_TO_TOKEN_RATIO > maxInputTokens) { - try { - const generator = modelManager.generationModels[0] // use 1st generator - if (!generator) throw new Error(`Generator unavailable`) - console.log(`embedder: ...summarizing ${itemKey} for ${modelTable}`) - embedText = await generator.summarize(fullText, {maxNewTokens: maxInputTokens}) - } catch (e) { - await updateJobState(jobQueueId, 'failed', { - stateMessage: `unable to summarize long embed text: ${e}` - }) + const messageStream = new RedisStream( + 'embedMetadataStream', + 'embedMetadataConsumerGroup', + embedderChannel + ) + + // Assume 3 workers for type safety, but it doesn't really matter at runtime + const jobQueueStreams = Array.from( + {length: NUM_WORKERS}, + () => new EmbeddingsJobQueueStream() + ) as Tuple + + Logger.log(`\n⚡⚡⚡️️ Server ID: ${SERVER_ID}. Embedder is ready ⚡⚡⚡️️️`) + + streams = mergeAsyncIterators([messageStream, ...jobQueueStreams]) + for await (const [idx, message] of streams) { + switch (idx) { + case 0: + onMessage('', message) + continue + default: + Logger.log(`Worker ${idx} finished job ${message.id}`) continue - } - } - // console.log(`embedText: ${embedText}`) - - let embeddingVector: number[] - try { - embeddingVector = await embeddingModel.getEmbedding(embedText) - } catch (e) { - await updateJobState(jobQueueId, 'failed', { - stateMessage: `unable to get embeddings: ${e}` - }) - continue } - - // complete job, do the following atomically - // (1) update EmbeddingsMetadata to reflect model completion - // (2) upsert model table row with embedding - // (3) delete EmbeddingsJobQueue row - await completeJobTxn(modelTable, jobQueueId, metadata, fullText, embedText, embeddingVector) - console.log(`embedder: completed ${itemKey} -> ${modelTable}`) } -} - -const tick = async (modelManager: ModelManager) => { - console.log(`embedder: tick`) - const redisClient = getRedisClient() - const redlock = new Redlock([redisClient], { - driftFactor: 0.01, - retryCount: 10, - retryDelay: 250, - retryJitter: 50, - automaticExtensionThreshold: 500 - }) - - await redlock - .using(['embedder:lock'], 10000, async (signal: RedlockAbortSignal) => { - console.log(`embedder: acquired index queue lock`) - // N.B. one of the many benefits of using redlock is the using() interface - // will automatically extend the lock if these operations exceed the - // original redis timeout time - await refreshMetadata() - await maybeQueueMetadataItems(modelManager) - - if (signal.aborted) { - // Not certain which conditions this would happen, it would - // happen after operations took place, so nothing much to do here. - console.log('embedder: lock was lost!') - } - }) - .catch((err: string) => { - // Handle errors (including lock acquisition errors) - console.error('embedder: an error occurred ', err) - }) - console.log('embedder: index queue lock released') - - // get the highest priority item and embed it - await dequeueAndEmbedUntilEmpty(modelManager) - - setTimeout(() => tick(modelManager), POLLING_PERIOD_SEC * 1000) -} - -function parseEnvBoolean(envVarValue: string | undefined): boolean { - return envVarValue === 'true' -} -const run = async () => { - console.log(`embedder: run()`) - const embedderEnabled = parseEnvBoolean(AI_EMBEDDER_ENABLED) - const modelManager = getModelManager() - if (embedderEnabled && modelManager) { - const pg = getKysely() - await modelManager.maybeCreateTables(pg) - console.log(`\n⚡⚡⚡️️ Server ID: ${SERVER_ID}. Embedder is ready ⚡⚡⚡️️️`) - tick(modelManager) - } else { - console.log(`embedder: no valid configuration (check AI_EMBEDDER_ENABLED in .env)`) - // exit - } + // On graceful shutdown + Logger.log('Streaming Complete. Goodbye!') } run() diff --git a/packages/embedder/establishPrimaryEmbedder.ts b/packages/embedder/establishPrimaryEmbedder.ts new file mode 100644 index 00000000000..72f349d655f --- /dev/null +++ b/packages/embedder/establishPrimaryEmbedder.ts @@ -0,0 +1,17 @@ +import ms from 'ms' +import RedisInstance from 'parabol-server/utils/RedisInstance' +import Redlock from 'redlock' + +export const establishPrimaryEmbedder = async (redis: RedisInstance) => { + const redlock = new Redlock([redis], {retryCount: 0}) + const MAX_TIME_BETWEEN_WORKER_STARTUPS = ms('5s') + try { + const primaryWorkerLock = await redlock.acquire( + [`embedder_isPrimary_${process.env.npm_package_version}`], + MAX_TIME_BETWEEN_WORKER_STARTUPS + ) + return primaryWorkerLock + } catch { + return undefined + } +} diff --git a/packages/embedder/importHistoricalMetadata.ts b/packages/embedder/importHistoricalMetadata.ts new file mode 100644 index 00000000000..0b805f18888 --- /dev/null +++ b/packages/embedder/importHistoricalMetadata.ts @@ -0,0 +1,16 @@ +import {EmbeddingObjectType} from './custom' +import {importHistoricalRetrospectiveDiscussionTopic} from './importHistoricalRetrospectiveDiscussionTopic' + +export const importHistoricalMetadata = async () => { + const OBJECT_TYPES: EmbeddingObjectType[] = ['retrospectiveDiscussionTopic'] + return Promise.all( + OBJECT_TYPES.map(async (objectType) => { + switch (objectType) { + case 'retrospectiveDiscussionTopic': + return importHistoricalRetrospectiveDiscussionTopic() + default: + throw new Error(`Invalid object type: ${objectType}`) + } + }) + ) +} diff --git a/packages/embedder/importHistoricalRetrospectiveDiscussionTopic.ts b/packages/embedder/importHistoricalRetrospectiveDiscussionTopic.ts new file mode 100644 index 00000000000..2469327a18a --- /dev/null +++ b/packages/embedder/importHistoricalRetrospectiveDiscussionTopic.ts @@ -0,0 +1,37 @@ +import getKysely from 'parabol-server/postgres/getKysely' +import {Logger} from 'parabol-server/utils/Logger' +import {addEmbeddingsMetadataForRetrospectiveDiscussionTopic} from './addEmbeddingsMetadataForRetrospectiveDiscussionTopic' + +// Check to see if the oldest discussion topic exists in the metadata table +// If not, get the date of the oldest discussion topic in the metadata table and import all items before that date +export const importHistoricalRetrospectiveDiscussionTopic = async () => { + const pg = getKysely() + const isEarliestMetadataImported = await pg + .selectFrom('EmbeddingsMetadata') + .select('id') + .where(({eb, selectFrom}) => + eb( + 'EmbeddingsMetadata.refId', + '=', + selectFrom('Discussion') + .select('Discussion.id') + .where('discussionTopicType', '=', 'reflectionGroup') + .orderBy(['createdAt', 'id']) + .limit(1) + ) + ) + .limit(1) + .executeTakeFirst() + + if (isEarliestMetadataImported) return + const earliestImportedDiscussion = await pg + .selectFrom('EmbeddingsMetadata') + .select(['id', 'refUpdatedAt', 'refId']) + .where('objectType', '=', 'retrospectiveDiscussionTopic') + .orderBy('refUpdatedAt') + .limit(1) + .executeTakeFirst() + const endAt = earliestImportedDiscussion?.refUpdatedAt ?? undefined + Logger.log(`Importing discussion history up to ${endAt || 'now'}`) + return addEmbeddingsMetadataForRetrospectiveDiscussionTopic({endAt}) +} diff --git a/packages/embedder/indexing/countWords.ts b/packages/embedder/indexing/countWords.ts deleted file mode 100644 index 75dae3effa2..00000000000 --- a/packages/embedder/indexing/countWords.ts +++ /dev/null @@ -1,17 +0,0 @@ -export function countWords(text: string) { - let count = 0 - let inWord = false - - for (const char of text) { - if (/\w/.test(char)) { - if (!inWord) { - count++ - inWord = true - } - } else { - inWord = false - } - } - - return count -} diff --git a/packages/embedder/indexing/createEmbeddingTextFrom.ts b/packages/embedder/indexing/createEmbeddingTextFrom.ts index ce76c7fc380..9fb3cceda80 100644 --- a/packages/embedder/indexing/createEmbeddingTextFrom.ts +++ b/packages/embedder/indexing/createEmbeddingTextFrom.ts @@ -1,15 +1,17 @@ import {Selectable} from 'kysely' -import {DataLoaderWorker} from 'parabol-server/graphql/graphql' import {DB} from 'parabol-server/postgres/pg' -import {createText as createTextFromRetrospectiveDiscussionTopic} from './retrospectiveDiscussionTopic' +import RootDataLoader from 'parabol-server/dataloader/RootDataLoader' +import {createTextFromRetrospectiveDiscussionTopic} from './retrospectiveDiscussionTopic' export const createEmbeddingTextFrom = async ( - item: Selectable, - dataLoader: DataLoaderWorker -): Promise => { - switch ((item as any).objectType) { + embeddingsMetadata: Selectable, + dataLoader: RootDataLoader +) => { + switch (embeddingsMetadata.objectType) { case 'retrospectiveDiscussionTopic': - return createTextFromRetrospectiveDiscussionTopic(item, dataLoader) + return createTextFromRetrospectiveDiscussionTopic(embeddingsMetadata.refId, dataLoader) + default: + throw new Error(`Unexcepted objectType: ${embeddingsMetadata.objectType}`) } } diff --git a/packages/embedder/indexing/embeddingsTablesOps.ts b/packages/embedder/indexing/embeddingsTablesOps.ts deleted file mode 100644 index 4ce843248a9..00000000000 --- a/packages/embedder/indexing/embeddingsTablesOps.ts +++ /dev/null @@ -1,201 +0,0 @@ -import {Insertable, RawBuilder, Selectable, Updateable, sql} from 'kysely' -import getKysely from 'parabol-server/postgres/getKysely' -import {DB} from 'parabol-server/postgres/pg' -import {DBInsert} from '../embedder' -import numberVectorToString from './numberVectorToString' - -function unnestedArray(maybeArray: T[] | T): RawBuilder { - let a: T[] = Array.isArray(maybeArray) ? maybeArray : [maybeArray] - return sql`unnest(ARRAY[${sql.join(a)}]::varchar[])` -} - -export const selectJobQueueItemById = async ( - id: number -): Promise | undefined> => { - const pg = getKysely() - return pg.selectFrom('EmbeddingsJobQueue').selectAll().where('id', '=', id).executeTakeFirst() -} -export const selectMetadataByJobQueueId = async ( - id: number -): Promise | undefined> => { - const pg = getKysely() - return pg - .selectFrom('EmbeddingsMetadata as em') - .selectAll() - .leftJoin('EmbeddingsJobQueue as ejq', (join) => - join - .onRef('em.objectType', '=', 'ejq.objectType' as any) - .onRef('em.refId', '=', 'ejq.refId' as any) - ) - .where('ejq.id', '=', id) - .executeTakeFirstOrThrow() -} - -// For each configured embedding model, select rows from EmbeddingsMetadata -// that haven't been calculated nor exist in the EmbeddingsJobQueue yet -// -// Notes: -// * `em.models @> ARRAY[v.model]` is an indexed query -// * I don't love all overrides, I wish there was a better way -// see: https://github.com/kysely-org/kysely/issues/872 -export async function selectMetaToQueue( - configuredModels: string[], - orgIds: any[], - itemCountToQueue: number -) { - const pg = getKysely() - const maybeMetaToQueue = (await pg - .selectFrom('EmbeddingsMetadata as em') - .selectAll('em') - .leftJoinLateral(unnestedArray(configuredModels).as('model'), (join) => join.onTrue()) - .leftJoin('Team as t', 'em.teamId', 't.id') - .select('model' as any) - .where(({eb, not, or, and, exists, selectFrom}) => - and([ - or([ - not( - eb('em.models' as any, '@>', sql`ARRAY[${sql.ref('model')}]::varchar[]` as any) as any - ), - eb('em.models' as any, 'is', null) - ]), - not( - exists( - selectFrom('EmbeddingsJobQueue as ejq') - .select('ejq.id') - .whereRef('em.objectType', '=', 'ejq.objectType' as any) - .whereRef('em.refId', '=', 'ejq.refId' as any) - .whereRef('ejq.model' as any, '=', 'model' as any) - ) - ), - eb('t.orgId', 'in', orgIds) - ]) - ) - .limit(itemCountToQueue) - .execute()) as unknown as Selectable[] - - type MetadataToQueue = Selectable< - Omit & { - refId: NonNullable - } & {model: string} - > - - return maybeMetaToQueue.filter( - (item) => item.refId !== null && item.refId !== undefined - ) as MetadataToQueue[] -} - -export const updateJobState = async ( - id: number, - state: Updateable['state'], - jobQueueFields: Updateable = {} -) => { - const pg = getKysely() - const jobQueueColumns: Updateable = { - ...jobQueueFields, - state - } - if (state === 'failed') console.log(`embedder: failed job ${id}, ${jobQueueFields.stateMessage}`) - return pg - .updateTable('EmbeddingsJobQueue') - .set(jobQueueColumns) - .where('id', '=', id) - .executeTakeFirstOrThrow() -} - -export function insertNewJobs(ejqValues: Insertable[]) { - const pg = getKysely() - return pg - .insertInto('EmbeddingsJobQueue') - .values(ejqValues) - .returning(['id', 'objectType', 'refId'] as any) - .execute() -} - -// complete job, do the following atomically -// (1) update EmbeddingsMetadata to reflect model completion -// (2) upsert model table row with embedding -// (3) delete EmbeddingsJobQueue row -export function completeJobTxn( - modelTable: string, - jobQueueId: number, - metadata: Updateable, - fullText: string, - embedText: string, - embeddingVector: number[] -) { - const pg = getKysely() - return pg.transaction().execute(async (trx) => { - // get fields to update correct metadata row - const jobQueueItem = (await trx - .selectFrom('EmbeddingsJobQueue') - .select(['objectType', 'refId', 'model'] as any) - .where('id', '=', jobQueueId) - .executeTakeFirstOrThrow()) as any - - // (1) update metadata row - const metadataColumnsToUpdate: { - models: RawBuilder - fullText?: string | null | undefined - } = { - // update models as a set - models: sql`( -SELECT array_agg(DISTINCT value) -FROM ( - SELECT unnest(COALESCE("models", '{}')) AS value - UNION - SELECT unnest(ARRAY[${modelTable}]::VARCHAR[]) AS value -) AS combined_values -)` - } - - if (metadata?.fullText !== fullText) { - metadataColumnsToUpdate.fullText = fullText - } - - const updatedMetadata = await trx - .updateTable('EmbeddingsMetadata') - .set(metadataColumnsToUpdate) - .where('objectType', '=', jobQueueItem.objectType) - .where('refId', '=', jobQueueItem.refId) - .returning(['id']) - .executeTakeFirstOrThrow() - - // (2) upsert into model table - await trx - .insertInto(modelTable as any) - .values({ - embedText: fullText !== embedText ? embedText : null, - embedding: numberVectorToString(embeddingVector), - embeddingsMetadataId: updatedMetadata.id - }) - .onConflict((oc) => - oc.column('id').doUpdateSet((eb) => ({ - embedText: eb.ref('excluded.embedText'), - embeddingsMetadataId: eb.ref('excluded.embeddingsMetadataId') - })) - ) - .executeTakeFirstOrThrow() - - // (3) delete completed job queue item - return await trx - .deleteFrom('EmbeddingsJobQueue') - .where('id', '=', jobQueueId) - .executeTakeFirstOrThrow() - }) -} -export async function upsertEmbeddingsMetaRows( - embeddingsMetaRows: DBInsert['EmbeddingsMetadata'][] -) { - const pg = getKysely() - return pg - .insertInto('EmbeddingsMetadata') - .values(embeddingsMetaRows) - .onConflict((oc) => - oc.columns(['objectType', 'refId']).doUpdateSet((eb) => ({ - objectType: eb.ref('excluded.objectType'), - refId: eb.ref('excluded.refId'), - refUpdatedAt: eb.ref('excluded.refUpdatedAt') - })) - ) - .execute() -} diff --git a/packages/embedder/indexing/failJob.ts b/packages/embedder/indexing/failJob.ts new file mode 100644 index 00000000000..17d293b49a9 --- /dev/null +++ b/packages/embedder/indexing/failJob.ts @@ -0,0 +1,17 @@ +import getKysely from 'parabol-server/postgres/getKysely' +import {Logger} from 'parabol-server/utils/Logger' + +export const failJob = async (jobId: number, stateMessage: string, retryAfter?: Date | null) => { + const pg = getKysely() + Logger.log(`embedder: failed job ${jobId}, ${stateMessage}`) + await pg + .updateTable('EmbeddingsJobQueue') + .set((eb) => ({ + state: 'failed', + stateMessage, + retryCount: eb('retryCount', '+', 1), + retryAfter: retryAfter || null + })) + .where('id', '=', jobId) + .executeTakeFirstOrThrow() +} diff --git a/packages/embedder/indexing/getRedisClient.ts b/packages/embedder/indexing/getRedisClient.ts deleted file mode 100644 index 7aaf65be33c..00000000000 --- a/packages/embedder/indexing/getRedisClient.ts +++ /dev/null @@ -1,11 +0,0 @@ -import RedisInstance from 'parabol-server/utils/RedisInstance' - -const {SERVER_ID} = process.env - -let redisClient: RedisInstance -export const getRedisClient = () => { - if (!redisClient) { - redisClient = new RedisInstance(`embedder-${SERVER_ID}`) - } - return redisClient -} diff --git a/packages/embedder/indexing/getRootDataLoader.ts b/packages/embedder/indexing/getRootDataLoader.ts deleted file mode 100644 index 304c0c01058..00000000000 --- a/packages/embedder/indexing/getRootDataLoader.ts +++ /dev/null @@ -1,10 +0,0 @@ -import getDataLoader from 'parabol-server/graphql/getDataLoader' -import {DataLoaderWorker} from 'parabol-server/graphql/graphql' - -let rootDataLoader: DataLoaderWorker -export const getRootDataLoader = () => { - if (!rootDataLoader) { - rootDataLoader = getDataLoader() as DataLoaderWorker - } - return rootDataLoader -} diff --git a/packages/embedder/indexing/retrospectiveDiscussionTopic.ts b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts index eb28b1bc3b0..4dc0bfaddd5 100644 --- a/packages/embedder/indexing/retrospectiveDiscussionTopic.ts +++ b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts @@ -1,25 +1,8 @@ -import prettier from 'prettier' - -import getRethink, {RethinkSchema} from 'parabol-server/database/rethinkDriver' -import {DataLoaderWorker} from 'parabol-server/graphql/graphql' -import getKysely from 'parabol-server/postgres/getKysely' -import {DB} from 'parabol-server/postgres/pg' - +import {RethinkSchema} from 'parabol-server/database/rethinkDriver' import Comment from 'parabol-server/database/types/Comment' -import DiscussStage from 'parabol-server/database/types/DiscussStage' -import MeetingRetrospective, { - isMeetingRetrospective -} from 'parabol-server/database/types/MeetingRetrospective' - -import {AnyMeeting} from 'parabol-server/postgres/types/Meeting' -import {upsertEmbeddingsMetaRows} from './embeddingsTablesOps' - -const BATCH_SIZE = 1000 - -export interface EmbeddingsJobQueueRetrospectiveDiscussionTopic - extends Omit { - objectType: 'retrospectiveDiscussionTopic' -} +import {isMeetingRetrospective} from 'parabol-server/database/types/MeetingRetrospective' +import RootDataLoader from 'parabol-server/dataloader/RootDataLoader' +import prettier from 'prettier' // Here's a generic reprentation of the text generated here: @@ -38,109 +21,14 @@ export interface EmbeddingsJobQueueRetrospectiveDiscussionTopic const IGNORE_COMMENT_USER_IDS = ['parabolAIUser'] -const pg = getKysely() - -export async function refreshRetroDiscussionTopicsMeta(dataLoader: DataLoaderWorker) { - const r = await getRethink() - const {createdAt: newestMeetingDate} = (await r - .table('NewMeeting') - .max({index: 'createdAt'}) - .run()) as unknown as RethinkSchema['NewMeeting']['type'] - const {createdAt: oldestMeetingDate} = (await r - .table('NewMeeting') - .min({index: 'createdAt'}) - .run()) as unknown as RethinkSchema['NewMeeting']['type'] - - const {newestMetaDate} = (await pg - .selectFrom('EmbeddingsMetadata') - .select(pg.fn.max('refUpdatedAt').as('newestMetaDate')) - .where('objectType', '=', 'retrospectiveDiscussionTopic') - .executeTakeFirst()) ?? {newestMetaDate: null} - let startDateTime = newestMetaDate || oldestMeetingDate - - if (startDateTime.getTime() === newestMeetingDate.getTime()) return - - console.log( - `refreshRetroDiscussionTopicsMeta(): ` + - `will consider adding items from ${startDateTime.toISOString()} to ` + - `${newestMeetingDate.toISOString()}` - ) - - let totalAdded = 0 - do { - // Process history in batches. - // - // N.B. We add historical meetings to the EmbeddingsMetadata table here. - // This query will intentionally miss meetings that haven't been completed - // (`summarySentAt` is null). These meetings will need to be added to the - // EmbeddingsMetadata table by a hook that runs when the meetings complete. - const {maxCreatedAt, completedNewMeetings} = await r - .table('NewMeeting') - .between(startDateTime, newestMeetingDate, {rightBound: 'closed', index: 'createdAt'}) - .orderBy({index: 'createdAt'}) - .limit(BATCH_SIZE) - .coerceTo('array') - .do((rows: any) => ({ - maxCreatedAt: r.expr(rows).max('createdAt')('createdAt'), // Then find the max createdAt value - completedNewMeetings: r.expr(rows).filter((r: any) => - r('meetingType') - .eq('retrospective') - .and( - r('endedAt').gt(0), - r - .hasFields('phases') - .and(r('phases').count().gt(0)) - .and( - r('phases') - .filter((phase: any) => phase('phaseType').eq('discuss')) - .filter((phase: any) => - phase.hasFields('stages').and(phase('stages').count().gt(0)) - ) - .count() - .gt(0) - ) - ) - ) - })) - .run() - const embeddingsMetaRows = ( - await Promise.all( - completedNewMeetings.map((m: AnyMeeting) => - newRetroDiscussionTopicsFromNewMeeting(m, dataLoader) - ) - ) - ).flat() - if (embeddingsMetaRows.length > 0) { - await upsertEmbeddingsMetaRows(embeddingsMetaRows) - totalAdded += embeddingsMetaRows.length - console.log( - `refreshRetroDiscussionTopicsMeta(): synced to ${maxCreatedAt.toISOString()}, added` + - ` ${embeddingsMetaRows.length} retrospectiveDiscussionTopics` - ) - } - - // N.B. In the unlikely event that we have >=BATCH_SIZE meetings that end at _exactly_ - // the same timetsamp, this will loop forever. - if ( - startDateTime.getTime() === newestMeetingDate.getTime() && - completedNewMeetings.length < BATCH_SIZE - ) - break - startDateTime = maxCreatedAt - } while (true) - - console.log( - `refreshRetroDiscussionTopicsMeta(): added ${totalAdded} total retrospectiveDiscussionTopics` - ) -} - -async function getPreferredNameByUserId(userId: string, dataLoader: DataLoaderWorker) { +async function getPreferredNameByUserId(userId: string, dataLoader: RootDataLoader) { + if (!userId) return 'Unknown' const user = await dataLoader.get('users').load(userId) return !user ? 'Unknown' : user.preferredName } async function formatThread( - dataLoader: DataLoaderWorker, + dataLoader: RootDataLoader, comments: Comment[], parentId: string | null = null, depth = 0 @@ -155,9 +43,7 @@ async function formatThread( const indent = ' '.repeat(depth + 1) const author = comment.isAnonymous ? 'Anonymous' - : comment.createdBy - ? await getPreferredNameByUserId(comment.createdBy, dataLoader) - : 'Unknown' + : await getPreferredNameByUserId(comment.createdBy, dataLoader) const how = depth === 0 ? 'wrote' : 'replied' const content = comment.plaintextContent const formattedPost = `${indent}- ${author} ${how}, "${content}"\n` @@ -172,60 +58,47 @@ async function formatThread( return formattedComments.join('') } -export const createTextFromNewMeetingDiscussionStage = async ( - newMeeting: MeetingRetrospective, - stageId: string, - dataLoader: DataLoaderWorker, +export const createTextFromRetrospectiveDiscussionTopic = async ( + discussionId: string, + dataLoader: RootDataLoader, textForReranking: boolean = false ) => { - if (!newMeeting) throw 'newMeeting is undefined' - if (!isMeetingRetrospective(newMeeting)) throw 'newMeeting is not retrospective' - if (!newMeeting.templateId) throw 'template is undefined' - const template = await dataLoader.get('meetingTemplates').load(newMeeting.templateId) - if (!template) throw 'template is undefined' - const discussPhase = newMeeting.phases.find((phase) => phase.phaseType === 'discuss') - if (!discussPhase) throw 'newMeeting discuss phase is undefined' - if (!discussPhase.stages) throw 'newMeeting discuss phase has no stages' - const discussStage = discussPhase.stages.find((stage) => stage.id === stageId) as DiscussStage - if (!discussStage) throw 'newMeeting discuss stage not found' - const {summary: discussionSummary} = discussStage.discussionId - ? (await dataLoader.get('discussions').load(discussStage.discussionId)) ?? {summary: null} - : {summary: null} - const r = await getRethink() - if (!discussStage.reflectionGroupId) throw 'newMeeting discuss stage has no reflectionGroupId' - const reflectionGroup = await r - .table('RetroReflectionGroup') - .get(discussStage.reflectionGroupId) - .run() - if (!reflectionGroup.id) throw 'newMeeting reflectionGroup has no id' - const reflections = await r - .table('RetroReflection') - .getAll(reflectionGroup.id, {index: 'reflectionGroupId'}) - .run() + const discussion = await dataLoader.get('discussions').load(discussionId) + if (!discussion) throw new Error(`Discussion not found: ${discussionId}`) + const {discussionTopicId: reflectionGroupId, meetingId, summary: discussionSummary} = discussion + const [newMeeting, reflectionGroup, reflections] = await Promise.all([ + dataLoader.get('newMeetings').load(meetingId), + dataLoader.get('retroReflectionGroups').load(reflectionGroupId), + dataLoader.get('retroReflectionsByGroupId').load(reflectionGroupId) + ]) + if (!isMeetingRetrospective(newMeeting)) throw new Error('Meeting is not a retro') + const {templateId} = newMeeting + const promptIds = [...new Set(reflections.map((r) => r.promptId))] + const [template, ...prompts] = await Promise.all([ + dataLoader.get('meetingTemplates').loadNonNull(templateId), + ...promptIds.map((promptId) => dataLoader.get('reflectPrompts').load(promptId)) + ]) + let markdown = '' - if (!textForReranking) + if (!textForReranking) { markdown = - `A topic "${reflectionGroup.title}" was discussed during ` + + `A topic "${reflectionGroup?.title ?? ''}" was discussed during ` + `the meeting "${newMeeting.name}" that followed the "${template.name}" template.\n` + `\n` - const prompts = await dataLoader.get('reflectPrompts').loadMany(promptIds) + } + for (const prompt of prompts) { - if (!prompt || prompt instanceof Error) continue if (!textForReranking) { markdown += `Participants were prompted with, "${prompt.question}` if (prompt.description) markdown += `: ${prompt.description}` markdown += `".\n` } - if (newMeeting.disableAnonymity) { - for (const reflection of reflections.filter((r) => r.promptId === prompt.id)) { - const author = await getPreferredNameByUserId(reflection.creatorId, dataLoader) - markdown += ` - ${author} wrote, "${reflection.plaintextContent}"\n` - } - } else { - for (const reflection of reflections.filter((r) => r.promptId === prompt.id)) { - markdown += ` - Anonymous wrote, "${reflection.plaintextContent}"\n` - } + for (const reflection of reflections.filter((r) => r.promptId === prompt.id)) { + const author = newMeeting.disableAnonymity + ? await getPreferredNameByUserId(reflection.creatorId, dataLoader) + : 'Anonymous' + markdown += ` - ${author} wrote, "${reflection.plaintextContent}"\n` } markdown += `\n` } @@ -250,7 +123,7 @@ export const createTextFromNewMeetingDiscussionStage = async ( if (discussionSummary) { markdown += `Further discussion was made. ` + ` ${discussionSummary}` } else { - const comments = await dataLoader.get('commentsByDiscussionId').load(stageId) + const comments = await dataLoader.get('commentsByDiscussionId').load(discussionId) const sortedComments = comments .map((comment) => { @@ -290,22 +163,9 @@ export const createTextFromNewMeetingDiscussionStage = async ( return markdown } -export const createText = async (item: any, dataLoader: DataLoaderWorker): Promise => { - if (!item.refId) throw 'refId is undefined' - const [newMeetingId, discussionId] = item.refId.split(':') - if (!newMeetingId) throw new Error('newMeetingId cannot be undefined') - if (!discussionId) throw new Error('discussionId cannot be undefined') - const newMeeting = await dataLoader.get('newMeetings').load(newMeetingId) - return createTextFromNewMeetingDiscussionStage( - newMeeting as MeetingRetrospective, - discussionId, - dataLoader - ) -} - export const newRetroDiscussionTopicsFromNewMeeting = async ( newMeeting: RethinkSchema['NewMeeting']['type'], - dataLoader: DataLoaderWorker + dataLoader: RootDataLoader ) => { const discussPhase = newMeeting.phases.find((phase) => phase.phaseType === 'discuss') const orgId = (await dataLoader.get('teams').load(newMeeting.teamId))?.orgId diff --git a/packages/embedder/iso6393To1.ts b/packages/embedder/iso6393To1.ts new file mode 100644 index 00000000000..f1e470e1232 --- /dev/null +++ b/packages/embedder/iso6393To1.ts @@ -0,0 +1,195 @@ +import {ValueOf} from '../client/types/generics' + +/** + * Map of ISO 639-3 codes to ISO 639-1 codes. + * + * @type {Record} + */ +export const iso6393To1 = { + aar: 'aa', + abk: 'ab', + afr: 'af', + aka: 'ak', + amh: 'am', + ara: 'ar', + arg: 'an', + asm: 'as', + ava: 'av', + ave: 'ae', + aym: 'ay', + aze: 'az', + bak: 'ba', + bam: 'bm', + bel: 'be', + ben: 'bn', + bis: 'bi', + bod: 'bo', + bos: 'bs', + bre: 'br', + bul: 'bg', + cat: 'ca', + ces: 'cs', + cha: 'ch', + che: 'ce', + chu: 'cu', + chv: 'cv', + cor: 'kw', + cos: 'co', + cre: 'cr', + cym: 'cy', + dan: 'da', + deu: 'de', + div: 'dv', + dzo: 'dz', + ell: 'el', + eng: 'en', + epo: 'eo', + est: 'et', + eus: 'eu', + ewe: 'ee', + fao: 'fo', + fas: 'fa', + fij: 'fj', + fin: 'fi', + fra: 'fr', + fry: 'fy', + ful: 'ff', + gla: 'gd', + gle: 'ga', + glg: 'gl', + glv: 'gv', + grn: 'gn', + guj: 'gu', + hat: 'ht', + hau: 'ha', + hbs: 'sh', + heb: 'he', + her: 'hz', + hin: 'hi', + hmo: 'ho', + hrv: 'hr', + hun: 'hu', + hye: 'hy', + ibo: 'ig', + ido: 'io', + iii: 'ii', + iku: 'iu', + ile: 'ie', + ina: 'ia', + ind: 'id', + ipk: 'ik', + isl: 'is', + ita: 'it', + jav: 'jv', + jpn: 'ja', + kal: 'kl', + kan: 'kn', + kas: 'ks', + kat: 'ka', + kau: 'kr', + kaz: 'kk', + khm: 'km', + kik: 'ki', + kin: 'rw', + kir: 'ky', + kom: 'kv', + kon: 'kg', + kor: 'ko', + kua: 'kj', + kur: 'ku', + lao: 'lo', + lat: 'la', + lav: 'lv', + lim: 'li', + lin: 'ln', + lit: 'lt', + ltz: 'lb', + lub: 'lu', + lug: 'lg', + mah: 'mh', + mal: 'ml', + mar: 'mr', + mkd: 'mk', + mlg: 'mg', + mlt: 'mt', + mon: 'mn', + mri: 'mi', + msa: 'ms', + mya: 'my', + nau: 'na', + nav: 'nv', + nbl: 'nr', + nde: 'nd', + ndo: 'ng', + nep: 'ne', + nld: 'nl', + nno: 'nn', + nob: 'nb', + nor: 'no', + nya: 'ny', + oci: 'oc', + oji: 'oj', + ori: 'or', + orm: 'om', + oss: 'os', + pan: 'pa', + pli: 'pi', + pol: 'pl', + por: 'pt', + pus: 'ps', + que: 'qu', + roh: 'rm', + ron: 'ro', + run: 'rn', + rus: 'ru', + sag: 'sg', + san: 'sa', + sin: 'si', + slk: 'sk', + slv: 'sl', + sme: 'se', + smo: 'sm', + sna: 'sn', + snd: 'sd', + som: 'so', + sot: 'st', + spa: 'es', + sqi: 'sq', + srd: 'sc', + srp: 'sr', + ssw: 'ss', + sun: 'su', + swa: 'sw', + swe: 'sv', + tah: 'ty', + tam: 'ta', + tat: 'tt', + tel: 'te', + tgk: 'tg', + tgl: 'tl', + tha: 'th', + tir: 'ti', + ton: 'to', + tsn: 'tn', + tso: 'ts', + tuk: 'tk', + tur: 'tr', + twi: 'tw', + uig: 'ug', + ukr: 'uk', + urd: 'ur', + uzb: 'uz', + ven: 've', + vie: 'vi', + vol: 'vo', + wln: 'wa', + wol: 'wo', + xho: 'xh', + yid: 'yi', + yor: 'yo', + zha: 'za', + zho: 'zh', + zul: 'zu' +} as const + +export type ISO6391 = ValueOf diff --git a/packages/embedder/logMemoryUse.ts b/packages/embedder/logMemoryUse.ts new file mode 100644 index 00000000000..afe3259aee5 --- /dev/null +++ b/packages/embedder/logMemoryUse.ts @@ -0,0 +1,10 @@ +// Not for use in prod, but useful for dev +export const logMemoryUse = () => { + const MB = 2 ** 20 + setInterval(() => { + const memoryUsage = process.memoryUsage() + const {rss} = memoryUsage + const usedMB = Math.floor(rss / MB) + console.log('Memory use:', usedMB, 'MB') + }, 10000) +} diff --git a/packages/embedder/mergeAsyncIterators.ts b/packages/embedder/mergeAsyncIterators.ts new file mode 100644 index 00000000000..e274e0cca6f --- /dev/null +++ b/packages/embedder/mergeAsyncIterators.ts @@ -0,0 +1,96 @@ +import {ParseInt} from '../client/types/generics' + +// can remove PromiseCapability after TS v5.4.2 +type PromiseCapability = { + resolve: (value: T) => void + reject: (reason?: any) => void + promise: Promise +} + +type UnYield = T extends IteratorYieldResult ? U : never +type Result> = UnYield>> + +// Promise.race has a memory leak +// To avoid: https://github.com/tc39/proposal-async-iterator-helpers/issues/15#issuecomment-1937011820 +export function mergeAsyncIterators[] | []>( + iterators: T +): AsyncIterableIterator<{[P in keyof T]: [ParseInt<`${P}`>, Result]}[number]> { + return (async function* () { + type ResultThunk = () => [number, Result] + let count = iterators.length as number + let capability: PromiseCapability | undefined + const queuedResults: ResultThunk[] = [] + const getNext = async (idx: number, iterator: T[number]) => { + try { + const next = await iterator.next() + if (next.done) { + if (--count === 0 && capability !== undefined) { + capability.resolve(null) + } + } else { + resolveResult(() => { + void getNext(idx, iterator) + return [idx, next.value] + }) + } + } catch (error) { + resolveResult(() => { + throw error + }) + } + } + const resolveResult = (resultThunk: ResultThunk) => { + if (capability === undefined) { + queuedResults.push(resultThunk) + } else { + capability.resolve(resultThunk) + } + } + + try { + // Begin all iterators + for (const [idx, iterable] of iterators.entries()) { + void getNext(idx, iterable) + } + + // Delegate to iterables as results complete + while (true) { + while (true) { + const nextQueuedResult = queuedResults.shift() + if (nextQueuedResult === undefined) { + break + } else { + yield nextQueuedResult() + } + } + if (count === 0) { + break + } else { + // Promise.withResolvers() is not yet implemented in node + capability = { + resolve: undefined as any, + reject: undefined as any, + promise: undefined as any + } + capability.promise = new Promise((res, rej) => { + capability!.resolve = res + capability!.reject = rej + }) + const nextResult = await capability.promise + if (nextResult === null) { + break + } else { + capability = undefined + yield nextResult() + } + } + } + } catch (err) { + // Unwind remaining iterators on failure + try { + await Promise.all(iterators.map((iterator) => iterator.return?.())) + } catch {} + throw err + } + })() +} diff --git a/packages/embedder/modules.d.ts b/packages/embedder/modules.d.ts deleted file mode 100644 index 8a2be20ba0c..00000000000 --- a/packages/embedder/modules.d.ts +++ /dev/null @@ -1,2 +0,0 @@ -import '../server/types/modules' -import '../server/types/webpackEnv' diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 47af8737dac..fdf98991ced 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -18,14 +18,17 @@ "devDependencies": { "@babel/cli": "7.18.6", "@babel/core": "7.18.6", + "@types/franc": "^5.0.3", "@types/node": "^16.11.62", "babel-plugin-inline-import": "^3.0.0", + "openapi-fetch": "^0.9.3", "sucrase": "^3.32.0", "ts-node-dev": "^1.0.0-pre.44", - "typescript": "4.9.5" + "typescript": "^5.3.3" }, "dependencies": { "dd-trace": "^4.2.0", + "franc-min": "^5.0.0", "redlock": "^5.0.0-beta.2" } } diff --git a/packages/embedder/processJob.ts b/packages/embedder/processJob.ts new file mode 100644 index 00000000000..8723b75de6a --- /dev/null +++ b/packages/embedder/processJob.ts @@ -0,0 +1,13 @@ +import RootDataLoader from 'parabol-server/dataloader/RootDataLoader' +import {Job} from './EmbeddingsJobQueueStream' +import {processJobEmbed} from './processJobEmbed' + +export const processJob = async (job: Job, dataLoader: RootDataLoader) => { + const {jobType} = job + switch (jobType) { + case 'embed': + return processJobEmbed(job, dataLoader) + default: + throw new Error(`Invalid job type: ${jobType}`) + } +} diff --git a/packages/embedder/processJobEmbed.ts b/packages/embedder/processJobEmbed.ts new file mode 100644 index 00000000000..d1b895cc640 --- /dev/null +++ b/packages/embedder/processJobEmbed.ts @@ -0,0 +1,102 @@ +import franc from 'franc-min' +import ms from 'ms' +import RootDataLoader from 'parabol-server/dataloader/RootDataLoader' +import getKysely from 'parabol-server/postgres/getKysely' +import {EmbedJob} from './EmbeddingsJobQueueStream' +import {EmbeddingsTable} from './ai_models/AbstractEmbeddingsModel' +import getModelManager from './ai_models/ModelManager' +import {createEmbeddingTextFrom} from './indexing/createEmbeddingTextFrom' +import {failJob} from './indexing/failJob' +import numberVectorToString from './indexing/numberVectorToString' +import {iso6393To1} from './iso6393To1' + +export const processJobEmbed = async (job: EmbedJob, dataLoader: RootDataLoader) => { + const pg = getKysely() + const {id: jobId, retryCount, jobData} = job + const {embeddingsMetadataId, model} = jobData + const modelManager = getModelManager() + + const metadata = await pg + .selectFrom('EmbeddingsMetadata') + .selectAll() + .where('id', '=', embeddingsMetadataId) + .executeTakeFirst() + + if (!metadata) { + await failJob(jobId, `unable to fetch metadata by EmbeddingsJobQueue.id = ${jobId}`) + return + } + + let {fullText, language} = metadata + try { + if (!fullText) { + fullText = await createEmbeddingTextFrom(metadata, dataLoader) + language = iso6393To1[franc(fullText) as keyof typeof iso6393To1] + await pg + .updateTable('EmbeddingsMetadata') + .set({fullText, language}) + .where('id', '=', embeddingsMetadataId) + .execute() + } + } catch (e) { + // get the trace since the error message may be unobvious + console.trace(e) + await failJob(jobId, `unable to create embedding text: ${e}`) + return + } + + const embeddingModel = modelManager.embeddingModelsMapByTable[model] + if (!embeddingModel) { + await failJob(jobId, `embedding model ${model} not available`) + return + } + + // Exit successfully, we don't want to fail the job because the language is not supported + if (!embeddingModel.languages.includes(language!)) return true + + const tokens = await embeddingModel.getTokens(fullText) + if (tokens instanceof Error) { + await failJob( + jobId, + `unable to get tokens: ${tokens.message}`, + retryCount < 10 ? new Date(Date.now() + ms('1m')) : null + ) + return + } + const isFullTextTooBig = tokens.length > embeddingModel.maxInputTokens + // Cannot use summarization strategy if generation model has same context length as embedding model + // We must split the text & not tokens because BERT tokenizer is not trained for linebreaks e.g. \n\n + const chunks = isFullTextTooBig ? embeddingModel.splitText(fullText) : [fullText] + await Promise.all( + chunks.map(async (chunk, chunkNumber) => { + const embeddingVector = await embeddingModel.getEmbedding(chunk) + if (embeddingVector instanceof Error) { + await failJob( + jobId, + `unable to get embeddings: ${embeddingVector.message}`, + retryCount < 10 ? new Date(Date.now() + ms('1m')) : null + ) + return + } + await pg + // cast to any because these types won't be available in CI + .insertInto(embeddingModel.tableName as EmbeddingsTable) + .values({ + // TODO is the extra space of a null embedText really worth it?! + embedText: isFullTextTooBig ? chunk : null, + embedding: numberVectorToString(embeddingVector), + embeddingsMetadataId, + chunkNumber: isFullTextTooBig ? chunkNumber : null + }) + .onConflict((oc) => + oc.column('embeddingsMetadataId').doUpdateSet((eb) => ({ + embedText: eb.ref('excluded.embedText'), + embedding: eb.ref('excluded.embedding') + })) + ) + .execute() + }) + ) + // Logger.log(`Embedded ${embeddingsMetadataId} -> ${model}`) + return true +} diff --git a/packages/embedder/resetStalledJobs.ts b/packages/embedder/resetStalledJobs.ts new file mode 100644 index 00000000000..592b468faee --- /dev/null +++ b/packages/embedder/resetStalledJobs.ts @@ -0,0 +1,18 @@ +import ms from 'ms' +import getKysely from 'parabol-server/postgres/getKysely' + +export const resetStalledJobs = () => { + setInterval(async () => { + const pg = getKysely() + await pg + .updateTable('EmbeddingsJobQueue') + .set((eb) => ({ + state: 'queued', + startAt: null, + retryCount: eb('retryCount', '+', 1), + stateMessage: 'stalled' + })) + .where('startAt', '<', new Date(Date.now() - ms('5m'))) + .execute() + }, ms('5m')) +} diff --git a/packages/embedder/textEmbeddingsnterface.d.ts b/packages/embedder/textEmbeddingsnterface.d.ts new file mode 100644 index 00000000000..3fdbcac0185 --- /dev/null +++ b/packages/embedder/textEmbeddingsnterface.d.ts @@ -0,0 +1,857 @@ +/** + * This file was auto-generated by openapi-typescript. + * Do not make direct changes to the file. + */ + + +/** OneOf type helpers */ +type Without = { [P in Exclude]?: never }; +type XOR = (T | U) extends object ? (Without & U) | (Without & T) : T | U; +type OneOf = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR, ...Rest]> : never; + +export interface paths { + "/decode": { + /** + * Decode input ids + * @description Decode input ids + */ + post: operations["decode"]; + }; + "/embed": { + /** + * Get Embeddings. Returns a 424 status code if the model is not an embedding model. + * @description Get Embeddings. Returns a 424 status code if the model is not an embedding model. + */ + post: operations["embed"]; + }; + "/embed_all": { + /** + * Get all Embeddings without Pooling. + * @description Get all Embeddings without Pooling. + * Returns a 424 status code if the model is not an embedding model. + */ + post: operations["embed_all"]; + }; + "/embed_sparse": { + /** + * Get Sparse Embeddings. Returns a 424 status code if the model is not an embedding model with SPLADE pooling. + * @description Get Sparse Embeddings. Returns a 424 status code if the model is not an embedding model with SPLADE pooling. + */ + post: operations["embed_sparse"]; + }; + "/embeddings": { + /** + * OpenAI compatible route. Returns a 424 status code if the model is not an embedding model. + * @description OpenAI compatible route. Returns a 424 status code if the model is not an embedding model. + */ + post: operations["openai_embed"]; + }; + "/health": { + /** + * Health check method + * @description Health check method + */ + get: operations["health"]; + }; + "/info": { + /** + * Text Embeddings Inference endpoint info + * @description Text Embeddings Inference endpoint info + */ + get: operations["get_model_info"]; + }; + "/metrics": { + /** + * Prometheus metrics scrape endpoint + * @description Prometheus metrics scrape endpoint + */ + get: operations["metrics"]; + }; + "/predict": { + /** + * Get Predictions. Returns a 424 status code if the model is not a Sequence Classification model + * @description Get Predictions. Returns a 424 status code if the model is not a Sequence Classification model + */ + post: operations["predict"]; + }; + "/rerank": { + /** + * Get Ranks. Returns a 424 status code if the model is not a Sequence Classification model with + * @description Get Ranks. Returns a 424 status code if the model is not a Sequence Classification model with + * a single class. + */ + post: operations["rerank"]; + }; + "/tokenize": { + /** + * Tokenize inputs + * @description Tokenize inputs + */ + post: operations["tokenize"]; + }; + "/vertex": { + /** + * Generate embeddings from a Vertex request + * @description Generate embeddings from a Vertex request + */ + post: operations["vertex_compatibility"]; + }; +} + +export type webhooks = Record; + +export interface components { + schemas: { + ClassifierModel: { + /** + * @example { + * "0": "LABEL" + * } + */ + id2label: { + [key: string]: string; + }; + /** + * @example { + * "LABEL": 0 + * } + */ + label2id: { + [key: string]: number; + }; + }; + DecodeRequest: { + ids: components["schemas"]["InputIds"]; + /** + * @default true + * @example true + */ + skip_special_tokens?: boolean; + }; + /** + * @example [ + * "test" + * ] + */ + DecodeResponse: string[]; + EmbedAllRequest: { + inputs: components["schemas"]["Input"]; + /** + * @default false + * @example false + */ + truncate?: boolean; + }; + /** + * @example [ + * [ + * [ + * 0, + * 1, + * 2 + * ] + * ] + * ] + */ + EmbedAllResponse: number[][][]; + EmbedRequest: { + inputs: components["schemas"]["Input"]; + /** + * @default true + * @example true + */ + normalize?: boolean; + /** + * @default false + * @example false + */ + truncate?: boolean; + }; + /** + * @example [ + * [ + * 0, + * 1, + * 2 + * ] + * ] + */ + EmbedResponse: number[][]; + EmbedSparseRequest: { + inputs: components["schemas"]["Input"]; + /** + * @default false + * @example false + */ + truncate?: boolean; + }; + EmbedSparseResponse: components["schemas"]["SparseValue"][][]; + EmbeddingModel: { + /** @example cls */ + pooling: string; + }; + ErrorResponse: { + error: string; + error_type: components["schemas"]["ErrorType"]; + }; + /** @enum {string} */ + ErrorType: "Unhealthy" | "Backend" | "Overloaded" | "Validation" | "Tokenizer"; + Info: { + /** @example null */ + docker_label?: string | null; + /** + * @default null + * @example null + */ + max_batch_requests?: number | null; + /** @example 2048 */ + max_batch_tokens: number; + /** @example 32 */ + max_client_batch_size: number; + /** + * @description Router Parameters + * @example 128 + */ + max_concurrent_requests: number; + /** @example 512 */ + max_input_length: number; + /** @example float16 */ + model_dtype: string; + /** + * @description Model info + * @example thenlper/gte-base + */ + model_id: string; + /** @example fca14538aa9956a46526bd1d0d11d69e19b5a101 */ + model_sha?: string | null; + model_type: components["schemas"]["ModelType"]; + /** @example null */ + sha?: string | null; + /** @example 4 */ + tokenization_workers: number; + /** + * @description Router Info + * @example 0.5.0 + */ + version: string; + }; + Input: string | string[]; + InputIds: number[] | number[][]; + ModelType: OneOf<[{ + classifier: components["schemas"]["ClassifierModel"]; + }, { + embedding: components["schemas"]["EmbeddingModel"]; + }, { + reranker: components["schemas"]["ClassifierModel"]; + }]>; + OpenAICompatEmbedding: { + /** + * @example [ + * 0, + * 1, + * 2 + * ] + */ + embedding: number[]; + /** @example 0 */ + index: number; + /** @example embedding */ + object: string; + }; + OpenAICompatErrorResponse: { + /** Format: int32 */ + code: number; + error_type: components["schemas"]["ErrorType"]; + message: string; + }; + OpenAICompatRequest: { + input: components["schemas"]["Input"]; + /** @example null */ + model?: string | null; + /** @example null */ + user?: string | null; + }; + OpenAICompatResponse: { + data: components["schemas"]["OpenAICompatEmbedding"][]; + /** @example thenlper/gte-base */ + model: string; + /** @example list */ + object: string; + usage: components["schemas"]["OpenAICompatUsage"]; + }; + OpenAICompatUsage: { + /** @example 512 */ + prompt_tokens: number; + /** @example 512 */ + total_tokens: number; + }; + /** + * @description Model input. Can be either a single string, a pair of strings or a batch of mixed single and pairs of strings. + * @example What is Deep Learning? + */ + PredictInput: string | string[] | string[][]; + PredictRequest: { + inputs: components["schemas"]["PredictInput"]; + /** + * @default false + * @example false + */ + raw_scores?: boolean; + /** + * @default false + * @example false + */ + truncate?: boolean; + }; + PredictResponse: components["schemas"]["Prediction"][] | components["schemas"]["Prediction"][][]; + Prediction: { + /** @example admiration */ + label: string; + /** + * Format: float + * @example 0.5 + */ + score: number; + }; + Rank: { + /** @example 0 */ + index: number; + /** + * Format: float + * @example 1.0 + */ + score: number; + /** + * @default null + * @example Deep Learning is ... + */ + text?: string | null; + }; + RerankRequest: { + /** @example What is Deep Learning? */ + query: string; + /** + * @default false + * @example false + */ + raw_scores?: boolean; + /** + * @default false + * @example false + */ + return_text?: boolean; + /** + * @example [ + * "Deep Learning is ..." + * ] + */ + texts: string[]; + /** + * @default false + * @example false + */ + truncate?: boolean; + }; + RerankResponse: components["schemas"]["Rank"][]; + SimpleToken: { + /** + * Format: int32 + * @example 0 + */ + id: number; + /** @example false */ + special: boolean; + /** @example 0 */ + start?: number | null; + /** @example 2 */ + stop?: number | null; + /** @example test */ + text: string; + }; + SparseValue: { + index: number; + /** Format: float */ + value: number; + }; + TokenizeRequest: { + /** + * @default true + * @example true + */ + add_special_tokens?: boolean; + inputs: components["schemas"]["Input"]; + }; + /** + * @example [ + * [ + * { + * "id": 0, + * "special": false, + * "start": 0, + * "stop": 2, + * "text": "test" + * } + * ] + * ] + */ + TokenizeResponse: components["schemas"]["SimpleToken"][][]; + VertexInstance: (components["schemas"]["EmbedRequest"] & { + /** @enum {string} */ + type: "embed"; + }) | (components["schemas"]["EmbedAllRequest"] & { + /** @enum {string} */ + type: "embed_all"; + }) | (components["schemas"]["EmbedSparseRequest"] & { + /** @enum {string} */ + type: "embed_sparse"; + }) | (components["schemas"]["PredictRequest"] & { + /** @enum {string} */ + type: "predict"; + }) | (components["schemas"]["RerankRequest"] & { + /** @enum {string} */ + type: "rerank"; + }) | (components["schemas"]["TokenizeRequest"] & { + /** @enum {string} */ + type: "tokenize"; + }); + VertexRequest: { + instances: components["schemas"]["VertexInstance"][]; + }; + VertexResponse: components["schemas"]["VertexResponseInstance"][]; + VertexResponseInstance: { + result: components["schemas"]["EmbedResponse"]; + /** @enum {string} */ + type: "embed"; + } | { + result: components["schemas"]["EmbedAllResponse"]; + /** @enum {string} */ + type: "embed_all"; + } | { + result: components["schemas"]["EmbedSparseResponse"]; + /** @enum {string} */ + type: "embed_sparse"; + } | { + result: components["schemas"]["PredictResponse"]; + /** @enum {string} */ + type: "predict"; + } | { + result: components["schemas"]["RerankResponse"]; + /** @enum {string} */ + type: "rerank"; + } | { + result: components["schemas"]["TokenizeResponse"]; + /** @enum {string} */ + type: "tokenize"; + }; + }; + responses: never; + parameters: never; + requestBodies: never; + headers: never; + pathItems: never; +} + +export type $defs = Record; + +export type external = Record; + +export interface operations { + + /** + * Decode input ids + * @description Decode input ids + */ + decode: { + requestBody: { + content: { + "application/json": components["schemas"]["DecodeRequest"]; + }; + }; + responses: { + /** @description Decoded ids */ + 200: { + content: { + "application/json": components["schemas"]["DecodeResponse"]; + }; + }; + /** @description Tokenization error */ + 422: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + /** + * Get Embeddings. Returns a 424 status code if the model is not an embedding model. + * @description Get Embeddings. Returns a 424 status code if the model is not an embedding model. + */ + embed: { + requestBody: { + content: { + "application/json": components["schemas"]["EmbedRequest"]; + }; + }; + responses: { + /** @description Embeddings */ + 200: { + content: { + "application/json": components["schemas"]["EmbedResponse"]; + }; + }; + /** @description Batch size error */ + 413: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Tokenization error */ + 422: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Embedding Error */ + 424: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Model is overloaded */ + 429: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + /** + * Get all Embeddings without Pooling. + * @description Get all Embeddings without Pooling. + * Returns a 424 status code if the model is not an embedding model. + */ + embed_all: { + requestBody: { + content: { + "application/json": components["schemas"]["EmbedAllRequest"]; + }; + }; + responses: { + /** @description Embeddings */ + 200: { + content: { + "application/json": components["schemas"]["EmbedAllResponse"]; + }; + }; + /** @description Batch size error */ + 413: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Tokenization error */ + 422: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Embedding Error */ + 424: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Model is overloaded */ + 429: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + /** + * Get Sparse Embeddings. Returns a 424 status code if the model is not an embedding model with SPLADE pooling. + * @description Get Sparse Embeddings. Returns a 424 status code if the model is not an embedding model with SPLADE pooling. + */ + embed_sparse: { + requestBody: { + content: { + "application/json": components["schemas"]["EmbedSparseRequest"]; + }; + }; + responses: { + /** @description Embeddings */ + 200: { + content: { + "application/json": components["schemas"]["EmbedSparseResponse"]; + }; + }; + /** @description Batch size error */ + 413: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Tokenization error */ + 422: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Embedding Error */ + 424: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Model is overloaded */ + 429: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + /** + * OpenAI compatible route. Returns a 424 status code if the model is not an embedding model. + * @description OpenAI compatible route. Returns a 424 status code if the model is not an embedding model. + */ + openai_embed: { + requestBody: { + content: { + "application/json": components["schemas"]["OpenAICompatRequest"]; + }; + }; + responses: { + /** @description Embeddings */ + 200: { + content: { + "application/json": components["schemas"]["OpenAICompatResponse"]; + }; + }; + /** @description Batch size error */ + 413: { + content: { + "application/json": components["schemas"]["OpenAICompatErrorResponse"]; + }; + }; + /** @description Tokenization error */ + 422: { + content: { + "application/json": components["schemas"]["OpenAICompatErrorResponse"]; + }; + }; + /** @description Embedding Error */ + 424: { + content: { + "application/json": components["schemas"]["OpenAICompatErrorResponse"]; + }; + }; + /** @description Model is overloaded */ + 429: { + content: { + "application/json": components["schemas"]["OpenAICompatErrorResponse"]; + }; + }; + }; + }; + /** + * Health check method + * @description Health check method + */ + health: { + responses: { + /** @description Everything is working fine */ + 200: { + content: never; + }; + /** @description Text embeddings Inference is down */ + 503: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + /** + * Text Embeddings Inference endpoint info + * @description Text Embeddings Inference endpoint info + */ + get_model_info: { + responses: { + /** @description Served model info */ + 200: { + content: { + "application/json": components["schemas"]["Info"]; + }; + }; + }; + }; + /** + * Prometheus metrics scrape endpoint + * @description Prometheus metrics scrape endpoint + */ + metrics: { + responses: { + /** @description Prometheus Metrics */ + 200: { + content: { + "text/plain": string; + }; + }; + }; + }; + /** + * Get Predictions. Returns a 424 status code if the model is not a Sequence Classification model + * @description Get Predictions. Returns a 424 status code if the model is not a Sequence Classification model + */ + predict: { + requestBody: { + content: { + "application/json": components["schemas"]["PredictRequest"]; + }; + }; + responses: { + /** @description Predictions */ + 200: { + content: { + "application/json": components["schemas"]["PredictResponse"]; + }; + }; + /** @description Batch size error */ + 413: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Tokenization error */ + 422: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Prediction Error */ + 424: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Model is overloaded */ + 429: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + /** + * Get Ranks. Returns a 424 status code if the model is not a Sequence Classification model with + * @description Get Ranks. Returns a 424 status code if the model is not a Sequence Classification model with + * a single class. + */ + rerank: { + requestBody: { + content: { + "application/json": components["schemas"]["RerankRequest"]; + }; + }; + responses: { + /** @description Ranks */ + 200: { + content: { + "application/json": components["schemas"]["RerankResponse"]; + }; + }; + /** @description Batch size error */ + 413: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Tokenization error */ + 422: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Rerank Error */ + 424: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Model is overloaded */ + 429: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + /** + * Tokenize inputs + * @description Tokenize inputs + */ + tokenize: { + requestBody: { + content: { + "application/json": components["schemas"]["TokenizeRequest"]; + }; + }; + responses: { + /** @description Tokenized ids */ + 200: { + content: { + "application/json": components["schemas"]["TokenizeResponse"]; + }; + }; + /** @description Tokenization error */ + 422: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; + /** + * Generate embeddings from a Vertex request + * @description Generate embeddings from a Vertex request + */ + vertex_compatibility: { + requestBody: { + content: { + "application/json": components["schemas"]["VertexRequest"]; + }; + }; + responses: { + /** @description Results */ + 200: { + content: never; + }; + /** @description Batch size error */ + 413: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Tokenization error */ + 422: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Error */ + 424: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + /** @description Model is overloaded */ + 429: { + content: { + "application/json": components["schemas"]["ErrorResponse"]; + }; + }; + }; + }; +} diff --git a/packages/embedder/types/modules.d.ts b/packages/embedder/types/modules.d.ts new file mode 100644 index 00000000000..276eb185fa4 --- /dev/null +++ b/packages/embedder/types/modules.d.ts @@ -0,0 +1,4 @@ +declare module 'franc-min' { + import f from 'franc' + export = f +} diff --git a/packages/embedder/types/shared.d.ts b/packages/embedder/types/shared.d.ts new file mode 100644 index 00000000000..1d6610890e7 --- /dev/null +++ b/packages/embedder/types/shared.d.ts @@ -0,0 +1,2 @@ +import '../../server/types/modules' +import '../../server/types/webpackEnv' diff --git a/packages/gql-executor/RedisStream.ts b/packages/gql-executor/RedisStream.ts index 173a493321a..22798509396 100644 --- a/packages/gql-executor/RedisStream.ts +++ b/packages/gql-executor/RedisStream.ts @@ -7,13 +7,14 @@ export default class RedisStream implements AsyncIterableIterator { private stream: string private consumerGroup: string // xreadgroup blocks until a response is received, so this needs its own connection - private redis = new RedisInstance('gql_stream') + private redis: RedisInstance private consumer: string constructor(stream: string, consumerGroup: string, consumer: string) { this.stream = stream this.consumerGroup = consumerGroup this.consumer = consumer + this.redis = new RedisInstance(stream) } [Symbol.asyncIterator]() { diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index 7c19ba2d254..70a038b712c 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -192,6 +192,19 @@ export const retroReflectionsByMeetingId = new RethinkForeignKeyLoaderMaker( } ) +export const retroReflectionsByGroupId = new RethinkForeignKeyLoaderMaker( + 'retroReflections', + 'reflectionGroupId', + async (reflectionGroupIds) => { + const r = await getRethink() + return r + .table('RetroReflection') + .getAll(r.args(reflectionGroupIds), {index: 'reflectionGroupId'}) + .filter({isActive: true}) + .run() + } +) + export const templateDimensionsByTemplateId = new RethinkForeignKeyLoaderMaker( 'templateDimensions', 'templateId', diff --git a/packages/server/graphql/mutations/helpers/publishToEmbedder.ts b/packages/server/graphql/mutations/helpers/publishToEmbedder.ts new file mode 100644 index 00000000000..c8a735f4ac9 --- /dev/null +++ b/packages/server/graphql/mutations/helpers/publishToEmbedder.ts @@ -0,0 +1,14 @@ +import type {MessageToEmbedder} from 'embedder/custom' +import getRedis from '../../../utils/getRedis' + +export const publishToEmbedder = (message: MessageToEmbedder) => { + return getRedis().xadd( + 'embedMetadataStream', + 'MAXLEN', + '~', + 1000, + '*', + 'msg', + JSON.stringify(message) + ) +} diff --git a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts index cd116c65ea8..f5a69be4f22 100644 --- a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts @@ -1,35 +1,36 @@ +import {RawDraftContentState} from 'draft-js' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {DISCUSS, PARABOL_AI_USER_ID} from 'parabol-client/utils/constants' import getMeetingPhase from 'parabol-client/utils/getMeetingPhase' import findStageById from 'parabol-client/utils/meetings/findStageById' -import {RawDraftContentState} from 'draft-js' import {checkTeamsLimit} from '../../../billing/helpers/teamLimitsCheck' import getRethink from '../../../database/rethinkDriver' import {RDatum} from '../../../database/stricterR' import MeetingRetrospective from '../../../database/types/MeetingRetrospective' +import NotificationMentioned from '../../../database/types/NotificationMentioned' import TimelineEventRetroComplete from '../../../database/types/TimelineEventRetroComplete' import getKysely from '../../../postgres/getKysely' import removeSuggestedAction from '../../../safeMutations/removeSuggestedAction' +import {Logger} from '../../../utils/Logger' +import RecallAIServerManager from '../../../utils/RecallAIServerManager' import {analytics} from '../../../utils/analytics/analytics' import {getUserId} from '../../../utils/authorization' import getPhase from '../../../utils/getPhase' import publish from '../../../utils/publish' -import publishNotification from '../../public/mutations/helpers/publishNotification' -import RecallAIServerManager from '../../../utils/RecallAIServerManager' import sendToSentry from '../../../utils/sendToSentry' import standardError from '../../../utils/standardError' import {InternalContext} from '../../graphql' -import updateTeamInsights from './updateTeamInsights' +import publishNotification from '../../public/mutations/helpers/publishNotification' import sendNewMeetingSummary from './endMeeting/sendNewMeetingSummary' +import gatherInsights from './gatherInsights' import generateWholeMeetingSentimentScore from './generateWholeMeetingSentimentScore' import generateWholeMeetingSummary from './generateWholeMeetingSummary' import handleCompletedStage from './handleCompletedStage' import {IntegrationNotifier} from './notifications/IntegrationNotifier' +import {publishToEmbedder} from './publishToEmbedder' import removeEmptyTasks from './removeEmptyTasks' import updateQualAIMeetingsCount from './updateQualAIMeetingsCount' -import gatherInsights from './gatherInsights' -import NotificationMentioned from '../../../database/types/NotificationMentioned' -import {Logger} from '../../../utils/Logger' +import updateTeamInsights from './updateTeamInsights' const getTranscription = async (recallBotId?: string | null) => { if (!recallBotId) return @@ -370,6 +371,7 @@ const safeEndRetrospective = async ({ removedTaskIds, timelineEventId } + publishToEmbedder({objectTypes: ['retrospectiveDiscussionTopic'], meetingId}) publish(SubscriptionChannel.TEAM, teamId, 'EndRetrospectiveSuccess', data, subOptions) return data diff --git a/packages/server/postgres/migrations/1703031300000_addEmbeddingTables.ts b/packages/server/postgres/migrations/1703031300000_addEmbeddingTables.ts index e5f6f813877..33718ee75a1 100644 --- a/packages/server/postgres/migrations/1703031300000_addEmbeddingTables.ts +++ b/packages/server/postgres/migrations/1703031300000_addEmbeddingTables.ts @@ -1,7 +1,7 @@ import {Client} from 'pg' -import getPgConfig from '../getPgConfig' import {r} from 'rethinkdb-ts' import connectRethinkDB from '../../database/connectRethinkDB' +import getPgConfig from '../getPgConfig' export async function up() { const client = new Client(getPgConfig()) @@ -78,8 +78,8 @@ export async function down() { EXECUTE 'DROP TABLE IF EXISTS "EmbeddingsJobQueue"'; EXECUTE 'DROP TABLE IF EXISTS "EmbeddingsMetadata"'; - EXECUTE 'DROP TYPE IF EXISTS "EmbeddingsStateEnum"'; - EXECUTE 'DROP TYPE IF EXISTS "EmbeddingsObjectTypeEnum"'; + EXECUTE 'DROP TYPE IF EXISTS "EmbeddingsStateEnum" CASCADE'; + EXECUTE 'DROP TYPE IF EXISTS "EmbeddingsObjectTypeEnum" CASCADE'; END $$; `) await client.end() diff --git a/packages/server/postgres/migrations/1709934935000_embeddingsMetadataId.ts b/packages/server/postgres/migrations/1709934935000_embeddingsMetadataId.ts new file mode 100644 index 00000000000..185b1e0881b --- /dev/null +++ b/packages/server/postgres/migrations/1709934935000_embeddingsMetadataId.ts @@ -0,0 +1,62 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + // wipe data to ensure the non-null constraints succeed + await client.query(` + CREATE TYPE "ISO6391Enum" AS ENUM ('aa', 'ab', 'af', 'ak', 'am', 'ar', 'an', 'as', 'av', 'ae', 'ay', 'az', 'ba', 'bm', 'be', 'bn', 'bi', 'bo', 'bs', 'br', 'bg', 'ca', 'cs', 'ch', 'ce', 'cu', 'cv', 'kw', 'co', 'cr', 'cy', 'da', 'de', 'dv', 'dz', 'el', 'en', 'eo', 'et', 'eu', 'ee', 'fo', 'fa', 'fj', 'fi', 'fr', 'fy', 'ff', 'gd', 'ga', 'gl', 'gv', 'gn', 'gu', 'ht', 'ha', 'sh', 'he', 'hz', 'hi', 'ho', 'hr', 'hu', 'hy', 'ig', 'io', 'ii', 'iu', 'ie', 'ia', 'id', 'ik', 'is', 'it', 'jv', 'ja', 'kl', 'kn', 'ks', 'ka', 'kr', 'kk', 'km', 'ki', 'rw', 'ky', 'kv', 'kg', 'ko', 'kj', 'ku', 'lo', 'la', 'lv', 'li', 'ln', 'lt', 'lb', 'lu', 'lg', 'mh', 'ml', 'mr', 'mk', 'mg', 'mt', 'mn', 'mi', 'ms', 'my', 'na', 'nv', 'nr', 'nd', 'ng', 'ne', 'nl', 'nn', 'nb', 'no', 'ny', 'oc', 'oj', 'or', 'om', 'os', 'pa', 'pi', 'pl', 'pt', 'ps', 'qu', 'rm', 'ro', 'rn', 'ru', 'sg', 'sa', 'si', 'sk', 'sl', 'se', 'sm', 'sn', 'sd', 'so', 'st', 'es', 'sq', 'sc', 'sr', 'ss', 'su', 'sw', 'sv', 'ty', 'ta', 'tt', 'te', 'tg', 'tl', 'th', 'ti', 'to', 'tn', 'ts', 'tk', 'tr', 'tw', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh', 'yi', 'yo', 'za', 'zh', 'zu'); + DELETE FROM "EmbeddingsMetadata"; + DELETE FROM "EmbeddingsJobQueue"; + CREATE INDEX IF NOT EXISTS "idx_Discussion_createdAt" ON "Discussion"("createdAt"); + ALTER TYPE "EmbeddingsStateEnum" RENAME VALUE 'embedding' TO 'running'; + ALTER TYPE "EmbeddingsStateEnum" RENAME TO "EmbeddingsJobStateEnum"; + ALTER TABLE "EmbeddingsMetadata" + DROP COLUMN "models", + ADD COLUMN "language" "ISO6391Enum", + ALTER COLUMN "refId" SET NOT NULL; + ALTER TABLE "EmbeddingsJobQueue" + ADD COLUMN "retryAfter" TIMESTAMP WITH TIME ZONE, + ADD COLUMN "retryCount" SMALLINT NOT NULL DEFAULT 0, + ADD COLUMN "startAt" TIMESTAMP WITH TIME ZONE, + ADD COLUMN "priority" SMALLINT NOT NULL DEFAULT 50, + ADD COLUMN "jobData" JSONB NOT NULL DEFAULT '{}', + ADD COLUMN "jobType" VARCHAR(255) NOT NULL, + DROP CONSTRAINT IF EXISTS "EmbeddingsJobQueue_objectType_refId_model_key", + DROP COLUMN "refId", + DROP COLUMN "objectType", + DROP COLUMN "model"; + CREATE INDEX IF NOT EXISTS "idx_EmbeddingsJobQueue_priority" ON "EmbeddingsJobQueue"("priority"); + `) + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TYPE IF EXISTS "ISO6391Enum" CASCADE; + DROP INDEX IF EXISTS "idx_Discussion_createdAt"; + ALTER TYPE "EmbeddingsJobStateEnum" RENAME VALUE 'running' TO 'embedding'; + ALTER TYPE "EmbeddingsJobStateEnum" RENAME TO "EmbeddingsStateEnum"; + ALTER TABLE "EmbeddingsMetadata" + ADD COLUMN "models" VARCHAR(255)[], + DROP COLUMN IF EXISTS "language", + ALTER COLUMN "refId" DROP NOT NULL; + ALTER TABLE "EmbeddingsJobQueue" + ALTER COLUMN "state" TYPE "EmbeddingsStateEnum" USING "state"::text::"EmbeddingsStateEnum", + ADD CONSTRAINT "EmbeddingsJobQueue_objectType_refId_model_key" UNIQUE("objectType", "refId", "model"), + ADD COLUMN "refId" VARCHAR(100), + ADD COLUMN "model" VARCHAR(255), + ADD COLUMN "objectType" "EmbeddingsObjectTypeEnum", + DROP COLUMN "retryAfter", + DROP COLUMN "retryCount", + DROP COLUMN "startAt", + DROP COLUMN "jobData", + DROP COLUMN "priority", + DROP COLUMN "jobType"; + DROP INDEX IF EXISTS "idx_EmbeddingsJobQueue_priority"; + `) + await client.end() +} diff --git a/release-please-config.json b/release-please-config.json index 29f302183a1..b8959848b5a 100644 --- a/release-please-config.json +++ b/release-please-config.json @@ -60,6 +60,11 @@ "path": "packages/integration-tests/package.json", "jsonpath": "$.version" }, + { + "type": "json", + "path": "packages/embedder/package.json", + "jsonpath": "$.version" + }, { "type": "json", "path": "packages/server/package.json", diff --git a/yarn.lock b/yarn.lock index 817e730db40..e6fb56f52bf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7577,6 +7577,11 @@ dependencies: "@types/jsdom" "*" +"@types/franc@^5.0.3": + version "5.0.3" + resolved "https://registry.yarnpkg.com/@types/franc/-/franc-5.0.3.tgz#7263cef3ab3512ac95a78c328fcc51c51396b49f" + integrity sha512-YX6o2vVkeiUvOF12bUmnSGf8sezOoBnCWjHHZGeh2lt3tqAutbJ9OL3cDRiZoiAYaZR638nuOc0Ji9bzdad2XA== + "@types/glob@*": version "8.1.0" resolved "https://registry.yarnpkg.com/@types/glob/-/glob-8.1.0.tgz#b63e70155391b0584dce44e7ea25190bbc38f2fc" @@ -10109,6 +10114,11 @@ codemirror@^5.65.3: resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.3.tgz#2d029930d5a293bc5fb96ceea64654803c0d4ac7" integrity sha512-kCC0iwGZOVZXHEKW3NDTObvM7pTIyowjty4BUqeREROc/3I6bWbgZDA3fGDwlA+rbgRjvnRnfqs9SfXynel1AQ== +collapse-white-space@^1.0.0: + version "1.0.6" + resolved "https://registry.yarnpkg.com/collapse-white-space/-/collapse-white-space-1.0.6.tgz#e63629c0016665792060dbbeb79c42239d2c5287" + integrity sha512-jEovNnrhMuqyCcjfEJA56v0Xq8SkIoPKDyaHahwo3POf4qcSXqMYuwNcOTzp74vTsR9Tn08z4MxWqAhcekogkQ== + collect-v8-coverage@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz#cc2c8e94fc18bbdffe64d6534570c8a673b27f59" @@ -12401,6 +12411,13 @@ framesync@6.0.1: dependencies: tslib "^2.1.0" +franc-min@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/franc-min/-/franc-min-5.0.0.tgz#5625d0570a18564dcbbfa8330254d23549294d9a" + integrity sha512-xy7Iq7uNflbvNU+bkyYWtP+BOHWZle7kT9GM84gEV14b7/7sgq7M7Flf6v1XRflHAuHoshBMveWA6Q+kEXYeHQ== + dependencies: + trigram-utils "^1.0.0" + fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -15026,10 +15043,10 @@ kysely-codegen@^0.11.0: micromatch "^4.0.5" minimist "^1.2.8" -kysely@^0.27.2: - version "0.27.2" - resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.27.2.tgz#b289ce5e561064ec613a17149b7155783d2b36de" - integrity sha512-DmRvEfiR/NLpgsTbSxma2ldekhsdcd65+MNiKXyd/qj7w7X5e3cLkXxcj+MypsRDjPhHQ/CD5u3Eq1sBYzX0bw== +kysely@^0.27.3: + version "0.27.3" + resolved "https://registry.yarnpkg.com/kysely/-/kysely-0.27.3.tgz#6cc6c757040500b43c4ac596cdbb12be400ee276" + integrity sha512-lG03Ru+XyOJFsjH3OMY6R/9U38IjDPfnOfDgO3ynhbDr+Dz8fak+X6L62vqu3iybQnj+lG84OttBuU9KY3L9kA== launch-editor@^2.6.0: version "2.6.0" @@ -16139,6 +16156,11 @@ mz@^2.7.0: object-assign "^4.0.1" thenify-all "^1.0.0" +n-gram@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/n-gram/-/n-gram-1.1.2.tgz#69c609a5c83bb32f82774c9e297f8494c7326798" + integrity sha512-mBTpWKp0NHdujHmxrskPg2jc108mjyMmVxHN1rZGK/ogTLi9O0debDIXlQPqotNELdNmVGtL4jr7SCig+4OWvQ== + nan@^2.17.0: version "2.18.0" resolved "https://registry.yarnpkg.com/nan/-/nan-2.18.0.tgz#26a6faae7ffbeb293a39660e88a76b82e30b7554" @@ -16876,11 +16898,23 @@ openai@^4.24.1: node-fetch "^2.6.7" web-streams-polyfill "^3.2.1" +openapi-fetch@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/openapi-fetch/-/openapi-fetch-0.9.3.tgz#37c1dbde7faec885eaa40f351cab1c231b794761" + integrity sha512-tC1NDn71vJHeCzu+lYdrnIpgRt4GxR0B4eSwXNb15ypWpZcpaEOwHFkoz8FcfG5Fvqkz2P0Fl9zQF1JJwBjuvA== + dependencies: + openapi-typescript-helpers "^0.0.7" + openapi-types@^12.1.0: version "12.1.1" resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.1.tgz#0aface4e05ba60efbf51153ed6af23988796617d" integrity sha512-m/DJaEqOUDSU8KoI74E6A3TokccuDOJ81ewZ6kLFwUT1KEIE0GDWvErtnJJDU4sySx8JKF5kk2GzHUuK6f+VHA== +openapi-typescript-helpers@^0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.7.tgz#1d0ead67c35864d189c2cb2d0556854ccbb16c38" + integrity sha512-7nwlAtdA1fULipibFRBWE/rnF114q6ejRYzNvhdA/x+qTWAZhXGLc/368dlwMlyJDvCQMCnADjpzb5BS5ZmNSA== + opener@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" @@ -20704,6 +20738,15 @@ treeverse@^2.0.0: resolved "https://registry.yarnpkg.com/treeverse/-/treeverse-2.0.0.tgz#036dcef04bc3fd79a9b79a68d4da03e882d8a9ca" integrity sha512-N5gJCkLu1aXccpOTtqV6ddSEi6ZmGkh3hjmbu1IjcavJK4qyOVQmi0myQKM7z5jVGmD68SJoliaVrMmVObhj6A== +trigram-utils@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/trigram-utils/-/trigram-utils-1.0.3.tgz#535da37a414dae249c4b023512cf2b3dc65c8ea4" + integrity sha512-UAhS1Ll21FtClVIzIN0I/SmGnJ+D08BOxX7Dl1penV8raC0ksf2dJkhNI6kU1Mj3uT86Bul12iMvxXquXSYSng== + dependencies: + collapse-white-space "^1.0.0" + n-gram "^1.0.0" + trim "0.0.1" + trim-newlines@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-3.0.1.tgz#260a5d962d8b752425b32f3a7db0dcacd176c144" @@ -20714,6 +20757,11 @@ trim-newlines@^4.0.2: resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-4.0.2.tgz#d6aaaf6a0df1b4b536d183879a6b939489808c7c" integrity sha512-GJtWyq9InR/2HRiLZgpIKv+ufIKrVrvjQWEj7PxAXNc5dwbNJkqhAUoAGgzRmULAnoOM5EIpveYd3J2VeSAIew== +trim@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/trim/-/trim-0.0.1.tgz#5858547f6b290757ee95cccc666fb50084c460dd" + integrity sha512-YzQV+TZg4AxpKxaTHK3c3D+kRDCGVEE7LemdlQZoQXn0iennk10RsIoY6ikzAqJTc9Xjl9C1/waHom/J86ziAQ== + ts-algebra@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/ts-algebra/-/ts-algebra-1.2.0.tgz#f91c481207a770f0d14d055c376cbee040afdfc9" @@ -20997,10 +21045,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@4.9.5, "typescript@^3 || ^4", typescript@^4.2.4, typescript@^5.3.3: - version "5.3.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.3.3.tgz#b3ce6ba258e72e6305ba66f5c9b452aaee3ffe37" - integrity sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw== +"typescript@^3 || ^4", typescript@^4.2.4, typescript@^5.3.3: + version "5.4.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372" + integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ== uWebSockets.js@uNetworking/uWebSockets.js#v20.34.0: version "20.34.0" From 9865bc966edd90bdadd963d07eef12d8899e8fc0 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Mon, 1 Apr 2024 09:41:02 -0700 Subject: [PATCH 106/529] chore(release): release v7.24.0 (#9581) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index bae38c98366..bc72873ade5 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.23.1" + ".": "7.24.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 294dbfc8830..a9adc2cad97 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.24.0](https://github.com/ParabolInc/parabol/compare/v7.23.1...v7.24.0) (2024-03-29) + + +### Added + +* prepare embedder for Production ([#9517](https://github.com/ParabolInc/parabol/issues/9517)) ([538c95c](https://github.com/ParabolInc/parabol/commit/538c95ce4dc7d4839b3e813006cb20e1b7d1d1c8)) + + +### Changed + +* fix tsconfig problems ([#9579](https://github.com/ParabolInc/parabol/issues/9579)) ([d1af0f1](https://github.com/ParabolInc/parabol/commit/d1af0f164c629e8fc075278cd63475e8913f4295)) + ## [7.23.1](https://github.com/ParabolInc/parabol/compare/v7.23.0...v7.23.1) (2024-03-28) diff --git a/package.json b/package.json index 7312d1f47d3..a7562ce29bd 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.23.1", + "version": "7.24.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index b2e5061c6bc..63593c4e0fb 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.23.1", + "version": "7.24.0", "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.23.1" + "parabol-server": "7.24.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 5775dc743ab..155fa89f5fd 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.23.1", + "version": "7.24.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index fdf98991ced..b7c510ef5b5 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.10.0", + "version": "7.24.0", "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 bed8c3473eb..bd7f9c61b51 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.23.1", + "version": "7.24.0", "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.23.1", - "parabol-server": "7.23.1", + "parabol-client": "7.24.0", + "parabol-server": "7.24.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 2cbc84391c1..893db5ee8df 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.23.1", + "version": "7.24.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 62295579ced..8cc20a60e6c 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.23.1", + "version": "7.24.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -125,7 +125,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.23.1", + "parabol-client": "7.24.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 8cdd9014c3277905605c6544de92d9ac2833a6e9 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 1 Apr 2024 11:45:02 -0700 Subject: [PATCH 107/529] fix: embedder doesn't dive deep into schema (#9582) Signed-off-by: Matt Krick --- packages/embedder/types/shared.d.ts | 1 - packages/server/database/rethinkDriver.ts | 2 +- packages/server/dataloader/customLoaderMakers.ts | 2 +- .../graphql/private/mutations/runScheduledJobs.ts | 2 -- packages/server/graphql/public/rootSchema.ts | 1 - .../server/integrations/gitlab/GitLabServerManager.ts | 2 +- .../postgres/types/{Meeting.ts => Meeting.d.ts} | 0 packages/server/types/custom.d.ts | 11 +++++++++++ packages/server/utils/analytics/analytics.ts | 11 +++++------ packages/server/utils/getGitHubRequest.ts | 2 +- tsconfig.base.json | 1 + 11 files changed, 21 insertions(+), 14 deletions(-) rename packages/server/postgres/types/{Meeting.ts => Meeting.d.ts} (100%) diff --git a/packages/embedder/types/shared.d.ts b/packages/embedder/types/shared.d.ts index 1d6610890e7..e94d0d14dea 100644 --- a/packages/embedder/types/shared.d.ts +++ b/packages/embedder/types/shared.d.ts @@ -1,2 +1 @@ import '../../server/types/modules' -import '../../server/types/webpackEnv' diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index 5f7311c0580..370482933b2 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -4,7 +4,7 @@ import SlackAuth from '../database/types/SlackAuth' import SlackNotification from '../database/types/SlackNotification' import TeamInvitation from '../database/types/TeamInvitation' import TeamMember from '../database/types/TeamMember' -import {ScheduledJobUnion} from '../graphql/private/mutations/runScheduledJobs' +import {ScheduledJobUnion} from '../types/custom' import {AnyMeeting, AnyMeetingSettings, AnyMeetingTeamMember} from '../postgres/types/Meeting' import getRethinkConfig from './getRethinkConfig' import {R} from './stricterR' diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index ac7e1ea6ec7..f10dfd1f257 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -5,11 +5,11 @@ import getRethink, {RethinkSchema} from '../database/rethinkDriver' import {RDatum} from '../database/stricterR' import MeetingSettingsTeamPrompt from '../database/types/MeetingSettingsTeamPrompt' import MeetingTemplate from '../database/types/MeetingTemplate' +import Organization from '../database/types/Organization' import OrganizationUser from '../database/types/OrganizationUser' import {Reactable, ReactableEnum} from '../database/types/Reactable' import Task, {TaskStatusEnum} from '../database/types/Task' import isValid from '../graphql/isValid' -import {Organization} from '../graphql/public/resolverTypes' import {SAMLSource} from '../graphql/public/types/SAML' import getKysely from '../postgres/getKysely' import {TeamMeetingTemplate} from '../postgres/pg.d' diff --git a/packages/server/graphql/private/mutations/runScheduledJobs.ts b/packages/server/graphql/private/mutations/runScheduledJobs.ts index aebb1e5eafe..5317aff0147 100644 --- a/packages/server/graphql/private/mutations/runScheduledJobs.ts +++ b/packages/server/graphql/private/mutations/runScheduledJobs.ts @@ -40,8 +40,6 @@ const processMeetingStageTimeLimits = async ( }) } -export type ScheduledJobUnion = ScheduledJobMeetingStageTimeLimit | ScheduledTeamLimitsJob - const processJob = async (job: Selectable, dataLoader: DataLoaderWorker) => { const pg = getKysely() const res = await pg.deleteFrom('ScheduledJob').where('id', '=', job.id).executeTakeFirst() diff --git a/packages/server/graphql/public/rootSchema.ts b/packages/server/graphql/public/rootSchema.ts index b6f1d26fad2..e65b1459189 100644 --- a/packages/server/graphql/public/rootSchema.ts +++ b/packages/server/graphql/public/rootSchema.ts @@ -99,5 +99,4 @@ const addRequestors = (schema: GraphQLSchema) => { const rootSchema = addRequestors(resolveTypesForMutationPayloads(parabolWithNestedResolversSchema)) -export type RootSchema = typeof rootSchema export default rootSchema diff --git a/packages/server/integrations/gitlab/GitLabServerManager.ts b/packages/server/integrations/gitlab/GitLabServerManager.ts index 2ffcfa826bf..dd44a3a5525 100644 --- a/packages/server/integrations/gitlab/GitLabServerManager.ts +++ b/packages/server/integrations/gitlab/GitLabServerManager.ts @@ -9,8 +9,8 @@ import getIssue from '../../graphql/nestedSchema/GitLab/queries/getIssue.graphql import getProfile from '../../graphql/nestedSchema/GitLab/queries/getProfile.graphql' import getProjectIssues from '../../graphql/nestedSchema/GitLab/queries/getProjectIssues.graphql' import getProjects from '../../graphql/nestedSchema/GitLab/queries/getProjects.graphql' -import {RootSchema} from '../../graphql/public/rootSchema' import {IGetTeamMemberIntegrationAuthQueryResult} from '../../postgres/queries/generated/getTeamMemberIntegrationAuthQuery' +import {RootSchema} from '../../types/custom' import { CreateIssueMutation, CreateNoteMutation, diff --git a/packages/server/postgres/types/Meeting.ts b/packages/server/postgres/types/Meeting.d.ts similarity index 100% rename from packages/server/postgres/types/Meeting.ts rename to packages/server/postgres/types/Meeting.d.ts diff --git a/packages/server/types/custom.d.ts b/packages/server/types/custom.d.ts index 2fc4dba2ce8..aae67c270cd 100644 --- a/packages/server/types/custom.d.ts +++ b/packages/server/types/custom.d.ts @@ -1,5 +1,9 @@ +import {GraphQLSchema} from 'graphql' +import type nestGitHubEndpoint from 'nest-graphql-endpoint/lib/nestGitHubEndpoint' import '../../client/types/reactHTML4' import type AuthToken from '../database/types/AuthToken' +import type ScheduledJobMeetingStageTimeLimit from '../database/types/ScheduledJobMetingStageTimeLimit' +import type ScheduledTeamLimitsJob from '../database/types/ScheduledTeamLimitsJob' export interface OAuth2Success { access_token: string token_type: string @@ -35,3 +39,10 @@ export interface GQLRequest { // Datadog opentracing span of the calling server carrier?: any } + +export type ScheduledJobUnion = ScheduledJobMeetingStageTimeLimit | ScheduledTeamLimitsJob + +export type RootSchema = GraphQLSchema & { + githubRequest: ReturnType['githubRequest'] + gitlabRequest: ReturnType['githubRequest'] +} diff --git a/packages/server/utils/analytics/analytics.ts b/packages/server/utils/analytics/analytics.ts index a4cbc61f189..305839e2651 100644 --- a/packages/server/utils/analytics/analytics.ts +++ b/packages/server/utils/analytics/analytics.ts @@ -5,9 +5,12 @@ import Meeting from '../../database/types/Meeting' import MeetingMember from '../../database/types/MeetingMember' import MeetingRetrospective from '../../database/types/MeetingRetrospective' import MeetingTemplate from '../../database/types/MeetingTemplate' -import {Reactable} from '../../database/types/Reactable' +import {Reactable, ReactableEnum} from '../../database/types/Reactable' +import {SlackNotificationEventEnum} from '../../database/types/SlackNotification' import {TaskServiceEnum} from '../../database/types/Task' -import {ReactableEnum} from '../../graphql/private/resolverTypes' +import TemplateScale from '../../database/types/TemplateScale' +import {DataLoaderWorker} from '../../graphql/graphql' +import {ModifyType} from '../../graphql/public/resolverTypes' import {IntegrationProviderServiceEnumType} from '../../graphql/types/IntegrationProviderServiceEnum' import {UpgradeCTALocationEnumType} from '../../graphql/types/UpgradeCTALocationEnum' import {TeamPromptResponse} from '../../postgres/queries/getTeamPromptResponsesByIds' @@ -16,10 +19,6 @@ import {MeetingTypeEnum} from '../../postgres/types/Meeting' import {MeetingSeries} from '../../postgres/types/MeetingSeries' import {AmplitudeAnalytics} from './amplitude/AmplitudeAnalytics' import {createMeetingProperties} from './helpers' -import {SlackNotificationEventEnum} from '../../database/types/SlackNotification' -import TemplateScale from '../../database/types/TemplateScale' -import {DataLoaderWorker} from '../../graphql/graphql' -import {ModifyType} from '../../graphql/public/resolverTypes' export type AnalyticsUser = { id: string diff --git a/packages/server/utils/getGitHubRequest.ts b/packages/server/utils/getGitHubRequest.ts index 76efb0db5ae..59ff1eea559 100644 --- a/packages/server/utils/getGitHubRequest.ts +++ b/packages/server/utils/getGitHubRequest.ts @@ -1,6 +1,6 @@ import {GraphQLResolveInfo} from 'graphql' import {EndpointContext} from 'nest-graphql-endpoint/lib/types' -import {RootSchema} from '../graphql/public/rootSchema' +import {RootSchema} from '../types/custom' // This helper just cleans up the input/output boilerplate. // It breaks githubRequest into 2 parts so the info, endpointContext, and batchRef are kept in context diff --git a/tsconfig.base.json b/tsconfig.base.json index adc1623c1b5..c207d6f1c07 100644 --- a/tsconfig.base.json +++ b/tsconfig.base.json @@ -16,6 +16,7 @@ "strictPropertyInitialization": true, "noImplicitThis": true, "alwaysStrict": true, + "noEmit": true, "noUnusedLocals": true, "noUnusedParameters": true, "noImplicitReturns": true, From 341b4b797ec6444066244f25916803e64c03258c Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 1 Apr 2024 14:46:12 -0700 Subject: [PATCH 108/529] fix: embedder errors in embed length (#9584) Signed-off-by: Matt Krick --- package.json | 7 +- packages/client/package.json | 2 - packages/embedder/EmbeddingsJobQueueStream.ts | 3 +- ...MetadataForRetrospectiveDiscussionTopic.ts | 7 +- .../ai_models/AbstractEmbeddingsModel.ts | 56 +- .../ai_models/AbstractGenerationModel.ts | 11 +- packages/embedder/ai_models/AbstractModel.ts | 9 +- packages/embedder/ai_models/ModelManager.ts | 155 ++-- .../embedder/ai_models/OpenAIGeneration.ts | 26 +- .../ai_models/TextEmbeddingsInference.ts | 29 +- .../ai_models/TextGenerationInference.ts | 24 +- packages/embedder/embedder.ts | 2 +- .../indexing/retrospectiveDiscussionTopic.ts | 4 +- packages/embedder/package.json | 4 + packages/embedder/processJobEmbed.ts | 21 +- packages/embedder/textEmbeddingsnterface.d.ts | 778 +++++++++--------- packages/integration-tests/package.json | 2 - packages/server/package.json | 2 - yarn.lock | 306 +++---- 19 files changed, 716 insertions(+), 732 deletions(-) diff --git a/package.json b/package.json index a7562ce29bd..427d88d96e8 100644 --- a/package.json +++ b/package.json @@ -92,13 +92,14 @@ "@types/dotenv": "^6.1.1", "@types/jscodeshift": "^0.11.3", "@types/lodash.toarray": "^4.4.7", - "@typescript-eslint/eslint-plugin": "^6.21.0", - "@typescript-eslint/parser": "^6.21.0", + "@typescript-eslint/eslint-plugin": "^7.4.0", + "@typescript-eslint/parser": "^7.4.0", "autoprefixer": "^10.4.13", "babel-loader": "^9.1.2", "concurrently": "^8.0.1", "copy-webpack-plugin": "^11.0.0", - "eslint-config-prettier": "^8.5.0", + "eslint": "^8.57.0", + "eslint-config-prettier": "^9.1.0", "graphql": "15.7.2", "html-webpack-plugin": "^5.5.0", "husky": "^7.0.4", diff --git a/packages/client/package.json b/packages/client/package.json index 155fa89f5fd..9261fb18d5a 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -50,8 +50,6 @@ "@types/stripe-v2": "^2.0.1", "babel-plugin-relay": "^12.0.0", "debug": "^4.1.1", - "eslint": "^8.2.0", - "eslint-config-prettier": "^8.5.0", "eslint-plugin-emotion": "^10.0.14", "eslint-plugin-react": "^7.16.0", "eslint-plugin-react-hooks": "^1.6.1", diff --git a/packages/embedder/EmbeddingsJobQueueStream.ts b/packages/embedder/EmbeddingsJobQueueStream.ts index 7f5b1bde03d..64f7de936dd 100644 --- a/packages/embedder/EmbeddingsJobQueueStream.ts +++ b/packages/embedder/EmbeddingsJobQueueStream.ts @@ -7,13 +7,14 @@ import {DB} from 'parabol-server/postgres/pg' import RootDataLoader from '../server/dataloader/RootDataLoader' import {processJob} from './processJob' import {Logger} from '../server/utils/Logger' +import {EmbeddingsTableName} from './ai_models/AbstractEmbeddingsModel' export type DBJob = Selectable export type EmbedJob = DBJob & { jobType: 'embed' jobData: { embeddingsMetadataId: number - model: string + model: EmbeddingsTableName } } export type RerankJob = DBJob & {jobType: 'rerank'; jobData: {discussionIds: string[]}} diff --git a/packages/embedder/addEmbeddingsMetadataForRetrospectiveDiscussionTopic.ts b/packages/embedder/addEmbeddingsMetadataForRetrospectiveDiscussionTopic.ts index 724aec6fb13..cd4489e3942 100644 --- a/packages/embedder/addEmbeddingsMetadataForRetrospectiveDiscussionTopic.ts +++ b/packages/embedder/addEmbeddingsMetadataForRetrospectiveDiscussionTopic.ts @@ -41,7 +41,7 @@ const insertDiscussionsIntoMetadata = async (discussions: DiscussionMeta[], prio if (!metadataRows[0]) return const modelManager = getModelManager() - const models = modelManager.embeddingModels.map((m) => m.tableName) + const tableNames = [...modelManager.embeddingModels.keys()] return ( pg .with('Insert', (qc) => @@ -55,8 +55,9 @@ const insertDiscussionsIntoMetadata = async (discussions: DiscussionMeta[], prio .with('Metadata', (qc) => qc .selectFrom('Insert') - .fullJoin(sql<{model: string}>`UNNEST(ARRAY[${sql.join(models)}])`.as('model'), (join) => - join.onTrue() + .fullJoin( + sql<{model: string}>`UNNEST(ARRAY[${sql.join(tableNames)}])`.as('model'), + (join) => join.onTrue() ) .select(['id', 'model']) ) diff --git a/packages/embedder/ai_models/AbstractEmbeddingsModel.ts b/packages/embedder/ai_models/AbstractEmbeddingsModel.ts index 9fd5831ea1f..f9fb669737d 100644 --- a/packages/embedder/ai_models/AbstractEmbeddingsModel.ts +++ b/packages/embedder/ai_models/AbstractEmbeddingsModel.ts @@ -1,10 +1,11 @@ import {sql} from 'kysely' import getKysely from 'parabol-server/postgres/getKysely' import {DB} from 'parabol-server/postgres/pg' +import isValid from '../../server/graphql/isValid' import {Logger} from '../../server/utils/Logger' import {EMBEDDER_JOB_PRIORITY} from '../EMBEDDER_JOB_PRIORITY' import {ISO6391} from '../iso6393To1' -import {AbstractModel, ModelConfig} from './AbstractModel' +import {AbstractModel} from './AbstractModel' export interface EmbeddingModelParams { embeddingDimensions: number @@ -12,32 +13,59 @@ export interface EmbeddingModelParams { tableSuffix: string languages: ISO6391[] } -export type EmbeddingsTable = Extract -export interface EmbeddingModelConfig extends ModelConfig { - tableSuffix: string -} +export type EmbeddingsTableName = `Embeddings_${string}` +export type EmbeddingsTable = Extract export abstract class AbstractEmbeddingsModel extends AbstractModel { readonly embeddingDimensions: number readonly maxInputTokens: number - readonly tableName: string + readonly tableName: EmbeddingsTableName readonly languages: ISO6391[] - constructor(config: EmbeddingModelConfig) { - super(config) - const modelParams = this.constructModelParams(config) + constructor(modelId: string, url: string) { + super(url) + const modelParams = this.constructModelParams(modelId) this.embeddingDimensions = modelParams.embeddingDimensions this.languages = modelParams.languages this.maxInputTokens = modelParams.maxInputTokens this.tableName = `Embeddings_${modelParams.tableSuffix}` } - protected abstract constructModelParams(config: EmbeddingModelConfig): EmbeddingModelParams + protected abstract constructModelParams(modelId: string): EmbeddingModelParams abstract getEmbedding(content: string, retries?: number): Promise abstract getTokens(content: string): Promise - splitText(content: string) { + + async chunkText(content: string) { + const tokens = await this.getTokens(content) + if (tokens instanceof Error) return tokens + const isFullTextTooBig = tokens.length > this.maxInputTokens + if (!isFullTextTooBig) return [content] + + for (let i = 0; i < 3; i++) { + const tokensPerWord = (4 + i) / 3 + const chunks = this.splitText(content, tokensPerWord) + const chunkLengths = await Promise.all( + chunks.map(async (chunk) => { + const chunkTokens = await this.getTokens(chunk) + if (chunkTokens instanceof Error) return chunkTokens + return chunkTokens.length + }) + ) + const firstError = chunkLengths.find( + (chunkLength): chunkLength is Error => chunkLength instanceof Error + ) + if (firstError) return firstError + + const validChunks = chunkLengths.filter(isValid) + if (validChunks.every((chunkLength) => chunkLength <= this.maxInputTokens)) { + return chunks + } + } + return new Error(`Text is too long and could not be split into chunks. Is it english?`) + } + // private because result must still be too long to go into model. Must verify with getTokens + private splitText(content: string, tokensPerWord = 4 / 3) { // it's actually 4 / 3, but don't want to chance a failed split - const TOKENS_PER_WORD = 5 / 3 - const WORD_LIMIT = Math.floor(this.maxInputTokens / TOKENS_PER_WORD) + const WORD_LIMIT = Math.floor(this.maxInputTokens / tokensPerWord) const chunks: string[] = [] const delimiters = ['\n\n', '\n', '.', ' '] const countWords = (text: string) => text.trim().split(/\s+/).length @@ -98,7 +126,7 @@ export abstract class AbstractEmbeddingsModel extends AbstractModel { 'tablename' )} = ${this.tableName}`.execute(pg) ).rows.length > 0 - if (hasTable) return undefined + if (hasTable) return const vectorDimensions = this.embeddingDimensions Logger.log(`ModelManager: creating ${this.tableName} with ${vectorDimensions} dimensions`) await sql` diff --git a/packages/embedder/ai_models/AbstractGenerationModel.ts b/packages/embedder/ai_models/AbstractGenerationModel.ts index c57d86c243a..ac0223e6eb6 100644 --- a/packages/embedder/ai_models/AbstractGenerationModel.ts +++ b/packages/embedder/ai_models/AbstractGenerationModel.ts @@ -1,4 +1,4 @@ -import {AbstractModel, ModelConfig} from './AbstractModel' +import {AbstractModel} from './AbstractModel' export interface GenerationOptions { maxNewTokens?: number @@ -11,16 +11,15 @@ export interface GenerationOptions { export interface GenerationModelParams { maxInputTokens: number } -export interface GenerationModelConfig extends ModelConfig {} export abstract class AbstractGenerationModel extends AbstractModel { readonly maxInputTokens: number - constructor(config: GenerationModelConfig) { - super(config) - const modelParams = this.constructModelParams(config) + constructor(modelId: string, url: string) { + super(url) + const modelParams = this.constructModelParams(modelId) this.maxInputTokens = modelParams.maxInputTokens } - protected abstract constructModelParams(config: GenerationModelConfig): GenerationModelParams + protected abstract constructModelParams(modelId: string): GenerationModelParams abstract summarize(content: string, options: GenerationOptions): Promise } diff --git a/packages/embedder/ai_models/AbstractModel.ts b/packages/embedder/ai_models/AbstractModel.ts index 3b114558539..322476c552f 100644 --- a/packages/embedder/ai_models/AbstractModel.ts +++ b/packages/embedder/ai_models/AbstractModel.ts @@ -1,13 +1,8 @@ -export interface ModelConfig { - model: string - url: string -} - export abstract class AbstractModel { public readonly url: string - constructor(config: ModelConfig) { - this.url = this.normalizeUrl(config.url) + constructor(url: string) { + this.url = this.normalizeUrl(url) } // removes a trailing slash from the inputUrl diff --git a/packages/embedder/ai_models/ModelManager.ts b/packages/embedder/ai_models/ModelManager.ts index 55bf3feeadc..bbbfa4f6273 100644 --- a/packages/embedder/ai_models/ModelManager.ts +++ b/packages/embedder/ai_models/ModelManager.ts @@ -1,118 +1,93 @@ -import {AbstractEmbeddingsModel, EmbeddingModelConfig} from './AbstractEmbeddingsModel' -import {AbstractGenerationModel, GenerationModelConfig} from './AbstractGenerationModel' -import {ModelConfig} from './AbstractModel' +import {AbstractEmbeddingsModel, EmbeddingsTableName} from './AbstractEmbeddingsModel' +import {AbstractGenerationModel} from './AbstractGenerationModel' import OpenAIGeneration from './OpenAIGeneration' import TextEmbeddingsInference from './TextEmbeddingsInference' import TextGenerationInference from './TextGenerationInference' -interface ModelManagerConfig { - embeddingModels: EmbeddingModelConfig[] - generationModels: GenerationModelConfig[] -} - type EmbeddingsModelType = 'text-embeddings-inference' type GenerationModelType = 'openai' | 'text-generation-inference' -export class ModelManager { - embeddingModels: AbstractEmbeddingsModel[] - embeddingModelsMapByTable: {[key: string]: AbstractEmbeddingsModel} - generationModels: AbstractGenerationModel[] +export interface ModelConfig { + model: `${EmbeddingsModelType | GenerationModelType}:${string}` + url: string +} - private isValidConfig( - maybeConfig: Partial - ): maybeConfig is ModelManagerConfig { - if (!maybeConfig.embeddingModels || !Array.isArray(maybeConfig.embeddingModels)) { - throw new Error('Invalid configuration: embedding_models is missing or not an array') - } - if (!maybeConfig.generationModels || !Array.isArray(maybeConfig.generationModels)) { - throw new Error('Invalid configuration: summarization_models is missing or not an array') +export class ModelManager { + embeddingModels: Map + generationModels: Map + + private parseModelEnvVars(envVar: 'AI_EMBEDDING_MODELS' | 'AI_GENERATION_MODELS'): ModelConfig[] { + const envValue = process.env[envVar] + if (!envValue) return [] + let models + try { + models = JSON.parse(envValue) + } catch (e) { + throw new Error(`Invalid Env Var: ${envVar}. Must be a valid JSON`) } - maybeConfig.embeddingModels.forEach((model: ModelConfig) => { - this.isValidModelConfig(model) - }) - - maybeConfig.generationModels.forEach((model: ModelConfig) => { - this.isValidModelConfig(model) - }) - - return true - } - - private isValidModelConfig(model: ModelConfig): model is ModelConfig { - if (typeof model.model !== 'string') { - throw new Error('Invalid ModelConfig: model field should be a string') - } - if (model.url !== undefined && typeof model.url !== 'string') { - throw new Error('Invalid ModelConfig: url field should be a string') + if (!Array.isArray(models)) { + throw new Error(`Invalid Env Var: ${envVar}. Must be an array`) } - - return true - } - - constructor(config: ModelManagerConfig) { - // Validate configuration - this.isValidConfig(config) - - // Initialize embeddings models - this.embeddingModelsMapByTable = {} - this.embeddingModels = config.embeddingModels.map((modelConfig) => { - const [modelType] = modelConfig.model.split(':') as [EmbeddingsModelType, string] - - switch (modelType) { - case 'text-embeddings-inference': { - const embeddingsModel = new TextEmbeddingsInference(modelConfig) - this.embeddingModelsMapByTable[embeddingsModel.tableName] = embeddingsModel - return embeddingsModel + const properties = ['model', 'url'] + models.forEach((model, idx) => { + properties.forEach((prop) => { + if (typeof model[prop] !== 'string') { + throw new Error(`Invalid Env Var: ${envVar}. Invalid "${prop}" at index ${idx}`) } - default: - throw new Error(`unsupported embeddings model '${modelType}'`) - } + }) }) + return models + } - // Initialize summarization models - this.generationModels = config.generationModels.map((modelConfig) => { - const [modelType, _] = modelConfig.model.split(':') as [GenerationModelType, string] - - switch (modelType) { - case 'openai': { - return new OpenAIGeneration(modelConfig) + constructor() { + // Initialize embeddings models + const embeddingConfig = this.parseModelEnvVars('AI_EMBEDDING_MODELS') + this.embeddingModels = new Map( + embeddingConfig.map((modelConfig) => { + const {model, url} = modelConfig + const [modelType, modelId] = model.split(':') as [EmbeddingsModelType, string] + switch (modelType) { + case 'text-embeddings-inference': { + const embeddingsModel = new TextEmbeddingsInference(modelId, url) + return [embeddingsModel.tableName, embeddingsModel] + } + default: + throw new Error(`unsupported embeddings model '${modelType}'`) } - case 'text-generation-inference': { - return new TextGenerationInference(modelConfig) + }) + ) + + // Initialize generation models + const generationConfig = this.parseModelEnvVars('AI_GENERATION_MODELS') + this.generationModels = new Map( + generationConfig.map((modelConfig) => { + const {model, url} = modelConfig + const [modelType, modelId] = model.split(':') as [GenerationModelType, string] + switch (modelType) { + case 'openai': { + return [modelId, new OpenAIGeneration(modelId, url)] + } + case 'text-generation-inference': { + return [modelId, new TextGenerationInference(modelId, url)] + } + default: + throw new Error(`unsupported generation model '${modelType}'`) } - default: - throw new Error(`unsupported summarization model '${modelType}'`) - } - }) + }) + ) } async maybeCreateTables() { - return Promise.all(this.embeddingModels.map((model) => model.createTable())) + return Promise.all([...this.embeddingModels].map(([, model]) => model.createTable())) } } let modelManager: ModelManager | undefined export function getModelManager() { - if (modelManager) return modelManager - const {AI_EMBEDDING_MODELS, AI_GENERATION_MODELS} = process.env - const config: ModelManagerConfig = { - embeddingModels: [], - generationModels: [] - } - try { - config.embeddingModels = AI_EMBEDDING_MODELS && JSON.parse(AI_EMBEDDING_MODELS) - } catch (e) { - throw new Error(`Invalid AI_EMBEDDING_MODELS .env JSON: ${e}`) + if (!modelManager) { + modelManager = new ModelManager() } - try { - config.generationModels = AI_GENERATION_MODELS && JSON.parse(AI_GENERATION_MODELS) - } catch (e) { - throw new Error(`Invalid AI_GENERATION_MODELS .env JSON: ${e}`) - } - - modelManager = new ModelManager(config) - return modelManager } diff --git a/packages/embedder/ai_models/OpenAIGeneration.ts b/packages/embedder/ai_models/OpenAIGeneration.ts index 697160513ae..2bd97a32822 100644 --- a/packages/embedder/ai_models/OpenAIGeneration.ts +++ b/packages/embedder/ai_models/OpenAIGeneration.ts @@ -1,7 +1,6 @@ import OpenAI from 'openai' import { AbstractGenerationModel, - GenerationModelConfig, GenerationModelParams, GenerationOptions } from './AbstractGenerationModel' @@ -19,16 +18,12 @@ const modelIdDefinitions: Record = { } } -function isValidModelId(object: any): object is ModelId { - return Object.keys(modelIdDefinitions).includes(object) -} - export class OpenAIGeneration extends AbstractGenerationModel { private openAIApi: OpenAI | null private modelId!: ModelId - constructor(config: GenerationModelConfig) { - super(config) + constructor(modelId: string, url: string) { + super(modelId, url) if (!process.env.OPEN_AI_API_KEY) { this.openAIApi = null return @@ -73,19 +68,10 @@ export class OpenAIGeneration extends AbstractGenerationModel { throw e } } - protected constructModelParams(config: GenerationModelConfig): GenerationModelParams { - const modelConfigStringSplit = config.model.split(':') - if (modelConfigStringSplit.length != 2) { - throw new Error('OpenAIGeneration model string must be colon-delimited and len 2') - } - - const maybeModelId = modelConfigStringSplit[1] - if (!isValidModelId(maybeModelId)) - throw new Error(`OpenAIGeneration model id unknown: ${maybeModelId}`) - - this.modelId = maybeModelId - - return modelIdDefinitions[maybeModelId] + protected constructModelParams(modelId: string): GenerationModelParams { + const modelParams = modelIdDefinitions[modelId as keyof typeof modelIdDefinitions] + if (!modelParams) throw new Error(`Unknown modelId ${modelId} for OpenAIGeneration`) + return modelParams } } diff --git a/packages/embedder/ai_models/TextEmbeddingsInference.ts b/packages/embedder/ai_models/TextEmbeddingsInference.ts index c30c59e8e8d..1a2a02596b3 100644 --- a/packages/embedder/ai_models/TextEmbeddingsInference.ts +++ b/packages/embedder/ai_models/TextEmbeddingsInference.ts @@ -1,11 +1,7 @@ import createClient from 'openapi-fetch' import sleep from 'parabol-client/utils/sleep' import type {paths} from '../textEmbeddingsnterface' -import { - AbstractEmbeddingsModel, - EmbeddingModelConfig, - EmbeddingModelParams -} from './AbstractEmbeddingsModel' +import {AbstractEmbeddingsModel, EmbeddingModelParams} from './AbstractEmbeddingsModel' export type ModelId = 'BAAI/bge-large-en-v1.5' | 'llmrails/ember-v1' const modelIdDefinitions: Record = { @@ -23,14 +19,10 @@ const modelIdDefinitions: Record = { } } -function isValidModelId(object: any): object is ModelId { - return Object.keys(modelIdDefinitions).includes(object) -} - export class TextEmbeddingsInference extends AbstractEmbeddingsModel { client: ReturnType> - constructor(config: EmbeddingModelConfig) { - super(config) + constructor(modelId: string, url: string) { + super(modelId, url) this.client = createClient({baseUrl: this.url}) } @@ -85,17 +77,10 @@ export class TextEmbeddingsInference extends AbstractEmbeddingsModel { } } - protected constructModelParams(config: EmbeddingModelConfig): EmbeddingModelParams { - const modelConfigStringSplit = config.model.split(':') - if (modelConfigStringSplit.length != 2) { - throw new Error('TextGenerationInference model string must be colon-delimited and len 2') - } - - if (!this.url) throw new Error('TextGenerationInferenceSummarizer model requires url') - const maybeModelId = modelConfigStringSplit[1] - if (!isValidModelId(maybeModelId)) - throw new Error(`TextGenerationInference model id unknown: ${maybeModelId}`) - return modelIdDefinitions[maybeModelId] + protected constructModelParams(modelId: string): EmbeddingModelParams { + const modelParams = modelIdDefinitions[modelId as keyof typeof modelIdDefinitions] + if (!modelParams) throw new Error(`Unknown modelId ${modelId} for TextEmbeddingsInference`) + return modelParams } } diff --git a/packages/embedder/ai_models/TextGenerationInference.ts b/packages/embedder/ai_models/TextGenerationInference.ts index 8fa4ab7cd7b..96d7fdde89f 100644 --- a/packages/embedder/ai_models/TextGenerationInference.ts +++ b/packages/embedder/ai_models/TextGenerationInference.ts @@ -1,6 +1,5 @@ import { AbstractGenerationModel, - GenerationModelConfig, GenerationModelParams, GenerationOptions } from './AbstractGenerationModel' @@ -16,13 +15,9 @@ const modelIdDefinitions: Record = { } } -function isValidModelId(object: any): object is ModelId { - return Object.keys(modelIdDefinitions).includes(object) -} - export class TextGenerationInference extends AbstractGenerationModel { - constructor(config: GenerationModelConfig) { - super(config) + constructor(modelId: string, url: string) { + super(modelId, url) } async summarize(content: string, options: GenerationOptions) { @@ -61,17 +56,10 @@ export class TextGenerationInference extends AbstractGenerationModel { throw e } } - protected constructModelParams(config: GenerationModelConfig): GenerationModelParams { - const modelConfigStringSplit = config.model.split(':') - if (modelConfigStringSplit.length != 2) { - throw new Error('TextGenerationInference model string must be colon-delimited and len 2') - } - - if (!this.url) throw new Error('TextGenerationInferenceSummarizer model requires url') - const maybeModelId = modelConfigStringSplit[1] - if (!isValidModelId(maybeModelId)) - throw new Error(`TextGenerationInference model id unknown: ${maybeModelId}`) - return modelIdDefinitions[maybeModelId] + protected constructModelParams(modelId: string): GenerationModelParams { + const modelParams = modelIdDefinitions[modelId as keyof typeof modelIdDefinitions] + if (!modelParams) throw new Error(`Unknown modelId ${modelId} for TextGenerationInference`) + return modelParams } } diff --git a/packages/embedder/embedder.ts b/packages/embedder/embedder.ts index 628d0d8decd..7971ba4f717 100644 --- a/packages/embedder/embedder.ts +++ b/packages/embedder/embedder.ts @@ -44,7 +44,7 @@ const run = async () => { const redis = new RedisInstance(`embedder_${SERVER_ID}`) const primaryLock = await establishPrimaryEmbedder(redis) const modelManager = getModelManager() - let streams: AsyncIterableIterator | undefined + let streams: AsyncIterableIterator | undefined = undefined const kill = () => { primaryLock?.release() streams?.return?.() diff --git a/packages/embedder/indexing/retrospectiveDiscussionTopic.ts b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts index 4dc0bfaddd5..03af3b510ba 100644 --- a/packages/embedder/indexing/retrospectiveDiscussionTopic.ts +++ b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts @@ -139,8 +139,8 @@ export const createTextFromRetrospectiveDiscussionTopic = async ( if (a.threadParentId === b.threadParentId) { return a.threadSortOrder - b.threadSortOrder } - if (a.threadParentId == null) return 1 - if (b.threadParentId == null) return -1 + if (!a.threadParentId) return 1 + if (!b.threadParentId) return -1 return a.threadParentId > b.threadParentId ? 1 : -1 }) as Comment[] diff --git a/packages/embedder/package.json b/packages/embedder/package.json index b7c510ef5b5..aea9068a6ca 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -10,6 +10,10 @@ "url": "git+https://github.com/ParabolInc/parabol.git" }, "scripts": { + "lint": "eslint --fix . --ext .ts,.tsx", + "lint:check": "eslint . --ext .ts,.tsx", + "prettier": "prettier --config ../../.prettierrc --write \"**/*.{ts,tsx}\"", + "prettier:check": "prettier --config ../../.prettierrc --check \"**/*.{ts,tsx}\"", "typecheck": "yarn tsc --noEmit -p tsconfig.json" }, "bugs": { diff --git a/packages/embedder/processJobEmbed.ts b/packages/embedder/processJobEmbed.ts index d1b895cc640..d7fecf2aab0 100644 --- a/packages/embedder/processJobEmbed.ts +++ b/packages/embedder/processJobEmbed.ts @@ -45,7 +45,7 @@ export const processJobEmbed = async (job: EmbedJob, dataLoader: RootDataLoader) return } - const embeddingModel = modelManager.embeddingModelsMapByTable[model] + const embeddingModel = modelManager.embeddingModels.get(model) if (!embeddingModel) { await failJob(jobId, `embedding model ${model} not available`) return @@ -54,20 +54,18 @@ export const processJobEmbed = async (job: EmbedJob, dataLoader: RootDataLoader) // Exit successfully, we don't want to fail the job because the language is not supported if (!embeddingModel.languages.includes(language!)) return true - const tokens = await embeddingModel.getTokens(fullText) - if (tokens instanceof Error) { + const chunks = await embeddingModel.chunkText(fullText) + if (chunks instanceof Error) { await failJob( jobId, - `unable to get tokens: ${tokens.message}`, + `unable to get tokens: ${chunks.message}`, retryCount < 10 ? new Date(Date.now() + ms('1m')) : null ) return } - const isFullTextTooBig = tokens.length > embeddingModel.maxInputTokens // Cannot use summarization strategy if generation model has same context length as embedding model // We must split the text & not tokens because BERT tokenizer is not trained for linebreaks e.g. \n\n - const chunks = isFullTextTooBig ? embeddingModel.splitText(fullText) : [fullText] - await Promise.all( + const isSuccessful = await Promise.all( chunks.map(async (chunk, chunkNumber) => { const embeddingVector = await embeddingModel.getEmbedding(chunk) if (embeddingVector instanceof Error) { @@ -76,17 +74,17 @@ export const processJobEmbed = async (job: EmbedJob, dataLoader: RootDataLoader) `unable to get embeddings: ${embeddingVector.message}`, retryCount < 10 ? new Date(Date.now() + ms('1m')) : null ) - return + return false } await pg // cast to any because these types won't be available in CI .insertInto(embeddingModel.tableName as EmbeddingsTable) .values({ // TODO is the extra space of a null embedText really worth it?! - embedText: isFullTextTooBig ? chunk : null, + embedText: chunks.length > 1 ? chunk : null, embedding: numberVectorToString(embeddingVector), embeddingsMetadataId, - chunkNumber: isFullTextTooBig ? chunkNumber : null + chunkNumber: chunks.length > 1 ? chunkNumber : null }) .onConflict((oc) => oc.column('embeddingsMetadataId').doUpdateSet((eb) => ({ @@ -95,8 +93,9 @@ export const processJobEmbed = async (job: EmbedJob, dataLoader: RootDataLoader) })) ) .execute() + return true }) ) // Logger.log(`Embedded ${embeddingsMetadataId} -> ${model}`) - return true + return isSuccessful } diff --git a/packages/embedder/textEmbeddingsnterface.d.ts b/packages/embedder/textEmbeddingsnterface.d.ts index 3fdbcac0185..618eea7db91 100644 --- a/packages/embedder/textEmbeddingsnterface.d.ts +++ b/packages/embedder/textEmbeddingsnterface.d.ts @@ -3,102 +3,105 @@ * Do not make direct changes to the file. */ - /** OneOf type helpers */ -type Without = { [P in Exclude]?: never }; -type XOR = (T | U) extends object ? (Without & U) | (Without & T) : T | U; -type OneOf = T extends [infer Only] ? Only : T extends [infer A, infer B, ...infer Rest] ? OneOf<[XOR, ...Rest]> : never; +type Without = {[P in Exclude]?: never} +type XOR = T | U extends object ? (Without & U) | (Without & T) : T | U +type OneOf = T extends [infer Only] + ? Only + : T extends [infer A, infer B, ...infer Rest] + ? OneOf<[XOR, ...Rest]> + : never export interface paths { - "/decode": { + '/decode': { /** * Decode input ids * @description Decode input ids */ - post: operations["decode"]; - }; - "/embed": { + post: operations['decode'] + } + '/embed': { /** * Get Embeddings. Returns a 424 status code if the model is not an embedding model. * @description Get Embeddings. Returns a 424 status code if the model is not an embedding model. */ - post: operations["embed"]; - }; - "/embed_all": { + post: operations['embed'] + } + '/embed_all': { /** * Get all Embeddings without Pooling. * @description Get all Embeddings without Pooling. * Returns a 424 status code if the model is not an embedding model. */ - post: operations["embed_all"]; - }; - "/embed_sparse": { + post: operations['embed_all'] + } + '/embed_sparse': { /** * Get Sparse Embeddings. Returns a 424 status code if the model is not an embedding model with SPLADE pooling. * @description Get Sparse Embeddings. Returns a 424 status code if the model is not an embedding model with SPLADE pooling. */ - post: operations["embed_sparse"]; - }; - "/embeddings": { + post: operations['embed_sparse'] + } + '/embeddings': { /** * OpenAI compatible route. Returns a 424 status code if the model is not an embedding model. * @description OpenAI compatible route. Returns a 424 status code if the model is not an embedding model. */ - post: operations["openai_embed"]; - }; - "/health": { + post: operations['openai_embed'] + } + '/health': { /** * Health check method * @description Health check method */ - get: operations["health"]; - }; - "/info": { + get: operations['health'] + } + '/info': { /** * Text Embeddings Inference endpoint info * @description Text Embeddings Inference endpoint info */ - get: operations["get_model_info"]; - }; - "/metrics": { + get: operations['get_model_info'] + } + '/metrics': { /** * Prometheus metrics scrape endpoint * @description Prometheus metrics scrape endpoint */ - get: operations["metrics"]; - }; - "/predict": { + get: operations['metrics'] + } + '/predict': { /** * Get Predictions. Returns a 424 status code if the model is not a Sequence Classification model * @description Get Predictions. Returns a 424 status code if the model is not a Sequence Classification model */ - post: operations["predict"]; - }; - "/rerank": { + post: operations['predict'] + } + '/rerank': { /** * Get Ranks. Returns a 424 status code if the model is not a Sequence Classification model with * @description Get Ranks. Returns a 424 status code if the model is not a Sequence Classification model with * a single class. */ - post: operations["rerank"]; - }; - "/tokenize": { + post: operations['rerank'] + } + '/tokenize': { /** * Tokenize inputs * @description Tokenize inputs */ - post: operations["tokenize"]; - }; - "/vertex": { + post: operations['tokenize'] + } + '/vertex': { /** * Generate embeddings from a Vertex request * @description Generate embeddings from a Vertex request */ - post: operations["vertex_compatibility"]; - }; + post: operations['vertex_compatibility'] + } } -export type webhooks = Record; +export type webhooks = Record export interface components { schemas: { @@ -109,39 +112,39 @@ export interface components { * } */ id2label: { - [key: string]: string; - }; + [key: string]: string + } /** * @example { * "LABEL": 0 * } */ label2id: { - [key: string]: number; - }; - }; + [key: string]: number + } + } DecodeRequest: { - ids: components["schemas"]["InputIds"]; + ids: components['schemas']['InputIds'] /** * @default true * @example true */ - skip_special_tokens?: boolean; - }; + skip_special_tokens?: boolean + } /** * @example [ * "test" * ] */ - DecodeResponse: string[]; + DecodeResponse: string[] EmbedAllRequest: { - inputs: components["schemas"]["Input"]; + inputs: components['schemas']['Input'] /** * @default false * @example false */ - truncate?: boolean; - }; + truncate?: boolean + } /** * @example [ * [ @@ -153,20 +156,20 @@ export interface components { * ] * ] */ - EmbedAllResponse: number[][][]; + EmbedAllResponse: number[][][] EmbedRequest: { - inputs: components["schemas"]["Input"]; + inputs: components['schemas']['Input'] /** * @default true * @example true */ - normalize?: boolean; + normalize?: boolean /** * @default false * @example false */ - truncate?: boolean; - }; + truncate?: boolean + } /** * @example [ * [ @@ -176,74 +179,80 @@ export interface components { * ] * ] */ - EmbedResponse: number[][]; + EmbedResponse: number[][] EmbedSparseRequest: { - inputs: components["schemas"]["Input"]; + inputs: components['schemas']['Input'] /** * @default false * @example false */ - truncate?: boolean; - }; - EmbedSparseResponse: components["schemas"]["SparseValue"][][]; + truncate?: boolean + } + EmbedSparseResponse: components['schemas']['SparseValue'][][] EmbeddingModel: { /** @example cls */ - pooling: string; - }; + pooling: string + } ErrorResponse: { - error: string; - error_type: components["schemas"]["ErrorType"]; - }; + error: string + error_type: components['schemas']['ErrorType'] + } /** @enum {string} */ - ErrorType: "Unhealthy" | "Backend" | "Overloaded" | "Validation" | "Tokenizer"; + ErrorType: 'Unhealthy' | 'Backend' | 'Overloaded' | 'Validation' | 'Tokenizer' Info: { /** @example null */ - docker_label?: string | null; + docker_label?: string | null /** * @default null * @example null */ - max_batch_requests?: number | null; + max_batch_requests?: number | null /** @example 2048 */ - max_batch_tokens: number; + max_batch_tokens: number /** @example 32 */ - max_client_batch_size: number; + max_client_batch_size: number /** * @description Router Parameters * @example 128 */ - max_concurrent_requests: number; + max_concurrent_requests: number /** @example 512 */ - max_input_length: number; + max_input_length: number /** @example float16 */ - model_dtype: string; + model_dtype: string /** * @description Model info * @example thenlper/gte-base */ - model_id: string; + model_id: string /** @example fca14538aa9956a46526bd1d0d11d69e19b5a101 */ - model_sha?: string | null; - model_type: components["schemas"]["ModelType"]; + model_sha?: string | null + model_type: components['schemas']['ModelType'] /** @example null */ - sha?: string | null; + sha?: string | null /** @example 4 */ - tokenization_workers: number; + tokenization_workers: number /** * @description Router Info * @example 0.5.0 */ - version: string; - }; - Input: string | string[]; - InputIds: number[] | number[][]; - ModelType: OneOf<[{ - classifier: components["schemas"]["ClassifierModel"]; - }, { - embedding: components["schemas"]["EmbeddingModel"]; - }, { - reranker: components["schemas"]["ClassifierModel"]; - }]>; + version: string + } + Input: string | string[] + InputIds: number[] | number[][] + ModelType: OneOf< + [ + { + classifier: components['schemas']['ClassifierModel'] + }, + { + embedding: components['schemas']['EmbeddingModel'] + }, + { + reranker: components['schemas']['ClassifierModel'] + } + ] + > OpenAICompatEmbedding: { /** * @example [ @@ -252,135 +261,135 @@ export interface components { * 2 * ] */ - embedding: number[]; + embedding: number[] /** @example 0 */ - index: number; + index: number /** @example embedding */ - object: string; - }; + object: string + } OpenAICompatErrorResponse: { /** Format: int32 */ - code: number; - error_type: components["schemas"]["ErrorType"]; - message: string; - }; + code: number + error_type: components['schemas']['ErrorType'] + message: string + } OpenAICompatRequest: { - input: components["schemas"]["Input"]; + input: components['schemas']['Input'] /** @example null */ - model?: string | null; + model?: string | null /** @example null */ - user?: string | null; - }; + user?: string | null + } OpenAICompatResponse: { - data: components["schemas"]["OpenAICompatEmbedding"][]; + data: components['schemas']['OpenAICompatEmbedding'][] /** @example thenlper/gte-base */ - model: string; + model: string /** @example list */ - object: string; - usage: components["schemas"]["OpenAICompatUsage"]; - }; + object: string + usage: components['schemas']['OpenAICompatUsage'] + } OpenAICompatUsage: { /** @example 512 */ - prompt_tokens: number; + prompt_tokens: number /** @example 512 */ - total_tokens: number; - }; + total_tokens: number + } /** * @description Model input. Can be either a single string, a pair of strings or a batch of mixed single and pairs of strings. * @example What is Deep Learning? */ - PredictInput: string | string[] | string[][]; + PredictInput: string | string[] | string[][] PredictRequest: { - inputs: components["schemas"]["PredictInput"]; + inputs: components['schemas']['PredictInput'] /** * @default false * @example false */ - raw_scores?: boolean; + raw_scores?: boolean /** * @default false * @example false */ - truncate?: boolean; - }; - PredictResponse: components["schemas"]["Prediction"][] | components["schemas"]["Prediction"][][]; + truncate?: boolean + } + PredictResponse: components['schemas']['Prediction'][] | components['schemas']['Prediction'][][] Prediction: { /** @example admiration */ - label: string; + label: string /** * Format: float * @example 0.5 */ - score: number; - }; + score: number + } Rank: { /** @example 0 */ - index: number; + index: number /** * Format: float * @example 1.0 */ - score: number; + score: number /** * @default null * @example Deep Learning is ... */ - text?: string | null; - }; + text?: string | null + } RerankRequest: { /** @example What is Deep Learning? */ - query: string; + query: string /** * @default false * @example false */ - raw_scores?: boolean; + raw_scores?: boolean /** * @default false * @example false */ - return_text?: boolean; + return_text?: boolean /** * @example [ * "Deep Learning is ..." * ] */ - texts: string[]; + texts: string[] /** * @default false * @example false */ - truncate?: boolean; - }; - RerankResponse: components["schemas"]["Rank"][]; + truncate?: boolean + } + RerankResponse: components['schemas']['Rank'][] SimpleToken: { /** * Format: int32 * @example 0 */ - id: number; + id: number /** @example false */ - special: boolean; + special: boolean /** @example 0 */ - start?: number | null; + start?: number | null /** @example 2 */ - stop?: number | null; + stop?: number | null /** @example test */ - text: string; - }; + text: string + } SparseValue: { - index: number; + index: number /** Format: float */ - value: number; - }; + value: number + } TokenizeRequest: { /** * @default true * @example true */ - add_special_tokens?: boolean; - inputs: components["schemas"]["Input"]; - }; + add_special_tokens?: boolean + inputs: components['schemas']['Input'] + } /** * @example [ * [ @@ -394,69 +403,80 @@ export interface components { * ] * ] */ - TokenizeResponse: components["schemas"]["SimpleToken"][][]; - VertexInstance: (components["schemas"]["EmbedRequest"] & { - /** @enum {string} */ - type: "embed"; - }) | (components["schemas"]["EmbedAllRequest"] & { - /** @enum {string} */ - type: "embed_all"; - }) | (components["schemas"]["EmbedSparseRequest"] & { - /** @enum {string} */ - type: "embed_sparse"; - }) | (components["schemas"]["PredictRequest"] & { - /** @enum {string} */ - type: "predict"; - }) | (components["schemas"]["RerankRequest"] & { - /** @enum {string} */ - type: "rerank"; - }) | (components["schemas"]["TokenizeRequest"] & { - /** @enum {string} */ - type: "tokenize"; - }); + TokenizeResponse: components['schemas']['SimpleToken'][][] + VertexInstance: + | (components['schemas']['EmbedRequest'] & { + /** @enum {string} */ + type: 'embed' + }) + | (components['schemas']['EmbedAllRequest'] & { + /** @enum {string} */ + type: 'embed_all' + }) + | (components['schemas']['EmbedSparseRequest'] & { + /** @enum {string} */ + type: 'embed_sparse' + }) + | (components['schemas']['PredictRequest'] & { + /** @enum {string} */ + type: 'predict' + }) + | (components['schemas']['RerankRequest'] & { + /** @enum {string} */ + type: 'rerank' + }) + | (components['schemas']['TokenizeRequest'] & { + /** @enum {string} */ + type: 'tokenize' + }) VertexRequest: { - instances: components["schemas"]["VertexInstance"][]; - }; - VertexResponse: components["schemas"]["VertexResponseInstance"][]; - VertexResponseInstance: { - result: components["schemas"]["EmbedResponse"]; - /** @enum {string} */ - type: "embed"; - } | { - result: components["schemas"]["EmbedAllResponse"]; - /** @enum {string} */ - type: "embed_all"; - } | { - result: components["schemas"]["EmbedSparseResponse"]; - /** @enum {string} */ - type: "embed_sparse"; - } | { - result: components["schemas"]["PredictResponse"]; - /** @enum {string} */ - type: "predict"; - } | { - result: components["schemas"]["RerankResponse"]; - /** @enum {string} */ - type: "rerank"; - } | { - result: components["schemas"]["TokenizeResponse"]; - /** @enum {string} */ - type: "tokenize"; - }; - }; - responses: never; - parameters: never; - requestBodies: never; - headers: never; - pathItems: never; + instances: components['schemas']['VertexInstance'][] + } + VertexResponse: components['schemas']['VertexResponseInstance'][] + VertexResponseInstance: + | { + result: components['schemas']['EmbedResponse'] + /** @enum {string} */ + type: 'embed' + } + | { + result: components['schemas']['EmbedAllResponse'] + /** @enum {string} */ + type: 'embed_all' + } + | { + result: components['schemas']['EmbedSparseResponse'] + /** @enum {string} */ + type: 'embed_sparse' + } + | { + result: components['schemas']['PredictResponse'] + /** @enum {string} */ + type: 'predict' + } + | { + result: components['schemas']['RerankResponse'] + /** @enum {string} */ + type: 'rerank' + } + | { + result: components['schemas']['TokenizeResponse'] + /** @enum {string} */ + type: 'tokenize' + } + } + responses: never + parameters: never + requestBodies: never + headers: never + pathItems: never } -export type $defs = Record; +export type $defs = Record -export type external = Record; +export type external = Record export interface operations { - /** * Decode input ids * @description Decode input ids @@ -464,24 +484,24 @@ export interface operations { decode: { requestBody: { content: { - "application/json": components["schemas"]["DecodeRequest"]; - }; - }; + 'application/json': components['schemas']['DecodeRequest'] + } + } responses: { /** @description Decoded ids */ 200: { content: { - "application/json": components["schemas"]["DecodeResponse"]; - }; - }; + 'application/json': components['schemas']['DecodeResponse'] + } + } /** @description Tokenization error */ 422: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } /** * Get Embeddings. Returns a 424 status code if the model is not an embedding model. * @description Get Embeddings. Returns a 424 status code if the model is not an embedding model. @@ -489,42 +509,42 @@ export interface operations { embed: { requestBody: { content: { - "application/json": components["schemas"]["EmbedRequest"]; - }; - }; + 'application/json': components['schemas']['EmbedRequest'] + } + } responses: { /** @description Embeddings */ 200: { content: { - "application/json": components["schemas"]["EmbedResponse"]; - }; - }; + 'application/json': components['schemas']['EmbedResponse'] + } + } /** @description Batch size error */ 413: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Tokenization error */ 422: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Embedding Error */ 424: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Model is overloaded */ 429: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } /** * Get all Embeddings without Pooling. * @description Get all Embeddings without Pooling. @@ -533,42 +553,42 @@ export interface operations { embed_all: { requestBody: { content: { - "application/json": components["schemas"]["EmbedAllRequest"]; - }; - }; + 'application/json': components['schemas']['EmbedAllRequest'] + } + } responses: { /** @description Embeddings */ 200: { content: { - "application/json": components["schemas"]["EmbedAllResponse"]; - }; - }; + 'application/json': components['schemas']['EmbedAllResponse'] + } + } /** @description Batch size error */ 413: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Tokenization error */ 422: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Embedding Error */ 424: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Model is overloaded */ 429: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } /** * Get Sparse Embeddings. Returns a 424 status code if the model is not an embedding model with SPLADE pooling. * @description Get Sparse Embeddings. Returns a 424 status code if the model is not an embedding model with SPLADE pooling. @@ -576,42 +596,42 @@ export interface operations { embed_sparse: { requestBody: { content: { - "application/json": components["schemas"]["EmbedSparseRequest"]; - }; - }; + 'application/json': components['schemas']['EmbedSparseRequest'] + } + } responses: { /** @description Embeddings */ 200: { content: { - "application/json": components["schemas"]["EmbedSparseResponse"]; - }; - }; + 'application/json': components['schemas']['EmbedSparseResponse'] + } + } /** @description Batch size error */ 413: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Tokenization error */ 422: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Embedding Error */ 424: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Model is overloaded */ 429: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } /** * OpenAI compatible route. Returns a 424 status code if the model is not an embedding model. * @description OpenAI compatible route. Returns a 424 status code if the model is not an embedding model. @@ -619,42 +639,42 @@ export interface operations { openai_embed: { requestBody: { content: { - "application/json": components["schemas"]["OpenAICompatRequest"]; - }; - }; + 'application/json': components['schemas']['OpenAICompatRequest'] + } + } responses: { /** @description Embeddings */ 200: { content: { - "application/json": components["schemas"]["OpenAICompatResponse"]; - }; - }; + 'application/json': components['schemas']['OpenAICompatResponse'] + } + } /** @description Batch size error */ 413: { content: { - "application/json": components["schemas"]["OpenAICompatErrorResponse"]; - }; - }; + 'application/json': components['schemas']['OpenAICompatErrorResponse'] + } + } /** @description Tokenization error */ 422: { content: { - "application/json": components["schemas"]["OpenAICompatErrorResponse"]; - }; - }; + 'application/json': components['schemas']['OpenAICompatErrorResponse'] + } + } /** @description Embedding Error */ 424: { content: { - "application/json": components["schemas"]["OpenAICompatErrorResponse"]; - }; - }; + 'application/json': components['schemas']['OpenAICompatErrorResponse'] + } + } /** @description Model is overloaded */ 429: { content: { - "application/json": components["schemas"]["OpenAICompatErrorResponse"]; - }; - }; - }; - }; + 'application/json': components['schemas']['OpenAICompatErrorResponse'] + } + } + } + } /** * Health check method * @description Health check method @@ -663,16 +683,16 @@ export interface operations { responses: { /** @description Everything is working fine */ 200: { - content: never; - }; + content: never + } /** @description Text embeddings Inference is down */ 503: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } /** * Text Embeddings Inference endpoint info * @description Text Embeddings Inference endpoint info @@ -682,11 +702,11 @@ export interface operations { /** @description Served model info */ 200: { content: { - "application/json": components["schemas"]["Info"]; - }; - }; - }; - }; + 'application/json': components['schemas']['Info'] + } + } + } + } /** * Prometheus metrics scrape endpoint * @description Prometheus metrics scrape endpoint @@ -696,11 +716,11 @@ export interface operations { /** @description Prometheus Metrics */ 200: { content: { - "text/plain": string; - }; - }; - }; - }; + 'text/plain': string + } + } + } + } /** * Get Predictions. Returns a 424 status code if the model is not a Sequence Classification model * @description Get Predictions. Returns a 424 status code if the model is not a Sequence Classification model @@ -708,42 +728,42 @@ export interface operations { predict: { requestBody: { content: { - "application/json": components["schemas"]["PredictRequest"]; - }; - }; + 'application/json': components['schemas']['PredictRequest'] + } + } responses: { /** @description Predictions */ 200: { content: { - "application/json": components["schemas"]["PredictResponse"]; - }; - }; + 'application/json': components['schemas']['PredictResponse'] + } + } /** @description Batch size error */ 413: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Tokenization error */ 422: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Prediction Error */ 424: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Model is overloaded */ 429: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } /** * Get Ranks. Returns a 424 status code if the model is not a Sequence Classification model with * @description Get Ranks. Returns a 424 status code if the model is not a Sequence Classification model with @@ -752,42 +772,42 @@ export interface operations { rerank: { requestBody: { content: { - "application/json": components["schemas"]["RerankRequest"]; - }; - }; + 'application/json': components['schemas']['RerankRequest'] + } + } responses: { /** @description Ranks */ 200: { content: { - "application/json": components["schemas"]["RerankResponse"]; - }; - }; + 'application/json': components['schemas']['RerankResponse'] + } + } /** @description Batch size error */ 413: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Tokenization error */ 422: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Rerank Error */ 424: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Model is overloaded */ 429: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } /** * Tokenize inputs * @description Tokenize inputs @@ -795,24 +815,24 @@ export interface operations { tokenize: { requestBody: { content: { - "application/json": components["schemas"]["TokenizeRequest"]; - }; - }; + 'application/json': components['schemas']['TokenizeRequest'] + } + } responses: { /** @description Tokenized ids */ 200: { content: { - "application/json": components["schemas"]["TokenizeResponse"]; - }; - }; + 'application/json': components['schemas']['TokenizeResponse'] + } + } /** @description Tokenization error */ 422: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } /** * Generate embeddings from a Vertex request * @description Generate embeddings from a Vertex request @@ -820,38 +840,38 @@ export interface operations { vertex_compatibility: { requestBody: { content: { - "application/json": components["schemas"]["VertexRequest"]; - }; - }; + 'application/json': components['schemas']['VertexRequest'] + } + } responses: { /** @description Results */ 200: { - content: never; - }; + content: never + } /** @description Batch size error */ 413: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Tokenization error */ 422: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Error */ 424: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } /** @description Model is overloaded */ 429: { content: { - "application/json": components["schemas"]["ErrorResponse"]; - }; - }; - }; - }; + 'application/json': components['schemas']['ErrorResponse'] + } + } + } + } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 893db5ee8df..8a344b6462e 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -11,8 +11,6 @@ }, "devDependencies": { "@playwright/test": "^1.34.3", - "eslint": "^8.8.0", - "eslint-config-prettier": "^8.5.0", "lint-staged": "^12.3.3", "ts-app-env": "^1.4.2", "typescript": "^5.3.3" diff --git a/packages/server/package.json b/packages/server/package.json index 8cc20a60e6c..8afddae459c 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -50,8 +50,6 @@ "clean-webpack-plugin": "^3.0.0", "copy-webpack-plugin": "7.0.0", "css-loader": "5.0.1", - "eslint": "^8.2.0", - "eslint-config-prettier": "^8.5.0", "faker": "^5.5.3", "file-loader": "6.2.0", "graphql-typed": "^0.4.1", diff --git a/yarn.lock b/yarn.lock index e6fb56f52bf..6701230bae9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@aashutoshrathi/word-wrap@^1.2.3": + version "1.2.6" + resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf" + integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA== + "@amplitude/analytics-browser@^2.2.3": version "2.2.3" resolved "https://registry.yarnpkg.com/@amplitude/analytics-browser/-/analytics-browser-2.2.3.tgz#7dbe4ad2ada7facfcf21aac4b47314f8e4c2903c" @@ -3166,33 +3171,38 @@ resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb" integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg== -"@eslint-community/eslint-utils@^4.4.0": +"@eslint-community/eslint-utils@^4.2.0", "@eslint-community/eslint-utils@^4.4.0": version "4.4.0" resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59" integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA== dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.5.1": +"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": version "4.10.0" resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== -"@eslint/eslintrc@^1.0.5": - version "1.0.5" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.0.5.tgz#33f1b838dbf1f923bfa517e008362b78ddbbf318" - integrity sha512-BLxsnmK3KyPunz5wmCCpqy0YelEoxxGmH73Is+Z74oOTMtExcjkr3dDR6quwrjh1YspA8DH9gnX1o069KiS9AQ== +"@eslint/eslintrc@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" + integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.2.0" - globals "^13.9.0" - ignore "^4.0.6" + espree "^9.6.0" + globals "^13.19.0" + ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" - minimatch "^3.0.4" + minimatch "^3.1.2" strip-json-comments "^3.1.1" +"@eslint/js@8.57.0": + version "8.57.0" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" + integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== + "@exodus/schemasafe@^1.0.0-rc.2": version "1.0.1" resolved "https://registry.yarnpkg.com/@exodus/schemasafe/-/schemasafe-1.0.1.tgz#e4e2d86ae176b7c96fbff033f3b1a8b1cfd390fb" @@ -3833,24 +3843,29 @@ dependencies: client-only "^0.0.1" -"@humanwhocodes/config-array@^0.9.2": - version "0.9.2" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.9.2.tgz#68be55c737023009dfc5fe245d51181bb6476914" - integrity sha512-UXOuFCGcwciWckOpmfKDq/GyhlTf9pN/BzG//x8p8zTOFEcGuA68ANXheFS0AGvy3qgZqLBUkMs7hqzqCKOVwA== +"@humanwhocodes/config-array@^0.11.14": + version "0.11.14" + resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" + integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== dependencies: - "@humanwhocodes/object-schema" "^1.2.1" - debug "^4.1.1" - minimatch "^3.0.4" + "@humanwhocodes/object-schema" "^2.0.2" + debug "^4.3.1" + minimatch "^3.0.5" + +"@humanwhocodes/module-importer@^1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" + integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA== "@humanwhocodes/momoa@^2.0.3": version "2.0.4" resolved "https://registry.yarnpkg.com/@humanwhocodes/momoa/-/momoa-2.0.4.tgz#8b9e7a629651d15009c3587d07a222deeb829385" integrity sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA== -"@humanwhocodes/object-schema@^1.2.1": - version "1.2.1" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" - integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@humanwhocodes/object-schema@^2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" + integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== "@hutson/parse-repository-url@^3.0.0": version "3.0.2" @@ -5065,7 +5080,7 @@ resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== -"@nodelib/fs.walk@^1.2.3": +"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8": version "1.2.8" resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== @@ -8208,16 +8223,16 @@ resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.11.tgz#d654a112973f5e004bf8438122bd7e56a8e5cd7e" integrity sha512-9cwk3c87qQKZrT251EDoibiYRILjCmxBvvcb4meofCmx1vdnNcR9gyildy5vOHASpOKMsn42CugxUvcwK5eu1g== -"@typescript-eslint/eslint-plugin@^6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.21.0.tgz#30830c1ca81fd5f3c2714e524c4303e0194f9cd3" - integrity sha512-oy9+hTPCUFpngkEZUSzbf9MxI65wbKFoQYsgPdILTfbUldp5ovUuphZVe4i30emU9M/kP+T64Di0mxl7dSw3MA== +"@typescript-eslint/eslint-plugin@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz#de61c3083842fc6ac889d2fc83c9a96b55ab8328" + integrity sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw== dependencies: "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "6.21.0" - "@typescript-eslint/type-utils" "6.21.0" - "@typescript-eslint/utils" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" + "@typescript-eslint/scope-manager" "7.4.0" + "@typescript-eslint/type-utils" "7.4.0" + "@typescript-eslint/utils" "7.4.0" + "@typescript-eslint/visitor-keys" "7.4.0" debug "^4.3.4" graphemer "^1.4.0" ignore "^5.2.4" @@ -8225,47 +8240,47 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/parser@^6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-6.21.0.tgz#af8fcf66feee2edc86bc5d1cf45e33b0630bf35b" - integrity sha512-tbsV1jPne5CkFQCgPBcDOt30ItF7aJoZL997JSF7MhGQqOeT3svWRYxiqlfA5RUdlHN6Fi+EI9bxqbdyAUZjYQ== +"@typescript-eslint/parser@^7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.4.0.tgz#540f4321de1e52b886c0fa68628af1459954c1f1" + integrity sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ== dependencies: - "@typescript-eslint/scope-manager" "6.21.0" - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/typescript-estree" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" + "@typescript-eslint/scope-manager" "7.4.0" + "@typescript-eslint/types" "7.4.0" + "@typescript-eslint/typescript-estree" "7.4.0" + "@typescript-eslint/visitor-keys" "7.4.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.21.0.tgz#ea8a9bfc8f1504a6ac5d59a6df308d3a0630a2b1" - integrity sha512-OwLUIWZJry80O99zvqXVEioyniJMa+d2GrqpUTqi5/v5D5rOrppJVBPa0yKCblcigC0/aYAzxxqQ1B+DS2RYsg== +"@typescript-eslint/scope-manager@7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz#acfc69261f10ece7bf7ece1734f1713392c3655f" + integrity sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw== dependencies: - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" + "@typescript-eslint/types" "7.4.0" + "@typescript-eslint/visitor-keys" "7.4.0" -"@typescript-eslint/type-utils@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.21.0.tgz#6473281cfed4dacabe8004e8521cee0bd9d4c01e" - integrity sha512-rZQI7wHfao8qMX3Rd3xqeYSMCL3SoiSQLBATSiVKARdFGCYSRvmViieZjqc58jKgs8Y8i9YvVVhRbHSTA4VBag== +"@typescript-eslint/type-utils@7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz#cfcaab21bcca441c57da5d3a1153555e39028cbd" + integrity sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw== dependencies: - "@typescript-eslint/typescript-estree" "6.21.0" - "@typescript-eslint/utils" "6.21.0" + "@typescript-eslint/typescript-estree" "7.4.0" + "@typescript-eslint/utils" "7.4.0" debug "^4.3.4" ts-api-utils "^1.0.1" -"@typescript-eslint/types@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.21.0.tgz#205724c5123a8fef7ecd195075fa6e85bac3436d" - integrity sha512-1kFmZ1rOm5epu9NZEZm1kckCDGj5UJEf7P1kliH4LKu/RkwpsfqqGmY2OOcUs18lSlQBKLDYBOGxRVtrMN5lpg== +"@typescript-eslint/types@7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.4.0.tgz#ee9dafa75c99eaee49de6dcc9348b45d354419b6" + integrity sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw== -"@typescript-eslint/typescript-estree@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.21.0.tgz#c47ae7901db3b8bddc3ecd73daff2d0895688c46" - integrity sha512-6npJTkZcO+y2/kr+z0hc4HwNfrrP4kNYh57ek7yCNlrBjWQ1Y0OS7jiZTkgumrvkX5HkEKXFZkkdFNkaW2wmUQ== +"@typescript-eslint/typescript-estree@7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz#12dbcb4624d952f72c10a9f4431284fca24624f4" + integrity sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg== dependencies: - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/visitor-keys" "6.21.0" + "@typescript-eslint/types" "7.4.0" + "@typescript-eslint/visitor-keys" "7.4.0" debug "^4.3.4" globby "^11.1.0" is-glob "^4.0.3" @@ -8273,27 +8288,32 @@ semver "^7.5.4" ts-api-utils "^1.0.1" -"@typescript-eslint/utils@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.21.0.tgz#4714e7a6b39e773c1c8e97ec587f520840cd8134" - integrity sha512-NfWVaC8HP9T8cbKQxHcsJBY5YE1O33+jpMwN45qzWWaPDZgLIbo12toGMWnmhvCpd3sIxkpDw3Wv1B3dYrbDQQ== +"@typescript-eslint/utils@7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.4.0.tgz#d889a0630cab88bddedaf7c845c64a00576257bd" + integrity sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg== dependencies: "@eslint-community/eslint-utils" "^4.4.0" "@types/json-schema" "^7.0.12" "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "6.21.0" - "@typescript-eslint/types" "6.21.0" - "@typescript-eslint/typescript-estree" "6.21.0" + "@typescript-eslint/scope-manager" "7.4.0" + "@typescript-eslint/types" "7.4.0" + "@typescript-eslint/typescript-estree" "7.4.0" semver "^7.5.4" -"@typescript-eslint/visitor-keys@6.21.0": - version "6.21.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.21.0.tgz#87a99d077aa507e20e238b11d56cc26ade45fe47" - integrity sha512-JJtkDduxLi9bivAB+cYOVMtbkqdPOhZ+ZI5LC47MIRrDV4Yn2o+ZnW10Nkmr28xRpSpdJ6Sm42Hjf2+REYXm0A== +"@typescript-eslint/visitor-keys@7.4.0": + version "7.4.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz#0c8ff2c1f8a6fe8d7d1a57ebbd4a638e86a60a94" + integrity sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA== dependencies: - "@typescript-eslint/types" "6.21.0" + "@typescript-eslint/types" "7.4.0" eslint-visitor-keys "^3.4.1" +"@ungap/structured-clone@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" + integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + "@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5": version "1.11.5" resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.5.tgz#6e818036b94548c1fb53b754b5cae3c9b208281c" @@ -8690,7 +8710,7 @@ ajv-keywords@^5.0.0: dependencies: fast-deep-equal "^3.1.3" -ajv@^6.10.0, ajv@^6.12.4, ajv@^6.12.5: +ajv@^6.12.4, ajv@^6.12.5: version "6.12.6" resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4" integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g== @@ -9722,9 +9742,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001580, caniuse-lite@~1.0.0: - version "1.0.30001600" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001600.tgz#93a3ee17a35aa6a9f0c6ef1b2ab49507d1ab9079" - integrity sha512-+2S9/2JFhYmYaDpZvo0lKkfvuKIglrx68MwOBqMGHhQsNkLjB5xtc/TGoEPs+MxjSyN/72qer2g97nzR641mOQ== + version "1.0.30001603" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001603.tgz#605046a5bdc95ba4a92496d67e062522dce43381" + integrity sha512-iL2iSS0eDILMb9n5yKQoTBim9jMZ0Yrk8g0N9K7UzYyWnfIKzXBZD5ngpM37ZcL/cv0Mli8XtVMRYMQAfFpi5Q== capital-case@^1.0.4: version "1.0.4" @@ -11661,10 +11681,10 @@ escodegen@^2.0.0, escodegen@^2.1.0: optionalDependencies: source-map "~0.6.1" -eslint-config-prettier@^8.5.0: - version "8.5.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz#5a81680ec934beca02c7b1a61cf8ca34b66feab1" - integrity sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q== +eslint-config-prettier@^9.1.0: + version "9.1.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-9.1.0.tgz#31af3d94578645966c082fcb71a5846d3c94867f" + integrity sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw== eslint-plugin-emotion@^10.0.14: version "10.0.27" @@ -11704,71 +11724,62 @@ eslint-scope@5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.1.0: - version "7.1.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.1.0.tgz#c1f6ea30ac583031f203d65c73e723b01298f153" - integrity sha512-aWwkhnS0qAXqNOgKOK0dJ2nvzEbhEvpy8OlJ9kZ0FeZnA6zpjv1/Vei+puGFFX7zkPCkHHXb7IDX3A+7yPrRWg== +eslint-scope@^7.2.2: + version "7.2.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" + integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-utils@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-3.0.0.tgz#8aebaface7345bb33559db0a1f13a1d2d48c3672" - integrity sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA== - dependencies: - eslint-visitor-keys "^2.0.0" - -eslint-visitor-keys@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz#f65328259305927392c938ed44eb0a5c9b2bd303" - integrity sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw== - -eslint-visitor-keys@^3.2.0, eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1: +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.2.0, eslint@^8.8.0: - version "8.8.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.8.0.tgz#9762b49abad0cb4952539ffdb0a046392e571a2d" - integrity sha512-H3KXAzQGBH1plhYS3okDix2ZthuYJlQQEGE5k0IKuEqUSiyu4AmxxlJ2MtTYeJ3xB4jDhcYCwGOg2TXYdnDXlQ== - dependencies: - "@eslint/eslintrc" "^1.0.5" - "@humanwhocodes/config-array" "^0.9.2" - ajv "^6.10.0" +eslint@^8.57.0: + version "8.57.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" + integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== + dependencies: + "@eslint-community/eslint-utils" "^4.2.0" + "@eslint-community/regexpp" "^4.6.1" + "@eslint/eslintrc" "^2.1.4" + "@eslint/js" "8.57.0" + "@humanwhocodes/config-array" "^0.11.14" + "@humanwhocodes/module-importer" "^1.0.1" + "@nodelib/fs.walk" "^1.2.8" + "@ungap/structured-clone" "^1.2.0" + ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.3.2" doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.1.0" - eslint-utils "^3.0.0" - eslint-visitor-keys "^3.2.0" - espree "^9.3.0" - esquery "^1.4.0" + eslint-scope "^7.2.2" + eslint-visitor-keys "^3.4.3" + espree "^9.6.1" + esquery "^1.4.2" esutils "^2.0.2" fast-deep-equal "^3.1.3" file-entry-cache "^6.0.1" - functional-red-black-tree "^1.0.1" - glob-parent "^6.0.1" - globals "^13.6.0" + find-up "^5.0.0" + glob-parent "^6.0.2" + globals "^13.19.0" + graphemer "^1.4.0" ignore "^5.2.0" - import-fresh "^3.0.0" imurmurhash "^0.1.4" is-glob "^4.0.0" + is-path-inside "^3.0.3" js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" - minimatch "^3.0.4" + minimatch "^3.1.2" natural-compare "^1.4.0" - optionator "^0.9.1" - regexpp "^3.2.0" + optionator "^0.9.3" strip-ansi "^6.0.1" - strip-json-comments "^3.1.0" text-table "^0.2.0" - v8-compile-cache "^2.0.3" esniff@^2.0.1: version "2.0.1" @@ -11780,7 +11791,7 @@ esniff@^2.0.1: event-emitter "^0.3.5" type "^2.7.2" -espree@^9.0.0, espree@^9.2.0, espree@^9.3.0: +espree@^9.0.0, espree@^9.6.0, espree@^9.6.1: version "9.6.1" resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== @@ -11794,13 +11805,20 @@ esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== -esquery@^1.0.1, esquery@^1.4.0: +esquery@^1.0.1: version "1.4.0" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5" integrity sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w== dependencies: estraverse "^5.1.0" +esquery@^1.4.2: + version "1.5.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" + integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== + dependencies: + estraverse "^5.1.0" + esrecurse@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921" @@ -12517,11 +12535,6 @@ function-bind@^1.1.2: resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== -functional-red-black-tree@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz#1b0ab3bd553b2a0d6399d29c0e3ea0b252078327" - integrity sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc= - fuzzy@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/fuzzy/-/fuzzy-0.1.3.tgz#4c76ec2ff0ac1a36a9dccf9a00df8623078d4ed8" @@ -12852,10 +12865,10 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.6.0, globals@^13.9.0: - version "13.12.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.12.0.tgz#4d733760304230a0082ed96e21e5c565f898089e" - integrity sha512-uS8X6lSKN2JumVoXrbUz+uG4BYG+eiawqm3qFcT7ammfbUHeCBoJMlHcec/S3krSk73/AE/f0szYFmgAA3kYZg== +globals@^13.19.0: + version "13.24.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" + integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== dependencies: type-fest "^0.20.2" @@ -13520,11 +13533,6 @@ ignore-walk@^5.0.1: dependencies: minimatch "^5.0.1" -ignore@^4.0.6: - version "4.0.6" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" - integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== - ignore@^5.0.4, ignore@^5.1.4, ignore@^5.2.0: version "5.2.0" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" @@ -13560,7 +13568,7 @@ import-fresh@^2.0.0: caller-path "^2.0.0" resolve-from "^3.0.0" -import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1: +import-fresh@^3.1.0, import-fresh@^3.2.1: version "3.3.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b" integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw== @@ -13954,6 +13962,11 @@ is-path-inside@^2.1.0: dependencies: path-is-inside "^1.0.2" +is-path-inside@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" + integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== + is-plain-obj@^1.0.0, is-plain-obj@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/is-plain-obj/-/is-plain-obj-1.1.0.tgz#71a50c8429dfca773c92a390a4a03b39fcd51d3e" @@ -15912,7 +15925,7 @@ minimatch@9.0.3: dependencies: brace-expansion "^2.0.1" -minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5: +minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw== @@ -16937,17 +16950,17 @@ optionator@^0.8.1: type-check "~0.3.2" word-wrap "~1.2.3" -optionator@^0.9.1: - version "0.9.1" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.1.tgz#4f236a6373dae0566a6d43e1326674f50c291499" - integrity sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw== +optionator@^0.9.3: + version "0.9.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" + integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg== dependencies: + "@aashutoshrathi/word-wrap" "^1.2.3" deep-is "^0.1.3" fast-levenshtein "^2.0.6" levn "^0.4.1" prelude-ls "^1.2.1" type-check "^0.4.0" - word-wrap "^1.2.3" ora@5.4.1, ora@^5.4.1: version "5.4.1" @@ -18850,11 +18863,6 @@ regexp.prototype.flags@^1.3.1: call-bind "^1.0.2" define-properties "^1.1.3" -regexpp@^3.2.0: - version "3.2.0" - resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" - integrity sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg== - regexpu-core@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-5.1.0.tgz#2f8504c3fd0ebe11215783a41541e21c79942c6d" @@ -21369,7 +21377,7 @@ v8-compile-cache-lib@^3.0.1: resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== -v8-compile-cache@2.3.0, v8-compile-cache@^2.0.3: +v8-compile-cache@2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" integrity sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA== @@ -21827,7 +21835,7 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== -word-wrap@^1.2.3, word-wrap@~1.2.3: +word-wrap@~1.2.3: version "1.2.4" resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== From b6ddfa5755394633e83dadd0178234ef740454ea Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 2 Apr 2024 15:08:42 +0200 Subject: [PATCH 109/529] fix: Fetch CORS resources from network (#9586) --- packages/client/serviceWorker/sw.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/client/serviceWorker/sw.ts b/packages/client/serviceWorker/sw.ts index e35d7f86db7..eb19da02807 100644 --- a/packages/client/serviceWorker/sw.ts +++ b/packages/client/serviceWorker/sw.ts @@ -79,8 +79,10 @@ const onFetch = async (event: FetchEvent) => { // request.mode could be 'no-cors' // By fetching the URL without specifying the mode the response will not be opaque const isParabolHosted = url.startsWith(PUBLIC_PATH) || url.startsWith(self.origin) - const req = isParabolHosted ? request.url : request - const networkRes = await fetch(req) + // if one of our assets is not in the service worker cache, then it's either fetched via network or served from the broswer cache. + // The browser cache most likely has incorrect CORS headers set, so we better always fetch from the network. + const req = isParabolHosted ? fetch(request.url, {cache: 'no-store'}) : fetch(request) + const networkRes = await req const cache = await caches.open(DYNAMIC_CACHE) // cloning here because I'm not sure if we must clone before reading the body cache.put(request.url, networkRes.clone()).catch(console.error) From 9b21ad405612a485b1603053d7802739a0866f1c Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 15:20:33 +0200 Subject: [PATCH 110/529] chore(release): release v7.24.1 (#9585) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 9 +++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index bc72873ade5..2fb3566ccf3 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.24.0" + ".": "7.24.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index a9adc2cad97..b5ed33f4256 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.24.1](https://github.com/ParabolInc/parabol/compare/v7.24.0...v7.24.1) (2024-04-02) + + +### Fixed + +* embedder doesn't dive deep into schema ([#9582](https://github.com/ParabolInc/parabol/issues/9582)) ([8cdd901](https://github.com/ParabolInc/parabol/commit/8cdd9014c3277905605c6544de92d9ac2833a6e9)) +* embedder errors in embed length ([#9584](https://github.com/ParabolInc/parabol/issues/9584)) ([341b4b7](https://github.com/ParabolInc/parabol/commit/341b4b797ec6444066244f25916803e64c03258c)) +* Fetch CORS resources from network ([#9586](https://github.com/ParabolInc/parabol/issues/9586)) ([b6ddfa5](https://github.com/ParabolInc/parabol/commit/b6ddfa5755394633e83dadd0178234ef740454ea)) + ## [7.24.0](https://github.com/ParabolInc/parabol/compare/v7.23.1...v7.24.0) (2024-03-29) diff --git a/package.json b/package.json index 427d88d96e8..a6b2e22b178 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.24.0", + "version": "7.24.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 63593c4e0fb..b8e0b4886a7 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.24.0", + "version": "7.24.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.24.0" + "parabol-server": "7.24.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index 9261fb18d5a..51b9eb2aead 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.24.0", + "version": "7.24.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index aea9068a6ca..975309bdb62 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.24.0", + "version": "7.24.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 bd7f9c61b51..3797dee3e75 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.24.0", + "version": "7.24.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.24.0", - "parabol-server": "7.24.0", + "parabol-client": "7.24.1", + "parabol-server": "7.24.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 8a344b6462e..84ae4bb4e29 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.24.0", + "version": "7.24.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 8afddae459c..d619f4b0860 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.24.0", + "version": "7.24.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.24.0", + "parabol-client": "7.24.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From dbc9f091a4c93efc0eca24c5cd42b80bae95cff3 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 2 Apr 2024 19:43:34 +0200 Subject: [PATCH 111/529] chore(deps-dev): bump webpack-dev-middleware from 4.0.2 to 5.3.4 (#9561) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Georg Bremer --- packages/server/package.json | 1 - yarn.lock | 38 +----------------------------------- 2 files changed, 1 insertion(+), 38 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index d619f4b0860..f131227f4c1 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -72,7 +72,6 @@ "vscode-apollo-relay": "^1.5.0", "webpack-bundle-analyzer": "4.3.0", "webpack-cli": "4.9.1", - "webpack-dev-middleware": "4.0.2", "webpack-hot-middleware": "^2.22.2", "webpack-node-externals": "2.5.2" }, diff --git a/yarn.lock b/yarn.lock index 6701230bae9..0ad5e645838 100644 --- a/yarn.lock +++ b/yarn.lock @@ -15669,13 +15669,6 @@ makeerror@1.0.12: dependencies: tmpl "1.0.5" -map-age-cleaner@^0.1.3: - version "0.1.3" - resolved "https://registry.yarnpkg.com/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz#7d583a7306434c055fe474b0f45078e6e1b4b92a" - integrity sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w== - dependencies: - p-defer "^1.0.0" - map-cache@^0.2.0: version "0.2.2" resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" @@ -15742,15 +15735,7 @@ media-typer@0.3.0: resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== -mem@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/mem/-/mem-8.1.1.tgz#cf118b357c65ab7b7e0817bdf00c8062297c0122" - integrity sha512-qFCFUDs7U3b8mBDPyz5EToEKoAkgCzqquIgi9nkkR9bixxOVOre+09lbuH7+9Kn2NFpm56M3GUWVbU2hQgdACA== - dependencies: - map-age-cleaner "^0.1.3" - mimic-fn "^3.1.0" - -memfs@^3.2.0, memfs@^3.4.3: +memfs@^3.4.3: version "3.6.0" resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== @@ -15871,11 +15856,6 @@ mimic-fn@^2.1.0: resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b" integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg== -mimic-fn@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-3.1.0.tgz#65755145bbf3e36954b949c16450427451d5ca74" - integrity sha512-Ysbi9uYW9hFyfrThdDEQuykN4Ey6BuwPD2kpI5ES/nFTDn/98yxYNLZJcgUAKPT/mcrLLKaGzJR9YVxJrIdASQ== - mimic-response@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-3.1.0.tgz#2d1d59af9c1b129815accc2c46a022a5ce1fa3c9" @@ -17009,11 +16989,6 @@ oy-vey@^0.12.1: object-assign "^4.1.1" sanitizer "^0.1.3" -p-defer@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c" - integrity sha1-n26xgvbJqozXQwBKfU+WsZaw+ww= - p-finally@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae" @@ -21620,17 +21595,6 @@ webpack-cli@4.9.1: rechoir "^0.7.0" webpack-merge "^5.7.3" -webpack-dev-middleware@4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-4.0.2.tgz#1436ae6cacee78475bd6bc1fbf063dfbfd6e577d" - integrity sha512-xyAICqIugWtT1RRH5aMMmZlPhDhEqPTDL0TWhmMZsuZ+cFlAvRxv4thCbuxdk9MW+OYK4c9BkfmgdQ1/7imkJA== - dependencies: - mem "^8.0.0" - memfs "^3.2.0" - mime-types "^2.1.27" - range-parser "^1.2.1" - schema-utils "^3.0.0" - webpack-dev-middleware@^5.3.1: version "5.3.3" resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.3.tgz#efae67c2793908e7311f1d9b06f2a08dcc97e51f" From 9486587c9f7d4b1c40a0eff549e819ed4565aa23 Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Tue, 2 Apr 2024 20:36:37 +0100 Subject: [PATCH 112/529] fix(single-tenant): application upgrades do not need --profile databases (#9593) --- docker/stacks/single-tenant-host/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/stacks/single-tenant-host/README.md b/docker/stacks/single-tenant-host/README.md index 59a6a4a8a3e..5f2ca04254f 100644 --- a/docker/stacks/single-tenant-host/README.md +++ b/docker/stacks/single-tenant-host/README.md @@ -18,7 +18,7 @@ To run Parabol in single tenant mode (e.g. simple docker-compose on a docker hos 1. Edit the `docker-compose.yaml` and change the `#image:tag` changing the tag. Ex: from `v7.15.0` to `v7.15.2`. 2. (optional) In a different terminal, run `docker compose logs -f` to follow the upgrade. -3. Run `docker compose --profile databases --profile parabol up -d`. It will start the `pre-deploy` and, once it is done successfully, then it will stop and recreate the `web-server` and `gql-executor` with the new version of the image. **This step implies a downtime**. +3. Run `docker compose --profile parabol up -d`. It will start the `pre-deploy` and, once it is done successfully, then it will stop and recreate the `web-server` and `gql-executor` with the new version of the image. **This step implies a downtime**. 4. Verify the application is still up and running. ## Running Chronos From a5ca7f10c5646dfe6cbbcf1988d494b49cdba4f0 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 2 Apr 2024 23:53:14 -0700 Subject: [PATCH 113/529] [Snyk] Upgrade json2csv from 5.0.5 to 5.0.7 (#9574) Co-authored-by: snyk-bot Co-authored-by: GitHub Action --- packages/client/package.json | 2 +- yarn.lock | 39 +++++++++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 8 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index 51b9eb2aead..c241bd9ef83 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -115,7 +115,7 @@ "hoist-non-react-statics": "^3.3.0", "humanize-duration": "3.29.0", "immutable": "3.8.2", - "json2csv": "5.0.5", + "json2csv": "5.0.7", "jwt-decode": "^2.1.0", "linkify-it": "^2.0.3", "mousetrap": "^1.6.3", diff --git a/yarn.lock b/yarn.lock index 0ad5e645838..a1a316381af 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14881,10 +14881,10 @@ json-to-pretty-yaml@^1.2.2: remedial "^1.0.7" remove-trailing-spaces "^1.0.6" -json2csv@5.0.5: - version "5.0.5" - resolved "https://registry.yarnpkg.com/json2csv/-/json2csv-5.0.5.tgz#b65bf4f1e1eeb81cb7097d383edec249f8bce3af" - integrity sha512-/UyvnfuUghRM+C/AiQ02X0LS+/AKfugcwaWo/gAz1pi203v29sUMrMSNEC088i+h0EG39eSsmeL9Z0iK+9MM0A== +json2csv@5.0.7: + version "5.0.7" + resolved "https://registry.yarnpkg.com/json2csv/-/json2csv-5.0.7.tgz#f3a583c25abd9804be873e495d1e65ad8d1b54ae" + integrity sha512-YRZbUnyaJZLZUJSRi2G/MqahCyRv9n/ds+4oIetjDF3jWQA7AG7iSeKTiZiCNqtMZM7HDyt0e/W6lEnoGEmMGA== dependencies: commander "^6.1.0" jsonparse "^1.3.1" @@ -20021,7 +20021,7 @@ string-similarity@^3.0.0: resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-3.0.0.tgz#07b0bc69fae200ad88ceef4983878d03793847c7" integrity sha512-7kS7LyTp56OqOI2BDWQNVnLX/rCxIQn+/5M0op1WV6P8Xx6TZNdajpuqQdiJ7Xx+p1C5CsWMvdiBp9ApMhxzEQ== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -20039,6 +20039,15 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.0.tgz#5ab00980cfb29f43e736b113a120a73a0fb569d3" @@ -20115,7 +20124,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -20129,6 +20138,13 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -21978,7 +21994,7 @@ workbox-window@6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.5.4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -21996,6 +22012,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From e372f5f7bccd0a2fd1b4fea414610ad12fe0ec89 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:05:35 +0200 Subject: [PATCH 114/529] chore(deps): bump follow-redirects from 1.15.2 to 1.15.6 (#9536) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index a1a316381af..b82bf6ee615 100644 --- a/yarn.lock +++ b/yarn.lock @@ -12339,15 +12339,10 @@ flow-parser@0.*: resolved "https://registry.yarnpkg.com/flow-parser/-/flow-parser-0.168.0.tgz#e9c385499145828b42fd754d3528f4cb7d5c6edf" integrity sha512-YMlc+6vvyDPqWKOpzmyifJXBbwlNdqznuy8YBHxX1/90F8d+NnhsxMe1u/ok5LNvNJVJ2TVMkWudu0BUKOSawA== -follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.15.0: - version "1.15.2" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" - integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== - -follow-redirects@^1.14.9: - version "1.15.5" - resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.5.tgz#54d4d6d062c0fa7d9d17feb008461550e3ba8020" - integrity sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw== +follow-redirects@^1.0.0, follow-redirects@^1.14.0, follow-redirects@^1.14.9, follow-redirects@^1.15.0: + version "1.15.6" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.6.tgz#7f815c0cda4249c74ff09e95ef97c23b5fd0399b" + integrity sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA== foreground-child@^3.1.0: version "3.1.1" From 8ab86b4cd8698ba6f0a4cdec8eca2bf31a290599 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:08:01 +0200 Subject: [PATCH 115/529] chore(deps): bump express from 4.18.2 to 4.19.2 (#9566) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 46 +++++++++++++++++++++++++++++++++------------- 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/yarn.lock b/yarn.lock index b82bf6ee615..18effd91074 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9440,13 +9440,13 @@ bodec@^0.1.0: resolved "https://registry.yarnpkg.com/bodec/-/bodec-0.1.0.tgz#bc851555430f23c9f7650a75ef64c6a94c3418cc" integrity sha1-vIUVVUMPI8n3ZQp172TGqUw0GMw= -body-parser@1.20.1: - version "1.20.1" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.1.tgz#b1812a8912c195cd371a3ee5e66faa2338a5c668" - integrity sha512-jWi7abTbYwajOytWCQc37VulmWiRae5RyTpaCyDcS5/lMdtwSz5lOpDE67srw/HYe35f1z3fDQw+3txg7gNtWw== +body-parser@1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== dependencies: bytes "3.1.2" - content-type "~1.0.4" + content-type "~1.0.5" debug "2.6.9" depd "2.0.0" destroy "1.2.0" @@ -9454,7 +9454,7 @@ body-parser@1.20.1: iconv-lite "0.4.24" on-finished "2.4.1" qs "6.11.0" - raw-body "2.5.1" + raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" @@ -10390,6 +10390,11 @@ content-type@~1.0.4: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b" integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA== +content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + continuation-local-storage@^3.2.1: version "3.2.1" resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" @@ -10497,7 +10502,12 @@ cookie-signature@1.0.6: resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== -cookie@0.5.0, cookie@^0.5.0: +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + +cookie@^0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.5.0.tgz#d1f5d71adec6558c58f389987c366aa47e994f8b" integrity sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw== @@ -11958,16 +11968,16 @@ expect@^29.0.0, expect@^29.5.0: jest-util "^29.5.0" express@^4.17.3: - version "4.18.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.18.2.tgz#3fabe08296e930c796c19e3c516979386ba9fd59" - integrity sha512-5/PsL6iGPdfQ/lKM1UuielYgv3BUoJfz1aUwU9vHZ+J7gyvwdQXFEBIEIaxeGf0GIcreATNyBExtalisDbuMqQ== + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.1" + body-parser "1.20.2" content-disposition "0.5.4" content-type "~1.0.4" - cookie "0.5.0" + cookie "0.6.0" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" @@ -18291,7 +18301,17 @@ range-parser@^1.2.1, range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== -raw-body@2.5.1, raw-body@^2.2.0: +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + +raw-body@^2.2.0: version "2.5.1" resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== From c8c2321bd4fb65bf67f65a9712fc31f031e4fca4 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 3 Apr 2024 00:13:00 -0700 Subject: [PATCH 116/529] [Snyk] Upgrade react-beautiful-dnd from 13.0.0 to 13.1.1 (#9575) Co-authored-by: snyk-bot Co-authored-by: GitHub Action --- packages/client/package.json | 2 +- yarn.lock | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index c241bd9ef83..44480517b01 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -121,7 +121,7 @@ "mousetrap": "^1.6.3", "ms": "^2.0.0", "react": "^17.0.2", - "react-beautiful-dnd": "13.0.0", + "react-beautiful-dnd": "13.1.1", "react-chartjs-2": "^4.2.0", "react-copy-to-clipboard": "^5.0.0", "react-day-picker": "^8.3.7", diff --git a/yarn.lock b/yarn.lock index 18effd91074..9423f37dd1a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18339,16 +18339,16 @@ rc@^1.2.7, rc@^1.2.8: minimist "^1.2.0" strip-json-comments "~2.0.1" -react-beautiful-dnd@13.0.0: - version "13.0.0" - resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.0.0.tgz#f70cc8ff82b84bc718f8af157c9f95757a6c3b40" - integrity sha512-87It8sN0ineoC3nBW0SbQuTFXM6bUqM62uJGY4BtTf0yzPl8/3+bHMWkgIe0Z6m8e+gJgjWxefGRVfpE3VcdEg== +react-beautiful-dnd@13.1.1: + version "13.1.1" + resolved "https://registry.yarnpkg.com/react-beautiful-dnd/-/react-beautiful-dnd-13.1.1.tgz#b0f3087a5840920abf8bb2325f1ffa46d8c4d0a2" + integrity sha512-0Lvs4tq2VcrEjEgDXHjT98r+63drkKEgqyxdA7qD3mvKwga6a5SscbdLPO2IExotU1jW8L0Ksdl0Cj2AF67nPQ== dependencies: - "@babel/runtime" "^7.8.4" + "@babel/runtime" "^7.9.2" css-box-model "^1.2.0" memoize-one "^5.1.1" raf-schd "^4.0.2" - react-redux "^7.1.1" + react-redux "^7.2.0" redux "^4.0.4" use-memo-one "^1.1.1" @@ -18420,10 +18420,10 @@ react-lifecycles-compat@^3.0.4: resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== -react-redux@^7.1.1: - version "7.2.6" - resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.6.tgz#49633a24fe552b5f9caf58feb8a138936ddfe9aa" - integrity sha512-10RPdsz0UUrRL1NZE0ejTkucnclYSgXp5q+tB5SWx2qeG2ZJQJyymgAhwKy73yiL/13btfB6fPr+rgbMAaZIAQ== +react-redux@^7.2.0: + version "7.2.9" + resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.9.tgz#09488fbb9416a4efe3735b7235055442b042481d" + integrity sha512-Gx4L3uM182jEEayZfRbI/G11ZpYdNAnBs70lFVMNdHJI76XYtR+7m0MN+eAs7UHBPhWXcnFPaS+9owSCJQHNpQ== dependencies: "@babel/runtime" "^7.15.4" "@types/react-redux" "^7.1.20" From c312f4821698ae7966dee8889a0c8c3353733dcc Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 3 Apr 2024 09:13:50 +0200 Subject: [PATCH 117/529] chore(deps): bump jose from 4.14.4 to 4.15.5 (#9515) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Georg Bremer --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9423f37dd1a..3722ca8df49 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14637,9 +14637,9 @@ join-component@^1.1.0: integrity sha1-uEF7dQZho5K+4sJTfGiyqdSXfNU= jose@^4.11.4: - version "4.14.4" - resolved "https://registry.yarnpkg.com/jose/-/jose-4.14.4.tgz#59e09204e2670c3164ee24cbfe7115c6f8bff9ca" - integrity sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g== + version "4.15.5" + resolved "https://registry.yarnpkg.com/jose/-/jose-4.15.5.tgz#6475d0f467ecd3c630a1b5dadd2735a7288df706" + integrity sha512-jc7BFxgKPKi94uOvEmzlSWFFe2+vASyXaKUpdQKatWAESU2MWjDfFf0fdfc83CDKcA5QecabZeNLyfhe3yKNkg== js-git@^0.7.8: version "0.7.8" From df59066523f3abc0fdb5f3728e1732cb0d722f71 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 3 Apr 2024 00:16:04 -0700 Subject: [PATCH 118/529] [Snyk] Upgrade humanize-duration from 3.29.0 to 3.31.0 (#9573) Co-authored-by: snyk-bot Co-authored-by: GitHub Action --- packages/client/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index 44480517b01..905829c12f1 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -113,7 +113,7 @@ "graphiql": "^3.0.0", "graphql-typed": "^0.7.2", "hoist-non-react-statics": "^3.3.0", - "humanize-duration": "3.29.0", + "humanize-duration": "3.31.0", "immutable": "3.8.2", "json2csv": "5.0.7", "jwt-decode": "^2.1.0", diff --git a/yarn.lock b/yarn.lock index 3722ca8df49..7e3593cb3d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13468,10 +13468,10 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -humanize-duration@3.29.0: - version "3.29.0" - resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.29.0.tgz#beffaf7938388cd0f38c494f8970d6faebecf3c0" - integrity sha512-G5wZGwYTLaQAmYqhfK91aw3xt6wNbJW1RnWDh4qP1PvF4T/jnkjx2RVhG5kzB2PGsYGTn+oSDBQp+dMdILLxcg== +humanize-duration@3.31.0: + version "3.31.0" + resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.31.0.tgz#a0384d22555024cd17e6e9f8561540d37756bf4c" + integrity sha512-fRrehgBG26NNZysRlTq1S+HPtDpp3u+Jzdc/d5A4cEzOD86YLAkDaJyJg8krSdCi7CJ+s7ht3fwRj8Dl+Btd0w== humanize-ms@^1.2.1: version "1.2.1" From 1bca19ad89281bcbee6a1006b014bc8c351ba5f2 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 3 Apr 2024 00:19:03 -0700 Subject: [PATCH 119/529] [Snyk] Upgrade graphql from 15.7.2 to 15.8.0 (#9569) Co-authored-by: snyk-bot Co-authored-by: GitHub Action --- packages/server/package.json | 2 +- yarn.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index f131227f4c1..4f82eb5a892 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -100,7 +100,7 @@ "fast-json-stable-stringify": "^2.1.0", "fast-xml-parser": "^4.2.7", "googleapis": "^118.0.0", - "graphql": "15.7.2", + "graphql": "15.8.0", "graphql-jit": "^0.8.4", "graphql-middleware": "^6.1.18", "graphql-relay": "^0.10.0", diff --git a/yarn.lock b/yarn.lock index 7e3593cb3d0..558fd3223ec 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13115,7 +13115,7 @@ graphql@15.7.2: resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.7.2.tgz#85ab0eeb83722977151b3feb4d631b5f2ab287ef" integrity sha512-AnnKk7hFQFmU/2I9YSQf3xw44ctnSFCfp3zE0N6W174gqe9fWG/2rKaKxROK7CcI3XtERpjEKFqts8o319Kf7A== -graphql@^15.0.0: +graphql@15.8.0, graphql@^15.0.0: version "15.8.0" resolved "https://registry.yarnpkg.com/graphql/-/graphql-15.8.0.tgz#33410e96b012fa3bdb1091cc99a94769db212b38" integrity sha512-5gghUc24tP9HRznNpV2+FIoq3xKkj5dTQqf4v0CpdPbFVwFkWoxOM+o+2OC9ZSvjEMTjfmG9QT+gcvggTwW1zw== From b92d96e2972560ee16f83d90a82ebbb946e39dc0 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Wed, 3 Apr 2024 10:24:07 +0200 Subject: [PATCH 120/529] fix: Add graphql-relay to predeploy (#9595) --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index a6b2e22b178..c6c630d2461 100644 --- a/package.json +++ b/package.json @@ -101,6 +101,7 @@ "eslint": "^8.57.0", "eslint-config-prettier": "^9.1.0", "graphql": "15.7.2", + "graphql-relay": "^0.10.0", "html-webpack-plugin": "^5.5.0", "husky": "^7.0.4", "jscodeshift": "^0.14.0", From 01f69de9eb809ef16ac954e5d75ac884b11f8342 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Wed, 3 Apr 2024 16:10:16 +0100 Subject: [PATCH 121/529] feat: update pricing page with template changes (#9596) --- .../userDashboard/components/OrgBilling/OrgPlans.tsx | 11 +++-------- packages/client/utils/constants.ts | 12 ++++++++++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/client/modules/userDashboard/components/OrgBilling/OrgPlans.tsx b/packages/client/modules/userDashboard/components/OrgBilling/OrgPlans.tsx index e355a1e8381..0e31205312a 100644 --- a/packages/client/modules/userDashboard/components/OrgBilling/OrgPlans.tsx +++ b/packages/client/modules/userDashboard/components/OrgBilling/OrgPlans.tsx @@ -5,12 +5,12 @@ import {useFragment} from 'react-relay' import Panel from '../../../../components/Panel/Panel' import Row from '../../../../components/Row/Row' import {OrgPlans_organization$key} from '../../../../__generated__/OrgPlans_organization.graphql' -import {ElementWidth, Threshold} from '../../../../types/constEnums' +import {ElementWidth} from '../../../../types/constEnums' import {TierEnum} from '../../../../__generated__/NewMeetingQuery.graphql' import OrgStats from './OrgStats' import useModal from '../../../../hooks/useModal' import DowngradeModal from './DowngradeModal' -import {EnterpriseBenefits, TeamBenefits} from '../../../../utils/constants' +import {EnterpriseBenefits, StarterBenefits, TeamBenefits} from '../../../../utils/constants' import SendClientSideEvent from '../../../../utils/SendClientSideEvent' import useAtmosphere from '../../../../hooks/useAtmosphere' import LimitExceededWarning from '../../../../components/LimitExceededWarning' @@ -91,12 +91,7 @@ const OrgPlans = (props: Props) => { { tier: 'starter', subtitle: 'Free', - details: [ - `${Threshold.MAX_STARTER_TIER_TEAMS} teams`, - 'Essential templates', - 'Retrospectives, Sprint Poker, Standups, Check-Ins', - 'Unlimited team members' - ], + details: [...StarterBenefits], buttonStyle: getButtonStyle(billingTier, 'starter'), buttonLabel: getButtonLabel(billingTier, 'starter'), isActive: !hasSelectedTeamPlan && billingTier === 'starter' diff --git a/packages/client/utils/constants.ts b/packages/client/utils/constants.ts index 08180a93209..b2f4a0f77d5 100644 --- a/packages/client/utils/constants.ts +++ b/packages/client/utils/constants.ts @@ -9,6 +9,7 @@ import {TaskStatusEnum} from '~/__generated__/UpdateTaskMutation.graphql' import {ReadableReasonToDowngradeEnum} from '../../server/graphql/types/ReasonToDowngrade' import {ReasonToDowngradeEnum} from '../__generated__/DowngradeToStarterMutation.graphql' import {TimelineEventEnum} from '../__generated__/MyDashboardTimelineQuery.graphql' +import {Threshold} from '../types/constEnums' /* Meeting Misc. */ export const MEETING_NAME = 'Check-in Meeting' @@ -160,10 +161,16 @@ export const SPOTLIGHT_TOP_SECTION_HEIGHT = 236 export const PARABOL_AI_USER_ID = 'parabolAIUser' +export const StarterBenefits = [ + `${Threshold.MAX_STARTER_TIER_TEAMS} teams`, + 'Retrospectives, Sprint Poker, Standups, Check-Ins', + 'Unlimited meeting templates', + 'Unlimited team members' +] + export const TeamBenefits = [ 'Unlimited teams', - 'Premium templates', - 'Custom templates', + 'Unlimited custom templates', 'Unlimited meeting history', 'Priority customer support', 'AI Summaries', @@ -172,6 +179,7 @@ export const TeamBenefits = [ export const EnterpriseBenefits = [ 'Single Sign-On (SSO)', + 'Org Admin Role', 'Annual Billing', 'Domain Whitelisting', 'Uptime Service Level Agreement (SLA)', From c6da00c06929d377b8b698c82352559d1da85467 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Thu, 4 Apr 2024 12:29:11 +0200 Subject: [PATCH 122/529] fix: trim inet address (#9598) --- packages/server/utils/uwsGetIP.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/utils/uwsGetIP.ts b/packages/server/utils/uwsGetIP.ts index b41765b100d..d5ccf554bbf 100644 --- a/packages/server/utils/uwsGetIP.ts +++ b/packages/server/utils/uwsGetIP.ts @@ -5,7 +5,7 @@ const TRUSTED_PROXY_COUNT = Number(process.env.TRUSTED_PROXY_COUNT) const CLIENT_IP_POS = isNaN(TRUSTED_PROXY_COUNT) ? 0 : -1 - TRUSTED_PROXY_COUNT const uwsGetIP = (res: HttpResponse, req: HttpRequest) => { - const clientIp = req.getHeader('x-forwarded-for')?.split(',').at(CLIENT_IP_POS) + const clientIp = req.getHeader('x-forwarded-for')?.split(',').at(CLIENT_IP_POS)?.trim() if (clientIp) return clientIp // returns ipv6 e.g. '0000:0000:0000:0000:0000:ffff:ac11:0001' return Buffer.from(res.getRemoteAddressAsText()).toString() From 415d03b2ce5216608a2dd144166666013d1752a0 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Thu, 4 Apr 2024 17:15:56 +0200 Subject: [PATCH 123/529] chore: Remove one on one meeting type (#9590) --- .../ActivityDetails/TemplateDetails.tsx | 19 +-- .../ActivityDetailsSidebar.tsx | 143 +++--------------- .../ActivityLibrary/ActivityLibrary.tsx | 5 - .../ActivityLibrary/OneOnOneTeamStatus.tsx | 28 ---- .../OneOnOneTeamStatusComponent.tsx | 40 ----- .../client/mutations/StartCheckInMutation.ts | 8 +- packages/server/database/types/Team.ts | 4 - .../server/graphql/mutations/endCheckIn.ts | 2 +- .../mutations/helpers/createTeamAndLeader.ts | 3 +- .../helpers/getExistingOneOnOneTeam.ts | 43 ------ .../helpers/maybeCreateOneOnOneTeam.ts | 98 ------------ .../server/graphql/mutations/joinMeeting.ts | 3 +- .../typeDefs/updateOrgFeatureFlag.graphql | 1 - .../graphql/public/mutations/startCheckIn.ts | 33 +--- .../typeDefs/CreateOneOnOneTeamInput.graphql | 4 - .../public/typeDefs/Organization.graphql | 6 - .../public/typeDefs/startCheckIn.graphql | 6 +- .../public/types/CreateOneOnOneTeamInput.ts | 21 --- .../graphql/public/types/Organization.ts | 33 +--- .../public/types/OrganizationFeatureFlags.ts | 1 - .../1712073121060_removeOneOnOne.ts | 16 ++ .../postgres/queries/src/insertTeamQuery.sql | 2 - packages/server/utils/analytics/analytics.ts | 33 ++-- packages/server/utils/analytics/helpers.ts | 7 +- .../meeting_types-one_on_one.svg | 28 ---- static/images/illustrations/oneOnOne.svg | 33 ---- 26 files changed, 61 insertions(+), 559 deletions(-) delete mode 100644 packages/client/components/ActivityLibrary/OneOnOneTeamStatus.tsx delete mode 100644 packages/client/components/ActivityLibrary/OneOnOneTeamStatusComponent.tsx delete mode 100644 packages/server/graphql/mutations/helpers/getExistingOneOnOneTeam.ts delete mode 100644 packages/server/graphql/mutations/helpers/maybeCreateOneOnOneTeam.ts delete mode 100644 packages/server/graphql/public/typeDefs/CreateOneOnOneTeamInput.graphql delete mode 100644 packages/server/graphql/public/types/CreateOneOnOneTeamInput.ts create mode 100644 packages/server/postgres/migrations/1712073121060_removeOneOnOne.ts delete mode 100644 static/images/illustrations/meeting_types-one_on_one.svg delete mode 100644 static/images/illustrations/oneOnOne.svg diff --git a/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx b/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx index b86554fb3ef..f45191fad8c 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx @@ -73,22 +73,6 @@ const ACTIVITY_TYPE_DATA_LOOKUP: Record< } } -const ACTIVITY_ID_DATA_LOOKUP: Record< - string, - {description: React.ReactNode; integrationsTip: React.ReactNode} -> = { - oneOnOneAction: { - description: ( - <> - This is a space to check in one-on-one. Share a personal update using the Icebreaker{' '} - phase. Give a brief update on what’s changed with your work during the Solo Updates{' '} - phase. Raise issues for discussion in the Agenda phase. - - ), - integrationsTip: <>push takeaway tasks to your backlog - } -} - interface Props { activityRef: TemplateDetails_activity$key isEditing: boolean @@ -143,8 +127,7 @@ export const TemplateDetails = (props: Props) => { } = activity const {id: teamId, editingScaleId} = team - const {description: activityDescription, integrationsTip} = - ACTIVITY_ID_DATA_LOOKUP[activityId] ?? ACTIVITY_TYPE_DATA_LOOKUP[type] + const {description: activityDescription, integrationsTip} = ACTIVITY_TYPE_DATA_LOOKUP[type] const viewer = useFragment( graphql` diff --git a/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx b/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx index 20574555b3b..d0721f72af5 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx @@ -16,7 +16,6 @@ import useAtmosphere from '../../hooks/useAtmosphere' import {MenuPosition} from '../../hooks/useCoords' import useMutationProps from '../../hooks/useMutationProps' import SelectTemplateMutation from '../../mutations/SelectTemplateMutation' -import SendClientSideEvent from '../../utils/SendClientSideEvent' import StartCheckInMutation from '../../mutations/StartCheckInMutation' import StartTeamPromptMutation from '../../mutations/StartTeamPromptMutation' import {PALETTE} from '../../styles/paletteV3' @@ -33,14 +32,6 @@ import StyledError from '../StyledError' import FlatPrimaryButton from '../FlatPrimaryButton' import NewMeetingActionsCurrentMeetings from '../NewMeetingActionsCurrentMeetings' import NewMeetingTeamPicker from '../NewMeetingTeamPicker' -import {AdhocTeamMultiSelect, Option} from '../AdhocTeamMultiSelect/AdhocTeamMultiSelect' -import {Select} from '../../ui/Select/Select' -import {SelectTrigger} from '../../ui/Select/SelectTrigger' -import {SelectValue} from '../../ui/Select/SelectValue' -import {SelectContent} from '../../ui/Select/SelectContent' -import {SelectGroup} from '../../ui/Select/SelectGroup' -import {SelectItem} from '../../ui/Select/SelectItem' -import OneOnOneTeamStatus from './OneOnOneTeamStatus' import ScheduleMeetingButton from './ScheduleMeetingButton' import useBreakpoint from '../../hooks/useBreakpoint' import {Breakpoint} from '../../types/constEnums' @@ -156,37 +147,6 @@ const ActivityDetailsSidebar = (props: Props) => { const mutationProps = useMutationProps() const {onError, onCompleted, submitting, submitMutation, error} = mutationProps const history = useHistory() - const {organizations: viewerOrganizations} = viewer - const [selectedUser, setSelectedUser] = React.useState - - - - - - - )} - {discussionSummary && ( - <> - - - - - - - - )} + + + + + + )} diff --git a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/WholeMeetingSummary.tsx b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/WholeMeetingSummary.tsx index 5bc27573613..820491bffde 100644 --- a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/WholeMeetingSummary.tsx +++ b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/WholeMeetingSummary.tsx @@ -26,16 +26,8 @@ const WholeMeetingSummary = (props: Props) => { } ... on RetrospectiveMeeting { reflectionGroups(sortBy: voteCount) { - summary - } - phases { - phaseType - ... on DiscussPhase { - stages { - discussion { - summary - } - } + reflections { + id } } } @@ -49,14 +41,11 @@ const WholeMeetingSummary = (props: Props) => { meetingRef ) if (meeting.__typename === 'RetrospectiveMeeting') { - const {summary: wholeMeetingSummary, reflectionGroups, phases} = meeting - const discussPhase = phases!.find((phase) => phase.phaseType === 'discuss') - const {stages} = discussPhase ?? {} - const hasTopicSummary = reflectionGroups!.some((group) => group.summary) - const hasDiscussionSummary = !!stages?.some((stage) => stage.discussion?.summary) - const hasOpenAISummary = hasTopicSummary || hasDiscussionSummary - if (!hasOpenAISummary) return null - if (hasOpenAISummary && !wholeMeetingSummary) return + const {summary: wholeMeetingSummary, reflectionGroups, organization} = meeting + const reflections = reflectionGroups?.flatMap((group) => group.reflections) // reflectionCount hasn't been calculated yet so check reflections length + const hasMoreThanOneReflection = reflections?.length && reflections.length > 1 + if (!hasMoreThanOneReflection || organization.featureFlags.noAISummary) return null + if (!wholeMeetingSummary) return return } else if (meeting.__typename === 'TeamPromptMeeting') { const {summary: wholeMeetingSummary, responses, organization} = meeting diff --git a/packages/client/mutations/EndRetrospectiveMutation.ts b/packages/client/mutations/EndRetrospectiveMutation.ts index 5d27418cdd1..f087fbe1197 100644 --- a/packages/client/mutations/EndRetrospectiveMutation.ts +++ b/packages/client/mutations/EndRetrospectiveMutation.ts @@ -31,8 +31,12 @@ graphql` autogroupReflectionGroups { groupTitle } + organization { + featureFlags { + noAISummary + } + } reflectionGroups(sortBy: voteCount) { - summary reflections { id } @@ -43,13 +47,6 @@ graphql` } phases { phaseType - ... on DiscussPhase { - stages { - discussion { - summary - } - } - } } } team { @@ -109,7 +106,14 @@ export const endRetrospectiveTeamOnNext: OnNextHandler< const {isKill, meeting} = payload const {atmosphere, history} = context if (!meeting) return - const {id: meetingId, teamId, reflectionGroups, phases, autogroupReflectionGroups} = meeting + const { + id: meetingId, + teamId, + reflectionGroups, + phases, + autogroupReflectionGroups, + organization + } = meeting if (meetingId === RetroDemo.MEETING_ID) { if (isKill) { window.localStorage.removeItem('retroDemo') @@ -122,12 +126,9 @@ export const endRetrospectiveTeamOnNext: OnNextHandler< history.push(`/team/${teamId}`) popEndMeetingToast(atmosphere, meetingId) } else { - const discussPhase = phases.find((phase) => phase.phaseType === 'discuss') - const {stages} = discussPhase ?? {} - const hasTopicSummary = reflectionGroups.some((group) => group.summary) const reflections = reflectionGroups.flatMap((group) => group.reflections) // reflectionCount hasn't been calculated yet so check reflections length - const hasDiscussionSummary = !!stages?.some((stage) => stage.discussion?.summary) - const hasOpenAISummary = hasTopicSummary || hasDiscussionSummary + const hasMoreThanOneReflection = reflections.length > 1 + const hasOpenAISummary = hasMoreThanOneReflection && !organization.featureFlags.noAISummary const hasTeamHealth = phases.some((phase) => phase.phaseType === 'TEAM_HEALTH') const pathname = `/new-summary/${meetingId}` const search = new URLSearchParams() diff --git a/packages/server/graphql/mutations/helpers/generateGroupSummaries.ts b/packages/server/graphql/mutations/helpers/generateDiscussionPrompt.ts similarity index 68% rename from packages/server/graphql/mutations/helpers/generateGroupSummaries.ts rename to packages/server/graphql/mutations/helpers/generateDiscussionPrompt.ts index 3a827ca20d1..bb7a245ca2f 100644 --- a/packages/server/graphql/mutations/helpers/generateGroupSummaries.ts +++ b/packages/server/graphql/mutations/helpers/generateDiscussionPrompt.ts @@ -5,7 +5,7 @@ import sendToSentry from '../../../utils/sendToSentry' import {DataLoaderWorker} from '../../graphql' import canAccessAISummary from './canAccessAISummary' -const generateGroupSummaries = async ( +const generateDiscussionPrompt = async ( meetingId: string, teamId: string, dataLoader: DataLoaderWorker, @@ -30,7 +30,7 @@ const generateGroupSummaries = async ( const pg = getKysely() const manager = new OpenAIServerManager() if (!reflectionGroups.length) { - const error = new Error('No reflection groups in generateGroupSummaries') + const error = new Error('No reflection groups in generateDiscussionPrompt') sendToSentry(error, {userId: facilitator.id, tags: {meetingId}}) return } @@ -40,30 +40,22 @@ const generateGroupSummaries = async ( ({reflectionGroupId}) => reflectionGroupId === group.id ) if (reflectionsByGroupId.length <= 1) return - const reflectionTextByGroupId = reflectionsByGroupId.map( - ({plaintextContent}) => plaintextContent + const fullQuestion = await manager.getDiscussionPromptQuestion( + group.title ?? 'Unknown', + reflectionsByGroupId ) - const [fullSummary, fullQuestion] = await Promise.all([ - manager.getSummary(reflectionTextByGroupId), - manager.getDiscussionPromptQuestion(group.title ?? 'Unknown', reflectionsByGroupId) - ]) - if (!fullSummary && !fullQuestion) return - const summary = fullSummary?.slice(0, 2000) + if (!fullQuestion) return const discussionPromptQuestion = fullQuestion?.slice(0, 2000) return Promise.all([ pg .updateTable('RetroReflectionGroup') - .set({summary, discussionPromptQuestion}) + .set({discussionPromptQuestion}) .where('id', '=', group.id) .execute(), - r - .table('RetroReflectionGroup') - .get(group.id) - .update({summary, discussionPromptQuestion}) - .run() + r.table('RetroReflectionGroup').get(group.id).update({discussionPromptQuestion}).run() ]) }) ) } -export default generateGroupSummaries +export default generateDiscussionPrompt diff --git a/packages/server/graphql/mutations/helpers/handleCompletedStage.ts b/packages/server/graphql/mutations/helpers/handleCompletedStage.ts index e5be795830a..8227ec6fe69 100644 --- a/packages/server/graphql/mutations/helpers/handleCompletedStage.ts +++ b/packages/server/graphql/mutations/helpers/handleCompletedStage.ts @@ -13,8 +13,8 @@ import {DataLoaderWorker} from '../../graphql' import addAIGeneratedContentToThreads from './addAIGeneratedContentToThreads' import addDiscussionTopics from './addDiscussionTopics' import addRecallBot from './addRecallBot' +import generateDiscussionPrompt from './generateDiscussionPrompt' import generateDiscussionSummary from './generateDiscussionSummary' -import generateGroupSummaries from './generateGroupSummaries' import generateGroups from './generateGroups' import {publishToEmbedder} from './publishToEmbedder' import removeEmptyReflections from './removeEmptyReflections' @@ -92,7 +92,7 @@ const handleCompletedRetrospectiveStage = async ( .run() data.meeting = meeting // dont await for the OpenAI API response - generateGroupSummaries(meeting.id, teamId, dataLoader, facilitatorUserId) + generateDiscussionPrompt(meeting.id, teamId, dataLoader, facilitatorUserId) } return {[stage.phaseType]: data} diff --git a/packages/server/graphql/types/Discussion.ts b/packages/server/graphql/types/Discussion.ts index 556401ba1cc..cdc01159736 100644 --- a/packages/server/graphql/types/Discussion.ts +++ b/packages/server/graphql/types/Discussion.ts @@ -155,7 +155,7 @@ const Discussion = new GraphQLObjectType({ }, summary: { type: GraphQLString, - description: `The GPT-3 generated summary of the discussion. Undefined if the user doesnt have access to the feature or the stage isn't completed` + description: `The AI generated summary of the discussion. Undefined if the user doesnt have access to the feature or the stage isn't completed` } }) }) diff --git a/packages/server/graphql/types/RetroReflectionGroup.ts b/packages/server/graphql/types/RetroReflectionGroup.ts index 87dca57750f..7fb07754d0c 100644 --- a/packages/server/graphql/types/RetroReflectionGroup.ts +++ b/packages/server/graphql/types/RetroReflectionGroup.ts @@ -87,10 +87,6 @@ const RetroReflectionGroup: GraphQLObjectType = new GraphQLObjectType Date: Wed, 8 May 2024 04:24:09 -0700 Subject: [PATCH 212/529] feat: increase team subscription to $8 (#9727) --- .../userDashboard/components/OrgBilling/OrgPlan.tsx | 3 ++- packages/client/utils/constants.ts | 2 +- .../graphql/queries/helpers/makeUpcomingInvoice.ts | 2 +- packages/server/utils/stripe/StripeManager.ts | 10 +++++----- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/client/modules/userDashboard/components/OrgBilling/OrgPlan.tsx b/packages/client/modules/userDashboard/components/OrgBilling/OrgPlan.tsx index af763e72a3c..0587c683d45 100644 --- a/packages/client/modules/userDashboard/components/OrgBilling/OrgPlan.tsx +++ b/packages/client/modules/userDashboard/components/OrgBilling/OrgPlan.tsx @@ -8,6 +8,7 @@ import useTooltip from '../../../../hooks/useTooltip' import {Elevation} from '../../../../styles/elevation' import {PALETTE} from '../../../../styles/paletteV3' import {Radius} from '../../../../types/constEnums' +import {MONTHLY_PRICE} from '../../../../utils/constants' const PlanTitle = styled('h6')({ color: PALETTE.SLATE_700, @@ -187,7 +188,7 @@ const OrgPlan = (props: Props) => { {planTier === 'team' ? ( <> - {'$6 per active user '} + {`$${MONTHLY_PRICE} per active user `} {} diff --git a/packages/client/utils/constants.ts b/packages/client/utils/constants.ts index b2f4a0f77d5..5fb9d20ba04 100644 --- a/packages/client/utils/constants.ts +++ b/packages/client/utils/constants.ts @@ -100,7 +100,7 @@ export const AUTHENTICATION_PAGE = 'authentication' /* Stripe */ // changing this does NOT change it in stripe, it just changes the UI -export const MONTHLY_PRICE = 6 +export const MONTHLY_PRICE = 8 export const FAILED = 'FAILED' diff --git a/packages/server/graphql/queries/helpers/makeUpcomingInvoice.ts b/packages/server/graphql/queries/helpers/makeUpcomingInvoice.ts index 84bff7ba4a3..13b53503b98 100644 --- a/packages/server/graphql/queries/helpers/makeUpcomingInvoice.ts +++ b/packages/server/graphql/queries/helpers/makeUpcomingInvoice.ts @@ -35,7 +35,7 @@ export default async function makeUpcomingInvoice( : undefined const subscription = stripeInvoice.lines.data.find( - ({plan}) => plan?.id === StripeManager.PARABOL_TEAM_600 + ({plan}) => plan?.id === StripeManager.TEAM_PRICE_APP_ID ) if (subscription && subscription.quantity !== quantity) { const {subscription_item: lineitemId} = subscription diff --git a/packages/server/utils/stripe/StripeManager.ts b/packages/server/utils/stripe/StripeManager.ts index 28db42bd437..5a2c74e665d 100644 --- a/packages/server/utils/stripe/StripeManager.ts +++ b/packages/server/utils/stripe/StripeManager.ts @@ -4,8 +4,8 @@ import {Logger} from '../Logger' import sendToSentry from '../sendToSentry' export default class StripeManager { - static PARABOL_TEAM_600 = 'parabol-pro-600' // $6/seat/mo - static PARABOL_ENTERPRISE_2021_LOW = 'plan_2021_ann_low' + static TEAM_PRICE_APP_ID = 'parabol-pro-800' // $8/seat/mo + static ENTERPRISE_PRICE_APP_ID = 'plan_2021_ann_low' static WEBHOOK_SECRET = process.env.STRIPE_WEBHOOK_SECRET! stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2020-08-27', @@ -116,7 +116,7 @@ export default class StripeManager { proration_behavior: 'none', items: [ { - plan: plan || StripeManager.PARABOL_ENTERPRISE_2021_LOW, + plan: plan || StripeManager.ENTERPRISE_PRICE_APP_ID, quantity } ] @@ -141,7 +141,7 @@ export default class StripeManager { }, items: [ { - plan: StripeManager.PARABOL_TEAM_600, + plan: StripeManager.TEAM_PRICE_APP_ID, quantity } ] @@ -164,7 +164,7 @@ export default class StripeManager { }, items: [ { - plan: StripeManager.PARABOL_TEAM_600, + plan: StripeManager.TEAM_PRICE_APP_ID, quantity } ] From 162de5e87865faa190e5bbe16e25432aeb29e58b Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Wed, 8 May 2024 15:34:56 +0200 Subject: [PATCH 213/529] fix: Handle invitation links with invalid auth token (#9741) --- packages/client/mutations/AcceptTeamInvitationMutation.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/client/mutations/AcceptTeamInvitationMutation.ts b/packages/client/mutations/AcceptTeamInvitationMutation.ts index d34825bfd41..9fedfcb8b06 100644 --- a/packages/client/mutations/AcceptTeamInvitationMutation.ts +++ b/packages/client/mutations/AcceptTeamInvitationMutation.ts @@ -215,7 +215,10 @@ const AcceptTeamInvitationMutation: StandardMutation< const serverError = getGraphQLError(data, errors) if (serverError) { const message = serverError.message - if (message === InvitationTokenError.ALREADY_ACCEPTED) { + if (message === InvitationTokenError.NOT_SIGNED_IN) { + // if the user follows an invitation link with an invalid auth token, invalidate it + atmosphere.setAuthToken(null) + } else if (message === InvitationTokenError.ALREADY_ACCEPTED) { handleAuthenticationRedirect(acceptTeamInvitation, { atmosphere, history, From d5520aebc6b518382510eb1ef2c5c4c10dad70e6 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Wed, 8 May 2024 18:54:41 +0100 Subject: [PATCH 214/529] fix: org admin can change team lead (#9742) --- .../server/graphql/mutations/archiveTeam.ts | 12 ++++++---- .../graphql/mutations/promoteToTeamLead.ts | 24 +++++++++++-------- .../graphql/mutations/removeTeamMember.ts | 11 ++++++--- 3 files changed, 29 insertions(+), 18 deletions(-) diff --git a/packages/server/graphql/mutations/archiveTeam.ts b/packages/server/graphql/mutations/archiveTeam.ts index 39970cd9f80..aaa5ec888f6 100644 --- a/packages/server/graphql/mutations/archiveTeam.ts +++ b/packages/server/graphql/mutations/archiveTeam.ts @@ -6,7 +6,7 @@ import NotificationTeamArchived from '../../database/types/NotificationTeamArchi import removeMeetingTemplatesForTeam from '../../postgres/queries/removeMeetingTemplatesForTeam' import safeArchiveTeam from '../../safeMutations/safeArchiveTeam' import {analytics} from '../../utils/analytics/analytics' -import {getUserId, isSuperUser, isTeamLead} from '../../utils/authorization' +import {getUserId, isSuperUser, isTeamLead, isUserOrgAdmin} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' @@ -35,12 +35,14 @@ export default { // AUTH const viewerId = getUserId(authToken) - const [teamLead, viewer] = await Promise.all([ + const [teamLead, viewer, teamToArchive] = await Promise.all([ isTeamLead(viewerId, teamId, dataLoader), - dataLoader.get('users').loadNonNull(viewerId) + dataLoader.get('users').loadNonNull(viewerId), + dataLoader.get('teams').loadNonNull(teamId) ]) - if (!teamLead && !isSuperUser(authToken)) { - return standardError(new Error('Not team lead'), {userId: viewerId}) + const isOrgAdmin = await isUserOrgAdmin(viewerId, teamToArchive.orgId, dataLoader) + if (!teamLead && !isSuperUser(authToken) && !isOrgAdmin) { + return standardError(new Error('Not team lead or org admin'), {userId: viewerId}) } // RESOLUTION diff --git a/packages/server/graphql/mutations/promoteToTeamLead.ts b/packages/server/graphql/mutations/promoteToTeamLead.ts index 11c03ff7b5d..975346055f1 100644 --- a/packages/server/graphql/mutations/promoteToTeamLead.ts +++ b/packages/server/graphql/mutations/promoteToTeamLead.ts @@ -2,7 +2,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import TeamMemberId from '../../../client/shared/gqlIds/TeamMemberId' import getRethink from '../../database/rethinkDriver' -import {getUserId, isSuperUser} from '../../utils/authorization' +import {getUserId, isSuperUser, isUserOrgAdmin} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' @@ -32,18 +32,22 @@ export default { const viewerId = getUserId(authToken) // AUTH - const oldLeadTeamMemberId = await r - .table('TeamMember') - .getAll(teamId, {index: 'teamId'}) - .filter({isNotRemoved: true, isLead: true}) - .nth(0)('id') - .default(null) - .run() + const [oldLeadTeamMemberId, team] = await Promise.all([ + r + .table('TeamMember') + .getAll(teamId, {index: 'teamId'}) + .filter({isNotRemoved: true, isLead: true}) + .nth(0)('id') + .default(null) + .run(), + dataLoader.get('teams').loadNonNull(teamId) + ]) if (!isSuperUser(authToken)) { + const isOrgAdmin = await isUserOrgAdmin(viewerId, team.orgId, dataLoader) const viewerTeamMemberId = TeamMemberId.join(teamId, viewerId) - if (viewerTeamMemberId !== oldLeadTeamMemberId) { - return standardError(new Error('Not team lead'), {userId: viewerId}) + if (viewerTeamMemberId !== oldLeadTeamMemberId && !isOrgAdmin) { + return standardError(new Error('Not team lead or org admin'), {userId: viewerId}) } } diff --git a/packages/server/graphql/mutations/removeTeamMember.ts b/packages/server/graphql/mutations/removeTeamMember.ts index 8c17c71b881..f235589a70a 100644 --- a/packages/server/graphql/mutations/removeTeamMember.ts +++ b/packages/server/graphql/mutations/removeTeamMember.ts @@ -1,7 +1,7 @@ import {GraphQLID, GraphQLNonNull, GraphQLObjectType} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import fromTeamMemberId from 'parabol-client/utils/relay/fromTeamMemberId' -import {getUserId, isTeamLead} from '../../utils/authorization' +import {getUserId, isTeamLead, isUserOrgAdmin} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' @@ -30,10 +30,15 @@ export default { // AUTH const viewerId = getUserId(authToken) const {userId, teamId} = fromTeamMemberId(teamMemberId) + const team = await dataLoader.get('teams').loadNonNull(teamId) + const [isOrgAdmin, isViewerTeamLead] = await Promise.all([ + isUserOrgAdmin(viewerId, team.orgId, dataLoader), + isTeamLead(viewerId, teamId, dataLoader) + ]) const isSelf = viewerId === userId if (!isSelf) { - if (!(await isTeamLead(viewerId, teamId, dataLoader))) { - return standardError(new Error('Not team lead'), {userId: viewerId}) + if (!isOrgAdmin && !isViewerTeamLead) { + return standardError(new Error('Not team lead or org admin'), {userId: viewerId}) } } From 47224c1e8429b10269a099d4febb3ee965645abb Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 8 May 2024 18:56:02 +0100 Subject: [PATCH 215/529] chore(release): release v7.31.0 (#9734) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 21 +++++++++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 33 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7a309c4eb99..3a9c391c003 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.30.4" + ".": "7.31.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index ea2af2e245a..a962a8a3916 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.31.0](https://github.com/ParabolInc/parabol/compare/v7.30.4...v7.31.0) (2024-05-08) + + +### Added + +* increase team subscription to $8 ([#9727](https://github.com/ParabolInc/parabol/issues/9727)) ([9d2fa5f](https://github.com/ParabolInc/parabol/commit/9d2fa5f4a53cecb3748406012ca1bd0eeaa6800c)) + + +### Fixed + +* Handle invitation links with invalid auth token ([#9741](https://github.com/ParabolInc/parabol/issues/9741)) ([162de5e](https://github.com/ParabolInc/parabol/commit/162de5e87865faa190e5bbe16e25432aeb29e58b)) +* only query templates when a user clicks the options menu ([#9651](https://github.com/ParabolInc/parabol/issues/9651)) ([7c75eb1](https://github.com/ParabolInc/parabol/commit/7c75eb1816162394dd2362e32d985521e860fd54)) +* org admin can change team lead ([#9742](https://github.com/ParabolInc/parabol/issues/9742)) ([d5520ae](https://github.com/ParabolInc/parabol/commit/d5520aebc6b518382510eb1ef2c5c4c10dad70e6)) + + +### Changed + +* More processRecurrence tracing ([#9736](https://github.com/ParabolInc/parabol/issues/9736)) ([881546c](https://github.com/ParabolInc/parabol/commit/881546c2240edb5be399e2b25e2d3e108d61f9fc)) +* remove ai summary from discussion thread ([#9708](https://github.com/ParabolInc/parabol/issues/9708)) ([2123159](https://github.com/ParabolInc/parabol/commit/2123159e718395a63589ce4f76578e271d1b2c9b)) +* remove discussion prompt from summary ([#9711](https://github.com/ParabolInc/parabol/issues/9711)) ([a02c935](https://github.com/ParabolInc/parabol/commit/a02c935d1f16f1ec6e4be410ac826fd239f36179)) + ## [7.30.4](https://github.com/ParabolInc/parabol/compare/v7.30.3...v7.30.4) (2024-05-07) diff --git a/package.json b/package.json index aedee107a6b..a2fc3dbc5ed 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.30.4", + "version": "7.31.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index a380423501a..3edb0647598 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.30.4", + "version": "7.31.0", "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.30.4" + "parabol-server": "7.31.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 0c528a632b9..f1b12613b68 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.30.4", + "version": "7.31.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 6ebadd4344d..7ff5e3462f9 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.30.4", + "version": "7.31.0", "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 f33e5ac44ba..2b4ac934549 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.30.4", + "version": "7.31.0", "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.30.4", - "parabol-server": "7.30.4", + "parabol-client": "7.31.0", + "parabol-server": "7.31.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index b8636d889d6..da23b924d9f 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.30.4", + "version": "7.31.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 10a8841f153..1470d60d1f4 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.30.4", + "version": "7.31.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.30.4", + "parabol-client": "7.31.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From a5d4badf63781ddf9023fcc169d4b90f0f9d646f Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 9 May 2024 12:50:29 -0700 Subject: [PATCH 216/529] fix: close websocket with reason on invalid token (#9744) Signed-off-by: Matt Krick --- packages/client/package.json | 2 +- packages/server/socketHandlers/handleOpen.ts | 27 ++++++++++++- .../server/socketHandlers/handleUpgrade.ts | 32 ++------------- yarn.lock | 39 ++++--------------- 4 files changed, 38 insertions(+), 62 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index f1b12613b68..711f673ad80 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -68,7 +68,7 @@ "@emotion/styled": "^10.0.27", "@mattkrick/graphql-trebuchet-client": "^2.2.1", "@mattkrick/sanitize-svg": "0.4.0", - "@mattkrick/trebuchet-client": "^3.0.2", + "@mattkrick/trebuchet-client": "3.0.1", "@mui/icons-material": "^5.8.4", "@mui/material": "^5.9.2", "@mui/x-date-pickers": "^6.3.1", diff --git a/packages/server/socketHandlers/handleOpen.ts b/packages/server/socketHandlers/handleOpen.ts index b002ac03168..f2920fe0ff0 100644 --- a/packages/server/socketHandlers/handleOpen.ts +++ b/packages/server/socketHandlers/handleOpen.ts @@ -1,9 +1,13 @@ import {WebSocketBehavior} from 'uWebSockets.js' +import {TrebuchetCloseReason} from '../../client/types/constEnums' import activeClients from '../activeClients' import AuthToken from '../database/types/AuthToken' import ConnectionContext from '../socketHelpers/ConnectionContext' import keepAlive from '../socketHelpers/keepAlive' import {sendEncodedMessage} from '../socketHelpers/sendEncodedMessage' +import {isAuthenticated} from '../utils/authorization' +import checkBlacklistJWT from '../utils/checkBlacklistJWT' +import sendToSentry from '../utils/sendToSentry' import handleConnect from './handleConnect' const APP_VERSION = process.env.npm_package_version @@ -11,11 +15,32 @@ export type SocketUserData = { connectionContext: ConnectionContext authToken: AuthToken ip: string + protocol: string done?: true } const handleOpen: WebSocketBehavior['open'] = async (socket) => { - const {authToken, ip} = socket.getUserData() + const {authToken, ip, protocol} = socket.getUserData() + if (protocol !== 'trebuchet-ws') { + sendToSentry(new Error(`WebSocket error: invalid protocol: ${protocol}`)) + // WS Error 1002 is roughly HTTP 412 Precondition Failed because we can't support the req header + socket.end(412, 'Invalid protocol') + return + } + + if (!isAuthenticated(authToken)) { + socket.end(401, TrebuchetCloseReason.EXPIRED_SESSION) + return + } + + // 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) + if (isBlacklistedJWT) { + socket.end(401, TrebuchetCloseReason.EXPIRED_SESSION) + return + } + const connectionContext = (socket.getUserData().connectionContext = new ConnectionContext( socket, authToken, diff --git a/packages/server/socketHandlers/handleUpgrade.ts b/packages/server/socketHandlers/handleUpgrade.ts index 07874d2783d..9120b5459fe 100644 --- a/packages/server/socketHandlers/handleUpgrade.ts +++ b/packages/server/socketHandlers/handleUpgrade.ts @@ -1,38 +1,14 @@ import {WebSocketBehavior} from 'uWebSockets.js' -import {TrebuchetCloseReason} from '../../client/types/constEnums' -import safetyPatchRes from '../safetyPatchRes' -import {isAuthenticated} from '../utils/authorization' -import checkBlacklistJWT from '../utils/checkBlacklistJWT' import getQueryToken from '../utils/getQueryToken' -import sendToSentry from '../utils/sendToSentry' import uwsGetIP from '../utils/uwsGetIP' -const handleUpgrade: WebSocketBehavior['upgrade'] = async (res, req, context) => { - safetyPatchRes(res) - const protocol = req.getHeader('sec-websocket-protocol') - if (protocol !== 'trebuchet-ws') { - sendToSentry(new Error(`WebSocket error: invalid protocol: ${protocol}`)) - // WS Error 1002 is roughly HTTP 412 Precondition Failed because we can't support the req header - res.writeStatus('412').end() - return - } - const authToken = getQueryToken(req) - if (!isAuthenticated(authToken)) { - res.writeStatus('401').end() - return - } - +const handleUpgrade: WebSocketBehavior['upgrade'] = (res, req, context) => { const key = req.getHeader('sec-websocket-key') + const protocol = req.getHeader('sec-websocket-protocol') const extensions = req.getHeader('sec-websocket-extensions') const ip = uwsGetIP(res, req) - const {sub: userId, iat} = authToken - // ALL async calls must come after the message listener, or we'll skip out on messages (e.g. resub after server restart) - const isBlacklistedJWT = await checkBlacklistJWT(userId, iat) - if (isBlacklistedJWT) { - res.writeStatus('401').end(TrebuchetCloseReason.EXPIRED_SESSION) - return - } - res.upgrade({ip, authToken}, key, protocol, extensions, context) + const authToken = getQueryToken(req) + res.upgrade({ip, authToken, protocol}, key, protocol, extensions, context) } export default handleUpgrade diff --git a/yarn.lock b/yarn.lock index d5eaa0e63b8..bb7170ab1de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4980,10 +4980,10 @@ resolved "https://registry.yarnpkg.com/@mattkrick/sanitize-svg/-/sanitize-svg-0.4.0.tgz#388c29614cf72aa0dd9803c77c9c9d070bd3cd2d" integrity sha512-TnPI97WVAxo8SQcPy8aV3OF9/2WjXB5/+pRNVudIWR7Bhi5ZjtR/ur162So08GkvsvB914AXCW2sxFh1x6KhHA== -"@mattkrick/trebuchet-client@^3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@mattkrick/trebuchet-client/-/trebuchet-client-3.0.2.tgz#336d687256fb9ac7bb407f656bf2f69f35f817e1" - integrity sha512-JLOx8gd+cGkDeHhdnns0oor2UNv5uRZ8A/Jfw3NhQcVqQe6R+x9xIHW29M2T78TDHUWqItTgntX+cdDLqVjVvg== +"@mattkrick/trebuchet-client@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@mattkrick/trebuchet-client/-/trebuchet-client-3.0.1.tgz#3fc49f7858652a55dca92cb0c14c0313df931619" + integrity sha512-5uHCldCqmVntoyujTzRfmGtjld8k4JuBdFN0SvhvRFf83FPaNeoit6Mh8VgrcxxxKujKeYCQDMQH6LWwpsshgQ== dependencies: "@mattkrick/fast-rtc-peer" "^0.4.1" eventemitter3 "^4.0.7" @@ -20311,7 +20311,7 @@ string-similarity@^3.0.0: resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-3.0.0.tgz#07b0bc69fae200ad88ceef4983878d03793847c7" integrity sha512-7kS7LyTp56OqOI2BDWQNVnLX/rCxIQn+/5M0op1WV6P8Xx6TZNdajpuqQdiJ7Xx+p1C5CsWMvdiBp9ApMhxzEQ== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -20329,15 +20329,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.0.tgz#5ab00980cfb29f43e736b113a120a73a0fb569d3" @@ -20414,7 +20405,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -20428,13 +20419,6 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -22287,7 +22271,7 @@ workbox-window@6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.5.4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -22305,15 +22289,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 9a904d3a35b0f82ffd67d6e7d4853b40cfc4f234 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Fri, 10 May 2024 11:33:15 -0700 Subject: [PATCH 217/529] fix: fix the issue where a successful upgrade won't refresh the billing page (#9740) --- .../mutations/handlers/upgradeToTeamTierSuccessUpdater.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/client/mutations/handlers/upgradeToTeamTierSuccessUpdater.ts b/packages/client/mutations/handlers/upgradeToTeamTierSuccessUpdater.ts index 8824f06e4eb..f47a9bbb626 100644 --- a/packages/client/mutations/handlers/upgradeToTeamTierSuccessUpdater.ts +++ b/packages/client/mutations/handlers/upgradeToTeamTierSuccessUpdater.ts @@ -5,6 +5,8 @@ const upgradeToTeamTierSuccessUpdater = (payload: RecordProxy) => { if (organization) { organization.setValue(true, 'showConfetti') organization.setValue(true, 'showDrawer') + organization.setValue('team', 'billingTier') + organization.setValue('team', 'tier') } } export default upgradeToTeamTierSuccessUpdater From 5ec8f457f44780036da79b54136f3c68c5bb052c Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Tue, 14 May 2024 19:21:39 +0100 Subject: [PATCH 218/529] feat(single-tenant-host): Embedder and Text Embeddings Inference added to the stack (#9753) --- .gitignore | 1 + docker/images/parabol-ubi/README.md | 2 +- docker/stacks/single-tenant-host/README.md | 12 +++++-- .../single-tenant-host/docker-compose.yml | 36 +++++++++++++++++++ 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 62486ca02b3..dc26e75b673 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ stats.json storybook-static/ test-report.xml webpack-assets.json +**/data diff --git a/docker/images/parabol-ubi/README.md b/docker/images/parabol-ubi/README.md index 7be2429b457..f6f8692cc38 100644 --- a/docker/images/parabol-ubi/README.md +++ b/docker/images/parabol-ubi/README.md @@ -14,7 +14,7 @@ _Assumes redis, rethinkdb, and postgres already running to have operational stac The commands below will start a Parabol container on the target tag specified in \_DOCKER_TAG export. It will volume mount a .env in your current working directory to the container, so you can pass in any .env in your current working directory. -For a more detailed how-to deploy Parabol, please go to the section [docker-host-st](https://github.com/ParabolInc/parabol/tree/master/docker/parabol-ubi/docker-host-st) +For a more detailed how-to deploy Parabol, please go to the section [docker-host-st](https://github.com/ParabolInc/parabol/tree/master/docker/stacks/single-tenant-host/) - Run the PreDeploy script diff --git a/docker/stacks/single-tenant-host/README.md b/docker/stacks/single-tenant-host/README.md index 5f2ca04254f..dfc04502127 100644 --- a/docker/stacks/single-tenant-host/README.md +++ b/docker/stacks/single-tenant-host/README.md @@ -27,6 +27,14 @@ Chronos isn't started by default. If it needs to run, it must be managed using ` This will run `pre-deploy` and thus it will recreate the `web-server` and the `gql-executor`. +## Running Embedder + +Embedder isn't started by default. If it needs to run, it must be managed using `docker compose --profile databases --profile parabol --profile embedder up`. + +This will run `pre-deploy` and thus it will recreate the `web-server` and the `gql-executor`. + +The Embedder requires a model. It can be provided using the **Text Embeddings Inference** container available on the stack. It can be executed with `docker compose --profile databases --profile text-embeddings --profile parabol --profile embedder up` + ## Database debug Some tools are available to debug the databases is needed: @@ -38,5 +46,5 @@ To operate them use `docker compose up --profile databases --profile database-de ## Running the whole stack -- Start the whole stack: `docker compose --profile databases --profile parabol --profile database-debug --profile chronos up -d`. -- Stop the stack: `docker compose --profile databases --profile parabol --profile database-debug --profile chronos down`. +- Start the whole stack: `docker compose --profile databases --profiles text-embeddings --profile parabol --profile database-debug --profile chronos up --profile embedder -d`. +- Stop the stack: `docker compose --profile databases --profiles text-embeddings --profile parabol --profile database-debug --profile chronos up --profile embedder down`. diff --git a/docker/stacks/single-tenant-host/docker-compose.yml b/docker/stacks/single-tenant-host/docker-compose.yml index a336201bfe3..49a918e985d 100644 --- a/docker/stacks/single-tenant-host/docker-compose.yml +++ b/docker/stacks/single-tenant-host/docker-compose.yml @@ -75,6 +75,20 @@ services: - "8081:8081" networks: - parabol-network + text-embeddings-inference: + profiles: ["text-embeddings"] + container_name: text-embeddings-inference + image: ghcr.io/huggingface/text-embeddings-inference:cpu-1.2.2 + command: + - "--model-id=llmrails/ember-v1" + platform: linux/x86_64 + restart: always + ports: + - "3040:80" + volumes: + - ./data/text-embeddings-inference:/data + networks: + - parabol-network pre-deploy: container_name: pre-deploy profiles: ["parabol"] @@ -162,5 +176,27 @@ services: condition: service_healthy networks: - parabol-network + embedder: + container_name: embedder + profiles: ["embedder"] + image: #image:tag + restart: always + command: bash -c "node dist/embedder.js" + env_file: .env + environment: + - SERVER_ID=15 + volumes: + - "./.env:/parabol/.env" + depends_on: + pre-deploy: + condition: service_completed_successfully + rethinkdb: + condition: service_started + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - parabol-network networks: parabol-network: From 1c8fa84444dc361d6bb8938d55accad31be2b6e7 Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Tue, 14 May 2024 19:21:52 +0100 Subject: [PATCH 219/529] fix(dev-stack): update text-embeddings-inference to 1.2.2 (#9754) --- docker/stacks/development/docker-compose.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/stacks/development/docker-compose.yml b/docker/stacks/development/docker-compose.yml index a4509d051b9..0b73269d898 100644 --- a/docker/stacks/development/docker-compose.yml +++ b/docker/stacks/development/docker-compose.yml @@ -70,7 +70,7 @@ services: networks: parabol-network: text-embeddings-inference: - image: ghcr.io/huggingface/text-embeddings-inference:cpu-1.2 + image: ghcr.io/huggingface/text-embeddings-inference:cpu-1.2.2 command: - "--model-id=llmrails/ember-v1" platform: linux/x86_64 From 341772af9da5923e7c22b8a253aaca0b2aeab7c5 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Wed, 15 May 2024 11:13:59 -0700 Subject: [PATCH 220/529] chore: Trace RRule (#9756) --- .../graphql/private/mutations/processRecurrence.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/server/graphql/private/mutations/processRecurrence.ts b/packages/server/graphql/private/mutations/processRecurrence.ts index 9b9e6decc53..57d1e8578d5 100644 --- a/packages/server/graphql/private/mutations/processRecurrence.ts +++ b/packages/server/graphql/private/mutations/processRecurrence.ts @@ -187,7 +187,9 @@ const processRecurrence: MutationResolvers['processRecurrence'] = async (_source // For meetings that should still be active, start the meeting and set its end time. // Any subscriptions are handled by the shared meeting start code - const rrule = RRule.fromString(meetingSeries.recurrenceRule) + const rrule = tracer.trace('RRule.fromString', () => + RRule.fromString(meetingSeries.recurrenceRule) + ) // technically, RRULE should never return NaN here but there's a bug in the library // https://github.com/jakubroztocil/rrule/issues/321 if (isNaN(rrule.options.interval)) { @@ -202,9 +204,8 @@ const processRecurrence: MutationResolvers['processRecurrence'] = async (_source Math.max(lastMeeting.createdAt.getTime() + ms('10m'), now.getTime() - ms('24h')) ) : new Date(0) - const newMeetingsStartTimes = rrule.between( - getRRuleDateFromJSDate(fromDate), - getRRuleDateFromJSDate(now) + const newMeetingsStartTimes = tracer.trace('RRule.between', () => + rrule.between(getRRuleDateFromJSDate(fromDate), getRRuleDateFromJSDate(now)) ) for (const startTime of newMeetingsStartTimes) { const err = await tracer.trace('startRecurringMeeting', async (span) => { From 5cbca8dfc11e27f5550a3a23bccd590ce5481bd4 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 20 May 2024 09:32:28 -0700 Subject: [PATCH 221/529] [Snyk] Upgrade @aws-sdk/client-s3 from 3.537.0 to 3.556.0 (#9752) Co-authored-by: snyk-bot Co-authored-by: GitHub Action --- packages/server/package.json | 2 +- yarn.lock | 402 ++++++++++++++++------------------- 2 files changed, 183 insertions(+), 221 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index 1470d60d1f4..6b7a62a7fc4 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -75,7 +75,7 @@ }, "dependencies": { "@amplitude/analytics-node": "^1.3.2", - "@aws-sdk/client-s3": "3.537.0", + "@aws-sdk/client-s3": "3.556.0", "@aws-sdk/s3-request-presigner": "^3.565.0", "@dicebear/core": "^8.0.1", "@dicebear/initials": "^8.0.1", diff --git a/yarn.lock b/yarn.lock index bb7170ab1de..4f01aebd743 100644 --- a/yarn.lock +++ b/yarn.lock @@ -431,17 +431,17 @@ "@smithy/util-waiter" "^2.2.0" tslib "^2.6.2" -"@aws-sdk/client-s3@3.537.0": - version "3.537.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.537.0.tgz#887b2c743da49378104054b66aa135fae805263c" - integrity sha512-EMPN2toHz1QtSiDeLKS1zrazh+8J0g1Y5t5lCq25iTXqCSV9vB2jCKwG5+OB6L5tAKkwyl1uZofeWLmdFkztEg== +"@aws-sdk/client-s3@3.556.0": + version "3.556.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.556.0.tgz#b32c12e7857326072df7c53c40b36bdf5d8e6169" + integrity sha512-6WF9Kuzz1/8zqX8hKBpqj9+FYwQ5uTsVcOKpTW94AMX2qtIeVRlwlnNnYyywWo61yqD3g59CMNHcqSsaqAwglg== dependencies: "@aws-crypto/sha1-browser" "3.0.0" "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.535.0" - "@aws-sdk/core" "3.535.0" - "@aws-sdk/credential-provider-node" "3.535.0" + "@aws-sdk/client-sts" "3.556.0" + "@aws-sdk/core" "3.556.0" + "@aws-sdk/credential-provider-node" "3.556.0" "@aws-sdk/middleware-bucket-endpoint" "3.535.0" "@aws-sdk/middleware-expect-continue" "3.535.0" "@aws-sdk/middleware-flexible-checksums" "3.535.0" @@ -449,19 +449,19 @@ "@aws-sdk/middleware-location-constraint" "3.535.0" "@aws-sdk/middleware-logger" "3.535.0" "@aws-sdk/middleware-recursion-detection" "3.535.0" - "@aws-sdk/middleware-sdk-s3" "3.535.0" - "@aws-sdk/middleware-signing" "3.535.0" + "@aws-sdk/middleware-sdk-s3" "3.556.0" + "@aws-sdk/middleware-signing" "3.556.0" "@aws-sdk/middleware-ssec" "3.537.0" - "@aws-sdk/middleware-user-agent" "3.535.0" + "@aws-sdk/middleware-user-agent" "3.540.0" "@aws-sdk/region-config-resolver" "3.535.0" - "@aws-sdk/signature-v4-multi-region" "3.535.0" + "@aws-sdk/signature-v4-multi-region" "3.556.0" "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-endpoints" "3.535.0" + "@aws-sdk/util-endpoints" "3.540.0" "@aws-sdk/util-user-agent-browser" "3.535.0" "@aws-sdk/util-user-agent-node" "3.535.0" "@aws-sdk/xml-builder" "3.535.0" "@smithy/config-resolver" "^2.2.0" - "@smithy/core" "^1.4.0" + "@smithy/core" "^1.4.2" "@smithy/eventstream-serde-browser" "^2.2.0" "@smithy/eventstream-serde-config-resolver" "^2.2.0" "@smithy/eventstream-serde-node" "^2.2.0" @@ -472,21 +472,21 @@ "@smithy/invalid-dependency" "^2.2.0" "@smithy/md5-js" "^2.2.0" "@smithy/middleware-content-length" "^2.2.0" - "@smithy/middleware-endpoint" "^2.5.0" - "@smithy/middleware-retry" "^2.2.0" + "@smithy/middleware-endpoint" "^2.5.1" + "@smithy/middleware-retry" "^2.3.1" "@smithy/middleware-serde" "^2.3.0" "@smithy/middleware-stack" "^2.2.0" "@smithy/node-config-provider" "^2.3.0" "@smithy/node-http-handler" "^2.5.0" "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.0" + "@smithy/smithy-client" "^2.5.1" "@smithy/types" "^2.12.0" "@smithy/url-parser" "^2.2.0" "@smithy/util-base64" "^2.3.0" "@smithy/util-body-length-browser" "^2.2.0" "@smithy/util-body-length-node" "^2.3.0" - "@smithy/util-defaults-mode-browser" "^2.2.0" - "@smithy/util-defaults-mode-node" "^2.3.0" + "@smithy/util-defaults-mode-browser" "^2.2.1" + "@smithy/util-defaults-mode-node" "^2.3.1" "@smithy/util-endpoints" "^1.2.0" "@smithy/util-retry" "^2.2.0" "@smithy/util-stream" "^2.2.0" @@ -541,60 +541,60 @@ tslib "^2.6.2" uuid "^9.0.1" -"@aws-sdk/client-sso-oidc@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.535.0.tgz#64666c2f7bed8510938ba2b481429fea8f97473d" - integrity sha512-M2cG4EQXDpAJQyq33ORIr6abmdX9p9zX0ssVy8XwFNB7lrgoIKxuVoGL+fX+XMgecl24x7ELz6b4QlILOevbCw== +"@aws-sdk/client-sso-oidc@3.552.0": + version "3.552.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.552.0.tgz#3215792bbce40a4373d6fca711e4b58fbf794284" + integrity sha512-6JYTgN/n4xTm3Z+JhEZq06pyYsgo7heYDmR+0smmauQS02Eu8lvUc2jPs/0GDAmty7J4tq3gS6TRwvf7181C2w== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.535.0" - "@aws-sdk/core" "3.535.0" + "@aws-sdk/client-sts" "3.552.0" + "@aws-sdk/core" "3.552.0" "@aws-sdk/middleware-host-header" "3.535.0" "@aws-sdk/middleware-logger" "3.535.0" "@aws-sdk/middleware-recursion-detection" "3.535.0" - "@aws-sdk/middleware-user-agent" "3.535.0" + "@aws-sdk/middleware-user-agent" "3.540.0" "@aws-sdk/region-config-resolver" "3.535.0" "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-endpoints" "3.535.0" + "@aws-sdk/util-endpoints" "3.540.0" "@aws-sdk/util-user-agent-browser" "3.535.0" "@aws-sdk/util-user-agent-node" "3.535.0" "@smithy/config-resolver" "^2.2.0" - "@smithy/core" "^1.4.0" + "@smithy/core" "^1.4.2" "@smithy/fetch-http-handler" "^2.5.0" "@smithy/hash-node" "^2.2.0" "@smithy/invalid-dependency" "^2.2.0" "@smithy/middleware-content-length" "^2.2.0" - "@smithy/middleware-endpoint" "^2.5.0" - "@smithy/middleware-retry" "^2.2.0" + "@smithy/middleware-endpoint" "^2.5.1" + "@smithy/middleware-retry" "^2.3.1" "@smithy/middleware-serde" "^2.3.0" "@smithy/middleware-stack" "^2.2.0" "@smithy/node-config-provider" "^2.3.0" "@smithy/node-http-handler" "^2.5.0" "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.0" + "@smithy/smithy-client" "^2.5.1" "@smithy/types" "^2.12.0" "@smithy/url-parser" "^2.2.0" "@smithy/util-base64" "^2.3.0" "@smithy/util-body-length-browser" "^2.2.0" "@smithy/util-body-length-node" "^2.3.0" - "@smithy/util-defaults-mode-browser" "^2.2.0" - "@smithy/util-defaults-mode-node" "^2.3.0" + "@smithy/util-defaults-mode-browser" "^2.2.1" + "@smithy/util-defaults-mode-node" "^2.3.1" "@smithy/util-endpoints" "^1.2.0" "@smithy/util-middleware" "^2.2.0" "@smithy/util-retry" "^2.2.0" "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@aws-sdk/client-sso-oidc@3.552.0": - version "3.552.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.552.0.tgz#3215792bbce40a4373d6fca711e4b58fbf794284" - integrity sha512-6JYTgN/n4xTm3Z+JhEZq06pyYsgo7heYDmR+0smmauQS02Eu8lvUc2jPs/0GDAmty7J4tq3gS6TRwvf7181C2w== +"@aws-sdk/client-sso-oidc@3.556.0": + version "3.556.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.556.0.tgz#4c19fccc35361de046d2cd74a7a685d71aa5dd1e" + integrity sha512-AXKd2TB6nNrksu+OfmHl8uI07PdgzOo4o8AxoRO8SHlwoMAGvcT9optDGVSYoVfgOKTymCoE7h8/UoUfPc11wQ== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.552.0" - "@aws-sdk/core" "3.552.0" + "@aws-sdk/client-sts" "3.556.0" + "@aws-sdk/core" "3.556.0" "@aws-sdk/middleware-host-header" "3.535.0" "@aws-sdk/middleware-logger" "3.535.0" "@aws-sdk/middleware-recursion-detection" "3.535.0" @@ -631,58 +631,58 @@ "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@aws-sdk/client-sso@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.535.0.tgz#c405aaf880cb695aa2f5070a8827955274fc9df2" - integrity sha512-h9eQRdFnjDRVBnPJIKXuX7D+isSAioIfZPC4PQwsL5BscTRlk4c90DX0R0uk64YUtp7LZu8TNtrosFZ/1HtTrQ== +"@aws-sdk/client-sso@3.552.0": + version "3.552.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.552.0.tgz#dea1533cc74e80f9bb49f8926c21912497a08616" + integrity sha512-IAjRj5gcuyoPe/OhciMY/UyW8C1kyXSUJFagxvbeSv8q0mEfaPBVjGgz2xSYRFhhZr3gFlGCS9SiukwOL2/VoA== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/core" "3.535.0" + "@aws-sdk/core" "3.552.0" "@aws-sdk/middleware-host-header" "3.535.0" "@aws-sdk/middleware-logger" "3.535.0" "@aws-sdk/middleware-recursion-detection" "3.535.0" - "@aws-sdk/middleware-user-agent" "3.535.0" + "@aws-sdk/middleware-user-agent" "3.540.0" "@aws-sdk/region-config-resolver" "3.535.0" "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-endpoints" "3.535.0" + "@aws-sdk/util-endpoints" "3.540.0" "@aws-sdk/util-user-agent-browser" "3.535.0" "@aws-sdk/util-user-agent-node" "3.535.0" "@smithy/config-resolver" "^2.2.0" - "@smithy/core" "^1.4.0" + "@smithy/core" "^1.4.2" "@smithy/fetch-http-handler" "^2.5.0" "@smithy/hash-node" "^2.2.0" "@smithy/invalid-dependency" "^2.2.0" "@smithy/middleware-content-length" "^2.2.0" - "@smithy/middleware-endpoint" "^2.5.0" - "@smithy/middleware-retry" "^2.2.0" + "@smithy/middleware-endpoint" "^2.5.1" + "@smithy/middleware-retry" "^2.3.1" "@smithy/middleware-serde" "^2.3.0" "@smithy/middleware-stack" "^2.2.0" "@smithy/node-config-provider" "^2.3.0" "@smithy/node-http-handler" "^2.5.0" "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.0" + "@smithy/smithy-client" "^2.5.1" "@smithy/types" "^2.12.0" "@smithy/url-parser" "^2.2.0" "@smithy/util-base64" "^2.3.0" "@smithy/util-body-length-browser" "^2.2.0" "@smithy/util-body-length-node" "^2.3.0" - "@smithy/util-defaults-mode-browser" "^2.2.0" - "@smithy/util-defaults-mode-node" "^2.3.0" + "@smithy/util-defaults-mode-browser" "^2.2.1" + "@smithy/util-defaults-mode-node" "^2.3.1" "@smithy/util-endpoints" "^1.2.0" "@smithy/util-middleware" "^2.2.0" "@smithy/util-retry" "^2.2.0" "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@aws-sdk/client-sso@3.552.0": - version "3.552.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.552.0.tgz#dea1533cc74e80f9bb49f8926c21912497a08616" - integrity sha512-IAjRj5gcuyoPe/OhciMY/UyW8C1kyXSUJFagxvbeSv8q0mEfaPBVjGgz2xSYRFhhZr3gFlGCS9SiukwOL2/VoA== +"@aws-sdk/client-sso@3.556.0": + version "3.556.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.556.0.tgz#7beeeebb6a437f09680edefc5c998822292a528a" + integrity sha512-unXdWS7uvHqCcOyC1de+Fr8m3F2vMg2m24GPea0bg7rVGTYmiyn9mhUX11VCt+ozydrw+F50FQwL6OqoqPocmw== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/core" "3.552.0" + "@aws-sdk/core" "3.556.0" "@aws-sdk/middleware-host-header" "3.535.0" "@aws-sdk/middleware-logger" "3.535.0" "@aws-sdk/middleware-recursion-detection" "3.535.0" @@ -719,58 +719,58 @@ "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@aws-sdk/client-sts@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.535.0.tgz#0f518fe338c6b7a8b8a897e2ccee65d06dc0040f" - integrity sha512-ii9OOm3TJwP3JmO1IVJXKWIShVKPl0VtdlgROc/SkDglO/kuAw9eDdlROgc+qbFl+gm6bBTguOVTUXt3tS3flw== +"@aws-sdk/client-sts@3.552.0": + version "3.552.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.552.0.tgz#ae6879022644348596e822e80accb468676a2005" + integrity sha512-rOZlAj8GyFgUBESyKezes67A8Kj5+KjRhfBHMXrkcM5h9UOIz5q7QdkSQOmzWwRoPDmmAqb6t+y041/76TnPEg== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/core" "3.535.0" + "@aws-sdk/core" "3.552.0" "@aws-sdk/middleware-host-header" "3.535.0" "@aws-sdk/middleware-logger" "3.535.0" "@aws-sdk/middleware-recursion-detection" "3.535.0" - "@aws-sdk/middleware-user-agent" "3.535.0" + "@aws-sdk/middleware-user-agent" "3.540.0" "@aws-sdk/region-config-resolver" "3.535.0" "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-endpoints" "3.535.0" + "@aws-sdk/util-endpoints" "3.540.0" "@aws-sdk/util-user-agent-browser" "3.535.0" "@aws-sdk/util-user-agent-node" "3.535.0" "@smithy/config-resolver" "^2.2.0" - "@smithy/core" "^1.4.0" + "@smithy/core" "^1.4.2" "@smithy/fetch-http-handler" "^2.5.0" "@smithy/hash-node" "^2.2.0" "@smithy/invalid-dependency" "^2.2.0" "@smithy/middleware-content-length" "^2.2.0" - "@smithy/middleware-endpoint" "^2.5.0" - "@smithy/middleware-retry" "^2.2.0" + "@smithy/middleware-endpoint" "^2.5.1" + "@smithy/middleware-retry" "^2.3.1" "@smithy/middleware-serde" "^2.3.0" "@smithy/middleware-stack" "^2.2.0" "@smithy/node-config-provider" "^2.3.0" "@smithy/node-http-handler" "^2.5.0" "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.0" + "@smithy/smithy-client" "^2.5.1" "@smithy/types" "^2.12.0" "@smithy/url-parser" "^2.2.0" "@smithy/util-base64" "^2.3.0" "@smithy/util-body-length-browser" "^2.2.0" "@smithy/util-body-length-node" "^2.3.0" - "@smithy/util-defaults-mode-browser" "^2.2.0" - "@smithy/util-defaults-mode-node" "^2.3.0" + "@smithy/util-defaults-mode-browser" "^2.2.1" + "@smithy/util-defaults-mode-node" "^2.3.1" "@smithy/util-endpoints" "^1.2.0" "@smithy/util-middleware" "^2.2.0" "@smithy/util-retry" "^2.2.0" "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@aws-sdk/client-sts@3.552.0": - version "3.552.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.552.0.tgz#ae6879022644348596e822e80accb468676a2005" - integrity sha512-rOZlAj8GyFgUBESyKezes67A8Kj5+KjRhfBHMXrkcM5h9UOIz5q7QdkSQOmzWwRoPDmmAqb6t+y041/76TnPEg== +"@aws-sdk/client-sts@3.556.0": + version "3.556.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.556.0.tgz#3aa20cca462839f1451f11efada2be119dd36a6b" + integrity sha512-TsK3js7Suh9xEmC886aY+bv0KdLLYtzrcmVt6sJ/W6EnDXYQhBuKYFhp03NrN2+vSvMGpqJwR62DyfKe1G0QzQ== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/core" "3.552.0" + "@aws-sdk/core" "3.556.0" "@aws-sdk/middleware-host-header" "3.535.0" "@aws-sdk/middleware-logger" "3.535.0" "@aws-sdk/middleware-recursion-detection" "3.535.0" @@ -807,27 +807,27 @@ "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@aws-sdk/core@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.535.0.tgz#f3a726c297cea9634d19a1db4e958c918c506c8b" - integrity sha512-+Yusa9HziuaEDta1UaLEtMAtmgvxdxhPn7jgfRY6PplqAqgsfa5FR83sxy5qr2q7xjQTwHtV4MjQVuOjG9JsLw== +"@aws-sdk/core@3.552.0", "@aws-sdk/core@^3.535.0": + version "3.552.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.552.0.tgz#7f744d7cd303d1fa60006d81f75a6f999b64bfb0" + integrity sha512-T7ovljf6fCvIHG9SOSZqGmbVbqZPXPywLAcU+onk/fYLZJj6kjfzKZzSAUBI0nO1OKpuP/nCHaCp51NLWNqsnw== dependencies: - "@smithy/core" "^1.4.0" + "@smithy/core" "^1.4.2" "@smithy/protocol-http" "^3.3.0" - "@smithy/signature-v4" "^2.2.0" - "@smithy/smithy-client" "^2.5.0" + "@smithy/signature-v4" "^2.2.1" + "@smithy/smithy-client" "^2.5.1" "@smithy/types" "^2.12.0" fast-xml-parser "4.2.5" tslib "^2.6.2" -"@aws-sdk/core@3.552.0", "@aws-sdk/core@^3.535.0": - version "3.552.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.552.0.tgz#7f744d7cd303d1fa60006d81f75a6f999b64bfb0" - integrity sha512-T7ovljf6fCvIHG9SOSZqGmbVbqZPXPywLAcU+onk/fYLZJj6kjfzKZzSAUBI0nO1OKpuP/nCHaCp51NLWNqsnw== +"@aws-sdk/core@3.556.0": + version "3.556.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.556.0.tgz#d0f4431a72282b71cfbcaedfb803f7f2807cf60b" + integrity sha512-vJaSaHw2kPQlo11j/Rzuz0gk1tEaKdz+2ser0f0qZ5vwFlANjt08m/frU17ctnVKC1s58bxpctO/1P894fHLrA== dependencies: "@smithy/core" "^1.4.2" "@smithy/protocol-http" "^3.3.0" - "@smithy/signature-v4" "^2.2.1" + "@smithy/signature-v4" "^2.3.0" "@smithy/smithy-client" "^2.5.1" "@smithy/types" "^2.12.0" fast-xml-parser "4.2.5" @@ -854,21 +854,6 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-http@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz#0a42f6b1a61d927bbce9f4afd25112f486bd05da" - integrity sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw== - dependencies: - "@aws-sdk/types" "3.535.0" - "@smithy/fetch-http-handler" "^2.5.0" - "@smithy/node-http-handler" "^2.5.0" - "@smithy/property-provider" "^2.2.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.0" - "@smithy/types" "^2.12.0" - "@smithy/util-stream" "^2.2.0" - tslib "^2.6.2" - "@aws-sdk/credential-provider-http@3.552.0": version "3.552.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.552.0.tgz#ecc88d02cba95621887e6b85b2583e756ad29eb6" @@ -884,23 +869,6 @@ "@smithy/util-stream" "^2.2.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-ini@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.535.0.tgz#b121b1aba2916e3f45745cd690b4082421a7c286" - integrity sha512-bm3XOYlyCjtAb8eeHXLrxqRxYVRw2Iqv9IufdJb4gM13TbNSYniUT1WKaHxGIZ5p+FuNlXVhvk1OpHFM13+gXA== - dependencies: - "@aws-sdk/client-sts" "3.535.0" - "@aws-sdk/credential-provider-env" "3.535.0" - "@aws-sdk/credential-provider-process" "3.535.0" - "@aws-sdk/credential-provider-sso" "3.535.0" - "@aws-sdk/credential-provider-web-identity" "3.535.0" - "@aws-sdk/types" "3.535.0" - "@smithy/credential-provider-imds" "^2.3.0" - "@smithy/property-provider" "^2.2.0" - "@smithy/shared-ini-file-loader" "^2.4.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - "@aws-sdk/credential-provider-ini@3.552.0", "@aws-sdk/credential-provider-ini@^3.535.0": version "3.552.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.552.0.tgz#436f328ea0213efe3231354248ab0d82dade4345" @@ -918,17 +886,16 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-node@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.535.0.tgz#6739b4b52a9cce29dc8e70c9a7290b89cdc4b904" - integrity sha512-6JXp/EuL6euUkH5k4d+lQFF6gBwukrcCOWfNHCmq14mNJf/cqT3HAX1VMtWFRSK20am0IxfYQGccb0/nZykdKg== +"@aws-sdk/credential-provider-ini@3.556.0": + version "3.556.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.556.0.tgz#bf780feb92a7920cc525cd7cb7870ea61b84c125" + integrity sha512-0Nz4ErOlXhe3muxWYMbPwRMgfKmVbBp36BAE2uv/z5wTbfdBkcgUwaflEvlKCLUTdHzuZsQk+BFS/gVyaUeOuA== dependencies: + "@aws-sdk/client-sts" "3.556.0" "@aws-sdk/credential-provider-env" "3.535.0" - "@aws-sdk/credential-provider-http" "3.535.0" - "@aws-sdk/credential-provider-ini" "3.535.0" "@aws-sdk/credential-provider-process" "3.535.0" - "@aws-sdk/credential-provider-sso" "3.535.0" - "@aws-sdk/credential-provider-web-identity" "3.535.0" + "@aws-sdk/credential-provider-sso" "3.556.0" + "@aws-sdk/credential-provider-web-identity" "3.556.0" "@aws-sdk/types" "3.535.0" "@smithy/credential-provider-imds" "^2.3.0" "@smithy/property-provider" "^2.2.0" @@ -954,24 +921,29 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-process@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.535.0.tgz#ea1e8a38a32e36bbdc3f75eb03352e6eafa0c659" - integrity sha512-9O1OaprGCnlb/kYl8RwmH7Mlg8JREZctB8r9sa1KhSsWFq/SWO0AuJTyowxD7zL5PkeS4eTvzFFHWCa3OO5epA== +"@aws-sdk/credential-provider-node@3.556.0": + version "3.556.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.556.0.tgz#51f3dc4506053249f8593765d1ab2cef53732fa3" + integrity sha512-s1xVtKjyGc60O8qcNIzS1X3H+pWEwEfZ7TgNznVDNyuXvLrlNWiAcigPWGl2aAkc8tGcsSG0Qpyw2KYC939LFg== dependencies: + "@aws-sdk/credential-provider-env" "3.535.0" + "@aws-sdk/credential-provider-http" "3.552.0" + "@aws-sdk/credential-provider-ini" "3.556.0" + "@aws-sdk/credential-provider-process" "3.535.0" + "@aws-sdk/credential-provider-sso" "3.556.0" + "@aws-sdk/credential-provider-web-identity" "3.556.0" "@aws-sdk/types" "3.535.0" + "@smithy/credential-provider-imds" "^2.3.0" "@smithy/property-provider" "^2.2.0" "@smithy/shared-ini-file-loader" "^2.4.0" "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-sso@3.535.0": +"@aws-sdk/credential-provider-process@3.535.0": version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.535.0.tgz#dfc7c2f39f9ca965becd7e5b9414cd1bb2217490" - integrity sha512-2Dw0YIr8ETdFpq65CC4zK8ZIEbX78rXoNRZXUGNQW3oSKfL0tj8O8ErY6kg1IdEnYbGnEQ35q6luZ5GGNKLgDg== + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.535.0.tgz#ea1e8a38a32e36bbdc3f75eb03352e6eafa0c659" + integrity sha512-9O1OaprGCnlb/kYl8RwmH7Mlg8JREZctB8r9sa1KhSsWFq/SWO0AuJTyowxD7zL5PkeS4eTvzFFHWCa3OO5epA== dependencies: - "@aws-sdk/client-sso" "3.535.0" - "@aws-sdk/token-providers" "3.535.0" "@aws-sdk/types" "3.535.0" "@smithy/property-provider" "^2.2.0" "@smithy/shared-ini-file-loader" "^2.4.0" @@ -991,14 +963,16 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-web-identity@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.535.0.tgz#f1d3a72ff958cbd7e052c5109755379745ac35e0" - integrity sha512-t2/JWrKY0H66A7JW7CqX06/DG2YkJddikt5ymdQvx/Q7dRMJ3d+o/vgjoKr7RvEx/pNruCeyM1599HCvwrVMrg== +"@aws-sdk/credential-provider-sso@3.556.0": + version "3.556.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.556.0.tgz#26dfdd2c6e034f66e82985d65bd6aa3ae09d5e19" + integrity sha512-ETuBgcnpfxqadEAqhQFWpKoV1C/NAgvs5CbBc5EJbelJ8f4prTdErIHjrRtVT8c02MXj92QwczsiNYd5IoOqyw== dependencies: - "@aws-sdk/client-sts" "3.535.0" + "@aws-sdk/client-sso" "3.556.0" + "@aws-sdk/token-providers" "3.556.0" "@aws-sdk/types" "3.535.0" "@smithy/property-provider" "^2.2.0" + "@smithy/shared-ini-file-loader" "^2.4.0" "@smithy/types" "^2.12.0" tslib "^2.6.2" @@ -1013,6 +987,17 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" +"@aws-sdk/credential-provider-web-identity@3.556.0": + version "3.556.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.556.0.tgz#94cd55eaee6ca96354237569102dfaf6774544f4" + integrity sha512-R/YAL8Uh8i+dzVjzMnbcWLIGeeRi2mioHVGnVF+minmaIkCiQMZg2HPrdlKm49El+RljT28Nl5YHRuiqzEIwMA== + dependencies: + "@aws-sdk/client-sts" "3.556.0" + "@aws-sdk/types" "3.535.0" + "@smithy/property-provider" "^2.2.0" + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@aws-sdk/credential-providers@^3.535.0": version "3.552.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-providers/-/credential-providers-3.552.0.tgz#cda713016c555a87dafad8b20bb0c881b4e5469c" @@ -1110,21 +1095,6 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/middleware-sdk-s3@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.535.0.tgz#3cb76342d91a5e0e94d9a380dbaba9a9ee4849e0" - integrity sha512-/dLG/E3af6ohxkQ5GBHT8tZfuPIg6eItKxCXuulvYj0Tqgf3Mb+xTsvSkxQsJF06RS4sH7Qsg/PnB8ZfrJrXpg== - dependencies: - "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-arn-parser" "3.535.0" - "@smithy/node-config-provider" "^2.3.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/signature-v4" "^2.2.0" - "@smithy/smithy-client" "^2.5.0" - "@smithy/types" "^2.12.0" - "@smithy/util-config-provider" "^2.3.0" - tslib "^2.6.2" - "@aws-sdk/middleware-sdk-s3@3.556.0": version "3.556.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.556.0.tgz#ff135d1fbfc843a93860eb3a4000da9d721442c0" @@ -1140,15 +1110,15 @@ "@smithy/util-config-provider" "^2.3.0" tslib "^2.6.2" -"@aws-sdk/middleware-signing@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.535.0.tgz#cf98354e6d48e275689db6a4a513f62bd1555518" - integrity sha512-Rb4sfus1Gc5paRl9JJgymJGsb/i3gJKK/rTuFZICdd1PBBE5osIOHP5CpzWYBtc5LlyZE1a2QoxPMCyG+QUGPw== +"@aws-sdk/middleware-signing@3.556.0": + version "3.556.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.556.0.tgz#2892d76cddf3cb956122618588d163ff7a42c43f" + integrity sha512-kWrPmU8qd3gI5qzpuW9LtWFaH80cOz1ZJDavXx6PRpYZJ5JaKdUHghwfDlVTzzFYAeJmVsWIkPcLT5d5mY5ZTQ== dependencies: "@aws-sdk/types" "3.535.0" "@smithy/property-provider" "^2.2.0" "@smithy/protocol-http" "^3.3.0" - "@smithy/signature-v4" "^2.2.0" + "@smithy/signature-v4" "^2.3.0" "@smithy/types" "^2.12.0" "@smithy/util-middleware" "^2.2.0" tslib "^2.6.2" @@ -1162,17 +1132,6 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/middleware-user-agent@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.535.0.tgz#2877ff5e42d943dd0c488e8b1ad82bd9da121227" - integrity sha512-Uvb2WJ+zdHdCOtsWVPI/M0BcfNrjOYsicDZWtaljucRJKLclY5gNWwD+RwIC+8b5TvfnVOlH+N5jhvpi5Impog== - dependencies: - "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-endpoints" "3.535.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - "@aws-sdk/middleware-user-agent@3.540.0": version "3.540.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.540.0.tgz#4981c64c1eeb6b5c453bce02d060b8c71d44994d" @@ -1210,18 +1169,6 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/signature-v4-multi-region@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.535.0.tgz#6a5413ab087d984794e12b04cac5d64c1e37a53f" - integrity sha512-tqCsEsEj8icW0SAh3NvyhRUq54Gz2pu4NM2tOSrFp7SO55heUUaRLSzYteNZCTOupH//AAaZvbN/UUTO/DrOog== - dependencies: - "@aws-sdk/middleware-sdk-s3" "3.535.0" - "@aws-sdk/types" "3.535.0" - "@smithy/protocol-http" "^3.3.0" - "@smithy/signature-v4" "^2.2.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - "@aws-sdk/signature-v4-multi-region@3.556.0": version "3.556.0" resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.556.0.tgz#34ff26a1617b885a845752e62aca7bc29deb33ac" @@ -1234,24 +1181,24 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/token-providers@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.535.0.tgz#0d5aa221449d5b56730427b28d3319005c5700ed" - integrity sha512-4g+l/B9h1H/SiDtFRosW3pMwc+3PTXljZit+5NUBcET2XqcdUyHmgj3lBdu+CJ9CHdIMggRalYMAFXnRFe3Psg== +"@aws-sdk/token-providers@3.552.0": + version "3.552.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.552.0.tgz#e0cfbeb1ff9fb212ab214f2ade9827e1032fdf42" + integrity sha512-5dNE2KqtgkT+DQXfkSmzmVSB72LpjSIK86lLD9LeQ1T+b0gfEd74MAl/AGC15kQdKLg5I3LlN5q32f1fkmYR8g== dependencies: - "@aws-sdk/client-sso-oidc" "3.535.0" + "@aws-sdk/client-sso-oidc" "3.552.0" "@aws-sdk/types" "3.535.0" "@smithy/property-provider" "^2.2.0" "@smithy/shared-ini-file-loader" "^2.4.0" "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/token-providers@3.552.0": - version "3.552.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.552.0.tgz#e0cfbeb1ff9fb212ab214f2ade9827e1032fdf42" - integrity sha512-5dNE2KqtgkT+DQXfkSmzmVSB72LpjSIK86lLD9LeQ1T+b0gfEd74MAl/AGC15kQdKLg5I3LlN5q32f1fkmYR8g== +"@aws-sdk/token-providers@3.556.0": + version "3.556.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.556.0.tgz#96b4dd4fec67ae62f8c98ae8c2f94e4ed050073a" + integrity sha512-tvIiugNF0/+2wfuImMrpKjXMx4nCnFWQjQvouObny+wrif/PGqqQYrybwxPJDvzbd965bu1I+QuSv85/ug7xsg== dependencies: - "@aws-sdk/client-sso-oidc" "3.552.0" + "@aws-sdk/client-sso-oidc" "3.556.0" "@aws-sdk/types" "3.535.0" "@smithy/property-provider" "^2.2.0" "@smithy/shared-ini-file-loader" "^2.4.0" @@ -1281,16 +1228,6 @@ dependencies: tslib "^2.6.2" -"@aws-sdk/util-endpoints@3.535.0": - version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.535.0.tgz#46f4b61b2661d6414ded8c98e4ad3c82a0bf597b" - integrity sha512-c8TlaQsiPchOOmTTR6qvHCO2O7L7NJwlKWAoQJ2GqWDZuC5es/fyuF2rp1h+ZRrUVraUomS0YdGkAmaDC7hJQg== - dependencies: - "@aws-sdk/types" "3.535.0" - "@smithy/types" "^2.12.0" - "@smithy/util-endpoints" "^1.2.0" - tslib "^2.6.2" - "@aws-sdk/util-endpoints@3.540.0": version "3.540.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.540.0.tgz#a7fea1d2a5e64623353aaa6ee32dbb86ab9cd3f8" @@ -6707,7 +6644,7 @@ "@smithy/util-middleware" "^2.2.0" tslib "^2.6.2" -"@smithy/core@^1.4.0", "@smithy/core@^1.4.2": +"@smithy/core@^1.4.2": version "1.4.2" resolved "https://registry.yarnpkg.com/@smithy/core/-/core-1.4.2.tgz#1c3ed886d403041ce5bd2d816448420c57baa19c" integrity sha512-2fek3I0KZHWJlRLvRTqxTEri+qV0GRHrJIoLFuBMZB4EMg4WgeBGfF0X6abnrNYpq55KJ6R4D6x4f0vLnhzinA== @@ -6850,7 +6787,7 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@smithy/middleware-endpoint@^2.5.0", "@smithy/middleware-endpoint@^2.5.1": +"@smithy/middleware-endpoint@^2.5.1": version "2.5.1" resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-2.5.1.tgz#1333c58304aff4d843e8ef4b85c8cb88975dd5ad" integrity sha512-1/8kFp6Fl4OsSIVTWHnNjLnTL8IqpIb/D3sTSczrKFnrE9VMNWxnrRKNvpUHOJ6zpGD5f62TPm7+17ilTJpiCQ== @@ -6863,7 +6800,7 @@ "@smithy/util-middleware" "^2.2.0" tslib "^2.6.2" -"@smithy/middleware-retry@^2.2.0", "@smithy/middleware-retry@^2.3.1": +"@smithy/middleware-retry@^2.3.1": version "2.3.1" resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-2.3.1.tgz#d6fdce94f2f826642c01b4448e97a509c4556ede" integrity sha512-P2bGufFpFdYcWvqpyqqmalRtwFUNUA8vHjJR5iGqbfR6mp65qKOLcUd6lTr4S9Gn/enynSrSf3p3FVgVAf6bXA== @@ -6978,10 +6915,10 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@smithy/signature-v4@^2.2.0", "@smithy/signature-v4@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-2.3.0.tgz#c30dd4028ae50c607db99459981cce8cdab7a3fd" - integrity sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q== +"@smithy/signature-v4@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-2.2.1.tgz#9b32571e9785c8f69aa4115517bf2a784f690c4d" + integrity sha512-j5fHgL1iqKTsKJ1mTcw88p0RUcidDu95AWSeZTgiYJb+QcfwWU/UpBnaqiB59FNH5MiAZuSbOBnZlwzeeY2tIw== dependencies: "@smithy/is-array-buffer" "^2.2.0" "@smithy/types" "^2.12.0" @@ -6991,10 +6928,10 @@ "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@smithy/signature-v4@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-2.2.1.tgz#9b32571e9785c8f69aa4115517bf2a784f690c4d" - integrity sha512-j5fHgL1iqKTsKJ1mTcw88p0RUcidDu95AWSeZTgiYJb+QcfwWU/UpBnaqiB59FNH5MiAZuSbOBnZlwzeeY2tIw== +"@smithy/signature-v4@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-2.3.0.tgz#c30dd4028ae50c607db99459981cce8cdab7a3fd" + integrity sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q== dependencies: "@smithy/is-array-buffer" "^2.2.0" "@smithy/types" "^2.12.0" @@ -7004,7 +6941,7 @@ "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@smithy/smithy-client@^2.5.0", "@smithy/smithy-client@^2.5.1": +"@smithy/smithy-client@^2.5.1": version "2.5.1" resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-2.5.1.tgz#0fd2efff09dc65500d260e590f7541f8a387eae3" integrity sha512-jrbSQrYCho0yDaaf92qWgd+7nAeap5LtHTI51KXqmpIFCceKU3K9+vIVTUH72bOJngBMqa4kyu1VJhRcSrk/CQ== @@ -7077,7 +7014,7 @@ dependencies: tslib "^2.6.2" -"@smithy/util-defaults-mode-browser@^2.2.0", "@smithy/util-defaults-mode-browser@^2.2.1": +"@smithy/util-defaults-mode-browser@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.2.1.tgz#9db31416daf575d2963c502e0528cfe8055f0c4e" integrity sha512-RtKW+8j8skk17SYowucwRUjeh4mCtnm5odCL0Lm2NtHQBsYKrNW0od9Rhopu9wF1gHMfHeWF7i90NwBz/U22Kw== @@ -7088,7 +7025,7 @@ bowser "^2.11.0" tslib "^2.6.2" -"@smithy/util-defaults-mode-node@^2.3.0", "@smithy/util-defaults-mode-node@^2.3.1": +"@smithy/util-defaults-mode-node@^2.3.1": version "2.3.1" resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.3.1.tgz#4613210a3d107aadb3f85bd80cb71c796dd8bf0a" integrity sha512-vkMXHQ0BcLFysBMWgSBLSk3+leMpFSyyFj8zQtv5ZyUBx8/owVh1/pPEkzmW/DR/Gy/5c8vjLDD9gZjXNKbrpA== @@ -20311,7 +20248,7 @@ string-similarity@^3.0.0: resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-3.0.0.tgz#07b0bc69fae200ad88ceef4983878d03793847c7" integrity sha512-7kS7LyTp56OqOI2BDWQNVnLX/rCxIQn+/5M0op1WV6P8Xx6TZNdajpuqQdiJ7Xx+p1C5CsWMvdiBp9ApMhxzEQ== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -20329,6 +20266,15 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.0.tgz#5ab00980cfb29f43e736b113a120a73a0fb569d3" @@ -20405,7 +20351,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -20419,6 +20365,13 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -22271,7 +22224,7 @@ workbox-window@6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.5.4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -22289,6 +22242,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 63e1ebdcfed8d746af81b3b2c2027fcde88148c8 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 20 May 2024 09:32:39 -0700 Subject: [PATCH 222/529] [Snyk] Upgrade @mattkrick/trebuchet-client from 3.0.1 to 3.0.2 (#9751) Co-authored-by: snyk-bot Co-authored-by: GitHub Action --- packages/client/package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index 711f673ad80..232f4240741 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -68,7 +68,7 @@ "@emotion/styled": "^10.0.27", "@mattkrick/graphql-trebuchet-client": "^2.2.1", "@mattkrick/sanitize-svg": "0.4.0", - "@mattkrick/trebuchet-client": "3.0.1", + "@mattkrick/trebuchet-client": "3.0.2", "@mui/icons-material": "^5.8.4", "@mui/material": "^5.9.2", "@mui/x-date-pickers": "^6.3.1", diff --git a/yarn.lock b/yarn.lock index 4f01aebd743..e598f18f678 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4917,10 +4917,10 @@ resolved "https://registry.yarnpkg.com/@mattkrick/sanitize-svg/-/sanitize-svg-0.4.0.tgz#388c29614cf72aa0dd9803c77c9c9d070bd3cd2d" integrity sha512-TnPI97WVAxo8SQcPy8aV3OF9/2WjXB5/+pRNVudIWR7Bhi5ZjtR/ur162So08GkvsvB914AXCW2sxFh1x6KhHA== -"@mattkrick/trebuchet-client@3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@mattkrick/trebuchet-client/-/trebuchet-client-3.0.1.tgz#3fc49f7858652a55dca92cb0c14c0313df931619" - integrity sha512-5uHCldCqmVntoyujTzRfmGtjld8k4JuBdFN0SvhvRFf83FPaNeoit6Mh8VgrcxxxKujKeYCQDMQH6LWwpsshgQ== +"@mattkrick/trebuchet-client@3.0.2": + version "3.0.2" + resolved "https://registry.yarnpkg.com/@mattkrick/trebuchet-client/-/trebuchet-client-3.0.2.tgz#336d687256fb9ac7bb407f656bf2f69f35f817e1" + integrity sha512-JLOx8gd+cGkDeHhdnns0oor2UNv5uRZ8A/Jfw3NhQcVqQe6R+x9xIHW29M2T78TDHUWqItTgntX+cdDLqVjVvg== dependencies: "@mattkrick/fast-rtc-peer" "^0.4.1" eventemitter3 "^4.0.7" From d6a775d4e8f5383588938e163b1e44025afa6624 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Mon, 20 May 2024 18:12:07 +0100 Subject: [PATCH 223/529] feat: add favorite activities UI to activity library (#9680) --- .../ActivityLibrary/ActivityCardFavorite.tsx | 46 ++++++++++++++++++ .../ActivityDetails/TemplateDetails.tsx | 22 +++++---- .../ActivityLibrary/ActivityGrid.tsx | 2 + .../ActivityLibrary/ActivityLibrary.tsx | 10 +++- .../ActivityLibraryEmptyState.tsx | 28 +++++++++++ .../{Categories.ts => Categories.tsx} | 21 +++++++- .../illustrations/favorite-empty-state.png | Bin 0 -> 2435687 bytes 7 files changed, 118 insertions(+), 11 deletions(-) create mode 100644 packages/client/components/ActivityLibrary/ActivityCardFavorite.tsx rename packages/client/components/ActivityLibrary/{Categories.ts => Categories.tsx} (79%) create mode 100644 static/images/illustrations/favorite-empty-state.png diff --git a/packages/client/components/ActivityLibrary/ActivityCardFavorite.tsx b/packages/client/components/ActivityLibrary/ActivityCardFavorite.tsx new file mode 100644 index 00000000000..1a703d338f3 --- /dev/null +++ b/packages/client/components/ActivityLibrary/ActivityCardFavorite.tsx @@ -0,0 +1,46 @@ +import {Favorite} from '@mui/icons-material' +import clsx from 'clsx' +import React, {useState} from 'react' +import {Tooltip} from '../../ui/Tooltip/Tooltip' +import {TooltipContent} from '../../ui/Tooltip/TooltipContent' +import {TooltipTrigger} from '../../ui/Tooltip/TooltipTrigger' + +type Props = { + className?: string +} + +const ActivityCardFavorite = (props: Props) => { + const {className} = props + const [isSelected, setIsSelected] = useState(false) + const tooltipCopy = isSelected ? 'Remove from favorites' : 'Add to favorites' + + const handleClick = (e: React.MouseEvent) => { + e.preventDefault() + setIsSelected((prev) => !prev) + } + + return ( + +
+ + + +
+ + {tooltipCopy} + +
+ ) +} + +export default ActivityCardFavorite diff --git a/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx b/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx index b0ca11e0583..54ec5449623 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx @@ -23,6 +23,7 @@ import {setActiveTemplate} from '../../../utils/relay/setActiveTemplate' import useTemplateDescription from '../../../utils/useTemplateDescription' import DetailAction from '../../DetailAction' import FlatButton from '../../FlatButton' +import ActivityCardFavorite from '../ActivityCardFavorite' import {QUICK_START_CATEGORY_ID} from '../Categories' import TeamPickerModal from '../TeamPickerModal' import ActivityDetailsBadges from './ActivityDetailsBadges' @@ -258,15 +259,18 @@ export const TemplateDetails = (props: Props) => { {!isOwner && __typename !== 'FixedActivity' && (
{description}
-
- - -
Clone & Edit
-
+
+ +
+ + +
Clone & Edit
+
+
)} diff --git a/packages/client/components/ActivityLibrary/ActivityGrid.tsx b/packages/client/components/ActivityLibrary/ActivityGrid.tsx index dbfbbbe9f66..e53b251cff8 100644 --- a/packages/client/components/ActivityLibrary/ActivityGrid.tsx +++ b/packages/client/components/ActivityLibrary/ActivityGrid.tsx @@ -2,6 +2,7 @@ import React from 'react' import {Link} from 'react-router-dom' import {ActivityBadge} from './ActivityBadge' import {ActivityCard, ActivityCardImage} from './ActivityCard' +import ActivityCardFavorite from './ActivityCardFavorite' import {Template} from './ActivityLibrary' import {ActivityLibraryCardDescription} from './ActivityLibraryCardDescription' import {CATEGORY_THEMES, CategoryID} from './Categories' @@ -42,6 +43,7 @@ const ActivityGrid = ({templates, selectedCategory}: ActivityGridProps) => { src={template.illustrationUrl} category={template.category as CategoryID} /> + {
- {(availableCategoryIds as Array).map( + {(availableCategoryIds as Array).map( (category) => ( { to={`/activity-library/category/${category}`} onClick={() => resetQuery()} key={category} + style={{ + color: + category === 'favorite' + ? category === categoryId + ? 'white' + : 'red' + : undefined + }} > {CATEGORY_ID_TO_NAME[category]} diff --git a/packages/client/components/ActivityLibrary/ActivityLibraryEmptyState.tsx b/packages/client/components/ActivityLibrary/ActivityLibraryEmptyState.tsx index 8b112783c50..f68ac23d1fe 100644 --- a/packages/client/components/ActivityLibrary/ActivityLibraryEmptyState.tsx +++ b/packages/client/components/ActivityLibrary/ActivityLibraryEmptyState.tsx @@ -1,4 +1,6 @@ +import FavoriteIcon from '@mui/icons-material/Favorite' import React from 'react' +import favoriteImg from '../../../../static/images/illustrations/favorite-empty-state.png' import halloweenRetrospectiveTemplate from '../../../../static/images/illustrations/halloweenRetrospectiveTemplate.png' import {AllCategoryID, QUICK_START_CATEGORY_ID} from './Categories' import CreateActivityCard from './CreateActivityCard' @@ -12,6 +14,32 @@ const ActivityLibraryEmptyState = (props: Props) => { const {categoryId, searchQuery} = props const showResultsNotFound = categoryId !== 'custom' || searchQuery !== '' + if (categoryId === 'favorite') { + return ( +
+
+ Favorite placeholder +
+
+ + + + Activities you mark as favorite will show up here + +
+
+
+
+ ) + } + return (
diff --git a/packages/client/components/ActivityLibrary/Categories.ts b/packages/client/components/ActivityLibrary/Categories.tsx similarity index 79% rename from packages/client/components/ActivityLibrary/Categories.ts rename to packages/client/components/ActivityLibrary/Categories.tsx index 57b66dbd474..099910ec28b 100644 --- a/packages/client/components/ActivityLibrary/Categories.ts +++ b/packages/client/components/ActivityLibrary/Categories.tsx @@ -1,3 +1,5 @@ +import FavoriteIcon from '@mui/icons-material/Favorite' +import React from 'react' import {MeetingTypeEnum} from '../../__generated__/MeetingSelectorQuery.graphql' import {CardTheme} from './ActivityCard' @@ -13,10 +15,12 @@ export const MAIN_CATEGORIES = [ export const QUICK_START_CATEGORY_ID = 'recommended' export const CUSTOM_CATEGORY_ID = 'custom' +export const FAVORITE_CATEGORY_ID = 'favorite' export const ALL_CATEGORIES = [ QUICK_START_CATEGORY_ID, ...MAIN_CATEGORIES, + FAVORITE_CATEGORY_ID, CUSTOM_CATEGORY_ID ] as const @@ -46,11 +50,26 @@ export const CATEGORY_THEMES: Record = { primary: 'bg-fuscia-400', secondary: 'bg-slate-200', text: 'text-slate-500' + }, + [FAVORITE_CATEGORY_ID]: { + primary: 'bg-grape-700', + secondary: 'bg-slate-200', + text: 'text-slate-500' } } -export const CATEGORY_ID_TO_NAME: Record = { +export const CATEGORY_ID_TO_NAME: Record = { [QUICK_START_CATEGORY_ID]: 'Quick Start', + [FAVORITE_CATEGORY_ID]: ( + + ), [CUSTOM_CATEGORY_ID]: 'Custom', retrospective: 'Retrospective', estimation: 'Estimation', diff --git a/static/images/illustrations/favorite-empty-state.png b/static/images/illustrations/favorite-empty-state.png new file mode 100644 index 0000000000000000000000000000000000000000..53067b97d419b4f4095594ddb9f77c774b8a5928 GIT binary patch literal 2435687 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

Bhn22u9Ln!7R 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⪼ zd-8#KSQ46ZapHWQGRD~DVsW*_)cFOQtBd(A|7|YmrLYOQP>B%EvKQ3xDcKK`3r^K3 z^z}IYvpCVky4WSqm@!5|rw6Py`fJJs7e1;m533j9fG6Pi@0-C8gdY9i{sFM+qRp=h#{e`VP!5d21K>8%*#kuNe)?Ia?(+jU( zPY)9X4OHk6pE7&<^yICVtOzx&=z_d+Zk@qmpC{C`IAy~`r0iKtHkAI!?ShnjY%%7M z-z5%Y*ao)pc1xzok#a=O;A0b(z=Sc4iZr3mbTdIA?L+8?iX*@{|S9iC}M$y;SEMdo<_O;s{fUK;g8d z4anFT4K{F2=gmI(NJV}E#f-aGyV$O#LddicEf8&{iVi~*C^`CA8Fv%f6_38D2K~f4RSpDLprz8 zd`dhvHe_rG#A?J$rRbb_1?%)d5~np92k#N7QSO>2$K-PTma}Bxg&iS0dDxliL z$tr~MTmK%A(E=0IqOXx|K>MY@n`?g|_hzA|rZ%Xs|KZ3<;G2+EgE9>P+KYgt!e7&N z9|@l)EX<+sR^j4Vq4Xp8!bX^ukm(UbZ@JSV8|Rmvs(`7R6hxUCcb%!9c=k(W-l5}b z8!IeZz}L6(e)GN<<&dd^?w7uT9M&r!=zTnda=R1~1^T|~rle2-lv!O+K`^Id!iTIn zZ)N7-SW{^n?HQ!f5Wa|P1Bzea#mXXbPP(Ve?@C&SZX;DZpPQ*Vof5tKyrhXaWThWWO zz0yA|Q*Rurjm?cHb18GKDLDk*|B7;PedIqEI{-c-ea~m94i{i6tY98px z*r!XR_!(>hUSm&9a^pM^=+lGQCiM$%t@21uLw&!8ezo2=wX)%0DybqS-z=g(1)V;r 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�@Wjd9 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%0ALy-}B-seDOW%2_>XIaGpF$ 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 z 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

639JkG&nF~6hY}BrE3k`GXMwvt@ckJWR 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 z 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

?h-YbuWly2fbpW4@zyV)Hjr6`4sA7hQ>>LPvN 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 z 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ℑJjFP48^*kTz5ZdTr}D+D>Rq6dn`+3uOa`3@+n$1z zGM=~$r#CP9e$)>3Z~kyp8UtxZ(U}Q>C5P;@!r3CW2Ob1wT%msjb`kbh`rZD{VCn@v zzoK1)_JXGcK{LBoF-f(;ul@HYkNpI)&?20BgsH$^ZL2(=KiLFe_x5@1!$xCq{%}Jx zU_y^yJyN}MNZ7r?Eb990o0Gw1uGi_?yH(bJ=6m-<_dfwVsXPzZNhzK0XO+cn*OmFp zK_N_a(crMeVx7P(DokYT(lh%hFnvx#uoGC?gHhN_`?VeS2<){b%&PimSYgpguupu6 zI%lu63*J54ovaKVtbx%~IXM-@S5I1Z`Q_!iyS1;GtR|UYuVr4;R-9J*Y4Cjl89IM~ zoNredzk3>736b3L=|=0OWjpnJr3?afD!~(>im3au+8?1mIDN8w>st2;m|`8fi!D6J z1baGmh-^FHu-ej9Zknc6Y)9v{{JMy@)@kSPw(I=VS1a;Kl<9a5Nt^POZ}p` z+;%1hhdnRgRaE=@@lL5DO!ppEoi#D&Y>wJv^GnL% zyw=t9laCb0NqI4xC5^>&OLR>(Wyrkx^l+!}pl%a%j1nC?z9^KZ26lIy-a_DVrC+EF zZ$LY;UP1-4oR3znWvA33+DlvQ0o0pkqhx@OxI@`?hqK8|-d)GC8St>Xo%7O}56x&F zflP0wz4b3Fkh%`J+yt`M18)LPEix24bN=*p&Eu$nCEF+g-?bXIwg$1*@@Sc4q3h+i z>gwINaIWogw)WQRIZjd*vB78K7Ziwer5KPQc$JOAAd^6IW6?^U2`J#hDs%FBbU5wv zmwSW$7Ma{V(DD9@ex9H13|?FoEY|xwf#L2WF?m={7JL*6INeqFXT}ECHd$a0fp@24 zSRU7Yx|Un!?Fu)J@vpA3HF^##i0?e*Gx@HD_I#_918Q^DatW+?-^hYCI<55r>gN9D zakcYj8(vF46)b`Jl(%2Q$D zMc+iPsA7ZBEuFDQ!~*M$K3ln;?yYy9gJNKDssc&#`RzJCzfqmq-+fr;CQo>`=vXt^ z7xKlrWtG(;Gy^(tu6|2}4UxZPG3_foDm=g3h?8Y`A;7C)4c_bALWiwq)76zyt9u5HJ# zLF^j}L_RlPEKM!LQy;Td-4n=VqY1ikiLXB6=d~QO+PL{~tC?Oy~L3hmKR7p`_nvtk=Mj79_y_d+pq_h}<&Ydc@{o>m!_+s_tH zI3H|tPBr@@W4vw{EoU7owZ2c*z71uF)f%BPIU>N_5cHEi{pAe>J;&Qs2c{>T7`O8c zV9BMi1)|^PhC*-TW0}~i8MGrp0Ye9~V^wVF?L)*V-Am%VP$OZkq`3RF>lVSSDA-6s z=Fj*d_HG>$Bwt-~S;yG<3v_;G6aePORE(3tMc<4@N*k#AY32)nO6~qZDO5y{9q%m?5X`I!6m^V+U2v1sq;Qbtc>d5ABjV|0uIximi93zr^wz9u}Pnl@lgvg z*~Xp;bvYP!^7-?s!=J40?A|`DegJ#`nLA*PDYOYwvpM-*=-u8T#*yL&LMI_~HQK&P z-DOH6)P6!wQmNAi=v!+Uq+iL>!^?-&Cww&e520+>-w&e+h z-~Kpby}#!x^KYdBfqrSsMAtr^O;=>$crE`)=AjkyWiR)Hn1IHak2)9CHI8qDBIF!# zh8RnBLdWUtc%yBp^%D9ZiMfkf?KR(s3;r~mG+tbTxvvs3%5^9Q+JPLK3IGC`E`o4(*n{M*0&mhM0a@%iTu z^fS<9{@P#ev4~qOlht>*JC{H8zp=dj+OPj%`oH{p;-h|`U(!{7=mF**e&+B0;{QC2 zr^ieGTP_}^9Pq-O{!ZE6&)+W+>%Z}p-_#K*+Z%NKLBFmPfT4{g-!E6+*`A^wcDK^@ zxAFDpNFKx=3Q}G#_aNrtF^ud34v*9K$dUL5zHhT0#O0Cm^1W$r`ZS!vy(rr@^Gh9x zC*cfj-@1_hko-0wPLJ`>E|)J;s1v52!;VI}QgTiMRemlX@aW~Hu`Egf-Ub?Xa+uQnc@;Zz2Hq@-uc@VB4L;o*L+O9{F6dbKOIq(g}HvX!f< zz0s*FI=(F@T|RP=cmLw=UVDL4UF_OIIAwL{eTYK#CEenbRv?xPS!KoHv1u9a7V1IO zp&Vl^ul1uqL&(p4Pknm5ZFgxO*LyqE_{>-I-V%Hj)@j?Hdj^!B9QtOmyPWche#n>g z$n{i9Q+l}y0(0ISQnwi1&2rkx@S@K7o^O1~2EfO*W|EC_TuB7iN4j(xF6xAx#8+(k zP@b~2KitdanYP^5DV*|}`gFErWh`8bU7mh!@a+cP%LHgsucS9A%?!gSR|2oF`}o`F zN?s^qy%^^&^L6{kn}i%Dub&q5{*Y{SQle1jY?FP*cjOOQsq^a@;WW3`E7hGxk0HuG z!0pl%9V)N>61JhYu}!c7Dh`2yL5J~|hZ`lz;_2FlGT5PENbpB6sq$-pqeI?&wU=4` zrCj!$wi%;_k5LD+0T=nj(e_1s_c%p1x~sfqnBBkw?7!gEY05|xd8E|<1xNxGQ& zYS`&iz(JMK|0{lrm$cA?6HMH|0}N#x|D%1j z`Etsub{QqJGhAUaYpPeeh5!mSQ)oV3hK)^e7~Xy-)vcuZ;a8(qDFf>7Rv(Pd7tt#QVkn;1ZxGt|y!U-qNeDvua#(J!yCLx;UH37DO{U$SzS3VR z;l%H7iy|tJ9&agsPqS`yDYaosZO$9${1M_G@Y~3%T}+s>e%pUC0#^Fr;_?$hxe~mL zK39f`4=NDur_V2JT&`=b6JIGqyDX26r|8T6Gq-VU*ltSDXVBkAec)jOPCxTX0mG;X^pe|_rIk&U9U377>jefJs&}o9Ub@uU2lTB^6g+eq;>iu}Jv1!orEE}{{8;UgA2)Ai* zuU+r|IgxfTMw@oPGCWtAuRIVXoPWFhhzs;j;bdw9cdgbgZ z41}T6aBuT$x4FN_xgHmn6==KORHFU+oVpHU+AHZX4Rhp+KDd=@-1lG}BxalvOx@a8`d-$hy7m`jW5}bGp zDue9zL_168kVYH(@w!2}FN2Z=^ z>}JkoF8vwX?TwEM5FI@0(u?3Hd%G1taF*?J=Ml7u+R1zqteu{vp`WIi1kpuc-4~*8 zLI))(0o~KwPA6?shSu zFL2LSp9g$5mE`!BfzrY$RD9ElrX$BOrcuwrC!douRsc~XX<&z+L#De1`Kfz#t^$tU z7AO34w(|7g3);c$Aas*yqu06pF?n17Aua9DQa_~xyKfM547AWLAKfL3 zdJc9c>GMF@;Ld&P0ZLt1b4YjMZd+7HD4TOAiwtT5tf2*HtsjjWcDSWJk_4djb4Y4p zg$r-w{f5H+#A22l&e(Tdi8@YzM<*I!@T%KYq7K84@(-|G*&aJQ+T8Dlje%0;0KH#o zD<&uf5P95@4x~@+r{BXl^z=b75EDx_Cw_|A3-!rSOhVD8OOhvF6fG74dl1Y1EKApI zv#ZsD7drw!b=Pt4H<3Uq9a0uH2*ot$Y}Mg4y)%dIw4i_Jx-7c30A{a3j`o|KLbTwW zwTl%MZNSj6X7$(i0KWz`95-91aYQu~6c4roo_bIQeu{7K$*|#+`cH3o%hv;OaiDjx zdWF1Qx0o(A2HB<)3nUDaQ?xImLWV|CdwIMFC>ue3IiTe>&}BNrDl+B9&^IZ{=f$oL zl_^*Bal$x3U6z|kg!%wL%UB%Jm|ga%Y`L%bghze9)XwDwx_orF5ZDCy5e`2&ry!1n&{#6nX6WM4dMfh*n2A zA<%s}fkKS{b4kq-*!L7R*%rfL4|O+x>At(Py4uDyJwWQ|m@&1Wq#(B$35wIpR&qIg z0`KV;8vJBG39m!STroaayJU`BZ(OCfSiC^o+c4V0^lia-s_^$agPmWRb#2b?CYfy0 z@&J$Z{nl-l+q#xE&9!j@9Ze%gxh?_%=?IV8Sk~ZEkoy89P!i%499w_ea<>wVc2XfE z(Oxw{HZt}4a`+kPd!z94wTUE&9rxhqW*Aou!MFrBH2gXD6&-pz^cFJoM8fwdCKt8q z`xI{~cB;f+v5V#xW~BP>P?lys8apxwej64Pi}jBwjkSzEt&r4g{z^kYJRs`@29|pS z411TzgO#ouIdE+m_$hJQf;rf^V!CAXBQ4TX4Y1WL+EV#s9{R=mFzRNUz=lX7r>&dg z43{O+FU}X`m!#2!9h)5k8oHUFJ>P~2$%OX8WT*L>ndENm0h=6)8c-=c^&R7h`8G;1;NvCeEiB;LRD5$2beS>^76kvUnf%pm zLS7MUZ4`(ZE;iQ%{ha*IOr6m-3l|J{*A8=Q_0;~|CpNLj)7)lJO^Y?Ah{^&~xWjZ4 zke)OEToEKMJuUTh;oCF=Lp_V&E23tM@(_t7Ugl7Ro5*qLE382C8@XRmFVdNc`9tOZ*rP^aV^zB zv-SBNTYA~>HK)0th#M%xyW0V>F|cm^*G8#7TH6_=i;TWF8Hj!=MU@lIQ!yy@b4xJz z(&RfIy9-X-xhT*#*f(qyP-6JR_sV8J#2y&Vz0n_ey#%4ieB}wTtcrGKH(YmERLR`n6l@cB?}OGgYPi8VA6_WRs?}|vjbf*JLtj|SHo2Y_gTF^KeN8Xf5RBmy z+L)jNx@inCk&3ZTQE}8pSn0CeAxCOJdsuQj#1Tr15?KA*W@TTz^g1|6j5f|#KtT%PbUK}lxF$XDX zmqgYv-*smL;KqZ1PY@c}XhCz!zbY8;u_r^mUhhUXKM3=D$qa zJ;FXG@y*cDc$lASZ<{#umN@xw(J+ud?zcocQou8%V^E@NEI7TdH>N&^XZWm47}sj5 z@Pc{G8Dl`Q&qJCKzhtX-P(V`Xq|Ra3Y_Jy9+gB@$G}FA*-Kt*a8gp}vtz_|QaeJJm zdE1itS2a-@weS-ts6bk(gUSUuRy>Z2@+ymtkH7EoT&R7;@i)?kK`DZQIlh~*gEx@(sMKlN)&ckUC8g}^f&&c|1<5d zKK1*5W(rUIw*;C0(RA`p{Ym2A{ikg)PiPl*2OBV6ZhBnekIOs1Y90muYfEvx`sw=b z7bySn>q2k;gnj-W|C4JCf5Y#JO6EJ94p$kXKd?c-DV&;8HO^h>8+>ASk;4AlD~E3k-d)Y{ z$*CVu4k|;L)ENOBy*eRl*`01+c|kekt$m3N7Xb%OMhC#49uT~PKeNp@TEonXD zLK7K%fPGfjd@Lp_CBilBL6eRoJZvk9hg6( zY(8+g1Dj|m=0ras_XRFq+SuB~?h75mbcs}ifgYc0uZz?}+Efi$qKeqU-+0hb@|QZu zPsrkV$=a27er4>v^$5~hs74|nJG;EY@gc`WVfayGyz)2G7OKH2>8I= z9bxN7=#&18lsWniTU$G?d!i8$nG5}d>PhYIN?lF<(sspYK1BEK+2kmL!b|VlLjB-% zw?2Xa>BtHJWKIUf#-H7LOUwI#-&gvH7n@b=-WqL${C@gSt(@}iEd!#zB>Q7}WSeV} zs0o7;29%blAEZ3K2)}>R`T}x|@m8r`=Eq6s&Lw)8cJr3Zq9reSw$ z9Z%>%oE}BW%sCDc+>_z&SUbX*+Pz(Z=40{GN-TOJ5c+U_j~t7ELRp~iGjF(f`_)!` z5eR;@Af{%*eJnR9I{y;oL#eB=Z`zBr`Z#C5*RuI2-b7oP>{!wSw>e}Rc0g!2&K8&vnt})v#Nt&MXpc-* z=E(2wMds$LWGaM3C?HTrfx&lun4j%kZ90($obYTza$n~;_5Er=49VmCVXssZ+D2cW z6z*Hc6sa#z29`&(1?m!%3IcyQn;stI-8UFqWrN1KVqvW){F+Xhk)D^$^+zZm1pZ84 z{k#uYHYtnM*XS^oQ<@OFFR|}1?AqqOY3q5?w$Me{_a~#7;PkHK|6mYsI##|pDYSl4 z8p<1zw7Me0*z8k+zCr|f_`f&buSZ(qSi;Vr*{a4ScziT@c4T2K= zt3q$N2gxEJ$<|-m;{+IMtS=zwmM6FhjqeM%N2eYjg~D@64#~glmoO(@h55@~`+&<_ z7iazV()*`UK5#KA3beaGV3uRX9nLAk{wtiGIhP!pt=Q5;ndg_CLD#B+mPkrv1>F*u zN2i<+$bSzADMiG7N##VJY?#nW`X7(0p4+PJ?)}qhuK@XYcDPqk$Vr1uHyi7d;=Mc% zXvFh(LTvyjW-j3~m~F$jemQsYTsgdOG*i{#QQQZ}Iv5xqH>oF%#Q z=i8w95J*&^E(lHZV7SXphMs3hXNPg~9FVEHAhc^^uunwubkI6?f7|`28nTtP(8VT^ z?fz`EE3NnI-qO%Xp@#rOXW4gXeTCt~1_6P(^+j~i3kLPi`HR(818DnmK)&w}y)Mqd zNqF9&O$9==g0E6HD_|pv_6et@z(SYSEp5=4KMOs|G1`=UaZbwV3);%dZT)Y$TiXH< z)61h!2A~WA%Pf%VF_be64vpx>cW+nu98F~e@)_{;0;fFfI~9oDTi-i>Ho5_Yp1%L0 z5X808aB{h=>50ts-unBr8`F8MELwiiJNH9Q2c=cWn~`*mAP~xc+?*fo3`$-WzXj5N zZxcm<#Bb-lwxh_w%HSI|(N7=mKzYDgUr&>T4q$IwdduS*IzL)f>=`ta9e_ zK})~8L%!nj%?7itn>k}6RZ)tP2I`876K1|M3{`NA4a>4(s=Mtz)IV`qX7jyP%F`vASr&N`qo_E#>B6ZEKW4+t-5m zws|$R>o#VfiG~AWF#afinT!^J(Z~DZwg-&J7PgUQ{w(&jU}J#LQiRP>m}j8<+Hn!U0 zXN!ea)l~*1M}=)yKje%}7Z_GFh~0}6i-aimJl`tpyWUoM4$yF%_GI)eGUnb$L{aZg zH*3HJ?I@7-B$N-HHu7O?1kff^3L1l6SGZ!7pQpKRIyl{SdJ*wCeN2?P)N>TveM{|w z8g*xT59P1>7acFHPaW(xiK8B#)&&%!a}v1~MtxHm3i_AkBmH-A2=txmv)XFzF@)T{ zN`rv*qWKtf43d9gvlTja`@*Zz$23b*Z8gdxc35Z?bm0K@5Sv1M6O`0Z%BorCgWmyh*33rY%gRBU1r>Ib0>(1RFOPK`xK`uj^NmgU9tL>E3mA*8XRll5^ONA5qa zvUsLpQ<%tn9c#oc>*vMNR9c@mpN-B#>icvb{i5DJ+L$Wxc{zknDAXw4H|N>-b9ELN z@cTvcH(P1rveZS_1@n0H=8Mt>5M=1E=ogzN(&h)_T+X(5><9{#99&&&r29O! zAkJaW@lBsmA6n8BXm_N!yL~oY99C125TxLr<&o3pw+aofGnhT>PbpaV#5`@;#e$pk z5Ajc0tOQ!BI)A}p>GJ8G&Mzh_+ZbWj;oWNgkEh{aj&FRm@4d86jphYaItXl_p!KuT zRQdDJmz_`v?A<2VxUZ29orgA?_dg3Ql>2GVS|8nbvbXu8+QuiNy3?#B&7;#6?Bjl^ zeGz_^KDb~Gy9W)5d`>ii8nf{Y6zMvBx;MH6#<=F>?WHeHO1o5rwJ$cG03F-${pz>e zgNowCAn`?>B4dv=2?bx2?O;@WqK~uq%unz!WL~qg-AB_QwIgU6)CMYbUF)a^o6CJs zub)t%$z}unrmBMY!!ku&Nb0)y236l^p=(c%R$oH9V{yL79Mj_!;u|1tpfqY8vr3z+ zgeI{-$Xk2FKy;&y(qft}iJkD+V1wVQ^HZVk zJokg$zPD)-`pBD3@z5YcO+S^vH-?-)8LgGu_78V}`#b;V)(L6!ftaD7vVV6(Gpx4ZE4NKGfC8f1> zI*GY&(4O8Pw$Pz!b-Z3a99Lf%^i1zg8cWsg1j@zM%{j!=V>?Pcf&L=23{p1Tl#jS; zzHF13`@a^)YToZ!ii4!*3S{*Bcm9|3?f>ClS-a^o+VR%>(e--%?(dcV?En6=Ci^@8 zoj0xy=tmTU zUX|PC=c_QbLpioD@?{p~%I|5wyqc#TRp#jZhIrxcmy34lH6@B7c)7C_Uu!mZ)C+&lLs#;<(rg*w!3u=@J;+9! zPGa_S{8VbDrnqj${GRuit|O)2?mJ*}GraP7OebZ*UeAKB^?u|R&7cvhl!h-F zs?GW#oxQN$2iz-n-R94T%_!h=}?UwzN`$-B@~)hu2Qy!h=(DB(wKB%qjLX4 z=6xI=>($a&`@N=~%f+oieN1wl%rJt(E_46ryl?nE>M|3hdGw%H<4Om9Cj>t)JL%AW z+V(w@54Vlc-Pq!`EnCVX9`(NMr%8s)Deh&c!E6h19$TFnc7*4LzfWCr`*@i1 z>SIrD-?4vls)s-4cDwhFvZ;f=RX1&A<5h`%*AvHgrda43F4%*-nIIIAdjV`AoyYo? z-kxet^CAPKIyedik0&8&S`^I_CcV>W7lDvo@xVCj=@Q!Ao0&JdO-XZoaWa=Z<#7SqiE;5l zew8A4n|XhuzU|Vc#pkV$&fBl!?*T*Va&4DA&bbvwDv2wJ>#ggZd}}Wl!RM{xP+|A% z^cVMbPxdI#4^sInU0YP{S5JpiA4C7z=z^WVB=5$T6)CB%ssG#F6DxB-aMPG}AcCPq zhw*xAE4cTQ6vvFVibX14L~U2AZksuXE+LCk5T&IA2mSJ~q4s*#oCO5$Xn0$@r22Hl z&kC5?@g%BOjt50gK$8JF9&`li(DHbd%ll|Y!cclR;VW7~3j zA@WBr{EwKo=*F~-J@KSRxci((=}Eh+qaT%kP``#8)brT%qdFRGUre!6P~JwHaWDDb zS1lLQmEpfnQ0m1ToO`))KAV2bDgU%NVpb( z4LeZyY8|N21-)E;cc6gSCLRvorS8VNxvyMFq|KYO@Ql{4l<~dXw;+=txvhWW_n5BR zxOV+}Y%WPM>bp%Sd{I4Qwe2I+>%-8IkOM&nsBan>=veXs3G-Fke6UGuz)p(u2F3O}Ka<8<$@(7~naccv$@MJ+tHvCaAUsAK7n?|$0U<}>i! z(s+^8BHU`UU+g>G_{=Wrn&Osfe_=z14L)HsH~nL)*L1~(6ZN=Swx6D)%jDv+LizX~ z?MYoGC6~ir`GIRoPvZUOTFj=U4GH-ro7;`)h93)*ik!|x*Wdc*7Ya=mK8D9}^{4Q8 zFZI@LmJ3lks%y3<7qu88OuD<}35H=!e@2ajGac+dVst zf8mZq+{OD&-wVS-h>6kPgPx8?I}V&d{Zt^oYpTYO4bvDt3960F1kN_Rr3A9!qOqPq zp$XZB_*~>E3ExyoIk~)X$jg~*YR>-B^-TSEYlo4!HcfFN{%xb@2ShDy4bko^X<3Z3#gdkn3@3bcY?ZWm#yNA>c_iW!bxb{J-{(QJvl*VC~vlhEwi@4sK?q zH(K^9^m9C4XtNq(?-UHujGaf-I!**eJe|3x$5+NIJ*VpFOB$J)!JYIr`uy1_@wK20 zrSfkgWL=qiAlRUsQ~ezFB=tdCykN@ED}KN8R2Im=eg`^^rMWSnVYO4a+=%GFm|>ZR z4KpZPbGS0XfQ<4=I=jAD>(DPHH+TV6ftNqYuZ-D*93n_fsU6v6WR;0La=i*+zes^> zw^d0F5rowtWGPWXP{1Q!rUChvuB(He+!HsN2A5Cm1BjzW#Ng*;&pO*mqP?&}YnBrQ zZ>XLchZCaI5MHNPc;Jwc{*j>felx5>r&b|~-S%bbr~xInpSyJ% zJ4M``9VXWYYloJcgK-@5wodt0tBX-}xW01NDFh8W#1I7px5L^+>UA_^@}6vho0{9Y zu7|_3g~I7o87!u2;gl3BqfPM1y`6Aaj0&O-=^89K%IJDf;6|eE!3X7b7$;Hl!kOE1 zvmtgG+4ZBQiMuzEBkxEO+744Uh)z2(z`}E{Z}cNfhFnj)PrAb|r}GQo8g+3fCSWD> zWZH!hik5E+-gDbgy8P9yW~UVOSb~Dbjb#2a(#mL>IC>GMShUZQZ#5Iir&{It*CKOG_Dizzl zneU-c$y3-$J*7=OFG_aO#uui3*puy4seE?V)M2wzV8AzZXc`OQwDx*`-}|x&-}Qt$ zMF{*B_Qc!|tlb-Jc+UIjwDgUB!vcKep#LykEfIL|Do^MiOcbZe}iD*U6bd*py_3lr>32qa@Rnl>z>@$Q9@6sJ;>sx1svX3Ed%#M7@4mg|q=m%OE;bdVo=Fhfi|!y=td! z>;+q>A%{8dr$gK?Ro~O~)e&15kf$?FP@u19m4VQo`~{B*y-pV_q@ghW3a0An*f1wk*_RTVZtgG6Xc_X8md)tLem zDC+fb`y=v#m0Oo)D4RifrjI{xv8I) zOv+lV?qE0}IWI0c*@mbyML%-t26n_E^qB%VXv)w#?`LNJvVT@$QAJK8>sxv%&Cn~P z?B@3K#xJgvSR(8l6-C3w4Qq#ZoH_c8KyfQ1cNxc8^zPmV9Wqtxlb%I7?C|Nu7iA0; zD+d~l8@fc2#c54EI1U~1z_i0S`vYuQuWS#Jqxi?A0cpP@2nyxh+4}0IK zB^+YPaL1mnz@isFN!}8acQcrAxfTYDJ4(qZ9XU~~lIO6J zrngAgIz~G*)WbH6_*he>5=aG67WENZ@B4ZWV+r!ox)hui)`T0H8Gdeqea8weyZ;P* z7aOYcsuWy~%~T$2P0n$FZQ-$W8YS2>n!hiEY&njss9=+Dy{Av^Q zxv2#Lf&S_g5Ab^!!QR$ww$17e zo60>Hp98OMe|$4tg>3z_yipuDJrYG9G#>zT16%?D#3)N@AG1?Tqfiu@C>9J#jOnTy zc#)BAlsWX>`nC9?m8i!~$j1e*U+J--R9}WqQ=o6w#`c@pG$N{fZmsSn8&Y6&XCJGg zxp~M(%6OQ>cJx)CmgYWg|7&_T(@lh|iu=H3FLV?u3tZP0zJ|~HN_3y5V|M5a+N)sx zJDbsp@=UGQC2yG4HpX++Vb6I9XRB62Wb*~~psC=lC$SmA{d6}W*$)T(l%ji3H9rdT z3Li%_FO1`{dDu%0-^TnxqP&>rZDuU;Nd@7Y|nk7a!> zSps5zL4I!2R91LCWI6RQw4rGzhMXR^4AeZii+OnU2{oI1cuI5_4v)_^ceD8&(n8#` zCE1!fpnQPJS6cz91^u$v#!5wiQc5?|?dX$TJFFazf=Z>wjiJj*jY6)?1#uM$f4HEp zs&4oRe6D>X7L74?^7&;2`guyLD~^xf1?^;hic_r==!}-x=hXgwCCZ0H^X47Bh4G@` zvrUI=d@Vo~J!MHF*{EOQ;YGNofEcxnrO&=X?lti*M8-GBCj z6IzZ;-Kal57&)V|GxY?>sZ?q zmHo8u90j(=F~(=BMr{-LiY`rA(Nu(r?BZ0Uc!%JT7HNA)d@Ts0j4_?W)MTa#2);b{Feh+<;I@$L{4*Tn4L-X9bBB^Dd=?cByhWo&qbd}2HMKTJ7pa5v7zvR6u0eK|PSevr8c{!rYr!Hq-lnEh5`*P@_ zw2M3bEiDGhGu{@vJ2seotyoV}7pb7P^M|5v`Z{HfHf4pzcYf^sTMn50JVjz)Q)aiOQ8v@X$DD6`oRUsUg9(Em3)SEw(3rDhWyk+Rw#*9YM0PAv(v zl)zV|y8HaHbsgc3lxbs0x6 zJ0OnV&uhNRLp3`fQ2(OmSXhY#^^gZE?SfDyxU6FFy1GuXQAiwYGPr!HG(VZMxR?6g zFQx;}H6ZTgUO6BLM7>L?E;(BEsVcO!I6G3`f<6%Q^fmwUcRSPJRVIh8L~aJvn8z=I z-BevY6@~lPYcfg&RoxdrNNFLM;xzJ(0R4x078=@1<#5xqEmVhhMl}-hLm5t=b_&<7 z&nKv{olp{h4#VtBiMqKSDm?)fAPxH7(%iq9qwgq{!;?)sckBIfv@*==eW5Xs&&Qvf zsJ>ZLp7WO}V8gWx2b|h@uCcfx@Lx*j;c`U4^z@5^zAvRaygyU9X)z-ki06g@nE5D7DKQ_Aj{;p?Z%e#x2xP9Iy_pr zTcWM4Uv~4861Q-K>7LOjn$FR_Yt_NUN+d!y6b^12IWU{ zS_Cq?>kAwmb)qQ5zLl928qfn3O&}Rh20c0{wBus!en1_iY@cqT9Wzy&zz^;~&0sP& znCl8?O{x2Q>adeSUl3FaY3v9)8vdEp<@_z67fHsf$5nqUua^(E3L_~6Rl0{egBn&^ z48SA`WThf35#24|!o=)#p{W+dtclt(~^9O9M{3bmicF}O$zEGQhk)5n0mLHnHO{$%5e(uQE%Jbk*gya*H$ zZ(htMKXsVUl62L0T5Wt3NN~pHrrqHjG%M$i_p81cbg)n%OrSy#eGY!tmV{d^Xr;cv zGhp7EQ8su#s%Zg9U~#?QnE*_t)A3~W6#KG1V=&XCFu4XzY!Ji_p)U~k!VPWu@S-rA z%8UHn+tuC~Wc2R-f$FMfs?R5*FHxT`(Ps$Fgi{F|?jP4SJB|9I=E?M5>#|#P{1Kb^ z^5u>SV26dafR096P%-|?H9sB|R$j|_I4RwQT+g51uATUx!EAw8->vnrh2N(2GT3V2 z>}$zB(CA~d-mcmg1pXc6VPBY0CFsFsqqhLO@W~*-HQ%+2vzHU?V$GX4x@E(lK)370 z+&;NdN{E9&tP2dgLNsdtbZxsS(N#3M@JqElgWtIOR6?U5a;vZ#9^4nZ;^mRFePK>v zuCb7`JiIX|b6vcwJz$Uav@$=h=@_JT5j((>ZRWduSo^~ZHZ+-^s0(RR&zIJ|HDj|F zKi#e61Vs7nY>Nq4r1fjMU4s<$YGXMukh^pQ-Xr8H(yr$enA@vHO5g# zfuLFtJ9hlI`nopBUmo7CdS={~0*No}q~%Gp2ny#;9UkAI`#Bc7g zDO41iSB){lHkRS}z3G_w&qwVqVlx!>zRqc_(&h;B%NqN17<`fc`W%;%7N?8Kzq*Y| z!T#mxol#>n%a?@KLf)GXy6T|m#6q<7yXJHuLSazxyv}>ghoA8LmPW!iacG%fTl7As zlb4B3qQb7(ymAfn0A5(y#ow)mPikM}nxEdUx^jnk^lHbZ7n7$zwd46o%Dn6J5H8GR zeV67NtZyT3A?kYjS=&^~J%4$(>WXvFdJvH`pN4~U(vPa!#&KBo`wR$m`FDf*ihu|3V*-LsCV zez+kP|^2*HNJI^AQ;lD;l=M{L9A*IKr@*W2s9lh~9}or=G@4=PEKAp=yPEq)uVy z#I8f0u9Lb?b1nOKzwXeuA+?URlB|K`KF{CT5J;J^0ke|Y`xS19M-Du4Db ze%NNq{Hr_q{Xb)6`~2{eH9uR6Z}WFFVbT5Db$w?47Iypm)73WFGfSS_S>}&VtAOHRbm}q;dv!BlKXUfGpx#wRynVcTHa=-t! z4YEhZW&8ds&-1mkkz?MWIQ)2?N#}eR|{Hl+2S4#>27yac`xvC@uu(kh6{JX zWAj5E+}&9wuS$u*&hzsH)>sy1dPSed@<$c3oC+bBkt#9NHJb=bST?N~^|;XM{9I~_ zXty!BnjFa2?8OiE%Rza1`TI!$>hdbx0rLJ5F!rf`_`=s{XVQJ@ZMH37w++_#{$#oG zOwNCR+N5zr^9@c!o7T>4SVmp#FC#tfx^!`+G8-&ypQx7fOz7kI+L%>IHBJGUtqzqk zN+y17ThG^3(d6Ekw#JCA8OyDe=kz6ZI#bg1E9w{RjK1rSZFRB-8pr!Q3|uAEC1-L) zib-{&%q~yb$i1XG6&m5md9*Dh)vNdS)bGpK2eK`0Lzb-RyUva+OQYUw>UnAJ{MB4< z9w6#_zS}g393I?HnOK#a3dK{)<6s_B`~>}I$MV8AZOU0TCEtz-L;niFb8lyjQf(E; zp1IaHU0-59!zMUZ{Pn`Gdcmb^>cweH%|jv;EbeXkB-3aQLy@eZ+vee`HfkfM=ree_ z*2}t=&07J1p6pN{p7@PwfuO6hNH(-=(uiICXm<^a;+cUuWw2?vYSzO#(gnq-Oh1V? z*y-BKR8umXP9nzIU*$3HB$S}cxMFAfc*vvlhX(TvNG5|mj%@qNl{#}zZ_tl6J{OzW zlFz$^LLEKNo=&6dS0GA7JKD*;Lv0{ff{>QJ##nHLTO2e`p}rjAgQAg{~Jk4~AqxN7&dbkD$Il zrmlC5Qz2@9mJu``vf?tJjk!Jdlj9D5i=?&AS|s0)JNPM(O&b3%I}wM;h)vRLX?A)L zoRF{7Q74El)62H%6=!|eK+-FH@tjk|Wg3BSXQo&5s$I#ej^7*Fen_xU1jpSTGSbWa zVheNHylwqG|JPT6Xj{it_;o`@6o?{MYDWVP`73>Eo3tYd_8Pv~|4hdgP|7 z_IJFTJ|5*UXe+atZ;=gKF<>qb5?7Gai23&ov$1kC(N#Y0Y*o&p%VybBq9vde;pXO)*<}bq)agRZ}eB+Qs=< ze2yn`xCgN(*JrmM`G8g1#J6G|$=zSlGQ-#XzEiMUUpHdf#KU`+a}yt1Y4F=y^9>}f zPfk_9J$)L--{{NR<{409Ne|>~uTE<~7#GS=pPWHM9#JnZu;Wdv4aOUV`FHWHo7C>K$La??tp zgh)ZcN-hH8M+V&d2=UWKvH=Sj%NT*%xX;;pe}C`aT65N5jxnAwYSdio{mudUw2WYC zfBSvkwdSl@HEPtTQKLpZPhMnigN~?Y!5Wb-*-yT(7@l_9#vGY&4#n@> zG+756!C~l!(sh87t?P&YB_{6z*++LOO#r8$pO;h8MpQB?zS#Ew-ju2W>s0F z1uev2b%`>+S;Q<|*6v(29^~m`rlpOtfo7%9;BYAFL7F@~cH^hZEE%KtkR5EraB87> zA_EN-b{oJ6zE1x+RX{f)8VH;w>WgsC&Mz*{67|a)H@@-Zh3iC0)OppCL+qz{HG`ON z+O1FoX_=_YJjtcj?lADw-`j1M)pyDCG;Rxt1B%%kp{DTXwT}9w$P46UrQz(36blS) zx4}7v-IGrYnI(dK?m$W#0mZL#$5H~8(fZNg&ITwshet;H%BJt!4u;-OeySgBYZTNT z{(Vh-;d*ZMgM3Sg_i9nIAwdj%#8^b3zo&GHo|ZoEFl0e!OZ4Aj`Yk9S-Qz26&a{0K zAlhV{Il9K2>gEBU7>i-c`uMPUv<$W)`g`e#Rt`Po!9b`$%z5U7a{TUr$9NB6%(vbY zirz|B={8?8>;qD|Bb#Vlb*`yhrzU@nBNN}`Y5yY7)le(8{;wG}E#}75PA%JHnCV6| zs~iVOv`s!YiS8%8&YPHN_}l3w$S`a~-VmrhCYqtiz}~qr82BdD*x0F)urWS&Bbwzw z^!T`@anvBY>bhVy&tStT?3*?v1>bbDh>N1MsV!EWH`!{yc;|M%3=aD+W_1p=K5&>d z(^pP&d@>~dQPNfI&L#OOQwpGHk&Iw3?0d4ex+q35is(0=^I>Xm#dRs!7=+XF%VDDd zLD`bAkh<)=K4xzlbYVy;05X&Qzt#9o$D>9Z3bOVST%p#d_Bp&@feYIZ)G%n9(^NFH zNl)FMx|8IzM_N=(_NRivWv1kdKv%qT!mh4*>vPQv`;GeBX#@^kv#-|sfe4^-_ERY0 zUzpQqb7M8R$@w`)CEx@(JaHM<1)?W@f@bPtN~uh|&#Rs6s&ay_mhr^0uN4-XL(Ly5 zwrRory*l5q!DTOUdc~nirA`4mpXLs7n`eK|fWB0D?E5LQA*sm64THMO3rZV-c{6Rn ziJHl&Gvpqb6F@=X@+Zi=Bzgotm87Rw0biNS8P*r(x2PQkWi5&%)YCu%}IrZ zoeZaZX*A>o^ry9w_BUupYoV&RJ1rwnA z^bHYv+Zea@>4CUcKf6%zgt>3j7BN3V4|2}GlZcJ z)}ia)y9wjNP+Y9-Vyi1YH`)%>i(_hK|%}YB=a5Y>C$f zUpBANJ$<(!d7_}cjF_S$6AXz(?1^bO3C|s|#Hv)_|5;gjy0x^fe`O(0zO+O6K0ov% zi4!t1d`QcDF>|7RfPGshT7*9>9ofv6%LmDjrBh5Xd>^gGKviR(Ev*h4(*^Zrwf$_i zoVyPuxCw@6z_vDuN14!5dT)@jv=S#|^+a*nd10)Ro#F?}bAmyjprFx`xg(o2w@U?7 zO&N;3g`Vh|lPDnQEA*;jU07Pd+|kz-K8IJ=YvZfyV`8(39I*x;U%e4M{-H`NXdvq#yo^JcYoYq>9PEZ zd)@DH|2$LNu{u+rMvC=<^}UtJ@wp*IcpN(GUFmVm;`29R60rvAo^{Z{<*_>XooVnW&6`$%(NQGGY#CpQlZp}Al3#Je2b*^JvDE$A_r|RD< zu!B4}c=mZM;lt`96ZhC7nh|Tvsh^4;Hj9_;uT$zrk1dJ*?i$8!Ge1P9&h(7N^eFky zjDBTGw3OXHQgpj%3HdKe3HfyWD7|iEbc#AA)B!}FNk2@sme+Ry)k~|ggRWvuN{!Rb zF5VBJoc?VE0(&n?Z4@!6{cNt+)9-FGd`(wrK;Tu{=~!rStfkaWp(+hBQ~zO~<_>Q= z#<1^G)_h2RXEY~H-N4)_j%w1u?hH}iYe>`#1?vb!9$;TvkQ#MGm%;L8$k$=)aV)Tt zt^238_H+BteLD3ZPlm`N>*kWEMof$aYcUrBlhX`EtexF(6_id98&TVZb+OwGt9R-8 zEq$=->nm@2px!Vg1CE~=>9um27yLji4VR;kNW91U-`4D&#ANFb*_!|HzxR)?d+v76 z-Aym!SHG_On}6IMKCWZ%2EmBiW1~x#pd~naL6uk#(3<2rJzEaI&4KB|$;$a!Q0)qk zZdPAxUi@9X{QatI`}V&4{$D@Q&)_Ow`jY?DAAd*h_`jupBp8SPtQY^;KYB+Ghb#Jp z|M5He8~;atsl&Oi3r^*Kmkvy@`2qtU8%0+X)B~z)Fxb2MP6H8}Z(^%o2n*Sw^aWUV z4UqmyYv6Osp_gyZ+V2WMe=x<$^(jGNusX9JRwn|Alc^R;o|0>Cmr)+rcLLoXbP4G? zeV|^ZBh?ku7lR?wNjODCT5sv4lnvhAXI7YZDNEj2A-B^<8mK6|{>u|96u4ZO)*)|l zb{Ya!+Kr$*xbF2<3JH`SGAsh`U$kHK*?md= zcSYGEXV(p(fbax|pd*m?30(Zq{uUYscZh7f*mzgU0BkbrXK0(W*X7{Q-}>yw5;Oq< z3Gc}l^4WZ}Nq4$6x&(FRu1!7~Z+k430EPR7h9I9n)-v}-^AM3+hecAF4>-0r9FRL& z&e4M9BCFj^3x^_S4O3Se^bIRzf!AlcvPnqivL}Hn^2xmS%l@tC0D=S$)?N)NJ$L$s z!}r#S;@$!R`uWMmyY-jp#Po(ioshK!_rvZrr-WeW2Ji7<}mYGx}W? z#y98-ysf0opU|qAVgc7a7o|VYuYr~>WnTT<8Wg+b*tGV=?eW{4QYpx_yEjOA9lKZD zxun=_)|hp_Jl3F=aK1QQTVzPFMtyN0R2|^e7MhF-2jr9}Dt2GZ+Gg);Pa&AsdxU~P z#U}UMTBn}A-SyAk&AK5dkIW9ME}VMnz4gi`9*QK0qtF(7n$D61g1$r?tW_S<-uhbU zCmcTgh{YV*5>DbNaatZb#fg0GoV!ZpfO6)S8a7dA0i15*<^688AvGLOPgD`Oth0Xc zGC>`pb?`-7qFI^lynk6??cM$e1ijaJe`io^ym26uVpxehf2n&E`m-&IN7X8SGvYIDMmI7^KfKzW^?eIwgcs00{irlZEuP zzA-OMO374JR)7kmQ6{HPBc;U-F#e~H2cxNw&l3#Ypbv8inuFpAGAwiSL1E)q%X|s= zyJfAvLfzqA4$&E#Vpu4u4#x8-hrPMsP<>w|Ix=27S?bOWV!4%mFpqJ6gyH^GC2OFDfjP>Tvp&$bYC z+q+2L9gUkybd3|mhWC}$9RWL`5Ad-d(3&0~Jbk`W$U#j$J{Wa^_T7|tYtyFm{9q7; zS_gr*gpHlvJnK93uBF7L;%p!^(^tv9PpxH1??mBEWc+@B2IP|B2zB?NY~jvkDh z7K+pJpnE7AB{(Yd=)owBguVd~ugCXBuVBfMi7z(=bnD)s7cGe)f_;)G z4g?Vuir6W=R{0CJs`(AJ;b8o=u-U6T6xx5Wv8kKQp2Tr&P@Hnl>66u21Mk-cuS&_M zu!@uzp9-5ioBwlI+g?_gDhX#5xE5w6W$v|cmhKtkg=9y8x%~jyCa+TPT#;3c0qOf_R*k#37ZkViI4(l z3iXWSN88-vh;DUQ{=Lqv+NXyXrM0kE!=Dj*B2dGikPujAr+aYppDYKp6_P#TG*1Vk z@aPc3u>Y&P1#-Hf4zgLN@_+kj)vafJ@BDD9lmxce+u25hv_s&~_kb;xdf{1vWVTJw zoxq<1j$2{35m;3SeS~_Kr(1ouRX;m|slS$~`?1s_$em%ag?dBvOv^v2?&+q_%Q`Qh z{H!zko2P(A*G)3lpY?vFD9H8D`yUnJ+|;pzJ`=TJv+n$2g=ZHyYn9v0r&TTpjx@7Z zda~$vv)VL+hiywbfPuejf1Q2oa^AG7j~TL!WPDfG`s?O=5&DmJ3Kwr`m^KiwIblk0 z)bfp=`)K1-ZWmgIRX%F7t{+xe8x;KU`Bvqo#FOftLrxgV2H~wE9}Foj||ibDr90LwGVS?}3jeOUGWiF~stPMDrLr||rmWG{eyPj8CxMa23ToI0eJ9%jI1(zLJB~OoDTRdf`);lC;v4%<2Hh{R z*j;~IZHV2&^Np6Pc183N`ZlfiRFtzkeYNV%oz`c%dcWF)^OIkv^`0^#NOb752)yd! z!uznBenQnPyZKRTFlGNtm0rJ=RX(l{EdB1(81h-z%Bowvo~#$A{FhF(K zBD70%&V75KWa&HMHb&*$>lm3%~MXMM?y`b#*H;eu3!^hY>CiPIF(M+|H)_5p-P3%!#{J!Ym}Fdo zS}0T1WMmZQ78%|79hR=XtoxaYo4H2q8lm$)a-WmOe(P!9Nb$M9*x|b_Ut~neHj$b=H$Amaljy`}QCcgAw5HdB9>y5FU0^>vI04cs#2uJ)V~`@3Qd z=pBW`0d1r7!#wFThp#@VFDmyt4TS1IqFp$NFZTjUzBi9bb*9u~>-|DYp)`*2`JK^$ z>HV^1k@@BB$=2zOl=h*)*V6Z3EF9iR-+|gAD6xv_rpM@%R`2QHkZBnkf_*#FNsA30 z`+9tR@9Y$HIM|??qA^&k<9d@b}aMPjgzoi@?io8`bn*@!5O<6NPWdyESoM|O|WFXWij&Z&Mo#YU#J<$%<3eRkF(K784V@khwfumTgxw?2owr~ig?%j?X^-}sHyn+Y1O3@Azd`~fNE-* z#7w+TrM|j8_npfrf_t(5qH!mz_osc2X~Y33u~*P53xOk+TWFvxscRf~!sfgs8sIRb zP%vPV+{c7mqO<-kmGRDeZJQ%IinCL4oE}Vuez44AP#(+D_42AqMytAxi+4gv3EGtB zPGg}oDNa@Y-Ida*sL%7&v&Nc5p34abO4k2&JBx3tuAX{KU_f%5dVG{gGc>eX6gmPw79pNCWY&{)0EG>x)fRdMv;4vq?nQ zUiFLLe*bi!-^uHHf5+cCe!#ZSAF@hvFme;P8iG1!7bkl%mRl|tM|lHd{P30XhpYUQ z_u*6iNzc7fMEMEd`)%cwH}fbr{z4h?)g?Le_MUk2rqAdsOK8}P9Lg@|?HleLzbf0$ zFSenK&wru(FMW7c&x_@cg@38tej1Q3lsLZk^6&XGBu@|TAJ8u19UPl!g)a1c3If>p z`GQhxFBI638u(1tUVSvjW>Hq|)A(LKYxGlH8Nxhg&hZ!e`|K+OQ)#=2UML*n-U#Xo zr1W_iGAv7K6Gz=@D&HqpPHmv>7vN7GjaXVt_*YmP>r)xR?OYDqfjo*hpIqi7)3M#z zbh@p@sd4UEjc$`N92O7HUa(DVe79byJ>it{xvcZ%6ZR0uPv3Sl1{!S)^>_7?c{Kd= zGO`T(yMD^LY>OTi&=BeC_T0x40X1NA-*9rXZTf^#{R@wMy?tN7=bN!*{X`e9Y&!-> zz}MykzEH^7^ofOXYDcMEGluTp_FdA^D8AX}+oT}PX0CJ*@}8QccZnXWxc&?2%dNSL^w$jE%Dm8yR_%xSZFwjrTM^h3AekZfWVBagH!0^HXarz1ihy$S;0B=B?LyDamYr zaD7G4t&IN9FlMt|h8}kWKOTmVkEr2<-!>@$m!9kd0tcqM4n_UbUM`cgh6w2YpHR?6 zKPdJ#INH0lQ_TTN8aN;IzK%ni6e!ZY7Eb&&ocFr`wwC3vZ8F-YNz~qeFsQ&_!zNyofDZ* z)&QYzgNg=!y5`z)`W^4w`2YTnfXYq@0@aa%{5^Pvu$ggoVZiIg0^aEeZJunPiCo;_ z#nn1(eHMR?yQ~JCY3IMH$hY%<<7Co$xmm9`zatmzZC<$FN0;l6|IWz=Te^ij&f4aM zPJ2*Ne|Ud4uQz^nI;mH#==RiS8=cA!`<2JB#o8^?o{a}A;{*^;Mp+?Jv3cAj2 z>Y4qcY$IN4uU}4A;yzW>2f?cWR(zxPWq5xXs;YjhIZvIghA9$G^3q@oc(qQG0FB4KW^lw!;yf$3lTq4oB+zg=keUQV=G zVnV^7P#8j--V`Xrf|MRjpjJ%;vvDtby%XDMl%A}AreoyLsb27R{I7GfS4A^rz z{ui7!7w5$5$z?2)+*!=CY?>%1WGm_E@AdI1P0({RD}KoajbSu6917f*WO7DnK^NL9 zft=O{8mB=xY$9?m)3b#-RfWkcWW^<5-W^U!FOk(U%9U-02-E`9Gsn1u;huAd(VAO+CX{PW);XDN%=IDyFmEK7+oucTI6}*&Hs1FV=_9 z-GYkyMh?B5ei)Q^B*oNSgvC z-QI2WA)pdi{bPe5S1Mmp^<7HfC=}z}GcolH0m@N`q^VOy^ngTKNlX`BQw$8P0j}Ad zYk8e)9Ces|n~2pRMWt;1EQ9_lDuGP)DnAq0GIx97a$*GgJF-Oy<0wrs)FsAe_PnDc zXgwzGu}oYK$sn*4F-P{<1PsLn9CG)yR^*|YRP=&vlr@%%HDSoP}(KFztY>K z-|w*rpPc!U)Q+*h&h3i8GG5e588@#mD6Q`rWR(_c)$3WvH_I6?&~5I0Vu>GVjz_(8 zs5?qTh9dXZL$9Mn1#p>ZlZ_R^mSYZAU23pNKvJlSb#yHDk}2x?z~_46V*z%*##lx@ z-Jh8_O%@azMJ2Vz7rgd44>SigNKE_t*`g(V!YdqpLCBB@FqZfp8RS~pSo(9xN$cmx{x^PLv>I0e1|T$5K0V zqF)sa^1!#&$H$W2TMQ8*f;{@YAsa1oPaCLsr`p1$pB!@tYzu%Vw1^X+!<)<2+GeLF zQ7OM4^~@&*0|g;%I9ABIxK4Rsm`k5)o9Qt@HYI!=R@mFXMo-UbvoIet*i@-jk71dS z_6=em(<%qIyAD(7bqC6Twy!~lrnGCpopM6WZw{7npBrPX4CD^mGy#HA*a7RO2E>)v zM6fEDnMeU1r;V6qReY7@f|FWmDpXgh>g|$Xaz(BSd~EWyKbf76lYV$VQHD{5e0*UT zPON>W{jSBe@b4nw&gTrQZQSQ^o$UA6+w=|7LY^pN8@`yk)eZxWUO%hkCm9{4zkquo zXqTdzHeNAwAdyrL+@trw#{Ogk{hTFcRnwD7Kp%)Pi@9uB6dFGV;R9~G^f6$G0ToFoT_5a6PC_BfIo7V0 z7GI@j?h$#rbX=odI(8u+(E+EGDW%gsYTcJ8#=TdYgJ1)R0)fuM0A4|-TPO%3>AI@) zWD}cXN}vn$ic-i#Pu0TL5r0POiFtA=9hThZ#R*g}X4Qw@v9>1@Wia9;#=R}vIpoH0 z_@u33-&8Cz-ggysX7Zhkf-z(IU%gR5qx8ON#KtR%$O!h=$)H^fl0$3ThhNG>$ojhPhNFit(tjt073!TdW;BatD#~ z`$&j$#(`m_a$`@BwRzu=jn*-R0GR5?L@9x{b-PFux*8jg+>IRLM7ZkEl;&=X!-n-; zg@2<|E(E)g#|!nPl@;8bGns3g)=59A?vLP~_+s;f1PH8m*dBIWwXth&THu>%-w}b@ z0scd6eRyz52dau1&(hTc{tkQ&i8rjeB>KiEU-c5cmc4i9Zwu+R`8On|9jI-4-1e!bH9<> zacoMufA0gZP^>@n{kzr^g%tI|{jz4`9|a1^gN8YXhD5GmqtWXxe>=5w=`=np4`@dfMXg6*0-4Kw$k~XpcVcE-^NY5)}0pb~MmODm9;2|%qnrP#Rk_SAg}^SdDO*5{z&@~G5f zk3>U^A<%Y|=F#HyK7^@TrXELw-p1NTM7*kL0qCoAkg;US`S>gdG}c4E@P5bt z)c@3O`T1XJ^iTeUG~WXf`;hoPuBYxBYgnoKvcA7hKYl3mOOBrZU`Jt0VyP{^^VgRX z6aMm#gu21#8-8$C>CgNLgE{*2!z0meSVRBjx4%gI?&tLP<1~}+3cdTAzM=p5Up9EO zm(v^{bGL=BSa^Typa(>f75$q9I{Y(7n{=4eRz8F~-87%(- z*!=yw5#Imh0R|Fu3G(jU8#0a>taJ-LuL`Y?8cI8$WO+wsf6zS}y@W<#{)oEGRu>(d z2MhG+l54Hkj39kZ&^UZW0kba<{p#?`7(7r4s)qzunfJfvwM+QP?vb|c?)&ErNJ9Iy zjpcAIccC3HsDFWbr%0^Oxf8y#QxHT_g~P@Q?ZOp|%3_;EaUTr8B6WB%0SSddxwdD$ zj&2a*Cw@^^S9IWHpEvWXqVIMvPMNAhC^Js(n;!Z*bfb4ydOzY4dJUs*Fq)70*4{1D z4a+B!iO>gJfxaPNq-0`}eta~8EO|cFQOXjfh|o8#BSYy>aFCThs1!1#WCKTzTlRc~YzPC-+ z3hm#l&wpi(kx)YL z3u(|q=nMAOHf4wmy}j)d*-5D; zRS#gd1#0YI3pJs)aB2gkH*tp-o6zdZ32nrS2N$U)p)g)AzQ=v17aiESLYtVV@ z-Y&HN{!H5wsJcK}tQ|6T+DkHGxV1>xH^@f5Zn0^3{AOp*Ncdvv8_x3m1i1cLXcW*F zYU4ebg*)|t&PPc0ncgg_2Ua%<$o+cP=y1~)I=UGlPU7vdImu9jq}c3cyS@es?=%nsS@sq| z3lEIreuuBuKruEObrGV$lD>!@kXyt?U9AC;9X85#=ciRg>OyUylobS8jjzD}-Admi zLfid>yU*bw2jeCd_)4RVkbCwwr*#w9HVSsvOHh6&>{$fJJ3N}mSg0(VC)YkCY>wf6 z(UE5?wXnIWVh}n7%uSqZif;7mZ0=N=1IYhydy11joVdsHB1FsISrAU?44{)r6>)2; z$$*)!U3nA+_VdBu=Hy*puM-j|U})_NS)e&|BRxCK$4rO2QzU5kr3=Uz{He|fOfpaH zgPud=H{Add_Qu*63y)^~B|7E_BT8%V29sQajVc!OVn8)2%)jfTz!F}Wb0&HykfvHU z(a9wR-Lc42e*~NALN_qo8lqwLThLfen$*;7bcsf4w$XKZO_sO@{gf&G@Bdb6$hS zGSuct;W*XLS?eQ|3{vX+>57h@;ApZ1vE3i4*o@x4S?>ppg$12qQf`l7TLkX1f_6k8 zD4o`Ut~mlK#a~h>B@7aRUmW8m_-rFcOiB;Je4aAh#)WaE3< z&l*$^IO2kscw2=vEjWGlvR4WNZ~KDHPl0CLt?wu*|0_^eAi)fE*su2#(NB5rv(PhL zVa_UH!wmvT4=YEsGo_=-)&2;kXwv(`cGT?A%Us0-GI_F@ByIi|V*M?kp0 zu`kpV#1Z_}O%b7+nN2qYMzKGx*DwJt}{Pe}i`B$V<3fq#Lk^-<~x zi1O9xlR+G-zDPhB!KRaJNKesdNNpNX_xdd zxN~QL#CMv88-v1DUtR9Sn69gQa&nf#fqFtSH`q4+KIU10*nQBx%O`=Xo~w*u2UNyu zY(-8YpC0a4pXv(w|FF(OpY8M+Hf41_dgw6Y_4>i!$Hk;R8I*J$L;9{=r-@M*>UAuu zpVwJZxj?C(I+Fv&XmbDzgPuYDx2>S{k%nIqGlK^dUqe$_p8*!jbY z_4j(u@$(ylotHUxZR76#lm0H{3FUw#8-bTlx=`TccYxx5e7owH!SV2A=N0N znztPrfKxT<1i9wNcLtp;f82anW%r0pmsM|1rrSbsu)F$*WUd{O8CYonX2@JO{v|m| zDxnasW^c=`!=Q5N0}Zod1Cuqe0>176aP(xGlLavKjJcAqacF+>Dw9=a$D{SjB(U+- zBkMi+tMC*s^=0>Y-5WUS8f%`fqi)^Ei|x8sDh(Nb(^ZGfpXjOIFYwOmCns4ato^ez z$l>a}%1Uki^W8c=uWiE(cDl}%kYG6lLQnR|<~8Z-)7sBi^r*gou0m)Xgs!5YPiLn; zK!Cl%_Y>P!aDS2erM+uC=V#+3EkdQi@4H>`o`5aUWRIJV>#r81vy^FpRAFW|b^J+tucChh&vyPEf=EFu{v;K8T1fdP_O%&$abrTpc`_r)} z#HeoCL5cY&)D?Ss{`sCxFBbQx42X5@yZ7r@GN@jqJbHfhB^?_RVX zCiIg+owuYKnmDk>+$;ShbP151CfU?EVheuAOvfO`tZuZO4xrB2#YVZ%WC$&bnC9vF zy^aCzBd294qO({FX!-ELw47DH8S5TPTIosBa?L`)14;{CuC$LtgQm4#>e^~ zB+wN;c}YHot!L+}XPqxZPl{9hXkXvbNoJ(I#=)6Ve zksEa6NvR6R>)>hsrLUsE+UnhKexu2y@a`c{no0*0VHUMcm9%15XT zPDW24bXC&#O1<}ZtM{m#;cHD2G#Ngtf2r#Q^$kGHbN8^02U`b~Pq(_psn9*`8(I52 zfF28eZ1K&<2?-Rb9hyV4`Z-drB>{k%s#)TiCFj}t)DyCV4sVsPtsj;h5@pGEQ87oq zgw8gl(=v{FX}eH*Ma`_jx&VcQAF#35VWpGMbBO=*=}!G)sfAG5HK7V1 zldJppLc!;oNi}V{3Ka#x$D3rWdXI69KG93+x%%Rw8@t0}#30(<*3t6f<2QHeuWLE7 z2E0FFoUV@E$#;7>5O?a`i_$-MhwnU||M8lR?u+6(4QC0(gUlbRK$mqL$`U`4RwBVl zi(lU}D{X;2CovAWgbu=!E5%=u^}gz`P!?isBKJ;+A=aJ6Of)_qNok+WFV*!Np!juN zj5U$0gPek4x<6v=Bk#RM4g#md^kU-!Yr7)U43L8!V3^J3XusjWnbMu;X8zjOAJ+c* zWPMoI7>u`ZCw+p%mgU%~C#S<=aVV+J$16I0dqZ|`aM5<^nnfrf9uBdlcDhNS8L4;{ zOGlkkXYLza5(lcFEfng>)3ezGp^dnDW0V2&hbyD9mG-7soE``O?S)b?N<2#LcS;Mf z1B;CfnWK+u*`DB|b=+H`R3!6gp*7a9^CbFe`wDghiXCr$oC);C+n77ys>6fD=fSg{Yv#_lvPTK*I^@ID<+OHVD z*FN8pQpL`oIMO)K@i*w-{omfv{hR%&GFI`A^nIGXhHrWLh7N}xTRHT0eV_UKfA#w3 z=;wb4zW4Hx{wII4_1B)B*SXKuNB8&LZ+}@opLe!#__U6-Klk@pIp6;9CHD8fC-HM^ zdi|B_wWL3e)yA*SKg;BV`o(W08R2*G;_|oIFC{*R#Km%cw|zbY!8gCZ_SKod&IM@i z?UToO4fx_K5n(?oZwuWKhb(A&|4;5rKS{cMhWV9yiFy(NZ|inm*X?JvM_}^w6X50I zV>#meXqxC{zzYxbVI>&;asJ%F8v<;HO> ztUGT+?X74R^bqUqO}9Am0#UaUC92{{V5#co^$Z(`wru50blxA-Pa*ItdNh8{p)d9h z>RmwQi0EUUHMp_+k@Z}Aork0ZPW4RhJgatb{kr^pW_sG%g1ot%p`OD2&}B;>-NmWy z{<#NH3P!yBmcL)3)sxqnu{itq59rCVa5M8$IW412*sIvVvaa0>2<{ z*E-F_&vg$wLt&F+{^uTavEV($2@9Jm&`MLdq0GStak=@uxILbQ-R%ifd^S)T^u^{X z$S$?FzT#(UTNoz=^JxX@_7sQj20b5b^;ou@sv-J<$%zew0V*70F6}#E4z?45HbgI% zIzTQby{=!ZMe2pxmDYWwmha=yDsehplVR|k&cVCK+t{$#rJIM`+i3r?-R$ykc?_TsJCmx#$Xzis@iY}1x-M*|D-TZ^KopOTmIg&%j=BxsJ+Z`&x1~{jZalmSLZtE zDNyR;4C9&ev)L~BJCY&G6d>uPXd7LO13094p2EENx_uw&yoK4<=YWoaB=&Sdm z_qT0cba|r@XJtqG^~Xlnc!b4m7dowG{mb(pkK<*aG+6qH@pw1rA@Y3bb!q-?w$p)m zST~&Z+(Q2PhQ^i_L{T7g&g2e@@3Mv5oPvOJ+tPQmN%D#Nv%k_PV90}l#^thbv?)XL zbLr?aUbeq`po~bqN=x_3eSXl0T((OeQ@I{_9?qw~2Q-G-(chv>f2Q`Id8lnwB&dH1 zV4aiUaXpQ;=5}Uz%T+edS;dFCg?ZS>W~d8MAQ|On^WUx?{yXKec=O!wg7MGrrSUtL z3*#)+eQ2kbUoQLEv7Tw^nKX`@Ka_QhnJ&>L474TLNNsO?<>KU54F26#FbHB#{Y z+$`S&^`x4!7x9Je@C2uN*}il531fK`<@p$=vQgvG=lYuJQ#S9e-WEUloCQ!f^BWx1 zycUc0=2xfnOY^$$s84<}zl+JxBAimf-bWOt@HCsOV4grRm1sm!V;Tx&a&VJ4)TnD~ zw54Ei=RUC?-12KnnVZ{m7Jo~+U-Pq;1&7DWm&XBlr0jf<_Be`FKojWVUH%jfhHZSw z$~+_`TwH_l*hlVPclxCiW7yhK*o1=QlXFUfj&kuD?TQr*U)T?N+3QmM#(ISRa28We za!PZLClo}zE{TECdLJK@-Vma*ca2xswrZH$F2B?Lmd0f$*AD9SIVIC1d2FP$9y`yn zU6ygaVULg1&D2MIo$DdCKpppYa6%Wt+o-{QI8XE_o?#w{o=(J8dPc1=Putu$!m&zCqq8V^I9*s zhx$x!eO|Y|_-*JzLgLR+(?tC+E_zaHy3tK9_OYlRSBleA zn(gD(7zdg&?%*gjMKA*BRDy7@JY+M=*!T*7W$`=z$ z7kJPvpw1@$J}LNY+Llqb3hGkewDtPhPlshz9ZU2XX0HJslN=XsqbI(&!y$sY%Xy z|7?}kK3yp4kjF@;bYYzKXIcb?8RK$TxNj~)pn#klo=?i0lg(zpclr3om=9Y`$sYT> z0h3KQ<>F9lX5ab-t;k?$c7iB^NO0yG=4iK_Ox+gxrEp=5KsnKaEAEi%a8CLWaBbAb zw~iTPJUcHq?yyq_{ufU5YdvOcvIhNxwM&9P6E;X4o;@}^vXfs^eTrp5$HED4nsUz2 zA@_5z> zB9q_*QotA@aBn_d_CC=JET+csWj1&6c^ZLEf%x^snN#2_dMpdoQ=8ld`Q*)0#A@^; zw70`xRnaZnDKiLqs72`sU^gC?oKP`$1cH-qs1<|CEK}D*=kJUulzJ6IzC=1ECO|#u zxLbUqu?J6dW22^E3GAWNVL27}F4QyD^EjfUgVki%AX^Jq6&nXwFk0Pe1jXzmv-wri zg4E7oAxUA2sP-gAqqphf)L#)ofQ`}q6j_L^aLN*IH)P9G*QwZ~%+xE88zp;8)b%zt znlbMxx>F3CuA4)`j|SIEz=Hdx@ZyOPdQY+O?C%4jM|6lhRxNPl(A#2XTH&6c0~v*X z>YaxErA;EF#Xc~mmOht9%rwuTQy-81U56e=(u&KVa3B|$pWKTvzmAO(0q7gHrovCB z`EkDmkgsZEfk3CH>RnbrF_IcZss1iTR7jDe(AUmi%O(C7$ubqRN_ra9BP$+cY8cav zdcvVX;#z$ij@2sSGzd(wc>up$u0`#?p=mgTFQfhiKHfGU6oEwbSElIWNVG}1r$Lw< zPQy%tl?+lC6|$ z%F)1Lw$^1OCv+Y1z4LCb<1FgmV3%x-MSz5r&jeoIYpbh|UDZK+`woG!tB z?{>{MqjJEzCn;OMRGboL*gt#DbV27BY@(Q7RqaGq^kecJt+9sC(x%iu-L89^!hfY+ zPv3BvpwE%$8ByO7@@PseP$=d$Hk+d7QMtq>hbJevOxYKH>wIB!Crc#CI&Fk)0{5T> zGulEa;9hKQQyK#8TK3_;w!dP0+I#!*7yjEiN`ejqdLE|_BUZMoW7z!xj`lRyXTnAc z8?v5`JM3&t=wGG0WNW9d5ld1nqth_oO(FCb%*)sSTvE_VV>XV4*=S6jhTPg_uBgdB z@R**RkYi9!7~PEPhtkM`{=+w>EV%;uwb&ZO7djPlLgT(+$+X1xYS)4MqU#3O09(>o z|L)hH3;MS~SLB~iSp|g_RZI&>o>&(J(ud3?t=m)&DlyQUTo4m<$S5tWjhg&{%u*>= zUfXJ48@O%tgqMlUcQ&+VDW|~?!nh8b9E#_?V0IEpAphTG`)*1OU?DkGYB$xXC7%$s z#S9}}!vV9eUwO<6<=LD(wt2%9x}nhMzOJUeHgP+h z54V_kHwB1!5i+T1JtcGE66Kh^IBCBup-0=qIJ56KrchwN9mPhdd)yZi-2w`u`*K_P zgl;4TEbrfm%ytemRa}Nmc&BIU0b2W}a$je;eODR!Iw<^djDc3+E0X^YxhrdE*qx=fjpyoYDzqs+Mrj%CHMK5@~TwW5TMoR6QKAFN&-}> zxczXSS>%@JW^^qq^HfbfLh|#)kJGtK7of1^#bmBAh*Ckz%kf^PDyzviu{C8=>R`1` zv+F$vMWxDg;^T zR%5d*{3O>)i~k#ejN3zZ#Pb@PF-L2x$=gWO*O@MR*g1A;z`XzHeo~5wR)2;l`o2ML zLws`50~|C`~1!N(Wkw&>lgpv#J~C9ahX&)JzF`yy#D-) z{}I!VesH1gzq42Q(wF?N|1Z8z)0?~aAX5UAJv#PefpfX`Pv4Y3^#6X)kAL%j^>Z{` zIR^ipe(8_?59sc1`A5BIefRz=fqg99u=uwp>KnxZLyk=hp+9IqkoA+Maw+WpmFb+` zU+L@;=>GQH@2}TZ4=JxXtaRzmDka3;sw%V&3Ola@vfpzM$ogA*cK(#=#?eHB>zB3e z+Q$960;&kMjVbhROSA|=6)^!-k2^DlRnI3>zn`rgP9dQ*4X4z`E7T>GBT)HXADuwm zK@n6YH!3k9mw|3TWwPtvc_XzM~2`W1&hP^#APo4VQ!Otw23k}D!PV|{k#5c;j_n92bV@y8FH!a#Kilj?NaO#jFn znyA~{9i!cwMmtajs63D6TnQY$k7dz$g&11@eEBe0zem5ClP%@!Z;a01<-18~1LSkj z9it2|ne=3q^lzfRG3$04I^%k?d$TC5MPEZ%1@gG_XDx}!^~YpC`6lzRge@gFIQnz$ z&d%q~hc^_L}npiHz;?pdCzCf&((c)KV?gq1n1w#%IK{Z}(hcQ$C3 zj}vTx>Gg2h#agTM1P=GaS-1DSJ}tf1DGXd<9??F0@1*)6zdOZ)_g6t1Gm^mR{jSU5 z-AwyCYw+>Cz*E~my?S?23JQ7l<(oB(_1U&C)n+RArUS$j_pJA+Lt=>$4tr5j>ZWO&fBJdrJeN&D9M2?fdy2|1R?LlM;RepH%+7lT{O z@==aKB;E0{Nm4+26y9yN;QI9T#i$Wv0sOc}KzOyK_X62xeewL`8(JRBVXFJoUu!*! zz(B_e4swuk6sDM!dSG`o_eC={(B`LI1ZH;9UL?86=MMWnt0n5CY0m&!zgE35%8Utt}-_qf_(cToDAd-~9!6nFP8 z_C5*NoNNs0xECn<$wKx@QvkgcC{7I|F!h8>^o@^mg)7`!&^{+t)L2}ubKJMLYa0&w z;Qsz4=nNvsg(#9$Ob~rumOk--LDX4gwKrMFvx|K$W9Itnhjm<-9NPNM8Sshq_C@I^ zRO5uA!eJGa%E6q<)5jZyq|}A))zQYLjKQaazEAahKXp6>YHg3WUB%A=Y4G4^D2-1+7|JlmQOdrTd9Wfnm0y3goHC zdHw42-94S2uKFf_@p-DBP8L}P+^ERa+jV^k3XE{?3rCR7ee?p|IelCgX@E-&PHWjr zTV>S{a22>*r96>-eMN6DxpkTDzmhtdrQY^wLmhr4)I zH)4_OFu&|=d`hzq-WJpWflz^jE!YuNsBZ*Q1&V(HeM7!ZpFr^-a=U)Edd}95?7JJy zHUl+KD0CBZ*Ea$OF0XPD$`D;suXS&;QFTcDHF1x*!->o1Z$8mM;dce{^v!CE4j5C9 z3aPAhdpaoWt#67@N(_nhl;|*9QbxW1wAv0^WD1>u&}x`|*j%lBtKzV7MCTQvpX?q# zCB8i4S^50-VI6}nvEe}2oYKSvwsMqu0{2f>ol-&Nuoy+cboZ!G zlbg@$Sg=K%`X_1r@lwMDOX&i6W5oWaU@JQ^m2h7;GTrbcHc#zZUEqcgEuo9^3wl|3N)5|Rt zm-FtZlo@@Ei|6Y+w$EO=%2tln>`WhKK#|M+dxN;w@=rGkJ8zXf=?0d{IoVI`2Z1XW zANBUV(MxRR^yTT@+P~M<)@hCnG?$Uf;S!pS!v4#~nQWd4L_MJ0TdGGnd|Y)5(D5hH ziwQ{@{mi4h*DajT0u*de%O*$lM23@509e^ePdL$j@Xe@wY*4pVCXww6=&HP%00DNS+QK);a8%Jn=xwZqySNw=xkbfMzFoY}4I+yZ*cO zlLz8a7foJx`f42m7F4SH&+Gf1?G!7<_;7LG5SB}*Kxlo|D4)Jl+7Ee$P%RMe&+5rZ zew3*b(zUEQ&qA{y;*s$UY7c>n)+7XZ@A1t#uC8q(U;4Qi&Di>8oul=hRnE)lI_SrI zD9>6lZG1Zy%dl^gGcc)J6(VzoL z!z8ppd-M0^C$njC+x`YrDY}z>=;iNh9BCIleATHa*7sD{oax5w?e-AI>BGC|FV|Ju zOg>uQiF=%XBx4P}`KbTB(&YCn)E9MT@ZIvMsUYgOO z*@pAJ`xR?l*!?JMHfn<2YQIaHIs%Qy_E&qp-ZNZw7m7{c zpy3mm3C!c_=gh`?s`IkYC+J$Q*Hh+&1{*87R?;Dl4auSpAN&-;VL3 z?+ia#DZOli@wm?8N^`U`3O}KT&}XzyDb}z;UnRb&+IjW;tiOadMEp7J3ZY<- z_Et;U(l*I@T9N{N6I;~)s0*vVB5vMNC z{oRwT2Rz>ANq;iF<{c6!D-vvsenQqh8dltOY6GKaF`3Z%(`Y}0YVAS&NL$reLaF#0 zLP=&6Yp5IIpW>fZUE>tg5y^d$B($@@CBN9(L7RDvHE$VX0kY2XM3o7!{QCJ*q8spM zvCHf}nXC(>evo*$)Y^hN#y`~mTy;-kJ2@Vu?>g-?nMLbx58t~W$rwJi&=$!0Q9i$Z z`=E3J>Z8gU>e=l1^!CZ<8`pOqzq_-~Rab3((L_-xV>TL1S=ZOwqs)wa`sEmo_b%{%MhQ?LaS0~7Gxku zdp(xoeyhcl*n@;!L9tOU_bCk0C9Xv7ZDcIt|vv=NJsM_((f_g;PfQE5-T ze@(wjiYZyVMcXw5uX_6o4YPlI`p)X6F;b1Q*+bLaX_U`}{c(v+wWnRI*Ze^F{>I~M zL~%fmgl>B+^RQSf15;Qw6-xK>c@H|?AJJd_^?!(#1LnkalTv6H0%Mv z7ttAX&ueuln*x}=_$-gFq1?~`L1Fuzvxk3D;a|SWYgN2ZcQ@wxzf0f84QVH5e0me; z9>4QP_<}5QV)I$i>Cd0oUw-!&ZnMqMUZZOzzdCa+fGmyue0&y6I1#wZtZe83;8gmww>z_r*{m` zwJ)nZqtEijXD<5U=MFKk%_!XH@WuC*aZc`Xzx)tiEP`XWwO;JT%%Hb?oY2LB{xjKx zq0e%H3H8t7yTP%eVQ4s9vHp3%-*NpYVH-9#WF09U6GoOhG9Awvoc@l@MCx!zR<7yR zXe&w-7pM_@!TxjC--~*ZTJym(-B^_ zkd@v3*>_`2^H{esS{f{zU&))c@#|XJ5pcSlFR$oQiH4p};ko{CWu5c7^5(M(SGH+| zHxvMk0H^#4gkfH~lpn?xRF&LPQQT8}>W;mdCbl`#inR>Q_cc!)$UOlNR=E zs9tL31?R8W4Z|31UU+%1EH>vKlBS@bls{)GquSCPv=e%4dPP#OEDO{ z9}jl;W$K^DSa3bh8%M<^!Md?c+v@t?anceWIIWKByd|S1c~FO7j}!ilq6|M>Zzpbx zB?i!Fll{ajF%K~J=K>o49Ax40*eGv|9quR46cM3WKj?-y-la)y_&((APDC0PjPml> zcmK%W5vUO7)s8xN8yNi>`s()Uf($~XF6kN^woAzy`PoZ!6m+57i&P<~t5ZExOy+rR zs!2Zv8$fC7!e^-5F^A>&yaNkLn^4> zurZygV7GZpwT#KZc@ClrJ{sYD`XkQ|sekPKqb{^d>q>uB>E#413X-Gvg8x722_?sq z`kc!g>$B)euNY-P^!nw#Jk!rKXAe~=rLXtkyJhVi4(-rJeJ`!(D+MBN9!GMO%7#!jAcxA5w z*_w;e+(qe7+4yYuJ+1_o=Be`v^mlt-TtE&j>1U3Ojbnv|Jr_r!)2Vf9e@Y1?m0ZnM z#&K9v+2rLY3c|Qn_vELuHNE?;ZJh5qDq0zR;?n1UxbKy=GNtym-e!-Zc~U~(=df|W z)MMGb%+i;zoaUo4+CVcuqxT;h-LUg+i7iKA)4qL-?*=}E` zqo$okY^=*V{xR$(>~z?H&DhL{vy|Hx>qHvnfEZ7xL-bSnJlnZUA(Gc-rKI(7y8kTx z-m4mI)BfgH_IJI?KK6JsAWCjK(>C^+$4DSCjztgtPfkkk*Tt(+Up#;N|E2%>((B7q z4u`K6;%BVRZdbwKq~ERlJ}0B%r{DLLfAf!92FB^r6e#L)qG5OUcTw7dupcro=F~a< zHJA9CV<3(L)PqT}dE?68Qpab7Qjenl>H(j)kD;jfy$L#~?0f?0&kX~(q`%vR&l!`O zE2(QY29v_pZQ8>AojWG47#Ge?(4}#h@5%Kn=Q8ql8k|%?-TE!sU;c+uT$GrcJun<% z^@+=^bEjjtu(?LRPNN9QE{o*s$r9`mUhv@yg~CSD(R~p90PL>oWKEBB1W|<}|^( z@U;JPnpr{H+2HsuO|mLRzTDS12M4HpHfoXxR8tSC%R$DeSiH0FIML(8yDq>eRHrMG z1fQm{UFCwqXQ$}9viHP~UZ2ejp90wFM4!J(PCojxrSv|P`+$or_3~5Hw$XrhDio5z zr0QvEoY>TNF6iF{f0s_1;lhnm!8j41oAfn}Fd3dp*KcoIn2^vhI4IP0tl06dD62U{ zJeLkPhw^InFOje#B6j{;{Qx*KNCI#jp6-cKX?PdM9p0}c!p?vtiD-=)`6|CdVKoa}eR5Pz@tvBQ_Sjm_g( zi(C5G%K8>Cq=f?Zu;h74?JA6}qhOFhvBvyQDG;X_@UU|tOXx{Vim_SR^^?;0?wj)* z<4)_?u)v&JEaW*M1^BxQp}sZQB*;Lkfh=c3y)oI_$!wE|+V%}{gx-~CC@2!A3^vYS zbkRRvj?TXH4*vFN(9lDilf}b##@v5henE@9WJCzhL^h_a> zEscxonjr|-D+}z5F1@(dSIeA`)lLFOS;Mz!K#MTcq7s|;4vU)SlPpFp$>-DPe~4hQ zd9iP-^eX%KD)`+{tT@{40sqBMUtqqPCJJ~+Ze`e%m4X?;7(30;?UL%nL}6PQ`p=Ll zW22?IJtG=n9YM>YG*WV(oQ_I1h?9M*(Ek`?(4o2sL^15I(g$Q(2*6fP3FSfOC$A5i zKc*0)wa*uJdX(bS8$%DZi!okyyo_TwPDGIEcEJNSR?&F)A#t+83oU-aQP3W0^l?Cb zs{do34=|O~-A{ug#o1n;_cj5SoN#H#9px;jXMNQAJOkTS^2u@7r~h1cL-!Xm9yUJI##HJ-Ugt0->N7(CH8uMtfql}h&FOQUhUR&-8N{b-n5Ro)zY%K9X9o#5|_ zpv))Zu>Fhb<`QcMopT9PN|E3rbD$vt$as`D`x0_V5&j!)#fNDcC_q%>09@iF0ZmikU)5bm}SYMN$x$K>=Jf~LZ-jXo9|bh7z;;eHeB z4DCwD&DVei#BUCV(043Y+{+2*E)F{-sxZZ^r186SKemAxsZghZ@STRVUh}atA!%Ug zQwg~s7Ee6bn?b@MaDDHb5-}Urmlc5wgPF&oN}#g6%))3V_c_RQJMX^X{iT#)1HvgD zg08~l*@t21q}p>wLNo_Mo8M_K2bl% zUysRHyvb;@VDqHkl%Eyv6ni?Q@eg~>m~Z!pOX#K$m#`@^_WhK&+2Crh_3kJ7obOA4 z_1~%WfTr5nVzfjqBDrZQecW>j)W!)@j9J@&T6&zOcKU`{sE$rdT@7I4Lzd1(-a_`B z8jRwky~i=)O8!>%6?N@fp=&8AQfYp?@!>mgN`|sJsVVVti|bg2zgI|euL67$rEA38 z6mgN-^}=a=V*_*JTj>1cklyYK>Dni4eRJuj*(W$Ng*N4Y`ZD<%8nZ2(s;}1vGO2y7 zt-cz30+)|p3cIetDC(olS}aUyE0-$5;g)hUyzO^C4_%?0!M4 zPeZ@^yqt%y*&+%VvX50lqPh5fv)ef)fUVIk(GEmBO`l>im)88$+2b?n>!R;>=E#(( zM6Aiym>ei1B_q|kjUkLr1X;{)=)Pt5d%K-r?((#_1AdpOYwBwZkE@JHOAN|y*f+#a zq^+fP-=`#bh0;qE_ATnA_Z3fyMKV-I24)&##g0npI#8hdoX`?-;f#YUM1#J#d?u`s zf`&R&wGEz_?V+LDJg-Lk36-9nnO@-NzF&Rt?}KXmIMvP;uwDmbAHLK8 zGhh9JLO=MC-SZniocPy%*vhZ{+SY0Q9lr|w7rgji`y+4Z?#7^{j{Ig-T7#fj+KWQu z$hBX4>JR)M|J*9dw>|hEKNf5}u|TP_QALQt2{iNtdIF(AsE6~<%`n=pnHC{$Xmj!r za``E4pr1Lvy;1`RmB6atySF9i6{t{bYP}}EOgSOt#U|BFe{VabcTfGE{OmyMkmQ!1 zHF^T8OQx}?iBe&CWQFyYdJFY|LD?so2BuCMAwJbzmvhLj2n342VV7u^Z-(}b?xN3L zs&nq6-a1<*vz4y!Q8tl37!67l(XzwX|GbBh~Iw2 zu`!jThQ8>}`X=%Gp(s^>228F&VIW0c`DUrW1(O=l>@cfi^(YXu_QBQHP4%z83)M#w zf%Y}%FyuOYa=MCD58pPU(kRwHzVTaX*K?(!KwQi>#=Vbhvsd7(St$ur2T~$JpSZl- zK2f_ZVd$`C4L+8D-zV z6)Tg&E}!Nb~{rF+G+D4Pl;q7c=Dfj96 z-|@RnyPz`EdYHWlxG40m&LG zx!13Tv8>~9Z(~Q{oxj`byS3*hTL>zoa5%0qzsyRdKv)cTQq;0~ZrU!gdz?aIh-EnC z$9mK8lhF`JnFpnBFepN$J3!rLP!*_67HWoDn^aC8_np$>UY=DYqvd((3y9JaZKO{W zmQdRGJVm{QUcu=Ylu7^;63geEmL>NezCJ3Ig~5x7ju}jxK%5CxgeMAkaE#JWQTbI5 zA{7hrk;z`+>`Pk0h>m?YtaUPHYyb6&Q9P8M^rbo=)B$d*KkrxFgHC?`*(eg^^G|!L zr`uTJyIafI2^`}Nr%7e;hG4CZ2|)KRC{tdy`|3q$A*8)8-&`xan#$@5)DKer9`=p_ zWxuzL8>yT3(f;kp;O0dRppjm^F-NGh<+LRanMgF(IN3@M8@ zhh&#>mcFS0^FY&EgQi#c2yETMK^MRRE9c1&jx!gaEMCTAhHx&m3eD1 zAD!OcD0P7b`V&9o{>`&eM8x#tfg`16*unp8*svu)Ut(OpNo|y}Pss^RfoHMd8u+;C zu&AP6kx(G@g|41HCaBWGY}6GZ{|A9g#JKzFaqWYbc(1@*jwlL!;jH&K2c!g!4ySE_ywqZt|VbHB<{& zNcjcSAqp}21irBpBs;u$T>Z$S>hI~}t@cM;=&Swxqta%KUx+V#(`C5t5U=gwN?}sf zB)ol4Y7Mb_Mq2>-4}sPfh+1ir$QoU89T0 z2J*kt!>!R$h^c+|NoAtn9jDm1n06A35V(Fw9xC@{+5XiDJ85wdA2L0Gv@{LCkT$2Yo%-QDMa z@{aN08w8Whx!P|Ge{#X5pRPY_v8PGhB6ESumwT?B71rBeuJhO$eL>62|rT4F@S=^{jIbzB%Ffd*c0KCgAg zxG1Ij21lWiu(89SONazlso4TOt+sBdx1SYKKA*1Bb%D@B0KR_GL@s--UVjdX3YxEs z7Dev47Wnrg{HwR?JawB+%$Y%%1ipR(jfEr{=E3KQ0MBm%HlG!`zQpE0ELNFg6Lh-z zpws}`zXEeDfowI1C*4HW5W(p_V9aTHFxrl0Q2+ief%|q{GFlIVL$4xBM)E?LHAz70 zvbLR_t^#xeIv!8A+Q(j|(^$xRDuEtlI+4>r=w^Sx#;@1`Wd9aq6&i}c-o}Z@VL!*_ zI43HEwp^{Z?`;0vJ*@wpb@&SXjmTLNKu)V{>kQfMAEUphl%VEPN+RFrf_>Cyg*sum zHd}Z2q`Id%K6jc7Nj_+3i`Zbs;&z&h{=kNO);~+$u?=b4JhYRmd$SMP7Svy4wRGrv z-3a&1+g1L}6Cc)lWTQv#pVz)vtS_dWPyv7zN+A5%_-L!h-1Sh#avVzY`Cee6KWQK9 zyr817BvYXeDDB2*2yWgR-2loVj61PmJYkMV?#n>^0Nu0X(bYF71}!dQK`26`f2Qk? zMp;39%F+!R8Sh?ajv%bj8hF3R99>Sfp|g85x&&-wyA0>`UF6gTi8iH*-2&|hD-3)M z-Bv$GH?$_&74vIDZLs0!$up(xjS51{>6JnEdlFX2lT ziU*;gk+Gz8d$}>X3;8|Oe}DZL^boO8iFbM((amxB_x!l(Jl<7>Mh6rgEzwCRl>jz! zrLScE((+`^MVw!BtmxQ~gx^kMA#=xqxnpaPh^fo?=z9g&zS<9#ik6I}x6 zx9H`{XdN);AiH5*&#pRigE``KfS*tX+J*gk&+gi6MZ;XLdL?yedt1Lqow1=bz4;Wh zM@F;Yf%VoYAR%X&O-`TRtaE|s)&A|rb%vp)(NOSoJ|^y2xk9HAEgf8L- z9?=)8Z|GEg@hq9`(sk?lZlQ0`IY_!J(VVqJEn(HS*r-xqc*5TC5cBL|^&#CSmp-DC z`74XlUKIGNxM%rrzutGF{((>rRLI|aSPf88S^dBzQAh}tozcN#Bb-b4e(Dm=pc|+} zp72t<4p@6OwlvfT&_XA6YxzaW7x{m_4(cwYW79f|&ZPAP-fw;SqBM)zcA->wIp}wX zuRg0^t?d<`8GgIY$FPYzSrhqMPs)D=-I!3?-6Tpo=!R0>p&Sxw22er>T|;~>@_>&w z-D2&Z%CUD{1B#!G<^VAel~uDjj+Lqp zE3^o*mX@1#?}Qri)DI-#YR}eRYJ;6xRCMp|2%5jL?n&LQ@f7nJ4&Pt<=mu0f?{8PV zI#4XGe2sNyG@9C$E6^*fy7%(oR@c&6&R0)C<=IDN-xZP1gwjE2LlL{GQ%_W%e2ovk zR#8`+B2(1z-s+GKvTDN7r%ngC6Ut5iVL+b0k1d>xUL^AM{6IrR+fqx!PL;X#QxN#brZ%EgG4afx`^ z;d`rW?=g=^dtY`Ik9~Wzn61P>zP-`dm--vK7KaY)V4vIhQ`hHw07d8%MP9yVLtSPb zHv>HBS)D+XG|W47b=NJzwip{cZmz3WB`?082f?9sP;nD5i$Iy6k@%gy@UdT;j}e?(WyC;k0pdP5I?`N!q&`Hh)=##g1^ zjq7jtdw+3l?c1}>VN*(EWk-4*B|TsoPj$t|+h5`;u|V8>US8S2DE%}6pBF|^8Q$Je z=)cI7Wl=5`b-BKmHrX%LCEpWp-F*3e-WJ6d%3>JEtqjUo=1(t`{_LgpTc4D%4ri6- zi^XQ6*q^lO&Z<~8?RZ5~hu=pC@Em(VW_fx2={}Wn%D$%9w7>Ku=caf@D$^@3Nx}Hx z8api@{RA(J=kQYv-*|cE#Ah~_d>#X_j}IDr7q747U1mD0WREs7CNt^2f)i{ zfBn53%F{j@os#UZr2gSEQ$e1^eW~h>#!%9`ZLP!!wr_mKbarE-nwN1|OS3MW%@I7B z!$9@e7BSeGC~#=+R#yY*!evHfINk5>zfjT3<;#Mg1OdC1RUTiV$DB@z_?Nw2{$W6q z()n=7dQp<(xAXP3d(Y;BSDlf~_dn_KN}j!t&QvMG=yBR?F?)-9VI#0qqB0EVbdUT@ zr|`xb=!@4-RZgdYVq$dJ`03tcn*(*yO}S{`|(_{@s<%|VqC>I9LJfrAnb+Llg!>Wyn10f$te2I0_-T-{dCok4 zhW)64UmDXWp|599r~b3#kCX4rn_JnE3G*oh`!SB|p&yXcbOwbRB_vyso5jg)ZQ6S_ zT)P4ELeKglM4Y%i{i=n}r8(?b_I7sE3O<7}_Ohbf_?hj+=F77RQlBIJ@6>!x!pqNh zlpT)eFAoMnqKdUQ_v==t21wsFzAN72Wx6lt<2vgz_x6=!EA6uj-f=k;26b5DOR zQ$w_93-!}OxgT408~W*R4b~^S?LJ7pc+=1Ee%OrMe{mAsDj1J-l(0=1#PRmBDJLhd z3?txD|Ar0g9~FQ1lAWDi-?q`)lGbthJ&$86f|*i`BSfS+SLj#NIj=v^kE%f%U2&c5 z2$JJ?2ng2X2MrMV{?1DMeSe3~O~Xs`c=F=6 zwi)GWvaXlUJ>lp~4^v&7ZXi^!jQ!(v+|mIomvp;gZf*d2Y)S5g$>|bQC1YOC3WR5t zPIJOE+Ng29fV|R}T6pa5FoC!ykZd-x^fBcKHvW9g<2C7<%Xg4VZgcusQ@^&eM&O;? z=l{4cc50qnnG=Oc(ow&qjX%|Pp$-EL36Cg5ZcA(O`v+`f|qSo+I*JeGt>v;y+-pnk`m6^(jAWeSz2rlK3I}%-LyUT zJu?qu-LI`r-tzM_+RMfXohAg!!yLjGH(sYLbx28T>#@=4{nc&FW^CfMype+^Z0O;W zjeXzB>TKQ5=i{HyhI8L~{JReJ*nH~q_`~^Jw!KHajcj|pE;YRubBWrM(2wMQys`~b z&gR&(UgWI{-jlAj#WvjU(|MrZd2UC`wgqvviasnCr($xb@*r~46|-h+A_(!4Afx8_ zbSu}_UegAfKtr3hV`F^0Y>MKDcgKaX(Y7yjovZ1-mVWmu#SH@ygQ%}P)A$`~m-?`! zGTUbVBy`gIDe3!}UZ!UG+N5k>>^}A;RxwHvKC#!m(2$g%KR6{`M0^PQ2C1aFsoyp- z)moQvM#Hgb)VWJn;pcfYG z2fK+*^s-py)||>^*W%=4nb1fAkf*M>B_hHoV~3aaM^lKwJ()E0JE?Av;Nbu;fD8> z{*HkPA-z!RSh~D97G(YXmaqo-Wd|Y+N-bha&mpyJLHV&iga@Tt&?m?Hv zQD<}9lfFXXx)G^69K@-&yX%lsFyOf~vAV9;TEbyyfHXHG z3mOf51^6WTEBB)w{6JkpHrOo6P$aA_nPwqi ztC<2}!Rs?PW-cGMvuVsb`b6)`-sj8G?O1kLGM$7w`57I<_3q3(`gi0g0@<;EIM?^@ zb}iW+eBU~~gTANP?^O?W0}aT_2aI@y+#CX2HpzB!f(&#K`o1jBnZhO)cb<7ucgRm6 zXwg3g)oKl{Nq#!k*yrLBke)hbfgHiG6Mlc*g!XsE(8140{z^LFHaf;4`mQZFRZl)1 zU1y3x77`7MIL)kgijc~2@?XC3&ututFbvYV`WzuLu4Yet;cWbn27^}eMtm})F}Jzi z0z*#6>6H2)XPr{A;ZC>VKF8$tkP}oIb@;o&{Uk<`iR(=aOs_+iu)7W}ESyF)H^qa% zoW`vFJ>%rhQu?Mqqm-;cF|Nz5&&iT3u(&rU&%id8DXm*uPvFt_XwZpNZrVm4>uy8f zi+CKjCjn4zWl6G5GNzL311H^+@)nbo&bO^^Tvl*8G(~uzwAe#iDqwtL{9@jw1arxK zj@J8qK0{r6e0v+TkAT!bOBsSda6N)4SBJ}Fr{)UU7qOa?L6&!eY6wEH3Fqw++a(X3N>W6QZq>2u z{oL{d$qG%$OXp^_7=YQ7NN9vgcNo1I@b3hu{2qw2dnSj{BqW?<|3T`+a^`bGRX_%J z-#lT@(Q{tzESQZJLAt*PEAMOtLdzIlAtFQ^t8!9?kIM8Kov#G&ttZ> zo|xfR7_t(SRZauon<5I~zQfolpb{XA4^Zmprp08C#fnN$x;PbDb}2z6(fvrw9a2rD z3n`Qxezk=Ou3xFG^Z=HfP7m6tX4Dl%U4qkSQm2HXRFdB^aN?pbwpi`D^ z5VZ*I+Ot-iQ+`-mQqrHa(|1~#Q(&`)_=e4f(i2M+a@|X+Bged=QLO*=rEl!CBwy!u zS0o}t&P5*N8_4E3L|HmN&jkCo(5A-_h%NAss-0pZVKQj3iRHG;Xjc|;Dicf(7{`pZ zL2%ggw((bxNKwsq2$qP&*6TXQx%OJ%LpPh0g2B*6)aRcZB3_Lmg1nV-EV!n~tWu01 z81Hl=D(^x;S;cTV%VLyveWSb3rHv>rM`fG(x1=?@qC%)#mqb~| zC(ni61EHfBNMC#+_NBVcMe$tb*RN>c_IF%bU zgRku{ypBAsW01_|t61F5Gp3K?lw{^d8>NptxxloyF2gEu0oYeE-D}VXJ)w>HUHu`Z zo(OA=2^txZ-)@>QzgUo`Vf;G`xZ7PnMI7ru86)h$3!C6QnX8#}X~%5IqOgU$1Ef3K zI#KNKQo0n>s= z{OH>!hJCQLgSxG-AA}fpNt6@0q@)xnJw7^Ls@<`+N#pQ!tAn z>4lMLAv)OHuktuexa=i#hVX8sG{C1^)0iwKOYz%%+-1rGA44wlf^lNBU>waQu2@&w zI?k_%FI2L1;({>(f0giih0drh?m&m+6-Yi@TRJ+3BZH(Rk=cEn>uQf7Bwx<`VVie& zkb&(PaLC@*2$W)mUT3E6>p4x{PRbYhw&bJq*gw`KI>g}t9fz)ah#lzI-crdpiX2nKHZ9g`dobDo zDG9dt`aS&L67(jDq|Fq_p}G!j-QMOHpvQl0eIF|wiXd`>tlc~Y{bK1py~j^n#UN+5 z8Qy(SHo>QN{l_T?w*nShGaYcB))6q&d_c#vd{8{7?b;zGP`Zt=e^y6}LovnrKObs> zO(?0)U2lEAFz(OmqRk zvp-?`^>0@HX#QXYU;Uuc{*zVe58PJv_x_nbyvhWKBzMnD&lTu$wQuI_CB6qI>2TZV z8GE~n`n&e|)%wT7v;F?&FFnxj(p6sjg8%Fv{ab0-{WbcFpFijafBp~5&Mm?J)E{|E z?;JzFzM=iWexKW4rI%m&sK5X4A6{KaQ<7C!zqbDGAGY!HOaW!j3-!bS-`u7I*B{k0 zwcQB~0bZ+A4nVFldIJ0|v;fGrIKBrR0|{&?>GORE*tI+v6#G^bT0ZK~=@{@V?%loZ zv;|t{BW4BD4SnJbN!c7Fbnl|JBb492Huce5~AdrIrAK+7y%!JklRb)HAuT zsL)!>UCslAgioG4w!c$a7J6d5*g>?? zUweuTc;6rlosqWVo{%-0Oi$mTzj)MheeW$t{beFiyLW9H(OUl}la)?XkGAP7az6YV zYaiICLfK?G-kwp8(s0-p3a5WE>*r<4A0|V4*Zjf8WXSf$K9~B>pG_$a-)FlYWd;=d zjw6wTpP^eO-Y~jCYvAn?UGjbuIQ@ctQ3`{!)(TzJg%O-m*a!WpQcMuOnePb{&abG| z0JrM=sYd@A9YdZ8;S7m$4leZR&D!2;o2*aYRXRP|{nOnl|7-3;%QvR>6q|TL53vLU zy*($l9lqaaf7f$e-I-lhnaroY!gV=YX+e>aT z8EU_qjh3)8@&!2IwZCo_m8V>%k2{6V7vIy%so6u0wM!3(X@83Tzu&K8p!0uv_Pw;R zqK!mJzs1o%*%(*-huyH7YdpgphDR6 zr&58u7<8&Y=6md~-Xsc!w1F+{E7umpJ$+c~`DnYjPFGJ`|4TcNYHbi<4>S+kV_yQ1tQaQ zxa*|QLpU|SUZ4)IjFLn8S{6jwtX*HSwDfi8B< z{sXcKOA$4(n*}AKED#h=>(UZ zz_$)i_RZ%SRG4fqy(1ob@!h4PO0)cTcNs&pJ|%-`pu2 zr?hN(`>4>9s+%5^)VXgTK|7&$aClOIM^zdEva#UMu2P@&bfxc8*vwm77|ajXfDC1Y zA>H}2)K$My-!fPPmJ?8uCEYJH3ju9<)gfL5uCkTBac+rpVt+ba#r_`9sWPXWcG0Io z3BpcIvP^xfz3tQqQqCfD9@-aA>sUB-#q|JO8W#$6>6@yjeL$W{eVy`vu&Jt=`)pJW z8Vp<8TLFDM31?(*)g(PU%)v1`e|K*X!EEsUGWLgYDdJemlW`(XzkI$i*jsJn95S4) zkNAD9^RdHqR)yz1L5D$iqOqS6bAeh&Kd)o&Io~x+9DQ0nSR}d+NL~qe`zE2tMJovcw)S_Kt7H>q#MXDSD;Lg_12@o3$OJ*J#}bb zg`js|T<8zH%n6%#B=G5v3fF8p|8_0cf)0l_>*8%K)8N+EzL*afVVyr*Eh0KcC=lWa|Fy)=!a;F!2ffiI|+K1I!AKZ{PK2f3gGeU zqrvwtxZh7-qzzBo&FgtKH*uN?`!CaLg3@&Lb2gG&z_tj{eLj30<~SIb&Ria(0I}Q<7(ec z=c7!bj`o0rfb9wQFI@r`?0TZKCPB?m!?s7_jeP!MbPwiFg8D=clFhN{DE;V(T|ztH zbOufjK^Xf=S>bIUw-0Mw90q@fIRbjLxXzzUS5*$Ht?)X}Yd_NZoo*MbZQ1SZb50aF zZPoWlXcb&<+{b>pSKFawUO&(QG!4x+UfIzfbMO^w&1RHX+N)l!^!46sf+n5GJnx(K zN26U3&DgE)BG57D7(Ag}LPL}6v&YhA%&lf~8tCLoB*aNM$B%D~HbeSCC@SOtqC9gj zdV=ASGC#dDdI-7y&8lNc14ESg&zI*L68aW-iBMmW*9~)%=$gn3xeY{o|0*Kr_oVIAUSWb@C?Ans?81KK5i zm+B{U&&R3gzSqa7>U?cziHiEGQ&BYdE%3nzn_I1<7@bt(3X~t>KYVzrG!-ga@!$5R z*f{rOf1_*wvPV)e^D$u?_nq2LeX2HfG#>4DR%#4~(4TJ2$Bu-2%rE@ZeIfUIt!{~? z0ezDKpr!+#Zr64DmBlC2e*?wOVeV9M;tynB3+q9pI5--Nzxr%K!J_diTfeBkArx{> zdDhJS=)phg{;E=yd4h0C=nD!nDMu*koOVRRD3+wN6WWC<*Z&+>O+pWJRH16UTVoFJ z6Z~D;_D+qrJIt|JJwGN&KBoed-}g(f=UI*IT+iW8HeWjwTSsUdp$LtF&=G<{=m@$E`F-x|8l_liJ#Og-$b?dGe+PT} z()&^G!M!`x2lJ_3bRBAI+uhf`4z=~;{IVMoBD5(}I`mUIWuVes?vQ}CzIS#CgWAy) zU1#)n_szUpeXPXiSfe73EuNJzF5^b2O^Q-WD5ZqNDBhU9vok24kx-y|*hAp4HrGC!E#X;h&un9Mx5h9g z)kPYR*)#^T<5;HqHyTS^D(ry81auAstwSV$RXa?q?>+Sjxef|-tj6V1-;=LF5bKsW z8zIgnauxboqb+MeBbyUUgsN95K=`lG`}hAl`lY}7xAgPh{5yB_{Xb}9=ihudmH*|R zv`p=P8w{dfQWx**A}%`y!@X&OY;ZFuzvs{ubu4k<-Q7e&Y30 z-8L)&E0Y^7|JzTNzcy68ZA`C>mhBkztUuS4jR@R%aW%Dj;OJ35RpVui^2#Np%0~OT6WQUc z(|4Yw$77w&N7eRyy={I?$MKo$8Z%Z!}^2zU_^v*Jj#V=?;9zb}SF? z8tu(c2kK+F*C&@#qL0I2l>em+#K9J9-s7i~qukQVwhzCc(ADeG?+Fwh?rn>|T}-JC ze%5M>=rP#eJTM|Ea;Yu-onIWY7oMSZVQSpe_o5zc%PTZ2A}Q4=`(F0LO6%~wJjx>9 zTf3fIvR5V9&k(gIx=`28f}h*zrMHmIRNaE$_Iphkv2kLnASt1|GMM7Fl)dhDA=`ju zYoxq>+`D>cdXhy;`kUp)U7_Q&y*9+XFhk1p$_d)d!^uC@*66))V z^JEt{`*SF>+k^;t`23Q_$Kd~!RPW8a)yCrc;(^X@VsH1Ry}fcYK8pa9e>&JRUypD< zf!c?1I)|0sUGsY`%qfHJyfPf~7^>-~>#x4&!m~BK)W>_-PRyx#m#(;72e*G#&Z@kf z10ub{etI2a(UGrEzK%tmfc>_HasJDtsC8Rf!Gp-?vj$zS$lFc0|fML)ukFsS!qO?Jp%*)}WJ7{c7Akvt~z z&9&dF<+~k-!trBklsppT;^(A)9`h=E4$qzIk;?ZgP9<4o;_Gcc=w}J3#u1qcHtr+I zY$!a*?m~g}Rm(tKzoM-C<%Rh_8L#}R0dbFcXrPNulVMy?HTER?7|4EG8sl1As)8ol zVD8Ma2pPBD?$mA3&~m{pnSJE0_g*=FXG`U9I(6vJZLgASP#sLK)yX%ZHvQ!%Y2B{X zEQEXw#<{oIPyJ_I;Ekmk>YmHXw&{|BVq^RKI){9xY`5!$vZ6-rA9A!K+i`y^5ACx) z_vbt~T+{2AH*ns)EjcM69`G{!d*)#iC$~Ab!RsBA3>TVQFWJI~qmTVb7s~7E#=Tuc zLsEji=RzMxqh_ygU9}L)Re6*MITkz^J>qw_V3g;hU{zY<6Vo} z*wk`q3{k(w(T$=CP(h~(@&vk;#{a8jZ(iZ$on3i~Efn#}n78UiKivP}%WXM_>32V% zuhzUBk;dRtar4rClpJI?(@ecZRulqyB^5(P8D$!y!MJt4t4Vv z`??)%zSv87-B#S^r;As6)yP0clKnk%Uy>ag3-$O*Nob;*TZjf)v-+AY`}dT_Zrk|k zTgQkuKmA>=*zhPL%_)Qbe<>!@zohm`u;bEGI?VEzqrP6wL#LNM`dv9*tG+~v5f}Hh zv#s9zZI-Gh%MJSgb*MNVfGyd^5c6}c+GiUAl>DMH)PdsB^Sub)cVdhUkHa|Q$HTch2H#@-S-=R&%}TFhj^gP}#WGiir6!GHp`bG&8N@`O%`3Gh0YgTn-qqCm7{ROOVoJ90Inai#@X$ib_|rBflu-;*uCtei6Hn@@t0tvB_0 z>1Qn)?h@nIC(r^1p0gq`fhUgvM`ibGCcBG7#=@LBXYwzSgC%cF&_nhCWaMz`mB-CT z@2}b&c1{k0+X1hGpJ3wU3hu%x3HO)AY~YpNa`b|zK8=Vhkmm_hNVQ{G*0={ z(DHNCjJ06{d?J@*JT0TTYO_m}5gU5w3(<$Jw+WJqaF1sLqyLAbHuR4Mm2#GFvTu4~ zEzbeu_7v?SK5LUc>T?@5!$a*`iNhMc_z0au2Dw7dH5>n&>QhqHA~?K^uh;Boj7gf~jDR$Edt5-T5H6F$qrvv{tyf%5Lr@4bN?k4l+4&grQxYb3 zd%wdb>*Cl?9i+q*gPr5(KZQE2sVV+G$hKu0>p>dtE2og4f>TK-B*XbHB?Yh{E21Ie z-$cwt#Q>-|ZQIV2!HO)sFOwaPg>SKJ^;#2r_@J|c8mi2cHh^$~-p4jhBN%LK3Xo)& z$3cFUU}GEDu%qjD%VLv&!Y3n>-fU#+u=(oKRI~HYbwd&_pe2AfQ&2VFps6PZsQoEj zH~}eIx||$JQyVk&hA~kD3iNg#%s%M6d?G-p7Ohkuc}%@6KGthqD@5JV$Dk0>sa$Ox zPWSP65+s+|yYm`Jaiokl6qe&?lJFm?*cUU1q+yHHN! zOup&S1BHsnKq%W!t-)UF%~(I#n5Ccv^2=>xox1I};E^9#Nn<<()+W~{Rqf8TnnN~z zTB!mySx~HuuBWJ9CMbO7gV^a=Z|_dwS6jTRG;13t-Oy#W`#DRAIlWLa_X|!|yKV%X z8RKL>{8q?csgfK{81k~)9SWI3pS)j9N6IKWt#dM`2rMmtFjjv}KCcNTT06ExAHf|m z+@U8M;SU=gyp_>vS_|80 zw}ptt!v#P|t9@f!^`w;KLj#sw$IGNnnyrocyV~USgYODIsUI5P7`sN90_bU-7oaox zIrKpjnkwo@Jz+6z`c!O0>Pfs55UCh9>O%=cJvO#G7d&k9JZXLBR=cex&?1o8#4dv_ z8*8809D)B;Di@g=q#qV@Y;+7U4=XQCrl=-!FRXzI3~xsLDH37~K??Um9u7;-*_m@6 zQ0y(nTg80D&_&u$BGt*o9YNM z$#)7Q2lxa=%|YZR#8sBfprIw!Gody%CTjTOl_-TZx_#BnW^9nE2hOdtJB81HIkCh; z?hN#HWnYgdZ0SzhhwU2g2Nf=jSMh!nU06aM6$`oMK1%C8YK2QAHr#mOi5BYf2qRHc z#Jbd}Z@yBe{#io!MhDOhuNz^^d$Ats?RA~gq`AfVrD|-=)_6u8I3ba)nq4gv@hN@Q zZGN$K6-RYp;APgdz6rUx0fv5f{7ZDCS?oyP=?EK@h9noFJ_YDMu^U*d3)0-~P_S+* zL4DHKVg)v?gdBQu8z@rPY=%f$WU>d^0~`;)-}kz7UjsHr`X$zP5E^fE#h-=$rSuB! zUxZJ~HlJbSPClkmY^{259{viY4mkGI5Z;K6@S#0}4VFR^^t^^EqXRL30zH8m4vahdY z?uOs##AYUw8gs-1o%5Nb$HOpR%D4_w$Kfxx#oCyW45N+W!x#7U+Mqk37RBd*PGz7! zrWP?9m9sj3ZeJ<%QvEBSNHo{yTm+5q!)-TlO9jV(6dbM;!AuYDT zM@+PU~)k`0< zI;LP(8G3F)q415p4o;<#4<7j;JGE6u$~pzJ)1UftKr{3hOZ3Z< zJl$sYXQ~9+7y>mOM{F&qAeybY4SlR(zo`r{C_z>+bV=%Mz}%&D2reTZN7B#OvoneY z_zgSk<@lf^>gzv@*`TT;8j#m?zN;PimVnM&eOkp@(`X?HF{Lzrl~f;}gUEHo`YTKe zn&S`o(#PqFFl@t~D8jt!{De50`dbzI;CK)JHRAVwivBl0D)a|X_VwRX=&wH5??3oa zujpxY()K_ZJFffkPv2Sjf5R`$^zmq)A6C2a@X0>^r~lx@zxfFbSmiw9p0b;R-fO;M zKkC5$@==gcO!KbE&VtD*MBZ37?jGvk#BsW76npz z0=`_QaFQq{+WC{fK$o;&^8cliwR69Ja121NpwKAqiqSEIy|kAt$6n9*VO8!YyW#MC zfnc_KGF61qD;zd$y4XkO3)2drF$USCL(Bi0fDaH`)2;WCv*f{e;Mne zR0TpSu(oagb8_}1ts^z(NVG!VJYx|qQPu1PUifT{c@ z%=4}fa3FlsPwN8|-u)X=J=eFnfh$IrVALe54DO541W0>CPn;%0pwp`(EHY6~mcit- zwmnl?I6a5--A>wdW%mm0fK!lErBR^nj5&Pe2ZQfdnY?9CTCDo@SUMd8+XjM^sgxE6 zdzb3)v&)lJU-fqAjOywm?vXkud_CIGdi(bGth5>Oz%29!sE<%a6i{g}IKtV>M-q_g zyhD~-zG_Nc;4)d9CZ-zSrt*L-)HhuZoi0IXCXR51TCao1aCflatM<_m!7GJs zvtoAljdnM19G;53m#j?{?OK$w!IWEcV>-CQ2q&VLteQ{qp1>ixD0O==2WR)TDWydy z7HqE8{~2dr1SRvuWFoW)3g1uE`RBP)JTyh#@2r_TnTwST)9^@G-j2lMCdpHL!j z@TgNH7YG$Vg`C@CF?fHS7Z&|o-u)t!4UaYIu-i4juEIH9U%h*?=={iN`*Nz?L!w?E zbNo%m$?0cYW=!Ew>fCqTETN-{t=>g{X_5jQ`~CM$t8OirT6@6f^r_QHICVnwQt^%G z-d{(df%t5J#QoPtI=q?tIvw-2(lI>sV5vVYlnpM!`Qt9eo08=s1u}RTDC8IO5c>6l@7q#F8#Bde1oFv7dyp?$ocD|Ldn?!7>kYcT?%=oI&)Nd z3u{UV{St}?r@&}06QJLu?_OvUoxxh$=O(Qe7N+8WyPW4wd!@UO-w*dk)ggjzd-903 z!|iOkR7Vgjkuh;8sYjsitbe7PX}kv+3kn?vK1PMg^NslZVze4)hf^w?gx+Go>Uknd zqLKR*1`vU?((TpDH#Z7Vr#9`~i^AM#Fzls9fHSBZL`@UzLKW(Y>p7`mAKR{X`4X5r zf~*w+aou$KM*NqZSQqGCUtu0rKh za}dSG-z?CnM>x*v&%o{peD2O{vQRuMfFTu2;Z|?=)q_zQl$4M_MqSaKuWTsqPSL-5 zuh0op$n*JN!2l102+X5UGYAERlp_=m(531A$!Idjb+~r9V1uu5-;fuW($Xr0@kiIU zB+vbu$Dq1U`ia@-59D3(`P%M9sWC_)^mAf@24xT|>l<-B2usqB$GzTj5(rNO@TQw5 zh4h!tkKesfS!sOv{_{GYK1a~4Q(H~KE!7Tk5Z3e^D*6s=_n#Jwgutz|Ulwmty<7eR3g_{sfyrE}2h<*<(1 z9oAf*R+$^Tvp~x_gtE|2DAkJS;nQ_Md29UzHrWG_zS*Gk0VW5Nano_5kfJF`S!143 zr{|kB*mLL!D!xH~yXxVgL%zFT(S*LC8U0EXIQQKgi2#;RRjgeyuB1rO+!^FF99%32rgugE+v-M{k9leNFv5s>2oXARhS1%35c ziqwmZUTV62S~ogO50}T=fK{K?I&(dx#00zK9)Y)xfG_^1V1g=(Q-BYy{o z`(}v-OR*#+`OHyEmM6flOW$2TD%`rOWY`%0o5z#tB3q%OAiRr_ZlL`D7=PUy_I{c> zeSpd(+3470lX^BJj_prT|3#qCAuFL>2x@>*0#ZH_2O{|a^*-LwEL0J$x5W~{6m}jI z6*)nR+fB%G+CQ%HJdLO`@^9^PX^RH(>r2om2(&&eSGLAnpXCOqe4#7Bd*|bub>1}l zvp;-L-OG8a3YqSS8(JumO=Lss<$l%AYg=PZPo{6G^v4+cO(+-IK6Ew`qf}u_j2qo- zKxDW?){VW#UcSzEla;^BH_>LIr&3i z{wL6aNT0jTpPs&2`}D?MyZ;#WWU40f7D;^YwX`nP>bKmj-(T4{y?MWuZ;-+~uX=mB zBIl=f`H<+o%Y3<4-PYu$!)N=>>o4VNGWwN{arOC?_6M{b>zJy%H&qZSldI>m`KSrC z#QMDU($&K%qhpBE|QG+u~Fd#pa{QsTm6VQ86)l$U)_NSo>&~sDd#s?2KYSe}@fMQOUdm>ZnC*EXML) zY<`)sfh}{zqQ5UrOCf8J=@gq_yv`lVsz2(D9*#;+r7H8(ZD|1=lG-})k#;r*2}OX= zJg9v3bBqA>(~HGeqgJqtD~+HrEbgT+n|dQ zUqh*m(2ooXMWJ&Fs-sA@V|H_&wY<1pvUQEp76=^}=mwOc3LEiCaUEI<<=Ol?LMKgcxJ( zK3nNN%<=+S3hC1~wqDR8UJhEWPWQ4d!(6J!T_F@~nqVpISlc%pFh@Tfj3$AK^~>I7 zTUnPWg{OQje(b&*L$@(!7ZPDy2W34av;qzEa~f}$cqqW-oT(X#DJ*sLP1Mbk*+qBu z=DTa#>&v=YlhZ+! zD3`=f7;)J4ViW@!(~>ojB=A=}N8Z#Gq2#Lky*hP#fvoAUh zoKjh1&rB&>+V*Chk_PhBL}22!}_<-yz06b zV$5rTSmVF!B9?Mk<7xF4nM|c8*I2w5<76#wLA!QJIciB1$Ipk*hy8umVFuNy9}sTG ze~kY8|MmYvr*-4(2M;E&zqH2l-uzyB_US)3(an=R`@`Q@N_jotrp5o-4|nv-Kd@(i z@#Y6~s7CQX4}Z1%%Afq8_xvk=Y>gYRZN~qN_aDpe`$yCU^1t=ReqsH;(LEIYt@3yL zKi});LLqUryD5L@|Mnly@8VVIXMKJ3E5D52m}nzd{lq8W^#8N#!pHfvV!p?-r+%7? zd)iFe0PvcmLN<$m*I-%Cu5$iHqCyud9^ccLlyFwgrg163q2J4|)Ww~lZLQARQ?|u! zZ{c1ZZSKvV*_w(sGV3><$&%^35dHk8l=<29pR=!W(|G(W$Eo`XH`8zbIXs(^3r4-l zqAN*v`>E=9E@gT3DsuFlqaVcS8y?@EPvv~syish_4JUb08@6QxG!~!NZ~M+cc9&|h zk@e=+&1WbfnK4d*w~{mA=#^Ko=j)*XoHLEfSsk_|mG1vWf-9#?LBGGEK9+gkWmr?8 zycxo|-2=2Rzt*M_-=8-W_Oe`(Ib_CXC8Y$8UK`J%&p-V=(2``=c_@$lf16J2XUSgI z&NF>-$80mEaPwI2jeeH;x@F)?&-t@0wW*|&)ffZmV8Kw}Yr5zg>O)^+mFAmdl$^-a z3m2!8EXACZkT2hK-iQQ6^|}tCY@-fPQkUj!4n4j+T6AnsD%u(`^%4cHs`rwiU`#Hn*m6*bm$gPr9wUCD_|UCxZ(Ip*7LKygnyf5EUgDwGzB7`L@QHdJf#cj&xl&2OHI zJ*o6apbGF-Kf5jsuV9>aFBb}Xy=JsMML2YxqiPhUj0Vy+aF-csm4-H;=yZFHwI_Uf zBwR>pm2ZeK@t^~J+tdp|J2nDR7~=Z^bxrjuTRA8a z9?H&w7z`#|zF=Dmehx_quibyCzc=#nd$;|z{oQZ(t=d4lNX67Mn=+1Te$AI4vw>~y z_1V@lY=yVW?Rur4X-N$tPk{43^Hp>C<7;{%jQ_8=y(vVMFSzn!AG@8)pLqo`{YKfy zD(g+%tlbJWFM14$2ec^8y6I!7bZn1WlU+(S-rqfbkcNF0%0)fnexLV?|Bn03|GcbM z!}P&+8s3u%^6PS(p~ic=&2-N6JkB#!hRgg69{z%jHzc0^9C_W+ZChWGvm7?x^rC^1 zlVr+xC*_}XUM+V&%4_q}rarD`CHZoLPHu@KL zw*Gg}C63J-p4I+N?;8E&jck%X=il&V!y?>}Ud(W1#OShF8>?v3c=FBn?3Z!Lv^Dhl z)n56$z(r%!J+Cc_>mz@0$1a<)>hTor{{UM+q`z|Ri24e--(4?hoVN!(s{OO=u*hqU zEZlY>k#2Zq<5$W&WB%x6oxD{4(G;WzX``R&V#1t69Glxwx6O5i0;`qr?|R+NDmJ|E za{$7RcKJMoYFiS1 zvUlMN@&kSQAOG|8Uxw@U=^Of8zII>y_V7^Rl>5_P+3~;gtMD^8xEo#IG`l=!cmt0&Yf_N&SrML@UG2k{Y0a|4s(}7*%+)sY4WG; zY%CYr;`bNEL_$ECa0CesXEA6&3U%6rROd=teX#EoZNO!qeUvKUdaUo|&`n;DG01MD zFj3X+P;t^9|GmP%>(w|pfxdZEV)dbahx|C+Q4owCY%D|mwFi?z$%#_CN(>L}_oZC$ z-Y6(e&Ne8pc=b9sqCv=r`tM%N-2b*wY{ow(|JwVj6dK{1Qb51pUd?3mzOG4U3Q!49 zFQ+ZA_y!s+{dFoHa|+#3Si;)Jo$4ZNL-b*co2XoDu1rY|+Q#+fnMyzLEAQ|O!qQQ7 zeKWzK(kceELd`o`%0xXIbkkR!G@xgO0ZmuC8M1rYrgJF`2v-Sr{t9RdAah4m@C}}+ zz zu>n{pQ9_J!pTX>+&uX$O5I=j<@k=fzl8`EO=h$VuUdvR%5a>8-!1tAM4hsQ zd)pPY!(s6!9#*EgU&7`S_xT@Aw%4}-Dp~c%Vd&%rPpm0@oRo0t%7CQMJY$(Q?OsX` z^03XPHr84f$JDytRZ<&qLRe2(IIKTp9?F@iVSrH3((l^tU6-Y`AsJM_cB;(cWIm0T z>ttFWYEXBnH1ZLaD6^uE9L~^!AFP6T+9J&^N$yHXrj7#v0SGxf`+| zvs=8g)1Dmf_qsH|vZBt#s&KwHp~1AJ;0_XIdLZ_C=v=!_zP{Tw~%c~n23B)cV)5D&#ptDG}6S0Og&SKTVDGUfm;puRqkf(Py|=j*C!4w81YaF=%r^v#MurFE z6r=O%_DyBbY)I?aS;o0}fRZ=Ej2KLJCKg$-p{(lLDQ+v5RCmX$72TgIKIXw6<<`f0 zY|LTp-{7C=rsL92#rLs>$%Q#kCQJr364{(?KxN2`jXdj@B~b9}{lymW;g^k+5O}XV z1K-d3A8|#lu|cSw0PNq~sBf@M)|Sxk;{N#(<>YPk+!u06t^lrb=rLJ;EtrFwk16+a zY$WLa9Z8@Xol5vlu-hf%Ukpw;HdH_>gVG`{ql1BCZu(Ez`@5g(mMuRF8dYW62QY0)ap(F@#;YQ*D z2!cfEmWZ9$X_EjkAjffR$7$Pr`s}^mkLPQxIjb_q7{BpRbFFuuPGq}nDf66nKWoi3 zXMKzsHEPtTs^8CkAD(R$W3bhCh5Wmw<34Cf8=|t`q3tWk7RXbPAXYS+EheI@hBj4- z%kJXTVN<==4d7`!B5eGy^s3s8UbEe%aE-D0puU@qiay2@jT;xR@Nb9^bJvCa3cIl4 z8Semy7dA`BZ8F<%b+}n)bTOfjqhnODc{z6>R-6YeLnA0AuGA@vnk0ro%V8WbAY_39i zt2~RwpgQ@&KVP%i?9IpIlgvwl-&I=Dx9E9OY6G=ByNa?ta`~)J^@w#0bKtIii!_gn zna%GD)eUqkrdyz-5t&S6OHqpzMimQbyP;^9O8kSH{e3 zY_)~#$^0L3^GN1%iQAIUnS9et)$xwogWF{S`dpaw)b1_xub2;vQ`7G2qg{kX%;HFV zsu=ycW1huN2^|<2^(N3g>>c*wZ5~c$my&&ee^OB%L$AYw@N-fOiEJ^kkVEaKy7`FV zb75@YC0#dh`cYB&Hq4RKiol)Ra&Z=WM_yf*$>gU zy^@=l&)=n6aJHfLt) zfg*v1mJV@XE`#}+(^T1f7VRdKh-mLjqhNDstGCcf0wKaF7D4^6vi`2w7MTnoSErBA zzEdIJ)u|MdpHJH7Y`Ut$Vs{!frH|;6udiv6Kj=cU;odhM^n$5sasRwe(cVrsu!f!J zYjN}n%c7x8hK(5L+d9`xW3l;WJYOD=1#0Ah_=r-}XP`fVvL{mf= zjg@M_N|FsB-2$2t=@XET$YnKO3p!~cv^9mMb1UBr1VjecZ~PS$Udc8caPjz|rC&l$ zpyhQ6U@1$8l9oS1-~C^HM7LXj<^1hT&rjO!zxVI2^jG1}Pd_!&4G?&L<(IaR=MP}# z>OZ@cR_#geKdkiUenaOEf9cyo-~J`7*YEo;Z*n{7=lOoKR?xkF?SE8hKI!xSzn96k zO#C~X7$_c&$J&4Wmx1iI>HP2ehcD5-{x)Cb`+nRC_ip>6aAbXXyD7XvK+HFPwfv)h z{9C#GCx2<8-}{vc@6%uC^rN5F%Kg%>=#onB|3^Cg_`fL-@Z59Bg%qA&;MqF?!d>rY$^eS7LY^--@YB~wCqJ`*{($p2yhi$bRq_S?NM9#}wuoA8 zl>R{6%$wrnQOX9CKSAzSqipEN_e2*>AruUFh9}p3&BigMiO+|xMmhtA(U-N$MCGs~ z+5%a;H!HPtH&BPSL33c2#C#vSL-+SIPiE=>qkhO62O{6TwR`b)VR{D(=Fe}G@*z(~ zEUw_+*Y2?ITrXh=DVmN^9?Wl4?)x_V(slZA=t-u-nBSs2w4;Q0uhas1pJ@W%&?Vht z>IRiVr}Kw+w}(%zZG?1$t^)1O)CaaeI=?2Tcn~Utmy+aSi&){v9Nu^DN5a{-sIm$T z$BT6U@z%5*5c7{svuXKXGJIDM_NP?wyeSUxraqEkhhqPuqFp(tg; z3V3#*fB@~n@$0tfmJW_aJtP)CK5)<@1vIe|WP-14cn zDE}JsXQ4$n$0G53P)FAmvbZv=FU-?41d}BF>V0;4!*mHd$GGDewo&Y89D2|h+>p>K z$Tw~maE&qad&S0|(|1g@P@_%EcHJmN0)G|fVpe&zOsUgK@LJzCq4{8)Sfo5~Y<&^6 z2vq+B*nD@e*)B0PgW07eZ74Z}u1;^ZFyF048JCZf&@Gq_G%Ns>+b-pnbk5y%cPZY?+IO18>r_Hfi zpLA84sUKK&`^lKd%IE-?c3@Snn)4C)7o|&B;fRSNh(0Bh4Yw`Nc)Tr)&(P(!3+%9t zbX&MMm*9Q9UxZ?TujShVogb!)nC#70Cz@U_>TtHO)eO1Mlnj=p>CDa7+w+`mf&E9h zPTw82@k^9uv)gB(Qjly+Z~5~}m5EOXtb2gd+EnR3G*Q+a4NR%t34D6fz(&W01l!ak zlkc^7th45YIOJjFC_dSVjS1_+ENv!DeyT!R^Uwg?kE%voagZ!(?BfVhI#XpofydzwbO6i3WS`<|Lu#EgR@^AXP-34J_%nC zBoBD_CKA@M*XPX^D7?Q=Z5XusqKoi8_Gt8C)1QEvm-c93pKRg!{ArWVGobaS77Mm5 z52oa4+XRs`3vZ+O%?UcwK@XvDaP48!t25xW-vY{#=l#!7_w_*`5deXNM0?Gz z&f0dJeYRIyn*o}8d#1x)F%+skQ_!a1D>k_JLg4L$t-e`QSNk~b3|E6JiuMJEcIR{e zC%Zf-OrNBA7qFt7zd_b_P;ywlWHcF%QFn%3G#FBWG)!%0`hWiJPUsL=wud)Nd7#ew z$`FzR{R(6#@O@y?#AI9B{dQ}2=;T)q0_mv!Lv`*)yt_ZZf=Anuhbk z-DWEeQg@+LxI4Mra%F9k$nx<{XcY)DF9@V%wVdu=1WuN(m+$VWKi-H8k8eM3{aoR! zIk;eB2XB(jaeixwk#j9RZLOwN+~KB zMz&-JA&|U)13yO;NawD+D;#Y9_8S+~c=s5y9}b}QU}$KT8*l!72K+Uft5+XsW=L=>LQvkN-)#6D z4YqMg3pK-8Vf=X^$F{-sdD90{*xUKIr9J6b%rM|>L7+<@ zm*Kbx@cRtY-s_?#fM4EHdA`+@m@WY{1j^sseZ#KVKJF_M7HNg9f|eBAM4r!YwsNX| z&UYWSHhNb3|ME(q$YqR%K5$)bK%dde;OiPif|O^{;0OoLm*-d8)M3`(?pEH?S4>5f zdc$<80e=5*`yKU7j&H{b)~{aTWVmfi$eR(KzS`=hWta}EpBh}~%hoSCD2IC5 z9Dn?7Yu|_Ap4x#)$4hp-7KMwKeI1k5OCiVK1^oWuyDcpZxGk+w@z5zEUKTS ze*ztsYyy6@*^?XD2vX|Sk3bvI`>onfPT~lf26h#;d?x7O`S~ktZ?p8W>a4IGeLoIkOp&TZ}S~NeoMZcUuk=hFRXL1+ z;D1aHu^5belt)Hy+pLuR_DzNpC>UybMs!RTHH-8)PP3Daj}<-KqP*h&eB5NRjmuo1`OW9e{{y|+$2WSPc&@$q z)pxc)qMLNsCQNb=NAnW;Ev_4f5s%uBB<8^Sg$*MuQFpPA$8#i!J>Hyx=8t_3jyG|C zWhaIh$9wU^LuV`?8?*&7A7L5@Y<_7@rFYakI(&ZgUoLmC!xsnG zVZJvRrQ>LWnF5dfaGm2kDK(&siIe;M>*rhbl?%hjFA{$gzi63V=Y+n*=8Q7tYhReR zIl^T0c8{A6^{fj8Jn!Hbi2HpK+bsf1?{f`AUqpt8Rqi)ZwTBy-`|&mNybY8G=Z|+X zmzFp`x0UE&)Scxd`koG=`S{3l!`b?2%zeb{_2S5QA;TN71)S3SF6jJhuAzz2cv(8s zy;xk{)#n#sPj0?`khqED_37yIcc)7bIUF~i@8HSn*^i=|=}RA)UHazhQ_PX2I9WEh zr>pwv!&{tjOL+Ns^!d0xuaSXiPBEF=)nh(QC|oh`dk&fj>JEA zlbXaCC!oh{`3Q|BZ^rm|3?10wa?{P~=_n1cmO&3mY`U?@xZ0*B?sv6EEE0Wle!Kan zcMIm&l$Q@xHiY<@9U5D!>%?kp*GOWFPGj0^^LUXMWIq`Np2g}pE`3nhVuNBn3~Gpm zlcb69?#Fi;ee%F|J(ml`prN6ofd)p?IY&Y&(HLXv8=YYbig9t zvVOajQ+>SC^s)Tnza;)!|2O{F&uv{_DHxvmZ*2FKg5lSH`KPwIE2tb!AL+KMzxO!( zG#v={U;aAL8=-ih=jEsA2KSduxRVX+haZz5)>26qte7=;gt^9rI`Q3F<-|xw1kbl{i z7isl!@qRJ+ds#0Fb+v8#?n`>FpqlY>@%y<>`%`?^(@r;kg1q*R4}UesF|C}W{WR=E zPyg(DAN2*v*tj3b_uZ?dr$wW&BIOHZ$Z{{cMS(G$LZc$)#QT`8rD=6Zi&=gOsfT*^ ztQvW7@`_H_PD=ef6_&_cprH<44CJwgI=+6{NS^AH^3!vFcG?m7v?w3p_gs2|ato;5 zsISQ+%4Y3tiASlL-QTHd-X$)j*9Zj`w|4rbPmZ^CJ6!blXqna#`fEu-h- zb%L?$%L00C({SpMseCY7yVlQPcL1)`o&lq>G&e8wMmlU&|m#Oao^#W z$EUnMnq;nzgZn|oI!&ZeB1sy@m9c|lh+ruf7UKPCW4`5z{9*#O)88a~Ju%(gB2k_o zBR7L73MS3mmqE8PgiD3{}R+Bjw9mEhq% zky0-8MKUWFE3@94aH->nZwP~;V{1?igFI*-pHA5wXlxiiWWKM~qvQ^FvC z2D35P$t#z&UmO0Ypc>LK+e6{A+II`C*a^~Wkh-#%g#K=JtES&&fdh4nlfRLN`)aoH zio#Y6Rp~psNGYvGgRyYEE9ocv+l$QK^1LljM!sOMryDTe+zt`vdqkr?*YvH~za*!z z5YZlkZf4u%il+u&if9QZA1|lt`nB&09rmtKLWJv1IZ(s3paGsW#2aOGe=#VRTFTsX ztNLp5_?s7ql3!g>hG9_7odUo0p2RcKR458wzZj40uz*pMC_)F$Qfa)))&ck!L-_}dR8TxHEB3<2NcV!HzqYN`0P^V;@QXd_ye;=|!BWd5h zGB*rpKAN!E_Yr`^1qGTx5!ZWQZ}=sq|DvrCzghUoJ1DHum55K45Oo zj!ajyj@fMrol*3x?sEkbS4q!O`=Xl0)pzAiZPKpD-TI3?Z}!$+?>&%D44U20P(+Vl z$GzTZtkKWisgb@^b~Pgfn+xcRm)NeF>M8#sf6K@0oY&>Tr-4_#8zc9dy1ic{#VKVs z9``FTmVT)Vigq-%!!R~jIZGe%HheBG*52eJZ5u=PGOj!SF_-ZkAocbUVdi?s@i9H#1x`j&)Y{GWvC8@8j*mf!Sa9LX2iCoRyh; zI(qF1(aW}fi^j1ReI(|bJ2`Mu40G9iyJ@3mS;jBs@2mW5o}3Gm`=y}Olt44I1$v8P z3KJ2snxiYW&?OaOhqo3Eu{}X-uGrviHFI;u` zNWTNuRVs)6!yl^1KmFwx(ckcqd+g#pthAJE@^?MGAp^`GY* zw*Q!X=aaqCS+GzgR|)+d;4^CaPI>SO<0YqFoRf`4Xx%*crvaj6y-mScDc3bS(|6y| zDS0r<@YxcGB7%c(Imo4$a0JH6SiX=$r`>9m{wXmy*x1l1IbA55%b~{mBJ(NI$$5J@ zTQc4$efgXn=w{`JQ_8l`<>k+t3Tl>L{O0gHRwn9$Q`2cIPt>Q3X{IY1qWpk2Wr>A{ zT(&W|Q^N4&AT#UZ3~hma`W)z-7U~Vo+71Iz$d{rDdJkS(arhYW!N8$rRtgBB?Bx}r zva65X^99#J<06s0+wDJSH$3G%f@V?P#CRT4*bZZ+7v@ z0foTytD-(>wqXmodWx2ELn|Cz(|Z!ku)$Z5Q;O@I542UG`1ObGr=w*YjL4$voK3zp z7(gZzGI)Q2yrv|dmioTswTFtc1#5%dU|GcZT#nA8uP~nzUy7Vr`e(S0=AfAJr4+>#CE7vgS7HktxOEnYLL7 z$v$?-&=#l_E~!J8k^_aSry|PzSz+iLO~>vR%RkU$s3-fpj%FusWatiq9%YvNQXgz~ zDte_Tujw(%r1yY}FM%3UvJMXUhfeyUttf3D=n|qZCX2K&Bq_BuT*a-F}p??xo@t5F;@tO+oElLZGkEy zm*A3`%^aV#wq_l)=2FN``%)s%uhAzBxB)Rkp~i|YCi!F^dF2H?A4W>57i@|>XS>@) zhm-HoOQ^8vc}ubtyL#5X+)LbdGjU}SH2Jd>6G}j#`*_q*Pfo)0Dos3CZf(XWu>iKu z0=qTAxi4zlq{utnz%Z?8S^9~NLe!p5yYjsxH`fWke*3s;zlm;C9hbOVhCH%cWX+Q< zgOKbvA#QD9G7wt~Dg-&5T2OZcYoydQ@=*DAU#JnFgQ<}rHfw>%C-0K>Qqf%4bWq6` zUbL+X;@Xvb=epD*#&#m;nKd>x-b*{0{kQU2-}W)8JJmur+9T=TyBVl0@^ZZ(H)+c> zmIXv=cO6q3R|XqyzKiLFm;nAfW2K7RiF5qOPFM5h*v&pcJjJ`ZETdNaJ|Z6uPz>9RkJ%r|n8QAe$vP4+OrH zK1EMxG~cY$<-TpysTd!*+%3t3zpTroMPVI*-y(dZOZ4Rx)DL!wLG16OIzZ_ExlcuO zvkdhtfIHR(Li@@%tjG?BxzRWZ(@5_FpQI#tSUuJK>|#qq-_FrD*QvWtc|c&+^eZ8w zr%N+w$p_)z^HkWVtQ1Doj3fb}rhtr$YR0sZ4SPzyVw zNvZsM_R)HbW$rFmFbid>Hb#)@ive~5`n z4kTHB*Lj9HTWuJBhsAs~YFld4MewRa|5rPyC^Hk-A5xg;f(^(^3kmu{mB5{15u&Lb zR@#W>a+2>*3|052*HxbTp89XMO*QJ14GYm2_tYk#T^Yhx5FDCA7u&OT+AZMA`Q@s> zEU2gGHo;B+3BqJ%(8#@N+7j5tsXs*>Xf7LrjxSBoOw=A?Z0Qgpsl5_(7L=iC=b=35 zHHg+SSF#0B{tv%n=puH%QN0XkCYh^VK!>HzH1Z8?CPVFEz5r>P%MRHlmF{V-qFERN zi_-PUxT=v`Z_{c%dfM0_y}lP(FrrU(N9WM@B&P%c`c2>4yzF`wa zUDR*dze~^p)#ZC|Uu=+ujMtb!Au}W&f{& z!g1nLg=j|^-_v-I7bZm?JAB|3dctA>LW0&~8196Y#onE&%6l=#625jptbN4lVZi%bjJ(|l6tZ%v`#XVJq+Z93*0GG_Pa zyK-v1B%A8ww2#}kul63c zp~t55((g8mU-9YIqJF5BS<7c-aB7UGiO&rc!7>o948orvXIt8sKo4QGAcJ1z zwyWXiTW1s5bf9dcu~qs_F}ZY~V3@9|UL^g`GIYNqd?OTU2Mu5;G#^L=<}HFoe8Ho>PnLi+zN=W6Wha$zlH$bE=gPuPNS79U>m}ZWt#m?yhmes#HK` z*9;|rqTM_CYR6}jo!1K=hJHlR?55{vc--%TX3~+eZ>-_D31rLdKKUHF(?FYH^0$86 zh=#@XR@}FCbPmV}V7A(SeZx!4r%Errmgk!ryS9G;OuwQncAvYtdJ)CkZ7DYSh+?7b z7@5WAJSLN#VhAbrqWu)ltl_Qwg2=; zv!;1^(LOxH7J4oRkx?JnyTvAg=)a0o9`Cvvml6?*WiNlWUnR*S-q#ISJ=R?CXvbd} zt5)bd>gStYY7t~f-3VTQwuv;B;&gd7XUr<6)PayG(1m<$3hgX&Ow+|w4MrHVM|)~7 zzC_N2!dWgfti=rr3U>AJV>OD3^oXY29W`4jWAQ*4V9_rSUxjElf9S{b`TzT`C_?WW z^Yhy_W@(`wHklocdVhM{WDR7!H>XD5Advd$7)Ii_`E*C+t)ov*-67H z7!Gu+><@CQs`+8qd0P|{Y5>6UPY#V<67>MXhceuTKC>*bfWuJy#~)~?Tt96Bf6?@X z3v$-aYoKXrwXHbS2DAdsc9d}8$qsqvFp3AJDX4StXPj~Z@N!x>5_o#)1hP1ya${$z zB0|WWxPm@lJ?T4qasHf#jQ@pVkyHA351AyMdBS+ha+qyCbc%NmZ^)?!)BkD+&j}p9W*8tVXDrOtI})gU@vqKFRxA_aYR3%Q7A&ni zYo?#ze}_~aqcBM2F-i`(*CHKlFUvP{km)3h0)r_PR)>)%X_wgKp>-75zTH_YQhErc zM&QR-2j};Y=Yz^q1|^2Cc{ImiyN7l!YlpE${nT#K{A&AquW7z~v!wvqh5Ab6SrFLN zMD(+@FP(^NQp7YALUX|KVXB3X=E0FKCYNJ(N{*r^XzFAonhBvrF#G$&`l>GN@qHKQ zf7^}LK_?+Fe`fDfyR(kluGi(Ux?>^bJZQmecdoUq1vR2?+O9#^kmrtP3hgJN9*_}Q z1YOwSer(hd^={MM*O-WH9cOvg0rvc{O1tt?w?AlGn__@XB)*~;-6gH;*@WoIm8sT)Mfy&+xVCP|Wwu?c2*Y zTU~w&PG}0g)EjMk(a%%yG}o6&0?)RI#5a{NnqG3?xV}O0py@83w*l=z74!JjBI%2R zbBt4Y?K4Ax*R*lQ8;fiP$&Negtpw{gJvqN1FtgRm?C^BAIk~U3P0l}_g?2*hiP3hH zaHM!`5xEcwg;nb^-!-K(Fdf9d`l<-=$22s?gdF5chOR%p9Ro;dY)c)!qb3g}mnUSnIdO@2VNaQ-kmbbof< z4qu<>_*17bzS&i0jZVcD?PQ97r)8G5`?LkEJ^}{q?OCWt_=nSP9m!}g#KAu7qrWjG ziQaDI5-JuEVuvG7vOri7?U4m(Y>H_gW?!&9aK2wHm`q0w>*LlJpw9xOZ`2rXQh0Wy zw4j=tRPSp=CSyL2so|(DTbW&+OYu!8+=l#)1dVF6DN|jv7aj2D_vcN26lU$^o1;L7 zarq~vR50q3IgWO%n~Yb$iwT^VEnL4|1cI!KO`ecGN=QK=qc*SBpv5wT&T$NJ%@fm`D7^jWAey7^4_Z$25#lbHrSK$erq zN;jIQE^@ouIKs3PKBuwS4441okxt+2qXJCp^Q1Hto2*U@*$3#z!qgK6k$1Pa@5ef0 z)B{Xa!Ekt-(fOMjrC8wm?-sgwbw-e}_`)BLQ5-Ok{S;?~GnF4#aw?58m*E+8sBPRt z1iLM?1?YoEzzzDznr8LAq4ubd%k?}tH=vvhW6A_s=uMo8B@jsjnh(B8`+V?v$-Ic3O~!hff;02gY3pBVtR=W|Sk;bg&zzQ^nPAI%kC{^+jpqar$^C^*@XJpX7a+6Mfj6VL(lC z*~(D^;44!Jyv*8<)6}cpp6CGjgM}$7PDdGH=C_ZVt|(kB!-N`qrES#X`YlX3@uIN0 zOmT2{_Y~0CDH{(~G4$xO^3n-SO>M(-^7%&TIXH)x5BCn&Kfit0Y}{x0yngpes3W8* zTf5JPMb`zNZs^32rtF8@y$D@_7DC~JbyWfkR^Tlw-49N`;Xou(& zi0M~w5MS8N&$@ukbQK4sbYZ#*lcB)tayw$-%5?g0&+(E6bvHl0-TL62`v3Ey<1yFo z^F5uKO-jkxQC$|D) zeycH=A`}A$Y($EMZX}__+4O`Vy9KWQZVL*`I-z9vafZNF89ph^K_%ZDf*FP{Ixbtg z6~I0-{IkHqbDCQjS1bf~z#~h$FUL&{CS4d}7;FyKaZ@r42Y4}=YC&MQIi~~blFFGO z#2Yqx(CxFs)VC+436MT|^C%Gc+Uk^|;R@}pC(+})U~8Lg1YI4&mY3;Cb>R8k)}}|% z8HRGNeNr7@Dgdq{+mpMk9|O9dwlXiLBD+so@sb_#pG zc3p^N@+~mN6coT6 zZ@Re@1g9EwpZ!(d%)+|Cn-W+aD=0ZYgCf@wy^-OQCxMRN-hF;6F#fV&b^KN-A;f9h zexJ3Dl1{sm=cl^$*CT?-%Ui&?7ugiTHjz!O(1M&S=o5=tLN8*QU$$} zzD#|vE;m@fyCvTm$W$9_{gF8I%_pUhVf$QSquhM^ zS!}dC&-M=+sV1H~SD>eCal)mnGTyUo<9cx&mS@!^nHO#LlaE@6&797@FNf#A6YTS23huT)-?Z0i;d`XIzQ~{vvsCz0-X>q5HZz)EYwj+ zn@Uu zb+_`cePA^{eZ2M6Ql>m8Q_R79bNC#7tk8uRRQ?42&1}2PYbs&EoIpvY&rGkLd;{Bg z^DE9z>L+tsaJ%XvV@(U}Mg5ARi}JRyNNlDp^qzHYDA(IhS_koaR~h0;UM%bp+Yz%g zVUj76)r0zH(mo7z{w#fb-F)ho2c;&N-x{sMI8l*2R;4IAFf|GL0o?bk?>3%m-}xMx z>8^aLx3wqNP4+o#^P~v}YL~(~V{?ho4d_^XH4Pcqk%DazwEP675`+6-e{-q3vdwABlB-^0yi4SB*1pA}+7gslWyUbO z&H32>XD?W&LXPG;%}(vX)GEg}N*Bep(6mb^AVJH>u?UN!jGHp5#1mv==kx4iv7HV& zNYTU3N*!WtXw=$jUpnRjDm3xYQ+$kd$td|+tA2oi<9SSNBJ(w-@c{)MrD1^MwH%9( zH09K%u2-Bs1ntGm4<70E-HYhk%QtuQ%ru#eq)iH_JxW@%zvY;Q$2o(Rg6T4hc7fww zEBY*#&1gouP*GHk4bBl0G4lgcnjG}xtO zTfFOV3p!v~r`Y0PpP>U=%>vG^ZJzc{%Y6Gc=nwv{6|vyo{$qdN_R3E~Zal`Sk5`W$x3*S>(V2; zE6@0*;hxg#bfM6?F1oup6t~B_8kWyLUgU>T({KX*C&<8mE4l7;{TX}oTA6#~fBi}h zgUh7se*3a4a=Tn)iqAv2`t>wy;cF;!8S8ja-|s2c{_1Gw@}*+pnY0OZ)PX*#R4jmW z*8tNihrPTkMeMk{_COi3L;7^7qZjfF!If(YoyzI~P>)ddt9q03-$NK(OTF*&+2>6T z-bmLA7c~g4?c(Qjqes`XVC_YN;>|0!O{`aI(~0uwqv*H&^1)(dj)G>hXU;_K&v_E# zb-7GA*%un~PqrhU2aTA29$t6bD5>sF;vud-e78>S~c-u`~uZ3qCn zdfTtrzSdmT^t^2#squARXDMpCln>?l>`OK*)OC-MjKkk(59AichW}Jo+j$mPC@WvG zTxJqWcip{k);sMwtQEQJo+~-oLGLAxI=qh0xt+ZV_%Z2a-~EWbo9k6}X?E!K;6vGO z?l0nRBch+A*6HfGuzBN&mBANqy-=v<bCL_ChwJUG)?3*MedDs+4Q`6!!+7w2gYZpMR--Udg->WoXaKa7KCTz>Moh5zRp z`MRF2AI7hOjeYRLF7&Q_!M=~HyRH9{ZAZJ3*7saJ1sCdWJZM zIoiyW>Xj~&jX{j>+hMRjee5gwpKmT-MX0jynm3#qbo^?!+D>d?SiRk>e7Q)MbHlMN z)cb{>WA9}>FvJ$=g^u>vy)7iYP6Gsc)(Ml@)pE(pbTOYEyMG^TO6@plYhRO1>brhj z(s*b3uxnTEmgct)4n+5KF8YkUXNOd5VH5AwD|tv#WwiNDpOMPna^CwZ`$}n?8Lq3Z z=JIzhv%a3_;8NMI4SVGx5E^Z5^bj^~D|HO&DKQ(!iekabFRp*4!9iW?wL&Btu-B<( zZCHrtyVzChW283w-=>d)J+Y~#-7=qkXV1M-se+A0oevSI?dlike0zC)sUPfpiOade z@NQPM7WJ#4peb$gjYrl-Lpx=8l`$?6@lc>a?Bbb{XY!@o-$>*0MR^Ew8QRSqzUTQ| z7x{VDF8UX`Uup1tJ*mAvg9(97m&ZEwK0fR69fLHN#p_=%{6t@jSzXXe z|6RLA6NAkG{R{SwV0V*ml04^K16@WMl}f8bJzBilp=VcDaIynGg!qb6U=HPk?QIm} zM877`o|-<5BdzCH0qmT`IPjj&1B%Y;YRXF!L0P-$%ia;eW32>A=YCFqMX!goxfbq& z%r(+_#6|qxV*gkB$~eB;+*jV+m-|XwYA@QBHdWiaB-6**##Hrr%h;Dwj4K{DEem^N zA6ok{efsnNCjGr~{g68S4qcas3IE&=3;p4LUp3rKEv#?GqWB6{;P>hZogR{E2_ zq|d)Pbb2}Kj+R%SSNZ@m^X_*obo}b)(}{Rh93Kd_i%(QKTnQ`Uf(BLX)kbYPG4X~* zClP4Y8iA&PL#CtvWj@tpiatR>(?9LF5v0uMED7Nb-}PCALyu?t;`CtYWz%=wc+w5> z$MTJL`aa@IkeNgAMjai#eoX^FO3xtok)@(IKptMln(ALQ0}ViigQL5&Qt=IbJw=c_ zAWJklMybrwp4pSm&nKs6==k~FqF$`D#hUITkK*k(4!(UCDKe@F(>Ifqzgy_SPbydH zrN3b#iR%whK82z#P==uk7x3LM+m?P>|YJrU>idy>3~28Cq2yV#i!1!Dzc;5n%JOLdl_~P*SWhR zX{RsT>FRnocwMw%1fio|82~`(;(E|W`FFf)@MW?y+Tm<-2c)45ChQ)S-K^m$M13pd zLs-PB(8m(x7#8o<`xQ9R73sJy0@@6DuJ*K1g0M4~67Q$!PW&UZ=UQXp*bHKQ3SAxP z2GrG6{@<}9MK1!b|hO+9l}jzLTREX&EAlg>3KD{dFX%^Pu1M*U2N*J^c;C* z2^+;bzfJ6lHlok^Ga12$$ag2#wfX6Bf_%uv=8TxoQd?)0M0=Ipf`Cz1bhL}v7~^!5 z7}!!A0@a}5B%d{=orAx#CcDuBt}$##zvi>MjH)$6S9{IyS4JPm?F7h1 znOqs9TPi=%u5Pwt+k#CT9IK-Z^cZh8e;xHQvyfJ=I)zR^{n>-0$*92!*C{8=1 z=S0t^v}q=_yPyShg-b2jXUMjCztN*Bw6;-?h>c&=Cy~XvBm&&Lg~XrxI`u1|y@?*LptqrV(5Z;USCX_oGOrA#->E*SJE|A*0BEPzhB zh{`_8IoZ$V`snITZqNW{0wWU&8(5#qSM5IaIs=jcM$BKe+O`yZc^iOFcL?5|JpVe~Du+?g! zqi@;5EI-RlYZz1m%znum)4HGN-ftEVKenrUeU8ulAil)-(X?X~z{ZOV9g>yAcM)nB zL*HonzHJK86XsFAjs-$n+-Sq3x;qV)&vD5pKvW-n10og|R-~`c)9AlTcVAbLc8Z>? zW*BVPhKbuB&Wy!}S(Xg7EimsqzpKzcGWCKN-&GdkznJo_GRqvqmzU29+M)x>8x{6? z%TqeXuYzH=*&x_)-^>e95EKpixS8jK0 zM{zf+`uu#YD?xwx_vYoa)21d}hb~X;>~_uUpimKw{Z2nlSnMxpA$+to!+Y>8Yt!(u z`XL4ONXNH{R)w*)bh?g-t2toMNsa$?fim~jDmo769J-`(!tY4;Bz?5|in5q5)Co4Z z2OWbh`e-?2+bDEZU1n`>_1CqpRg6tsU)_YE^wEy-;zRq%;tuCy-awqXGmBe>un2irN=DacdiK z&Bv3v;sqHeFox#M78(<-9)mQytJFaaeJ1xulZPS;@QY@i*v`!S4#qXp2mM-viqYuz zgv%Swl8CBz$_rKobKWhjZp%8_QdKAz>^thXJ$nYFLb3XnG(M2|Evj$Yx6)|ecfJ

&$K^jc66%u!P7jo5$W82K{EH1cx(e&q}`;N^BCE)h0{V- zxR?WM_p@S?V7?(lGXa*cTTTe-N~~VnUhMuV`i1Ls(O!E~PQOYDwO!+Vv=0C?_wasX ztnD4K%$Sv;e0(k|y`U2;GhUZf#g2$4_FT1=|U)J-41z=%9h>Ukx^gdnwJylX7tmJftPhLx zg`w$gSX0G0{8cQ1JW_=#XJf_{cWBtRpcX8;MrHO}pSOYD2jAT@>ZO;=R8|np5>P6g zUA-y8Vrm-QlA9c~ms6{6%yI zgNfejY9{Dtfy>*|wL!Hd^V?LL(HUa|<0rL-q@(a5>$*E@>imf?AoCjLu&EAH+O zF~S{Bt(1*l)YgR59wvXO#lkpgMu<%Q=g8cfduV|*9(~kv1OKCw>eF>B)K6(Kj~eK?rKTsCC<8KF0WsZRC*6VT~y02$y=dl zKLK%@92SB1`NwajQ2?ZqQ;$;0$p#y<8Wt^<8;>5g0dSpPI#53#R(;bE4GcaxCuMA; zqOta@y&qz0`$gg)jAK=^+o%uPWf*O>n`yP0o%(A<`p6H;gflwqHXU{a#(U4*rFoIhRL+Rq-)$iN>5%b+Z~nbxb9NaD44{lY{nzYrF|cYK z`qAWVV7`Y}F0Xa=GzpqTHZ#@6j{!nm;7&ln#@1OzuT%+7htc()fBm~M!7`#qVP%S= zqLPsZs=?HzeEUuw%XhAiXqOQ)IiD*?U$Ac{UKC1%=ir=O68-T7KT1zE?gH-orpvMA z-t#c#gSW8VeQEDcD(*qpbAltcJwv~x)&8@YFUln*{l8(K=~w2kc{$)kM*iGEe%C(u zR^M!{Ro?%0M!Fn)H7xCk*Mfxd%LT*<2-l71R`oeZzyV;AxJ!wXpUuOsu{9?({|d)? z9yIy?2726`-#g`L{i$_xX4BNfKgE`dPz7MRc}?8=dla0u;w+xP}@sn4ywZRfbtAi@l>NJ0Ah- z{q&kLfUGR@P~&w$zA7y+7if3{eDKiUC_qZg0jbY!A~{=+Xkm<>W{hk;G`gEFlL4aY ziE4R+UuPd7Qxjn+QfR8;64SsY?a6|IES@cu}B6d~>)-QiUPY95W#9!u-2HIdG2HRd)QlsZZP%I4i) z{c@*Jd!_Z9m$6?!hIjvob^eT7U1}Yk|EIOBVm%tE>~|o30}OD^M?EARM&90)^f;To zKmu2vpF_8}5WPvrb77B5t2H@5|-oQ}#%)FxTG>@TZdOwMIY@U7l$F&6Jn=a1*S zOM}n>;gSt#$e;hSLX*>F?Y5dk&VRd*`~rDOBgPuic_n zYyZB@Ok9-W;5P`m>$oqmG|O?@npOGhP}#45EGNz*!o#mQ&_L60n~8-b)DONV+QF~# z2qPDK5_DS{%b;(>pd53 zpC8ztW6Hw~T02Nw!zGz}3CJD{Ts`5i;hF!iB>W67e$2vO$9ACOkU@Y`auLl{Ct~z= z8`yI~-%n@TL4EZiegr~VTq zAe})zsJ*%x-i-oDl>3NeXcKsP04uMgk%~9(@wvD6(;o7a#bY(*YTH(xMX*hF!742IEu1}h zvtR#R^@lQm?cp=QP-;n}>l1s2JuS(hf1U(37u^f4oP5DqT2cWWt(=Eq{)b^fth&{R zi*EY$^;eBj#8P{A(HiZT8FW>Mrf)A>%iW5mTEs|a3-F+8EFJcJO-9kwOkW>W#@<+4 zM--CguDA^x`gmmI`ia&OM8yxgdjuT6K^>U2oWL`OCd>Fxu_o#wF}1QL<9NI6E67vw5tDZ*Lg^gz4yobx) z8&&*FE|xip%6*mrTqaYVK?(eHF=+@ym$A!_Js3_cyRkBz1;$u4m5SjKH7mfaQa($L z*q|31^OWzIK`j^keEi;>^XFzE1pAfB*r(XgS3|y`2%e5~c72afWi2p&4YTCso{#kK zzZ$W;qo_n92N%+KR1Qe`XEw0RT*Yzzoqo^y~fPoF7KDyUg2c1+wPYpdM;puzSkJQ^Jf>*N_l)|efy6e?5ZV=UD` z4o3zBTOO9E#kil3d=pHAu13A<0SCQnyb!s~Csd;TIm$7VM1WI_8>vvQ%HFi zwx7+@Yq?Yg@ly#V?f8pEVyw_JIxYrU_|bAjDJl!21eQNZSJph600J#=xMCv`v*@ZJ zoXrPQo(uR{iDaEp+FN>za3Db57h9NqjGMfZai$${{uV$RPg!>65x6wS=ok8Dd(?NN zGj3gKqH#G&P=;%B%}`Hc?qi-L_au|PMWEzXGz=~n%u>f0->d$w#94-W&ad`WgI})R zp(iYXEQy6hNnTv$qt3VJiLP)~ewo(B-%HCm-T2tvi-trr@u$l4(Oztm7j$S-r;nkd zUvW3kvZeC3QR7ZAKig*ttE?yH&ug}=q5-!>VJ1l`iqO(y4@d)3h!WenbVkPV6g`Ix zt!fx=MJxZ-;S_ATA3cbS zW5;5=qDDD51Y8F5$45Sf^YjK1{EFJ5uZ)kFGW=Reu(oPXV*e@1qJKSc-E*6997^Jp z;zHz+*Mi^?y|vyw98?b63!byS|8>@ZG_+X&=Ma3Kq!a6jviY@Z|9r&mvWgYN z!ka3Z)(;%+k6M&X)?gbveP>un3>uYXR13lY)KnC=K?kj=XilBk9jqm8cTiuHgK&&+zMUD-6aqbnu zPw4+L6wFx*fhZ%m3i4lZ8X;@xL+79lAP?O*nK!h(t}gi@4<8rbxAQMvx>oO|%(#H{CRI90D?gb)S8;OeX-V%7l3ezbXL6nw^pw=j z$eaI$rhAHGqg|ej%RhR$A02@^)RGjNN*5cS81mCB*GeIQ48YTi)AqVs=zV%=Cs!9 z{r%FZbSZMZcTNiF-{F5=?tnb@XQ`J-@%||oKD-f@rX(VgHRF%cvA~2N*!2!yzwVJew{Q7FyFjB7P8ii#lqMHGpm$1W0DE72cFCN>jh_6_Gv z$%i(?h&%DVbzalQwht=U${~N{f67EHI5n9_5lm#HXG&L?q+gd_pK zRk(c|!&~~0@5)q=>{t(|eQ4T-@X$b$`}XB4A%|G>k)~UJ99OeK=K}?^TZTUz1y}mw zwM|cnPC-P&9wr9+sF0U?*+FCrr58QN_4h>cBbKH(w?z7ZbqX3ch(VpJ z{`A(F_eC%rtkZ0puLot1{g`(^ghl;uNjzJw!ko}wibqb~=#9Fuo)b&F_|?}ut3D@z z5<|-m<~w3bFe)N&LKOYg;t8fqEz8qYaI$2QrFN3)F;N|11xsbdyUp{v7j!Kdl6@=1 z8g2eF-gHd*Qtb62dL2 zFy=ihRf{zTz31tU>UrKFWEs93$8xw5N`We>b6aa96Y!($9hrUa#~(L90N?yAT=PHP z_W`>q#h&LrXbVwUd4yoQTZ&U=l2EB%ea}TXCr&EfE5F`3p-bCV#`2ldqjwU&&iLCu z8|>H|2CA^2x`C_6LpuRIrLJN*fr#LF1|HeK7AeW^eXFz&(ms9_jw9^aW++n-8Gp@-olk^>5;3GxEY z$>-#&CP88ElI!od;D0V*OP%w2)y?xK=^F>D_OoA&;%k+I{{(q;U8(N=!aCn;Nm!VXq9g@jKj2D! zF#V`oS6^6fK)bbns+|98@-M_an17nlAyeFGs0q6?v8Mu3F=H3Vp(OGE?(B!{=oIi4 zSxPuuvtsY5uM`I9w-M)D^uP#lMx}m*aBR zFg}mPq1G*}*3)!iEjHAh*OA`BBs7o{EVR~g6SXXKyt^bWq~#@vni-fRERXpo(eO8T z<-?zzglnnJ-uUx>MDL7M%!A@}a-3VqUQnTBh*XZRI-X80ZCDA63uP<@@O>dL2J8+0 z6Q{dARR8Yaj#6u@e(-l6ixKa-EQ60C>AGWNFr8+6znp5auovRUb!%n5K;mF50!*^o z(E8i^M+l}A@ky1k_L>zgQYL>B*0QpYQ>mh54!Z1mcm;q86^(DXymg`#ea3ZtOuP5>&)f=;f6h8O4E3`u1%{j_~ z0dOg?5IGcpy&`Ct;cE9seB^1j4B=sbYkgGKD5I4mZSHWgVB=Solf%29CwTaAEI{AX zF<>Q)jbl=hcD>Bv=IOW;DGBO)JgBidE7}ge>`gBoJRieB5bYs5kni$IOJbXsBf>23Yh1gC-Tob*!Ny&EnUUaVQ&P#0-`>KRnY27W_YwY3@vi1g#R$9X%d)(ErKYk{YaY;SkZ?Ap)U21`gmN#HJc z&BNeH`&z;?MvLMq``m>b)sZwNuMXHIFr z%Y_$#)t~l3`rKaaKVd-13-{(2ZOSLtFDN&L=Y(1(cng4Q($N^I74qjUse@*6%{X3{ zIRx4ANoAdrT7TgYWG)WRc($G%z1eDu(_h)&mtO|?Yix8wLHn@YZ}OoV)Im}4 z%>V(U^aqyBQ6y)LzpwU%#xjx`pa%y%AY)* zzVEl&x{3r}_c%NO3E?39{nnaYU{6MEtD_?0fuxGy&n7l&lYv_goWiX_%=;V=KY3X7 zS@R5+;G;ujtsLj$SyE|-)^Vdd4kH<1sISah*sB9Knhl7^UlVy&31Wymy?yJR{oK2> z@YPHz z9EJTg)j6g&#+a~CDRi_`7D8}L7f6@(AIHJ8Cuk#J(-S08&P+^HGP{<8yTLk#qqzfR zxTW+Uv2Nv)!0Ns@Zil`jw(;O%txz=K`Qnjubpp1edqZvdZKX5p&8_K+86oFw`b!bC zB%SD&0%)^zEu8liz5c0Wr(06i;S0rVrtVzVL7qgv^>d(g9`Gs)T^hy9k<$$GuU z8TzAX!xo+tOS0b_IL_oejpXb`2c;pwx7WzPLuAe`WDPx@2DGa_F{Cy93W!-`A?>p#KXa8@`g)tg)CCOFA?D+GJ@~OgT0<5_FG^y0d z55e0be~wr|zUlD0C2cb4Chyk@U2^94>`S?N&1X`}#r)CqDd?vxwOl1@2~dj2%?^*3 z59KQ_69-^Bgh}|F{A^5efOz!d<=#6TLkn4h`-qGPp8m#C2zKwE{Puzg`3iT=Ry|qCH?Y{b!kH4=aFukA?UnZDQ8IGH z{Gw@?m8991XWQ2Y+n;x~TBSo_3DRu2v$Nu{7^fm{EK_Pu)CNMWaDJ|LCG4P^929DV zP{R(MB0i#CuGteB1d10j3uW=dO+Evf-08{3f}t(rh0?M}9E*Qg(GD#$ZFO`<+|kQK zd1F}T0vCdsZUoRBKT$E6P<5=;;dvlcu`V~y;a>@m9_vAS5vRfzWou~aOH7O-zIzqV z-%nFp#{l{c5{C@~(r7OM_dL>_)#Df#VbeGJmJY%nx6LIh{gH$zp&@H`Ft(@Dx_VZX+3-an;ZtwP9g0f8u&8<16XBQrd9N#V2&WBgk{B3N2StS91Ta_iPZZheosX zHg8sCugG4vJqVt<>z6$X5Go73Sg2kzPn+6eOh(p8tGlU+J0}-y6G5krwr52X5mgM1 z8%|Q|dlF&?vq+|ERD>1k5u?whLi$vT)o};{yJR3{SnD96arvfbe2nLx0lHs88Nq%1 zZ{6;uUfc;<%6y3Jhwsb|%oxx}eZH1(S(X%U;98Ox@oo-q*m?8KR*9{lfXG{kgGB)K z0-p}E7P4qlc7(35ub}Cob;uY`T#EWa0=sEXdXqJ}Pc7H%fQRN#X%xqT zeF}eipTV%!hg&cz!=JsCJyEAb8hoq;}#8;j=sy|KTQm>M{**r46bYo`Vos`m9M>7kn z`i1J;OiTw+@r(nQf2H?gWN0Yg80WmhqOL!i-pSetLAQUGv$7v>8 zCY%>!#qCM85imp57!@Ov++0TgLqKz!@EDy;1y=YIgCa_vNg3{PD^5R<$G(gal(=ub zsb}*2rum`#kWw*EaoSgtQ1Lh-#L_qKZ>Z}}&c2G(%zpxWWh@dOR=roIN|cWS!g|Rl zYABPB00v{;FG0?CXBxsR!BCi@PF6<`-ROw5p#r@wvgVE_GxZZyVm&~E-iBs{$F!S@5}=NZ*lF!X$K|kDITS4gV`y21iv7QDz3A)JS zF9Quc{N~#*!J2%ASz@L3JG(O{67+zoi#m89SJ(D_p z%A~`>Qj4}tK=`4O`%cd#C+~gljcc#j(!+Du+%o{NH&;3#nk+6B`*npcST zDeeXVGvr1)GDQ9!fM-RVf45UFN74%BrISlOew}H#Q+461svoYmi!$9USQ#oR zeYhMF?Cob$Nd(5milU|aoa}}9W*q{w^cFQSt0J>FQxK3!$mr(_J@-bnxkF*1Al}Ng z3bTz95h}s4>jDbKC*PDv8wrtbx}Z3~O?|N#p|>lvH-+OJY+0h{EbKoxK9S ze)FLmP##r#T@f_$5o4G&M3ncz?Bhd>R26io^;cjsxAjh@;V-`H`Fn=q5Q0>F%&e-`^Z241s2IJ$Ag)3e)O@A_pJ@|PkEvv zv$rRj_XjQWHoa*5Sns$x^6vu$Wl=J3JtAsnyhzB7oYuA2)eb4Yn%DKVvF^tGJyi=m z1ZE5L-k`RZe3q4xQCZ^?Z9XFdMzx@NC0i_voLNTJ<3F~ohSjwpjW*`hhjpEURSMr>y#?nB6US<5Y4 zxPM|0O2V<$KV6(3F4PxnDT?7$vh&3Pic27vR^Qa>#vM(~AoL@k!&>)h$>}34@e&E+ zlX_Krz3tiSVAQN?F>)^<(A7heVw;XyVR;%N`Yz(74gXaZD{hsj&zG6dRzIx_6fL-0 zohzvVFnP}rR?n7bBg*0dxH)}~#7vzNFSu9*co z5V?PMyTolP)P=_lBhB^lobuhUj;bU+p9=;_GSBj-$;2>&M7cauYSq6NxJ_6UWAOT0 z`tC^S>eG4O((61#6_Qu5v7u?2{H8c9NWzsk7W%UdN%A%U=2@RmgsY#k?=c)12qB=W zeb&9ZnJ2Cy2Uyiz1NEJL(`CM7yfoqxk66m>FoRx48m1ckQ8{y^go=V!LVkEY8$$T) zxe70twXT_)&)=jG4pGq$(CdmEVKOc?egL7@YG(6pZG5XJ%Lvq}TCo}ZI|Ps#kU9uk zln7j?7@qgPp9J!@GTOv1S_dyQQ&{`bpLjN<)?;=i@P7?&J{L^I57{3zSjZQdO8A!k zqd4hC?+8pQ$&C>)W6V-IroDdN(~q)%Q)qh39>pU&T?5isroo5Xx;7fe&HFs+J{Odz z>(j3q!SW;L#{Z0qarGpc1S8e0u36LgwY;}~()6HgU$q;&OxhoN_b*fq;@G*89L%|E z*+8WPIbzq~qBy;>fSMb{f~br)?teJEv}92%PYDe8K3%RBOXgI=_%`Z6J^jP@%3(Xn z?k5c(snDPNOX5>@IVQ<}JN2*?NA;iV+)VkeCvMXF8HuZ}hYImSo0NaR&vy~u7izoi z>^e2N4M0S9_?8+jcj@^BfSw@EgMEJUcRdfXnbYB%kV#1DwiMyu{Q_+1faeQmfKl#j zReOFcTpND+ow(Ov#HTK?&Cc5G+*+7e*cB30X2)TNIKJmeaj zLvt;vA)<8zbUur4M1Av#S|?#r{TycW&xQVxU6PO2uiAaAd0c&gR?Bzw(80kx`^1|; zqU-@TuxNWwxcK8*(88nefdK8&@)Wk~?CtR~zSy_Np z(xeW>f4=`2=h3f#r&%BO>kN{7MG^IyTd&c zOVjgyQU=6`!R0JwvT*#nh4|pAcr3Beo_VIPDOv`53=NZRwSV_AMwKz)y%{dQcofOY zq0*E`4vl=r$cll>UT<+Y+bqz@6T+*}qXh-laIPiJ+Sn^gQse7TCBl9Kwud73Z{6Yo zSJk7hpQGR8rsetV#>gu8#xk%=hm{nJPE()rQj(q`aJDdglzQDJbD2xWSGki z5#P@G@C$DM_Q=%eqBVj~&6$lUbtMS}T@3drF?yoC*d_IvfA0N$`9(W0W5H(w08niF zt;qI%3`f+icY%t>s#h%%X)v8rlvT|(=4#dN8L76)BS!tQc4;G|g?nKZzVd9j z!PS#mShX;o*_D!ppkYxw!BzoW0TwDAm5PrAF5&;m3tg*b9sphGysM=bC`Y4qeDo2m zr^pcv90hire_=N)ha(`2;~{q{sl52{2|78s={n%9UqS9GEd3jpvT74{=(%n6>rtH3 zTJyVuBB@G5faK?o>riAVT@hFOOlZD9i`?u+D#}VmGIjpsv~1<4-HKe@2l{`>%A29R zi1Lzs8gl!eSU{_iHROkifKOjM>c0AZqJ&x|<0RCGoZ`i&t%y#bw`5(VTBjWG+P+aU z_Tjpp$P!?A%GVa<`r%=M>)D)Q1NFnABQKEJO}IbcU|bs2wpB$6yL*71<-(uH7aoBB z)?Xts{JZUqyUynKS=*gFH1FOnkJynT1FyrK2wOJ|?|St|A%ZwNN+=d)HaBqH?GJ|aY*c@>Cd=<1Mfj}1S_JSzC{|_Z6 z#zXVp|0d$Eo^{eeI;T&(h>T#)?)?M6J1_C3OI+z{V{=AEAFoI4@F+^g=0)6KyZ}aa z@_a6=^$n?(sFl%zvAUby%NgAg?*d+4C>Br~vew&CU_98^V{q$rDF+F1^DiJY1OZ^1 zT)h{1s)G>81wzCgW05lWvE2UdA`q3Jwcq>A?MhC zqGXJQT;!uKx07DPf3331LI+4ZNm6xH?Y)jbBQSI|V%7dNr!i4r{`0ra71=cr9!ryh zs0Iz7@f`cznZ|7}HIOnfWrClbtujT}=Izy=%9{l=3|5yYZTo4&^A?++R&RoN>`*HRwp$5}$G>#Vdk@X>{z%+@^8dv`Q5#&GO7 ztn$VysQ7_HtYij`rJRY!_sx)|_pl<2)NTPbkDt0C+d_RyxP5_Q=vG<}+Z{L#91-8@ z`-A{+)a^S!*Z6uJ>9_ncW=iiL;2yVmCWK!^qs#Cke?0NL4lBY(x6k1&lJH-7y(QL3 zZcI1?`?xo$e*k4EkyM77_56~UIT1ee+!zgcDmL@6!RmGWVi!UvvgCX3A9Z%tWxWyd3C5et{#)e^d+Ygxn|4i(- z8H-h0cOh5m4+cIgn`BS<{YmAg+aHi~#y&YR5Ak;!TDXvkjpo_*yepWuUo|1Av7~vO z>MMUMz1;|+0iTZ3SiR&Ejp4@lseVcYuH(h6Fv0a0*2K%5kcH7o3UTjGnt?>C<%6?A ztM$6^wU7i2GPb;M${*g1H zgZ~3Qh}owJ+J)9f6%7L72eg4%k~U8-#c--utSyHP58lzlmLUD$TaZ674luB}ks?zq z_%)aRXlk&nVuauea+Ha9>`+LHe#}xYWxGTrWs?16q{W^U6ISt<*=ZT#qL5FNMITdw zd())RfjMHxco|`F!&V>1*d+?cZu(_f*)F#y;ZF&Wr%ka)EBN7KKEGc zIy*eL#P}ivx5ql3LP7sXT|i!HzqaDkeGN*nEj>wzJX98Yl0TqRHeh#Q7lJm7E*msdy(JZL#sHyjvEl z--uf{2S=c@?0to?>!}pN#-i=AO)xvjwvp#9|9DE6s3HbR(qB$_FBFIXF}F6lM%l)c zU~!%Eq`PpnT^g$nzJtJqAE@voyIzKjT3Toe1&BQ7mq0x(ILUbIz2|_PDXe^|z(l z=(4v_==NpNtstal8i{y1LLnf!%mOeP#Bv&9c}mTIP9&DOCq;OS?B06 zJ#M7%ArZK5jP0F9%2W&euSm$B#;Sww}I6%(cSE8Nx{K z6RaBqt^S*RkJt~^k;?CQx}PD|#rXM#iarX?2YBEWnZ5M&G$+=5GOYhaOt=utnQ z8-5wrDowTy#HEfh--q>_m>&w*XVQ6pACFz?*J>Tl;{V_}XT9;4soH53-k7tnqS>ipJRf-8ST%2giR;X*N}!6g0R_iy04;ua#jY5|F1;ZH?;^ zWBdzDbxn{?*=0=yLb9$-v48I_Vfe2|HTzuKhVhV_WCV47{=M<(toIcs&W2;G_Xl;_R z%b96Fm`Y8NaN)3ooUrhvw8w$Z3OTFwW9kHi$qlIRR$zj%JV+YwS)Gj9#N~1h#a!UE zhe=Na;Pz6hv*b~ZmqW|8(qYvNzOO*Ue~>&W5bFSQ7Z88Q&oL8 z2*(YRgdM=k-@Hh^A(XDtuRKSXUzdM`%M1SBm<$-?V5jFpkPYag%lODoggC}_1=1xn z=uU+i=CY_+pkgDPL4#f80)g)B-o6iTpbiNbD=~T<@b@EBZ+#U_Q83Zl_T_JhpD{L4tGdTgE&^mN_{p`Z}5hlG5z<>G+H#Ttfo-u z=|Y;#d7FcW*I&(40V&H>JEz#@0qE+b-j<#@tX-#Ag3qltI?6ZBwbS=owlA^+( zEc+tgEypIBsokIowT<;Utn0@VA(#>(8WoPVI@ci$ z5JGeoXi)HF4w>5*5Wohe4LR!gJ3PSABMrQ|cKtO?qtbreJ*0r$_7pbd<@Hb%!d7GI zhYpGVSU)EOeWeX~g)s70?xgnpQC_C zZ7--|FE+4t!cP< zPoZ^N6PVTq^}VG30H}6^!@Yo12n)d2B1}En3iV&fhpzYf|k}f$f#_gV^5=iTl7|$+N%B&v%Gt$vB}^arq3Os=?-98m}0mfp0Ev zqpwVPt9JetV|&UCBbQGzX3iL1N{WiUQTU*X%yKjLH4DJ$WAQW!tvvXg9O<$6AN}d0 zoL~6dMCoD6FHM=ctJ%z_tZ{EFeEYcM$4CWby2GZ+Wr5y!+(qp=H&C|)`JMTFe9_Tm zz|x|{%cNGP!L#=lxdtaL7C$t32p?$Awb-hUb&Cz!_*@PrZHKk$OtZz^en%V*DWbye z4Y72WPfIVg$5dZ-Piq$;?yn+M3R(IsC8oia`vyRuG}5nhUm2+rZ2si>i15^$-D@%v zFzhc=zi{r*>hTxC&1Z!?d%7F}9thP`db|AGZ?o~lYxsh64{4z=&DI*?TXnqO|4PRl zUm`Xi=dkUUZ%gpgWwOt^ZTD{byo|rV51|w1u3t^>gj1CJjNU1_JEY{ z{=;5Ev&s}+_d31Ae2OrJr-*Nfavs{+K>6%6S~?86(ew)AhNC_onZ&y2Q@Xxbx&gYN z++?xm`ts2D@98RfTYE8kt7!sydI;-#tV_JD>k>T;$~Saeqf+BVmtUXDK$@-o|KX*4 zkN?{^zSQRx!rfkgKWLkV%y)d8a)p~b+$P7%Y_|i~slSWGt2t*u<{95!ai&jtTQbIm z7r8jE>2U5BYEy6DZ}tx33g5s&R9}E3{Qz!Jk`xt@W zhpC0$iz#4U(=c+BI?fi)*xE>>uxVptvg%{!X*hgNjpzjJ{+lCjq^H@kR3lf$w+~ca zy?wFy;Kj2{7p1(_&;414_s5iEkh%zc5YFSeAGeJenC_&cWr_4F+}E`<#TZ-qJnId_ zlNyJ+-S8qkeTvVE@!(EL-wE@(h))RP6HCEqe#=wvwEm$y#Cj1^Nh{Oq|H#JibCXzC zAa-i8#0!o!7EQsRpFIU(8<(E$0Xga82x}U#&I2hYkY@ahY3xcGP>aWU_kEMrxX$-k6iL7Q zY3fK(wE^=@aajK-(sxxWr|3FW+3YvFCPN&8Yeyba^F5mB*MH(T?0=QcM%)6c5IY3E11J8+Wnj-!3s|ZKFXx(H!?xMXMyk5 zk2u38+l-N!{`0A4P8IA7(v~MTR%3*`ppD;7v=%oCl@l-cIZlsJo+4ZCVkaWr8fVb| zVD(!6WlR5gXl6_Qc`z0zn0UR^1J^g?e5Py86HKi`L@Qa{)Kk8HrKDTLH~pXS?T6P)@i%o?<@aL>Hm2c<9iSHES`nVtQI0?Ht-Y0MU*~u zkYTTG1~D8b9nEg|%fNB$7;Pt=p#MJl&md&8czCjOeN`bZEdKxYeOyCA@A!Ju+;FV>hu&Z9BfUU*2qgW!5GPDcynJM*0 zU8mlAAeS8w56QlGECbAQZPsT88CLPon`g3EyNHZTI`glMYf>@%zYd+0x`QRf|A#$} zlb3lt>CKnxYe~o#Hu!><@g%2IeMHE4Y>R0$7r2pxn^)&CNS;MN?(YITq=q{>O6J^ z-rbe!^S(%}8`{Nts=KPspgnZkkM<~^VUX=X&j@Wy)qgBuL&vUz8Iyrdx5H+1ZP@HL zOR7BPVF^4M;uiXl$|#DZZKFuK7RKeOLU?BTe@av8Zn38SrQvHr}dZUZ(T^sFzn2 zeADOut8V_4me%2DQht`szbc_1Ma-f54UU=*2o-W_ht*`(2J51mEQ~L0u8#>mj z_wODsef$l*e)DJ{KP+&0I2_y1XJ7I{7Zx19e6d8@h#&rMAAkQ>{`q4iKSMX~e=&E5 zN1P-E^O#Xhfs0%+h_*O7ev1;Piz!c&?lNNRlifYze4FsZNDssfwp1C+b#Amzf_Z^J zvd&$M1|ftTk0J?myINqe$qeo`cG&Z>fMs+Av^7(O3y!!x188pwq246wx4r@-q(UQF zg`2}5XwHQETqiq zDIcfH_{Is7*Pq!Hwj~s^%LuWhY@n#qkturWj|BxENbw2u0qRWBhV^72U(mq`Qa@dM zVHK|+plM)$^$Va21AfpUgpHnneZENjZ>Q=U5yKbMe1XFuY7?fBOMhehU1l#N^d?RO z0Ssj6M;nKIi1DhAY)mUJ6qm|u8RREY0LWlx80-K{Bs+{b(6{WoBGwbLP79ve4CqlV z^%K&G{P7c3fQTVLbJ(Hwx(3fyJQBTs@?K{HNc1`@A>i@ zZLEG?LS{gwgMLb%f+;g3H<7rUtF0GWEeJ5hDOMV2@PA>uiSiv+{DnFBZJs;#Wv*zS+73Y>CGooBX)D&9uI_!!`M{_Uk+X+)AB@V|K0n) zr&VmdS&**wrJw9gg~;?hRAUT(cKDky70MXE2=B~Dg>4sWhiw&`lb%NVLUD>d-phEk zOVYnMA{t;s*0{D7|5%1~Gh#@i8KHe+L?2_ zkbbd#k2T6@eJ8~UPQ6sA~s5J z_Qmh%^5$(pzcucakUhPhJGB2{??WvlU^o}ohhnpJ-sfpMf|ueO0wl(x|Hk=vrdJD5 z$*vA|x6`=KdRc}s7n;Vj${THWv6uInlEyc|)UsSCAD3k=d)@pRvQN%;#Oz7Z>8Jq3 z3ZX3JV-S04JIjz#&aYz}LeR&4YF`*SoAn4%fm9wvj>`(*OA`1nzFG7)|1xQ}x&t1{5~ z#tl|J?p@~h@azF-hVB9%K6pXer(f`cSpe z9Q3&OEIjuM+E1cYsyG6<2)U(_!pO%Z+AVx$LJ?&5JI-IBiGP~R|A8Qa22#bB|Id-s<8Ozm0NSZA~(dhI-dJmE}v#{GB%^ zoC_AFb2*oDIhS+!bywsUe(I7wym&zGe)yPPJ$ORPLAMB8!Xn7+%>$BMZa{M=OQL^# z{P(_neoV`)KKq|te1recU;fzY|1HNq&-?X@$4S)gI1~8Z|Nifxw|?l)(8bf=XE#v`A7H_-#a+nj)Qs6(4rAl&iw*Apfhq2>dQUMegq7KA{=Nhv~(m-_)`P5Wbi z0jdtu93XVC!>_eR0@5D?Y9fHAR_H-CxJZX_ZZz%?D$t-cTSWzy|1!s}!81mxiw*d` z=(}vdEK`O3Z}6fHn;#J6OaWQl@9FYjv;iMd-lRaD?f^y@({Ly~>!82GA->Xu9)uu9 zSa^ijLkMVu9A6wFn3KnG|4Y4|(6SYBAEBO`UWRRf$#g<=Dslf-f zq)uI!{~8p5}t{b|suqdgCL4#rRHZ^&>aIYF{u0aJt5IJcJ$IJxSlKaM5-v*bU6L zta`NeiJImkZAj={q!6+V4Zh$C3*d#fd%KUT{FKx!m{ttob0JPc9jPytsBYGwy_&r^;4dMtD^#zLg&5*!xzLHZ+wCbsK+GqN$C zi2P-0z*e{T@MTM1XPZ_cm4IZ7Gz1V$zC4ygn~4kZJr#m)I=)T%dm6ZAGmkvHHhn2@ z^rSvNVBukN3Z}x@q3s>7%<-KSqgFP)!WM3Q65~j^-Esw=~p}sX$c%2)#y3aMJcHQ`?&&M2uG=ATkPF+2B8YK9mGHK(_YBQZbc$ymH5KU>J z{BJPeq1P}*oj(WNAhdwb>NfDgUIMq&inrG1g>!T3rM;wPO_Sl9M0Rbivv7=;^Z)QM zMLLzHBRvg)a7hp0Fyu*0kLOz3m%AVSR3 zNmB;26c>(g;{3emkp6o87E>c}zNqiNU*_0(YN39B$v63$z87CYIgah(9^%8@GfPY4 z&wA|iUZySZ)COHgZjAwAsWW^xjc+#hO@!`?pL4gxHp3v_99mLaLYlCUZPE3C!;m^e zzi`YSd+c!oeY8Vby0 zMLlf5PG=`dOwt>2#dYV2ne#*17wU#y;s2UkCoI_~$| z*1f7jo7LR6oT%Qf#|DGV_V8xf;K!-{{?+5Zzi^zoeuK{CJHDI|6VBya&gEQw9ToY1 z{=vue-QV@O9&kUozM8n2%vSp12Ta2_m{df@Xau%vqz+;zQb zXU2$ildBS4Ke2-r7eMm43eD$@%w+>8kvELk8T;w036q-P6jdy|5tT)+fH>q+jg5i6 zk1Mb5t+&|1^LiE)yV`{|%7m!lEAF7zQ%p47F^xOMt#`9~#~tZgb?MEM zCPzNVoQ_gEZ`=}Z1JoAzRbq<%Gd@=tzJB(UjjGSlDgDR3-feQG7gu4!hW_Kim=8DVszT#3 zZ4<#E->39H>8kzYyZpZp6}V0*=HPzfPx)-s&XW?dqA+FPH!momdZ+5x)>x36r5-4vkG zED+l8WX?mA7*2w|#F?RZ_8|GPuImh(u=T%1o^c%P{(6!h#-g7xl}-9&Xv#l(9}&L4 z^auQm6Fj?KZqH&`j&w1Q9YBXT7fMQw+_+&OIX(56YLSQ{(KsmR00ZgTdE$LD!+Z@pzU|95*bpF@pp%&YHrfEfFD&es2GdpxYOjP1;2 zJ!$-jX*&=;Plp@b*1BB2q{Y%M z93CG3T55*tqsM<>s0p`xaf|~K^YX&5w65qthqpBuu5$*B737})(YfAUA@;rF)<2|p6o147Hn!63kT z2nlHs%1bv7UixVe&WWp^Qu8`l>qn@~fFzXMNsKVLSkEj#bDH(E1`(zq=##_(kCwY= z18#T03Mr@fta7Cu7mB_@FZ@r`4+)H?bJGp}#352GC@gczND!49Fe4G!8rV?kZ)kgS}ip8dM` z2ciLL9{5pY&KALzTsY)q%_}55+FI)PwCDFOdrrUD;x(L*@wJ&!r+qJw;BJ?e;aTLB zeNpI6g{)M&5Zx>MDYQX{7NZz9lfaofU#KF#e7xt8={mv->G^%aM|>$=4r za^R`0D~7^sYsknU?F{!*9f4tLvhbe#vwU$+t-T<6|T< z?Mt)S9f6*>%7>3j={te%=BT%o1H_MIeLGI1v&mWZsl4z!aE|{Whc!@#psiKRHh~VV zDZSu#KZyzq4nFTIE&k&w-WIecdHS=$1!ruAMJ$lzMPm5E$F-Nj7j}KAF!=b{Q!=^! z(TwkK-IZU_0UXvFjRK`S9a2C0{dh_qY)jf!Frr|y59ohbEH|>HkU40BB}8g3Lrr$c z<8$L-JLTW6TZ#=a9j>NHB5pZcB$RrOiCHqXqIJQPI(mAX;$?4nq1_6cRxN(DyzoS5 zc}jg@DYEKJRbE>=fs{dDs|b9Py+-Ebe?VbdAB=@IKeehLRR^YuSU_Kv3m9MmGs9qN zrFMxZ$HPm!>}?I~(;h|rOP&DtbZWhOO(s1)=wm6xZ<)H~#tEj3)qNbyk{wLG8{%Qv zSA#a6u)A5O(?o_C;3*-(4lMa1_<(#y28r7)ig}^e-RD}li>EwBA!Y}S)fx+Lj4I=H}GJKDFk2!EmNXcLYS(#CnWlRVWd zyz)DkatnPv`}ckJyTo#KZ%3#A#7wjUVOy7!8BbNCwt+LvkHN|JF={m5n#}_FIUE2O z0d0=;-T77^=>3H!3DtBnRBfxc|1hpEdWY-5IESRsZxNOCv5W!Z)PSA;NCU+0;}NTUDqH3LTwSS(d*f27Cc z=&xKkGvz^S0C8R}6F!2DhK@V^x4;0uzNa{)uc>fAZwyHxbj0cxTW2UKP2t(NF8YAS zf$qy4ZuU;lx?Rflas6*-jP6_T*_E7Z(2Mhf=-a7McBHGdfuGyon7uPtmkqr=W;IR@+~j&H-GZGj(@(Ze_{C*{#}1%1$QKW=f|#S|4=s+ z-28{v^uvEcBj^A3Pkx>r?4I_|Pi|k*tE-5pX)!p&XHP%i-~O*DcG$o7N57XY8qQY# z_A$`|#qi?&RiuQtI7Yi-n-Cr?|Aha`|K^AN?j8(&>GJk78RjAmT*B)*Vf zYIK>40oMRGjga;Ip7v5*Dhw>bx)vD&xI)^P93mVjrUD5FWv;qEj-!tS!sI#}aqIIe zMTOf^r@z~xOQ_!=qaUdL2jDk}3It_I)d4!?@S&}r4Gz~KIx$rZ@^ggBEcV|1 zqQ2v^F56vJn|&}?b=mN1XhSZ>v^9i$fcp^!)BQ4}#&D?ZR>cL_d^X$I>js~WejjPX zwh}(Q($oF;W4+#Vs>yV`G~_|H#o_|jkr&FxzU~8l8Pnxpsw1^UI=;66Q;(@kRPXd2 z)E9)xcj(>d7wWRf4~|Q>d#`d_bP-W`6DdXBM(F;^M`M2YS%NX`J{S1EH~3}W5IKW# z#dh171z)to^gsv|>%M?_Zufc&_My@fV*zQNBHnnSkhPsx57IstsNP3;{SXk)VsPsW zW2E~BNE|eLRRUF?~ z^E00t_7-8`p&s4ud6sQ;DEMoG@_tE?LgK;a zrPI{u`9_;7=>qqIcciCNO3qu)o%KhWufNJBw|un8(o>WLhkTQSL$wQA${zi_L3!VN zwDfzLO$a*{aXSU4lCXhS66r#E zy!2B0xa(=!27a>H8S|mt=39ENHZI+Fw68GN(3eYOK08kzH{Sc$wQ2cnc)`Z5>km0nu&M8c@uJj?a?xXEfYxt%+~bKo%P zm`+PyY7=dRV(s$5H*+zSRAM(DSegu=rLP-2kg)l!U9woT$%v<@*j~@$7 zi>ENNz7VFnaviy>_s8Z<+*oNexXJQ&Saexq15eq|ba8j7FQ4}>DAJnX##o%!v=_fF z18#@=m=5bCo_e6A66onHS!r0T-rlaI<9Yq{Oikix+WP5xr1?eaOU#1?*_L>T%DFbu zuD_C$6G&Y_mOiYffcZFe{L;oW;&PvZcxs-m<3u>0#0|XOuFH>ZB1qr1^Ek?UQ-4WL zp2Cu|K3o&?K6YPOH|aI{_n3MHd_l+wQ>1Kg^>q0Vh?)1pJl%ce8@sZG!{KZ8u!eI$d8RZU=r6h-yw>0LFb7orz&J?KdH7h8=ahlFEDZNS> z0*_I-&hcZNL~NdD_xG6YGVOoNh2J0Qv6xWf`SpB9)2Ev6drXm531j-=VL3^c(0U4r2p;`H-g_?+SNDD8`0u7WSI?xcuyB3~PlSX14qe-(7X_c$Rr{ytuco#aDZ zlJ$)3;_v1B zpOJwsm#fLGL}kGRb!Stq?jmbg3Qn&*KOF6#75N%ebLq`s{tmIKewf&bCah zZr{-V^|(#+pYFJ)Hec)I`F+Z-4gDWgTV{3P>LIP0kTMC<29ExrpVxOw zK>7~6wyKzZ9s7S?`~OWD;Hv-Hao2wd9x?L3`4;TUHdnsfpY`wXFMWfAoT9uMdB%Q& zQjhkt3-KAo92dJ&5UA3IVmgSO*o}HyCE8y~J6%W(M<23`wwEKOUyq4z0L*(2>y4$9 zd?}}MrFKAo|7+Ql7`lK+hEv)Bhd$^~T#Ip%LN{)f^zlj_!hKN1nhVek!G9e6DtuC* z$bJHYkO$d0lrWQ9>m*JM$u13dxzm+hQ1E6 zAXHdD41LFx3|`pk;=(^l8FxNQl^tv-bwx_OKKA$-I6%-?xe|1M?uvhB>p$a5Dghk< zeQs?ppvy$7v8K{@sqvuGKjDja1!-Oen}zk3K1G|AhlS@@5%RQ(Q?#A-SIV=a3``zh zUqcqjpyMK|c0-<_-!iLrqg|B2gCRj=3Io(vL%Q%b93c(vce-3g(1^$=qdt~He`WTW zPV`5Jrt?Ull}%26Tkmlm*G&u*`tJ0OermLlI1T~?;eBIhSNeuO0{?lmIYO(?D;t7( zU&*GCn?*KLkgEL2;J|s*GlKZe^uIQek6Rn)=O<+8B^(2u-CC*xS%Mb`Knec|}8At(G0oy&J*Ij3?smvcFnbNMzX@^Ae&AJDUR9@CS5 z^cG!x*Vm4hbUXRg;Xt>xt@!q#^t2AW{EV;%HxE8ydTk#5clQ75=f{^4=@?#JG2I+= zOX-9Cfvy(a>VEMd=SGNjQtPE%zmnLx_5n3SJ3p4Oryc7(uWt@Vg5ExU`t!#g{P|;W z@lN)LA>>5JKG@Y-VzWJjNfFc<)LRQ2eJ88KIR{)VXF3C>fP6><%A`Yp#gF8=OR7O4{eJ} z8~TwGP8R6yb@Y=YkU9dM>hfqZ9QTXNC$0!b=%uI)(1No zCk78x7EW_g=r+pwy#pI`NZL{vbBuU3H#MC$=x3*yLBZnq}?egcpN6yX&0b#@o_|xUyUr%<-m0fOY_aXGX>8bav-+V#gUqhCLeyLw(!gNO7$Fg_?jPOM=w;Pf2 zHbJ#D9G4}czmN~moqU7aU3pvw;EC4&TRryd1f4!I01 z5;n7Ny4|xR4-f)9eBXgenwvmMg47bNns^zvaGbhq_cthem&0X|v|X^d2JOGgTA@_j zFRP>-tZV%w?~?VDc)(*0mZEDRZwEMux@Veho?@USbPfEO)!ETpY<@GwR4xMJOCOhS z*KLG(_cp}n4Es!yHkvu-n@xU951Ej+Qv7AWbdzKZ##m>@KVov@*HLzSLs!Nj*4z&7 z@vdeaCFP(%ji%3RhHP=&!c*vCLi zvey(A{n{9lLs>ixhoyV#ylnTt33<0sv5o??2A!UA;*fUPWyjYq)IJPjXm+)%wYW@z z?`|=oYeA%XJb0a~v9o<9vnSdP$4k5eq!=Zqx!ng4_dS-jR04EJzuHaDF8RDa zNs8FIKRQU*^KtX8u-h0FVvTLtWKv_f5|;z|N!T0Pyy5ekutkS_Zds_t36ye$=^NC4 zNyKjM|JnS5&Rv}riaAC5sqtj7xX#msuqmgf8Blx0>EBId4-kVzOt8ocH%^`(3Fdmn{~wGnX5@Hn;HDpmg;bya?QK- zJk0%LuWdKV3YlM^uFqY!h1T;dGMTqNcbji9Wuo?|0^+(EwiWTe^E~28*GEbt!1eRE zcI0x^%R+#PRp;YA_&!gS0cj@aENBbSRJ40|t*HjveTwYHWxkQ!#MpvP6wXHAYbiEl zr|TK&n-*FBJ{k=|nt6SVXVu_^$_Ka2bRXJRy!ry^=7c|T6vHpmxqL^JdrAAboXfeK z%ej1Wiu|1~UD3@q9@EobJ<$74bk_Fae|*h9`elvef8nPd)9zZa9rv#<=<>NnQ*XWc zfPdRx={R@t%g1}Zwo@Fp>wol`9zN1~4xfKQ*L%ejCld_Abd& z#rk|HH*~Yt$nEyx0X?+z4VQ=4w6io2)-G&F_~~QiZwG{Ui!v@6Y^gz+71;9*OQ^w2 zhcbRW+`|ed+u$r)LD=vJeql1vf|(9W>rnp=`^FU(cnrpa`2c&nnxw%?*0{cdNfYE( zN4Rx_YetCUfGA`#7;xnSF8t(V$0Jhx+n~Z*-&)Xp@tgy4t?|%ds}YLraFhJJ{UFDs z!NJa8oUh(dsM+j`w(C%ht-qMEfq+DWz7Lp3pOh}iXT4uKyrs9Rkirn$;>68nZygHH z7c(H&+>Oak&RU)x0e zG8cw%aUx#SP)%omV}x>b^FitY$_7C_jZXnTJHl39Q7pp7L?;$H=41m&mwOCHJ>?jrNh2PiVf&j*lMZ$a(mksy{=Q{5z@bqOZei$9CdnhJRhGGve`rXr(|xqkc>(E`5Pn?YzKvIx^97;X55?q@OPUgt zrO<1k3Xcqn937(en%1@m>|TLm^@X#hn@1ZKP~i^EDhBInF$O~Aqklp9jQUp#JNX4u z8}!W=CWm{FZHCyXf46#ePX<1}9{58Jg*q5&8f-E^k)uu_3_gy#4oH4GKHlH>{7KF!!^%!7IzP~KuNzn*&Ib-sM|mtI}3A6jfCYRBj{H`Mrx0q1ju zC3gKf7vl>*Zzv$L{iR>TZ>HOxGOT^wyKIojg4UC6A$MOe zj-!9>FW5Y_h1-EcZkF-QOK3wdU4^DI&<9n{#fBx}y*q@p#T%MV$gW$8g!ZlVyFjphs|9J|KnM^rYdyX3bR$D!u?SXg{~fR}$Iwuv}jD(t_derrAL zy5qjvV--$&qw_UOML=3#Y&?O@4n2j0>y*cYZG+2$gX$VN4fgWuHea~4`(1x~>|uS3 zvS71Ht&00cu!C>jqgmg>$S-rM65 zo3%LKbg=8fjlY~1rQwOm*f%JAanh4~_Gd{=i>69^XxCePhqO`hsz)1i*;2ziRvKbc z2SK_M&eRXxt}MP$L>ot2Y|h#Xf)0 zel;5@KA(DQHkNa)+DfLs)HW91s1`ng>E`QcZ!ezf^JxRsKeqNgMMaZ0kH^qPVQF!-piYC=X=Bk+ zX@ngx`RzXO!;CR5EVW#pbG?>`E0%|_5if?@*3CWw9VYm^q%OFu3T$|fO)v!W7ty(} zrhlbKGbA3Xzf#&RASIB?sbBX^-^BF*6cdHrI~d<^y~{V4^f~g^U|n&T`!mYhjnby} zK-;@|H{;|?9cXbZtQGhH3tUUY^)#Hb;CjmrQ;R~mjg(cqUK21owu7{TeXWCm2AhXq z`jU)=cr%SAu3?rK&*o^j2~%TQNVf#(DpXc2{=BsfMMs^vc)}-BJ#3bfkIa5N-Ntky zK!4;D&^95*`Ln8Pb;<4diOOa_mdoh)e&Q$wf0fSVJEkmjF6VMC=W;IJ@^XB!&+?!A z%Wu*CpS(@Wt1J5TQl9+Tw~w^{%<&pnFZ?C!?Yh{~-SC~_bU+rEN#G0~ z=f=FgHu;(}yRoh{Vfj_~t z$>j1(|KF&bklWC|K`$rIe5U%(RsZFl`X7^GI<5aQo7BdN(TN6V(0_h&{a>BjuqH6 z{*N%>^_lzYKcCWns(wUf!o;U84EleUtd3QU6ubF7#i!x^ss0b+j`ywZjR&NDmhpPF zSz-L)5!5piWAyRrlRzN_;L_nQ*?oEfQO_V2-_0w9ooKe#nKy9hm$#>NyT3GRR@;N` zE>8k&I?S?P3x3=6P-CYD#%w%ASvHCieqN6QUPHS47Zn=`i^52c#>$nkHDB?TR1lJ(I05yQaeQ| z6Z?z&N7V?}5CRf;n5VEry@J=IYARFZL!lFR`+eZyE1jD@TRWHlQ28-q=W>Z@*`zI&qrd;p?AUB6nF%9OAhp!cC$11)0}er+qR z;XwdhmWsek?L4Ij!q1@(XlF_TlJ}PDwB|<@yu7WD+y)&H%kuwusy^Fh`0Mwq3+OM; zZ#VgLDP*h<_g1D9pvWp?%wr5`7H@AEraUg(cEPna&>(%+u_;Bi{=^ZNNe`-?)q{6dc&v=e%sj^QgW zE+PfP$1g7E_DVm0_~?e{@2SUsc}eu(NkfH$`$@_rA}1tt+UEu8rrLh#uMsiA=WFFaog|OHT`1aSj6>!mPr;H@S z$2g$`yUhI^UMyCwX!icvhh3R>t`Z-gSHqW zJfT7BxdO;s{Jc~svsLl2!$lwQNd;SOQ7#h#KlQhO!1YueOa|pkCTCnu9Y^1cPsnqG z1)eQPxNi5$2xANU(+^zr&Hu-Ff&%*yb^_>1VWAa|_4K1nsCW5nd70!iOP(TYCnOnR zJRyBXrWJ7bR7%-%wOf*Qp_~9BPXvAo{h&Na;+6Ty^^lV#+s6*)sSYl-K~HXQ?Yr9& zn=m?rWLZ?B7@6E)QBI=VF6YnlW7qZNb_*e~vmW4h6h~cxzRlr)<#l0ZWT?5f$50 zo~4wx4#NzNXl64VLR0MX?$T{k+Stx^N?i!Kg%lx4Gf{zpUaBrcU+h}@7h_B5b2`~f zu)vcj5~D55#mZz(F#|RhdK<{KY{(w!*B}=-4zUc_#@GCH{O+Pps!wIyiAp*iKb2aq ztH1PnFeLXDVaZ zC%fc6D&WU4mLM1lI3?Rjwv;;keoW~y$Q)T6huiga{I2zYVo9(pPTz&IXKgI%LAgU& zLfsFU@e|yfG=m(=#LosiH)UO8{pCDJjHk;&LbGRz7K+n-$!LR^4xV$$lHh65JZVaj zennx^B}fQyq~uMf#m5@^+WD5J{aZ>eAKy&9MA4UwGp!ij zedj~41{Ovoc*O||-|W>vf9r78BJr}N+e6cjadFo9;HUH#k$j&zERMVUM6B2T-MYQa zaU-Jgvb5Lh2n*nwI&^erGnbbjG564+?qd?Sf<;y`Q z!kYUFlb&j70h=Q!Plr9jwM=t>!M(@6ZF%Cb&;B34PCEaWNC6|=cpM-NpMMDd1~1Ax z@}0MhD%}kb5vFDddug=At$PX(fT-j^gz1DlRYdqA_lE}2pK}Az@o_O;lJDvxtRcw( z5r`jibB5X51K1A;6e5+tt;)W~TxE?y>UzaOrvK)f`bMNfI@t6b(${wwZ8IDFp1xB! zV&LvuV4kIR!)#3WVoCdI?TfYP=`DSJ-2De^@YeZ@>SmW)UvI&bG-NzRyJ>bR}cz>G5q?O1IGh^Jt63^(8)sjVPPJ zJ0B?OkC=6lNV^d0K+aG0O>`31RyyJ=iT_o^xLff$a`7yLcq!Ty~~JTLwi%tAw_xc=(VRz2d5 z2-VR`X8j;A)zF?h=5bA^+%kL%J%|o$G)QpEJ%1bH>@ueoyvFf4?&%~lVT#*n+l3Ev z;%vrp7=;${3t zcs+!~$L>J82x}bmTYq~hisP4yw=zYAL#q0i#sVs&%uuI*4t2=NE-={OdsgAPlWywcAIO^i@`4o}+XaCq&$*x}HxgmC>-Qo0~a zbV=pV*Hv_4-0jC+KVQC;@PN7ypu7qg>b}ZEncBc6P+dm>ejLXOQ!Ru|V}v)Xdg0K= zebNhZhzkJm(q2@?I}9LEENaF}A=IOQPdr|>`t`bY0Od2%ItY~%47VTy2-lBkCeU78 zINV};93ZI=AOo7hL+e*4SE~kWDMHBNnBpQgwp$?Fp-zPuWg6}A^#=+G?Y771#B>-P z)*DhG_6`yF9YdAL-GkH-q(9O$v<1FqYi+U4A+o(+?)$J0E{i#lxAA(B z(7KD;b6;>|;HT5}IOV@BfZW(-#s`pgA)zwKpw5Zvn;kB?uu3xlw}x$jG!~e$p?A~A z;w^)PH~u^QD0`^mdf^leLWtngAYh$_v}rabVDoV?DDqX~Sb#smh1lSesGTm*`Xvp4 z@&#=X$~@{0!h{ANSY_P%i+0BAY-9vfDI2`E^9|ynu7j2ar9+RtuB`1Lz}uPH1LG@A zkz;lS#uWS8zni>v=z1ShNWX31Kg( zdU~&il77>nl^-Oua_F6C>YPYH1t}FMQaDu^a~}5i`6$nqk>X9Ep#PQMtgD&rO0AnFY^C6Jd0aJT48LMeWbiu#reTR`}PsL@f>_Lu& z$8RAEzCniS=0tx<*!6B_Jb$Tu=r5m+wnbspkG|>sfJLK|5 z>VF&EYG-_sL-{`Y5^Z>@CH-z*IW!q;Dal{@TwyWZ0;K8E{W>-7t_4=slL zFzxLUb2kde_b9%R_6BU=+XFqG%IBJ5BU>NGN9(ckbR?~KdV*30LGS6+Al8k|L2cYG z(jU!A_x08mZg`LJ8cL?};WDSVc$Ax@G<^Vfy>6+*8t=M1d{yJfzB%S$u7Nata-R66 zX7-oWCIijRE3d@T@=(9t+p@mvd{brY`d4+VW3$Ol$F4)BKg~IKIh)7U<3 z(V?VJbo&o!?|Nzjt>?O@r7#_NN|6dp*T=Zp_@*!B%Q_#rf7f`UrNHu+|Cdj-{#II> zem&CnnE&!L2MlXX*8o$$_$Ive?Aw`c=I}b#D?BY-ABk%T8^{n#=seo=>um{nIDl^U zcr((3QKWj*G-Rn4K3Cd*r2f?Xb)<5$)EmkZHaZ1TFkrd_fz%|&`aaY+AU0PqjJv2pj?OCWWv1LkF!hQ>B=*rqd|N2nbB7wr9OtZiFz9)?xT zwf8=cd(M4zx74E8q)g}$lc6JtiWCY_-1I*uwqXHj1+f!~WMn}_2|L=hlZ36MYR9}Zx|{$U_&1dbnwXuW8`v23ZM7bKB=``*6yy!PH}me;7~S7Xk# z&$&%m>~8IWd(U2Lt}(}`QKPCxjn`M4At9C#*5e{*D?P&byN=;b!%eJp==ca>AWW2Z z=hSK2Ka#Y!5xzm?2j*50Vodq@+`~qA5kXH|O9B7W=5a>buuA(}OSl?8!Cw@w~-^&ePtlUy+@H&iO^VP8h%D zB_f({_KnFm(D3@S<9U51XKL$2rc(wqG$##D?(Q{hhY$Jw%v0yPP7=@3AhaJkfp!l? z>^jXz8{Fc^UwHfI&Lrm^@4U=!P$af*hX$Lp^3)L8?f&z%F(H$AFR$zufhVl@Ghi89 z{G1wN3p7yIS>EhF#TyswksQiScE62W}we?d#Vi+4&CYm2MwN9zM|?#+$8^k5$mPH9#OK;>A%H> zRq0wmcxOKJ!Kx!kpdCTa8WS@qvIIUv*?Ge~`iVi;V?1DKVe z^apiw2+d1gWJ}jx5d73^TC<0(;QHGRwL;1pwN78jHPA#;zlO{OS_17@tDv2$&<2Gp zXa%G*rK+|+Jk>2s9dcc!@df_hqhG45sF%-Fj?Wp50T+WqQuJSzUVhZP?$kfikZ5M5 z8+6uoPbu`A5eeXYcE5n$pZP!AjV7VJP=H1AT}j%wl886X^RP#Ic0cAFXhWb_Ks|_z zV5127uE_(GG}*P@chwWr8cca$8E^GU7_J=aBn?8h)vS4hCs|7SGtupJ&KlMvLnfY$ zja@_zBuv4pVV)7=<7Rrcq}*~R#_fSChR^*y{sy|?cUc-E4}^t1Z|rj4Yaip zScHE;tq}_QoQ+93=aE^c_+D#$Y?lcDEkJ0N!;U^#I@HTT8OHjf9*IMuP(w2}Y-0nC zLEIF0V$LW|(M+ly(B_se-d_Ek;1gykzXp6c=`}ORKbU=>m3udCWSw?>m_YCW@oI=C zvq-aap2;jTjRF3`x}_!iLv<5Bm*Ab-n1{l*kC|R?)pzDdcg_FNKJ2_n`-x#Zr6$LB zlI91W6iMk~zmDDcA@V6?h^YA{=jTOy=Ih<`o}K@1-s^PDdC<=7y+&{JobUMY1BH6N zKb-JAo$e1N+^73=pYGE?z$71hu+WeH7mw&SzP#c8`d953_04gm%Yz*qJ%De#Udw5t z!v^2Wr$@SaZa-YSv(odAY?l6?|L8$}=?msLKcmB`EX(tlkMzv(!mduAZ;m$0I;^ZA zBS!k<^5mkwu>9Y6N%Uy(ng8D+dhw|f@%7XHU$)P;f9>@9G0t9 z7@fD_6!nEsS-DO8#_iH@sSOg-p93^y-pWfSjMCwY=P?n9choUF*3DRn*%`UBi3=K0 z%mUt4OeXS2P7>O>0{q-2E9J%4WQ-l|&QJC)aZ`v28V+wjX}eaS%^gu$X&2VG!3z`I zmgLTxn7rfy#rRcW0vTp6_FID#2HFZ9&H6T=y5n|OPd;%v(3ummeL|v5855+7ni$9q zTyt}_xtAGj+dv|7Wrs@|*$(X|Y%d|4Ce+K(Z?$;BIH3`45Y-5)PB4RNu#MR7GPRAS z{IJpO+SBvQyqU^NSzX|_cpjT@IqS!X8ujo5J_%N>4tWNis|>o12TZcFHQx3x(NMU%a18~w9RMZ4Ppy59eLo3-t*$qIsq`F6$;HJ=xS zf{}9R_05}-NOEhh%S=0nhW69NYhfS@%HFjtNcvKn6kUdkAfAXb-3$FHj#%HDEaKsu zycxx&|3b)1B06&u73~i}`vPt^GHsx7L%xG}?3b)->cb{u{g7aX+ocUZWAQH`;cttU z+;s4;M+NN+_Vduoi;8elQ++IKUo=7Ozl}$w<7j`VrDi|Tj-if(ALVsfRSYohWo`3z zbi4}!owvW5OxC!72RT&c9hNq4HIMh#ZrXU)UkaP=zkv=Wl=Fp^W$hKa2}b=kZqt9H z*?m`Wf1!o)St(_VK)=&x@bFC`zIdCd*#Mzt_Qcx}V}@3-fDH&;gN1eNAGfD#$dzpgxj5|`&>>R>qg^r@d{}P<>>VEDV5S#JPAp+cEQDCE@+WOtO{}b2!_?Fd>OUTCpJ(<*1Csq2BRq z$eQ~Eb$G$$cF3xS7&^+%;YFbeDdKvebPeeJ3zwY*^vX9ZO@@8Ei4aDh&-#A!6;u~8 z8gS0RPMdd`-mp|ZWWOgwy6FaP!nL_rprts9jn;9jg8;@uAD5yYb~B3hDCQAkPVeDq zbdwM5-dgpyc?a+@n;gH9%K_L~%$Ih9sG;QK0hJ@eoFC+q$dMqdkMssFsC><#B;W0G z+tR-b?G{F}9UAq}4WLtrV4^ZENrQzclpNPW^75}BPpq7kxcT;Yh+{mDP))fB<%DFY zIF2@RwM*bOHdWsY-OxG##Q>q#5n3M)FhpG%h9K7qr15fDh$0L^%-j4!9HW5R9HUP*I+ZfH03XuEg*}xVD_0LY zw+I}IV5z=~&?;fWmZ~R1h~}KkbiIXg3*w0y5}d*3mn?;A5aBR5$EoV4k+iRZ#>H$s zIj>aL7HiXTTRgEaF`~9Nc-TUUcs~7QB?#XG`V_X<=VdWAElcqV{f8CMl%mZh{N!OH zPUVd7D0BEQfkRs;hs3$26d4wf^L#(lKWIVuBrzZW^}XxRN3Kg8YwTkW1tMPd3SniU zk8nKI@!Wl_kvHt+BAtg!rBesjae{e$hMMs{kN#Cug-r-EaogVQPK-G=_X(Y+98cG% z7VLSvt9)C4jXrmtWj+iu9$X|Uy$pL23LG{{qN%gb$+2c2B{z78iK*KpsxgU#CObg; zYoY!Q>SSfAHmCiMH81z)(MMd7rB0cvLy4!sJ?pNNSciuA0!R3|}`f8-Vb@37ZbAS5zCjSWE zaCrE>epz3>BD%5z4!nL$&)IO<9$qYTbuf?Tlk2zXN(>L{(J9XjS)RZ8>^c7dJ1Aka zeqiZJG>`{1RWqbo3kX{Tlyiuw!b&QM85(j+(q>QLVjF-!ys!%LdfWw0Ym+wCLp;; zHspPUKJqqReNMdI^L|_Mfp@|v1gtSaB|9H*qDWJ}x{2`D7_tGMcQ|Z&t5wGKI|% zZks&`2}^yq(K8TIQ79PcXA<^~@NM)uzzPE8F!jq97Fo_L{M+R+gw7^EL%7%T{@NJ=YJE1+o zS6sn=v++fAoIFPS>XrH-N9SkgvK=a^je6@}-*C7o8{R^G1T;F8yy^QFRPf**SJ(*6 z@BKc)Ed|87zVCMQ>K%i%&qEPT^7S`Z`)NEkU(R{u|HQ(3+6oAxvpl2*ovpCyBx(~Z z+=(Ovy*LjOy4j(sGyFl&eBi#}9Ao6Q^V)e{w$9tmMYH~KQ>3;CV7&?S_v-UfettY> zr^`1wjGtcD2;=h5Y>tNZKtI{#d5o1B#*U-U8Rm?Xvb9-&$Hj6fy)Zv>wKht(KUH_Z zUu*d=$2p8JZ6Vuh<-rg<5e8}$gi!O93?e%08((>hbl=EojQ9NLbJAL4kcFQp2Pij% zAnZW@L4wbh@0IUy`$<8;AD$KrdxWbS=@|SR8-WqBA^7L=7EtXY+1O#7 zVH-D`?ac!P$GTBp?KRl=oZ~}iEu!Mp@=z*-%{9CK>^}L!at!M%RUNFsId#h;Fg5`+ z`XM2~d&r3h1)}^sjU9{gbHM!Db5_XkJj65(G8}DU*5(m!7tL6{jcd?HudK{G{C8?k z^<#wz7(ZG_o$5#Ng9b;S`Qj;tt$|P%7o0xr0^w=!U>~MqwzvBSqmOcY$9VySW6Q(c z0^tCZHmUgn_6uwzYDk@z;4@sAvAzA4^+C*Qj1LypWbt{i%9V$cyG;0cn-8gev4=SE za6;=VZWk}Y0HyE2K40|By9`Stlxq_sUA-sWx8dz))g$Lao12U=;eosM7v-a@?$3`g zNW)5cdv)&Pp(S$TRwTouj-+|qK95idc1ZH{#>?^*AAA8qrL05|CP_mO-uT$$b0T(m zIJ*1~8V?5>HzU-hw&fwrU!Ipg@ZfV*myXeCPe7Kye~3^Bb^xf@*g~Cp2t1@SGR7e# zl&b~wuIvM+&ojQ}T9EYd2iGk%#GBiVSc}ku6cA!b$DO0e$>9NIIH6dll3CyO#y+Bc z(3pUpkE?$VPdLMZ==h8CqX$0DLWoxk6^Cn>74DNQEGHhG^#OR4XZBM{Pr0w5VIQGw z!H_E$HUih^FzK8c=RRKl?7d{p63GNdK2l)W6%E6!p^U zW88|sOz9L0-{m&Z-i_7zap#$Ik0##c$w`|xpz?KTKIfhFcG%K8(#C=E1)n?G#m(`n z#h*7zoo9kKt;1QLCK;chxH}TuDkW}z=K42p@_Bxrd%o)!?_}PFxDaZd@bo%EgL#K$ zp1(Bya4&4H^q9yKy;%dAt^{-={3hqh*`Z~U`+XTc)8X9tQa5eye!XB&w=$V+Y5Di- z+|sl28Ez~jpE-yk+SPyf3~P<&tDE)mbxG#W2#~p-@b>Js&*S;IcW1gLIhcRC?K{uUx=uqiZH%1DDmtwVF^<%@Bs(^&+Ar}Q^}4bSzul*8bsptr1kJqjioK#8 zm)z>!w1K_c&VMV5`gA9r;rB^Cq>J57efcHJ38?*$jVyDr^HX81fp{5C(wsHH0X~C_ zB;rT9PSkG76R)<<;5CdD3|@N86eMA?$v z>bB*B{hP|e##^)O?cgXRHgDq!U_swhG?rqp94Ki_ZG~7sZ39d?3GoQ56+I*za?B zvpg38kpYSX9!J@cFV_B~{XQ{elYmFFIhxblrNMXJ7L^kCS=av@^=TZo)59ozfsE*N zZNOrA{vP(^j(72S=ne2icH{-sl0&X{>E<238o%7Z{9QKmzOV8|Ie+3FIpKRV-5*T2 zPxt9Q-KWo(G+e_!^Z$FF{^DPIpRVL1`t(cB=$+FK|IEK7@+aJz{pufB=!f33)sP?i zz9aJowp#J@N;I^?NKeKf>!&<_bX|8VyM3^poESTvxm?|xhH87^!&4at57Y4W=NGzt zY4-fV?P;=bVR??HJja)o=ZF3pI7uIzD*j;`=>IqW>`&5i@vokorFUtDTt@gu;&RCG z)}X@W4Ttd(Zib9}39CzWyV&6+Gf$hqmE##u$OX^K-D$)LD>}6SX$bU^zlLp**$F3~ zXkgp%cGjM+Hy$70CJI0@cBh_lV-g{r4Iu(f9%s2YW3bu#-pw6!nCDHVJeeJBMf8i9 z>>MXP#Kb*lVu1-5>FtFDCkJt(4`2&76P=)M)Co65`-CtYe1g6J`61d3_fXJ6sQ-)hotv)yXlH>eoL=A3JA1?TrBrG4Vv4XX*ii3?bJUYpt3i)NC?L+V?_wn z!dvKOJU_*&V&GkGR5i7TRJge0jNGDed%;GJ;i?a6_eDRXgkk*!*W0$q~{TYqHPmDhFd)) zpX-tGc3eVHYkw9WWRlf_k(`Va>fI5V5^}f;C+PXO7FJBAfuG!Um60ch(oY5%K-pu^ z6+(U4nb0;}jrLOMcbXYk3=z;Hg-&z`+~C_(#Z(U1C6MhL@0=Jstie?BA^=Ofrp*E6 zT6+Dl=o%29DQi*-j%A<0wojn}6b*z3V7#gEnoxJZHb%$}qAv4zYYW4G7&Ij_J&@}^dR4iU&!AHlG`(3b*sx#z{_s@n5lmorEdHh94<}oBY)yGM-ozb z2Xq?@-Lm@4u`o|jfZYNTYT;7G{}=AVq(4*@&h4tUN!dfRQE_t^+Nrmc!)6|6gLY(_ zhYYaNV6X6&Pm@`Isy=MQ00^pAtIZ%cs9!|eK8!qYP!>aepq-Bmfk0OoKOI&u`8d{( zzhGA*tcLQ{ZLy9I7#7EUDlH?SdZlPxrQh5J9>f@3#8N#Bd0!;u)?`e4ycTL90lYj# zK8kjo>JCv!nQ=6LX!iokKWRgtdj_i@~e%3=&(!M5k^I_O{A=i}gUjMu(t6uy!|UAu!Fd<}oQ5E)EIz($KUZxA-%)*Tim9EpVb9d1Jn)WE(Usp@YER=pUd1-wmYeCc!TA=hh&C!nz~=!Nl}bv_;C_W6>SFXOkw16{$|D+%#m z{)IpY3mDtj`JA`$B|{EmTfD#a( z{mzq3%5{ve(ZC<;PSGu+2vL;d)S&;O=E3!%x6}JcX-Ius)eEEXD@CEZ~ zo_+cSTCU#J&o@{9fBwCH=EJ1-?>;nKs;j5v8UEtmc}V~GPyGa4eeQop$4~!>g1|y( z{Rpq1Ka|5bpsj8jti3zTOF0h%Vk@C^pAMK>b2h%d$C?}>)hElh z0cFM()*%+qY+huvjCNf-t+otaP%N6XvV*v4BSJtc^yLP=nEQrR#DgV=7xOj;WN|Dc zurG9va3Tf!5A|PALS;k@c!0Sc4T$m(BoAnRCzYm$x5G{;tVY+(X?0~lMnL%XgF9Fp{ z26HVri7);fpVx#(?aw>*{Nl`nWmUUm-v=D))%(#FpXianHpUJ%U)REy4Z+@y7>k7%nE{xjfApO#Ew$O*hbUEw4g zLiv?dc#{hT1;ILKQCLxg)Ax_K8LQCH0TKVANFR29-Ii*5eGx^WbOYw`R?_||&)Q)k zqwVrtrc1B*=3{o70QxHq`yV=fW46Nh!<1{&H)Zz@534z<2hR5kSg=XkF4RI5C_L{q z^@SRuUgm=uJ790O$wq{QQvO_)jXJ=%qjK#~w1 zyQ}-N3`!_hIRm2#vS|%K7}Z*wy8z!to|~QFu)%&%XbI?7F&lIA{#qf7#ps`dhdomI zT_1J4lIWx1GuUShMfAcJ*9MP=Ua4>M*mQQhqR{b6m2>l{H7tE*b|1sTmy8fyp%R+M z+ocfMe()jUe#c^et}Xj!Er*=Eum#zRW-CCR5Agh2==xb0BT3lvg7i0eUH#`((*_)Fx@3Nww zUrE8gV@S0d(Xau!(EG}4k*KZ>|G@3}M<*IRGz`Br{CWEvW$D(s#EW*d4R#fE=Y~JV_Xx5k&m!qk9-am8%DRX9iu&jkhb#DZQscA!sJ-(!$V0O z#F+7Uqj7vaO7pSukmGgDQw};ua39GJ@u>(tiunuG;bpOLFyc$A?76L0NOK6A6(RYw zU&C+twC002-x=W{$U{)6em(66C&mv$k4=}mGoYa#J~jG>Z^I6yuKu&XYq0iiK)_E{ z8eA`I6D;19JPbVg$6(S&hzEy@KQZIxCFeW^LoDe$!sTK-cvuRBxPJ&ctuQb17FL<5AG0uWqmo*?W&luh(+2Yc5lLJ;Z2)4;cETzocIz z10B2$yAox+rbp%kYj~P(IiDX=g)KNz9=Om0Kzf+rROUxDe~W`H<82Lv@YYPHg=h(% zT;1olqI_@H>S0h^e$ud$`1~R|hI)zvExzeG_;B;hQ+=-E>SH`LJsaT$ZrtXpuW|(j z)IVxCL7#gLyLfnFGBfz9{TQS<*%5k9`8=TYpF5F$7<_E@YFLM~OH}h0n7&!#1MTn&hW=~2Alw@!^-0|C*RjR*ZCu>xb1wU9;i#nihod=WI@kM_ z$;w!((6BY+e(06eJ9sTF{}=B`j$sxKhIHH=HZbHoH$I20csNX*eb({B*H~^p8A@aI z>Y;!LGxuIJ6&+)Oa5li>I9cO|*~SZwk|7~q!4M$1KAMze$ z5BoWEPe;CMw~s^_u4+8jIr78m#~AAc)&pa$V91h&5{O#lH>h3bsGA56GU_vt>}r`M$6Zqc9rncqM!_}A+?%JJoCR&}_g@4|E%#J_x^|L0E5(SK+9 z;rvS*KO`m{Y!7hrZQoJiHz@e73VRZw?P*K6byqh``Sp&LdX9A7fr>dK-_0Y-+daxW zVahbmUtgc{lsz|M0L%;@6JZ}vu|tS9P2$$%xV6Ch&LCEmixW6+bYK5z zPygY!T-=`CX!Q`vD$^DMub++e{&-5Sw7din!nF2-|52FD^old4gE%A3|F~A$3Ikfv2k$wUC0cj^_ z>+kq;JJ?KTWVIVsM5V9;ad8`yw{{_EgxiAN&VCS6(vG#8=se$cq~3OtRGXD{Y@EPY zn|zT}x6UUo+kab7YTcIQhR*`<+Ih3SdRI3ouxVM2^f5LGtbEL}qw^B%X!H_rVs1Bn zQ5hvkUvWEjmF?OuQ+7TB12^{+L4x*Dmn|G>A{<96V-}~W zOJ_ab%sX=9cP=-05o2EDk)FqGKq52HUHm2N`d%MR^bk$;&iPHBOFZp|+8hI!65yv) z+c)DC)`fIziwdOKyeXx!2f<6Zy+ViO*0mD4`cC8D69>z;Gyss39#4_5ezQWY+k>C8Z zGd3D^kc+1ReabUWB}H{6%jHODXBc$Qm04%F+QT7<@Phk4iK2mYyV*+%3ig^^KAVm= zVJQz4W+Uhtk@Ls(Quv+K2$$vH8h#16ZA54w(+XdC1?Jvc24y{ zdrR+`(BIBFP(JXzY(tF~d0~r)Fia%sW*(M+#6>F>Q@`sC&TJ$-CPHDA6OArCIt0&~6VemKlJ{0}&N z5^(NXHaNXZeU5m5BGiEk57nJ$geQx4rU_)g{dJM1vqn2*==S!1Gq!)S#Ff)sI}tz z2D7?>>x_Q&3p)iJ91|;QWe_g@Z1`aEk^DwK6($OM6C|q}h+3gtktNvb6TO1Fqzx?p z+%B6C%@c+wi(tZ0`EgSM>q5B-XxC7Hki*_)Xk+q)bHcZdlh;n~a&Wjva(xfzOCmUp zK25tBXh4|{8kqB4gpd#Wz%_fcSNyhJH6F{N@|`Elc_RVCK#W@e za^O~G%m>?v|KKgrcGAn3p9w<@XpwQVt&IoLLZxI|{H9sRZNRr8tZ>;DpVYRn8o-~Z zODGW;=EM$pZ8ufx9lJf{^&O0MCfdTA#$PJENZUKf3>C>qB;LTWy5KXwlV)4sZ*K^J zz6jVZq>;)V615Gz25SjpjjW}Mi=znCxMsH|^;R(ICAoe{9fiyAE91wAH|ilIsz$F$ z7eKk&Kq6{T;dAq~{cfu!`=5FI{TMr}ZiirxaSVAKGC%1V`U%)ynP);+*hO1_{)%yq z!j2qw%3q3(KtsX$dim&&RXZ&Y=%OMM!RGtXFI$4_Rmb>P>(A=;a-p8sm;j4+NhkME z4ndcXy)D$yv9jS0h<(dz6M~}B$#z3_z58lXx+02M0GF-V9&jwy1?x> z(qkU8rY|Bk7JFZ7yFl*4o{^(GkiVPb`O@oYwjUDvdokwfs~(0!Z@x2U=!NPG3U|%g z)A8L#%ZeCw!`~1+!0O2Yae-Xf5(XuUEe%&fZ3q^O(y3>F@_&0*C~pYqH9~eA!3F?7 zn@q|*3V^c#BJA*_s|zbFkE3>hSDUMT_*coK%QH; z$&kBbnaNd#_c)T=e7&EFI6QmOvY?9P{116y$oZ1Ul=9a3@< zsHx&@j|xL)QxC$wl2FakZ{>-jP!)9oXO(QdXI*O?4`iz*CqkR`inOC5nP(BHzIMLF zoFeVVQS?A&F&fIGZrkFyt3wNyz-}Y54&q$%knF(7&fQ)K)qD@a`bh9cLzf8XeLsMj z$DdZ1^U!wPdn61QmN{`vQGDH%iTK%HirU3iCbrE zGEAQ-^YiZEfjKYbxV=|bU$=n+B6pAO0;PGauUeY#Kg z=^wcyfBmN((%*XYh#no^KP}=p_TtUo=HKyWKicH|%Rl`kx?#h}Ua$W?U9#br-^kmv zUK-}+LGjlN8|(Sy-{jx-fBxpCoDs(1hu$*}WI2A{$&qE*lgd_>7CzG3|BWZ~{r~Jw)1&YE8Co8`t&Ro4 zha&vG!qzz)o5IHh-0OgISEzY%XlSSUD7j%ZAf_`61i*C$#I8)vl)~n%0Fpp$zxBCo zT!gcBpqy+d-1DsQw~hyE>h8CN}cT47K`TC(kz} zAqO!W#tqP!5YW-yfcm_ogf&+9FK^3$hm3khnF^IS(TCwJE*={cTlViubJ`TLIH8rx z>9Y_9)-RV&yj@fI1_$W$yeODF4gKISWfmr2=BLHpMrh?|BRcxpX0J2*l*pkDfhz$m9@vsOgpH9Pa$+gG|sVy|aBp4yz2UOkRq2)b7Utm(`w&<@<1NyboX$v{_ z5oJJS<3-FkDgFG*C)STv0p;cr(ww0m$RPRyW_)gAPQF3TTL_Hz9X6AD$OeQ8SLl8O zhvA}!r51;ffTru-MC4V%`17RuARD7y0e`Kq$H!OzqY7;RwB={%^heBg)r$z*F>O>9 zEaxO|F5~U+0d+fLe6vO_Xg|>|tZ!ru3+C6N$_O;9O#VkLdFR@5`coOd#L~B7V z7o8#7t*?Y2w1LTRKz|3Fn)gtfk+Es2O9>$7L#_j+GvVc_+bbMvP@d6#Vrrp$xb$MQ zMdhCcw0`k~U~vIyK$$_E(&snZzDP)2K2Mv%1wY%`+cv&HnHAb*z)*Ac1y0P7PTa`V z@hEK66LRQJdzP?}Ps?`Qeq1~mAs&W|j=oZwKLwrxJb1z*!(kKEoq)^@A7CtWUPi2S z&afuBuoW=Qu6Lt7quv-pV}oQK543#Huhn5%Q@=tdFQ_dx^Uk`9>4?*oV{09b842YO z+Z$s6F2*IVTn1{477ZH`zCjyDd~8j!aNJpdx37AiYB?bD&89*4m^#2E8d_q|q|S-C2tZ=n&rvRA$-{bHdPp-+oGfKQ{~C%k8l-A&nJ}QyV(;V^p{iz17?<8t{zbH zSC!IYm4+e;S;G6F1EM%Xp*^&XFa7NHU%!PeoPe&QJ}P=fJm%Fm#`yu)!nyaUhWYr2GE9c~fsSr)T_GWctRB37 zhu^3h2UL0|(JvtzpHjk)U%lh-_!2%96|Y?HO#Y4LLCXj~po`Wp7URZ+g(IohZUB2b=!RzIznOJtlsfA zHffd5Ph9TNCg7L=_Gf+N@}WMSxEyl5;PMu>H{xig+&(Zq%WWJKGTH-Qe$1rqZ1>a)@IT`Y{q#cHXP8dqbbu;)D z9BE-nt|Z4|JqQqO2_6c2C_1-7x3$5wcnA;uWt^_?0}*D6Y%I`F9k1Lc44IkQn765i z{dvpZJv|-a2}Vec@p;I?h0C7Gn;#5Oc- z7`8h4;`*ZslX37ce0o^n*e_nh9?n5Sgm^y=|L}!5$1&#`*#=+Z0OPP5;hzuq@PK~@ z6N|&MjzvM|&}p?bkWW9uW}}9zA}UzQ&;wCU{OtYJ4rov(QX%mDVAtrA zfY!fyPcj6I*$kJfLme85Fl5FDSX(%voh;vCeO7iX{8vp&*>gdxp zs6qE14z44qzeFv>D$42s#^u&K7yaT$9MoHVe91#*{>t{IHf`IBzUwodd|e+?A|#4X zg`dy^j*?+cG;E!R155vweJv}(B7i+fA8ZmnmC1n3*AOAjXBao@;EeGt?5%|kF&f5L zuOU&yc^QZAu3#PMrp61kpD8m;ZA~D;<9@EjxWSX~C3LKL3)(@}7bEOj_?cmQluoxl zRsrRDg08d0A&5MjLRyZ^*=r+y{2lXC3)3QIXWU;B50N?E)zCBU=b+zv2{hlr9LL6* zFnp5onTPhGjcKoH&V~NG`lSz`uVc+^;S8mr?$H4bG5VOI^93KjG?d$A9X^Pp{J_I~ z1mjWopVyr8O`C_h-GFenINz*wGhg$B1L=`;P2h>M2iAOWjH_|eeD<7k-TFBCU|%oE zN!(kz-g~}Tn@e82CZTz2lUb&D^w?Yfn`t>m^J^>Lr32Fk_lFa{yVE^l!hO0=_vt>- z7$-ki=+A%Sb9DXU0sY;R_jxfK%Z0wH({KJ`|9yJv`~TOcPjA!N6YytJos7hX&CjC_ zUB2(6MB+Q03HS{@$NgskPekzLH);#_tTP1GYaN{_@0?MP548Z%^6^a3)DAksZ+z!9 zbj4=jo+FAipDnjJn)PVA9Kh|*K1?^?@oig=_#Jr| z3*-Iyb^!n(4PTdvhDl$z^OHAiBHHEM&Lm)`R@#*?p8nEn)Kjq4oPPnUpr5|x9U9*_ zMT6=lO2|2uO*`$V{^F_r9sS??OcUKe&KBx2>Ii7ctwEV2n2264Cuix{OR`tbEVp;- zf8HcGo7?JPxS`l~+Okf5O%r!D^UMqT&#_&xG28X{bu>)n3zUmLhd#;t4#ACpS|Iss z{aUxT{VVnN@352j?6tB%NgMC!%$p?MQI9j@|F^CGo3S_^xc<`}`aeDEHg+e&8>Wr^ z&x8um`IVls-@K8Z?U=@&jkKw_$<9H!mG0309lG|(nPg~3|D(UorDi|5*WL8G@l#-t z6<&eQb-Npsxo2qyv8-sxn%ahm;H_=~f0RlWq$EEwwZ%}$RuLi+xqC9TF^jJJvQ?L$ zf0m$5yV<7DnfN}#BbTj{Y|!2UB9_nbS?YFbjQ@3jkF!5=ai5(3`Dtnw(v=TtJZgNB zf^E)zm~3y<75wBXvg#m0v$ezQ?bo@6Yw%U;ViYZ<7(; zd4bWs%s)%>2@tJm-fT%W_$|+w-L3n%#_)m^Pc2NF6UA-2t46?$>84IkwtsvMqE`l+ zM7<9Q##ol^Z!E1k^nu&WenwjdhZ52j0ohXZn%+q31C@ZK%P@`$Wm|Set1iy)Y&(W@ zeoU`R)sSa0dg--+h&vFu(ni5u-+QO=38yl8{K;byO=A#({$pOIfkZzb95J1?k(3u!_Dg$Y&v zKpzDy*e+lTMR)|7+EL%BHc*ifH2H{8%UxTu)?{e^sAWv6YyeF0Jh@|=KtsF-I&Fa z`q!@iW%@wDw(buneAlM?g9-QPKHaB(q?5Whf9`+rHvRj5{quCZI?%FyN)KPyvB%?E zCpXlOSU!KmifS;@SAKrwa8#aM3q5*Z`A5#1myT92ZbCYy6)#7+_54p>NS5=3UuJsx zTWxfG_)Vsl?h;*|-n+T5W0eorTY7n8&mSIyp1E`I{_!CH_CIaMJddv~M#KrL>&Wzj zfADwH+rRaX(&5qX0SKwMoiojC7OMFK+zgx@n|8AZeeJcJ;9B6aKe+^gC#}PYTc8|- zFhyuJxQQ3jseAo1T=I1Ke_4u; zGrV@*APt)0e=>PyhtpR$Lqre&XuGBh2m2tCPNhE0@t$435CV>ozo5IDzF4=D1+6z^ z17L3(3>`pEx7)Mud~wL)(9!Gyo;UFA8Nmd!8Kez~(2)#n&!VKk9k*(z9_RE)dG7MS zT~PAIUhr)un+eV|bh=8XDnt&gz;l>r8_(-`3!ySEqU=qY0b)?FU2rR=Ry(Na7=z5L zD{X9jZ59eqgH{ASLwgD3(DjCCk`t*LCfeQuWkvyBMWK_9HSAt9nw|)y9 zNvLv3^=US+$$;$e6Zw`0GNwhAq{(&p7N8;#X8O1-b0cWUC~gz{(0{=(h3p0-?G)8+ z)@AsQDesOQFgV+Kj?@LR^?K+$3D|t&KgvlgCemj7-PmE!k))5fw|(V`F&%yf$cNx- z=vxpa`iJ42!XnblGq3|G8WWn5-UJG|z+cJ5N-5cDHb2>8Z$!AI%|g+YO72Od0;5V2s_y`0YK-b|_hGn{UuYfhHsZ4uQ<-#wuk<2E?P}^HZXAgF`BY(LTa&Xp=J>70e$&H;hj{ z4mf|%PmLx;vy_%Ym96?8#fHsfw=Mp1g)@-WUtKpP`GqzuH<8_5ss62$(C%6m(2#UZ zbK;QE2&s|$fw`N7E1LQWAn}!?pg&>xrP|}q7qAh%*F(RATF9@dK2sH;7RD>uqW*(; zh#M+<6}H0VX+_BTxNRG^kKr(r@mxWb2Y)uaQBsh_CWOt`p~y4Rb;cZxb8?L7di&SSh!Fk{+KLz>4UBa1+sL1 zF~)@@=d;p7kDb0B(a&O&q1AmAwM7@;x`GT1SvX!g@>GOoVru?E$OmDc5-^|KcHUC< zcZ3g99rCps^&u}x7ORQocSXpnKq3eqe9)gZN6gMu$cet5lYYCD{?2pfLfH@-PBF=KsXNH!xO_ zrUMd<$C!1O=ILO?RDPB9!TsTc@80yQ5Y+p0pYGFr`tD6Ot^LYEU;U8-{S~_94?b8o zc}9o?Sqy9SIsEY9#Ygnd{b|F%`h}l*hn`*;J}e*KIyppkSU}cK>DIBRZl8V3zxS(} zU;f>%yhHE2W%(cSW4iJD-@N(z^x=z7^z&c&k)%id+#mitU9L~9%J=r zl=kXUzZYPi2eg-Nlm-3BALj$Y;A5Ey=|7y~YpI2S#y6qEBXj03#0Hj+(C!F1u5i4U z+j`MKMd*HoM0U75g-_o=B74}5n<^*zXuu!`+&7k~Fk{{?312_z4ahPH`0mL`9qmQ< zbokXKFaB8}&g@-y3(w~!Z0tbI`*jwn9W9e`}gp$`aStC0YMz}8|-8*s=;I`=n*J0LsZaYmwfUz3c%1Xrc>TmE#`BTj}ZMB z>IHT1^4+a{8)e*lND0xLeK+Rk0WZwK2cw~da#;5HJnG7jJ-3uG6%zu~W&Y@)bF$1K z+wXAlHhE56HWI3osUZE?LIn-|&&|D%2dMWZAn~P#GnfzAMM4-!gpS{azagS&zJ&Qh zZ}d6X&%5-ch2}|h^#zG(@;#uMBb>+KL5-n0E)a_67SB7G7y=x(nV&Uxu^b_q2gb&!WIZ7wka2$Ub!vi5C`voj2x(tW@o?q~@ zq~R@GCXH{_fWg}se=%Ih#glrDWuotqG;JHEiU@CSlwVReR-SHJ>3uS4`EL-7`8dG?j&xu;=1X@MB@v;JAy=0j1vB z;(kSK9uUn)52G()u~wm)!AB+E@KgxyD|>(NBSLZ{1hT)IG(W2G)VkFOSUp5nZctG5 zwfU>!_RxsXa0#Cpr5W-7+MznoaIN}IfbEsQ!20QHZUPxQq%?$(yJEaiT5q=2D zya9OR%K;~z_MxOpdH5`x``FkKAG6S-+a{BL+fKcFy1I^A$dZ6DSG)8;atzJQii`4N z6dwQDnX3w6eQ^k=c6L~MZ=*sSJM=dxtg+Fjp+>H@vSdM?#2aYad(q59g1DIB_`ofPmKLZ%Ge5(+wDPyUcS6%IlyjHhN!!Hn=QkgYd4=(F*dp_j z-RFTkx6irgj{}u(paY@r1Ezi+6Vq-`jWtTjRc&D2M#uvu58YzrXjq3wmS^Z<=tzXx z)H40>!bq2+Up)T^Ne~;(>x&9ufT0a^UjInkjmB}O4ve=@1v$SU59#9l^0?X_;q9dB%!=*AsvlD%jKH#ui-G@;A8oGHCWnQJ_b%nUXE~5=J#^zi{*h-xCE&( z!&X^cT!&KKFVYscuIP6UAEVv-EzWSIV=fkl_GLla8n8OcJRx{h4MoUtQepm^M32*szI{#7-={YOV6 zS|EH68qrw4wS!`mYaBKQF}YbGj8_YlD`o!&|5Yv?*MXaJ>wo*HWRyxxr;UHeE!|e_ z6}Ks4&pDyH>p5HZ8|LqHesF&{;kz#_bf50ieY#KoD5hWd@`3)HFJ91-Z+wOk@;fv= z`fcyff9-$yv-J7j_y0LLOuw5paDJY6_QY|LOErPsOLc-;+QgxqHpYB8Yk=?Zcw)KC zA7q;!Cr{fKrq8_B`fa`q{1~tJ) z`yLil_*|ushh`oUo%_(g%3hh%BS7QG*MOCFv!%BUCs&(CDcpW$s=ooDn%z~;x;JAaOwuq^xIbTkRg27YIFC}-L$ z6TS8BBui2i%2Z*EnIPk5WLY(Nb4yL8&sewdVq>!*Uu=}(*sxE(wBN8zJk?XT8*6(p zAbKO0A(WtA`!6!t6!^R=zS-Z#*@yr%0!1 zlcx6YBwf*`8y^{qK>l-QD#}vnT&Ek;EJId)*Z1Zs!G(fD-UMDVk-{KWe{2 z9kMNp{QcZm4?^el2W-GOUixDk?iZtDqq+6eQq+JSK77rYMnfrIt# zUT0tfHt$c_=P@c|2=!#wkA*UYKo6wKo#aONEJ;xAj_sKAu8BkPSHSl0#+QoxsIM_5 zsLr_{1xuS9=mZh9vg`RgMl|~izE9`?uJZ)F6HELZ-t|wD0+iW_)K0Z=sm4ZWw4x4k z_Uqa1N_zHwsIi&Tj;r+IT82=wyPLL9*~;KeQZmZ~of&^=J0}-9KlvPo9Q5+Zr!+C| z_4EH$SDUNtr?##SJVd$3d{Nil{9ov^&Hs1u@7u2?%wFaPCl}#Q(0%%@O8220?$dp` zPya|H`RhORkZzv}{ontx9k2fWA3e~Yzp#~u-~At5%Flnr2DTqQ{r@*UwUO}haQgjd zv#F11gio-~H|yGF5OG=c({-i8Vn;q-+}vhJh%dZ#8XrHfy8ffTDs=J8o_}(Aq=%0z z|6w`OH~*T^@K65oBJyR+{~vyG@VHW^$}YxjB>((mrN`?necvDXNqXx`f0&jlLsg)3 zAOLD_{28!(hk)v(2K}8kj*hI5u`XO~DNr^sf*W5+U>x4nBEa z&aooQrekK61mvB-M7BY>PDud5biJXPB~W!xgLaO^BW)fRKeNATV^7=cG}`S|?=OWy z1uqj|lcSD|^~_8yw1iB&<9dd0nl*_8enOuJ@ROL@N$#8stORS#++grCd;|1B-qd32 z9F%bk`QbB$7TO@`);!=PPbc6rRLV-tQRis_uql(a&&Q-ip)@a`p3!EPcZyquaKj|# z43?Mc##hqQ;qFnk?RJgnG#!w%8NM$%l}i*QZqAoJ1dL=ZMy=brgFl-|%~J9>GU_c$ zxD_c&(9TI8zB|fW+PPbR2xLwfMjG-q2!mY3tv;f5Ya-XaeX(^rne1~hgUqdlZbCMM z{0Q|)u=6-kCx~(}!(=;dpiwOnX1$hY@!OBQAoI&kYCT8K5Ac}Ur+$M-+j9V3`mNV& zQ}FB-+P_qPAgm>Z%xF3u&QK+wJ@p0Pt^-ED_5W06O~!+7#gnpQ3B14YVL-c}9uvN{r<>nJln6c{qk7v4WEo-1M+E{fszxVw zQ^lZ14o~01xU}5V!e=-SgZF6v6aKrs?u&h)50j21>@(Hxb#_KtUn~FtjG+L;Lo+0l z?%W?E@8aP=T;FRUHa5^E+gc#zH}JX3 zgXCIc!s#Zz{8rkM)LMBg2_p?%VC@u2UC2Raka?=-c;wce;2-+lWsoK7D*v#mD@b}< zkX;WhA8c~VZdXT}7Wd7@4ZV=*CBk`dk*cK~xh;T}4EwD-Ikb1rgZ4qoNhGyC9vPYj*sjPgAlq&?;y22_LU^RX_*}PGIpNQX-0>QsgV8Rl{OeW9Y6BfgZ}&n+i8$g0 z)RkcwlHtfFdnt5MX>}f?j|c6y5DOt6RO5cqVe+0nA?OIX{xMBD9%wD@qb7tm_@5dA z?6#PjP~-wG2FR_pL5O7b=fZB{oRBE}@Q}5jc@3K%ZCR+zuQSYp2Kj0|xK zp0dwjqOZbSM$Qe^+WL$3S{KGscu#yhb6!T$_IYI$_M+C0IMzlk`WR2h3#cq? z#R_X9sYf_x7s@$gA{4P?D6nXc-Bgdcs*Ov_D%DuGpQ_8kb&hNN>ik^X=Hr?U(qAQ2 zv5Ee$mY+6cEJ9(W{nSjMBcgu&1;M7U7mW7Y#$tbWapZzfp3p&Z=f&-^p;^uSM0t}R zeUd!1B~u$=EsK*lGP`?>PT{nH^E#x-Fw{*n`9&TU1?+W(h78{ec~3RY8jmRF3>l6D z4m*_mNb)s(8+~lT7^o9%en4R_+}{*r1osb1X-+Ch%w0VxP$^gx6vikN7t9 zQ5-46n{ew-*W$g(-V*ROh00-}&8jWguGI-h$ESRc3|n$w2s2b;UW7*Cke{><)Iw3< zA&_PRP}#sSkK!IG6$0(gqDW%ijWiF=L-=S6L=qJt$7!u;!WpS~;7>xA?^-KYC>pT4V; z{LQaCqQCR*mrlOZ6S}?rN6#M{Ui0nxG2Q$%!}0v`*Nz-h-GA?Ap3-A6Y^kSDzQw=gPrka7 z=D+wyejVN3ysclJ+`ORcE2~$YU(<76&HLg1)lbla-|#0-PRN%!gg(FsE7Wa5e7C7l zE|eg|G(xK(46MR+It=W9y4-`@9kA!Iz>pB!W777}A=@U0^4ej>bu;0zPf&IzsIkC5 zoTr;|(vh|mpjHtQ86l%FY(&&IlEOF#>^VSrCuA8@gT-#)CyvD{Ypc@<5Yq^ihIJwA zm_oDP!X`>My8s_L!ahX1a8aQke!)vAW8U3qId`%jzEA6;jM)rpZRMSEw%r z#Ns6z&!R0V54YU&kkud1WC^o2B`kl2GLEnsa$V&s1n#4#7wURcb@d981>2AWUXH<0 z0STBZU3CQ$8LdJ~OFCk<}r7l|`{}OWR^cdW< zWySCo!&XSZ%LA19?Z+U)?;0HHEW@Z%?UoCK@W8$>IB|kS9HZ05J1Yn6GtXfNJbVno zwqqEJX0JduYYY;D=7x3+h-0o{lqeSix%i18lXgP4M15(Wot~5pwy+mrhm{WwiBAwF zDHmg#mK8IKryQ07*@m5A_G*bzftK^im~EG;YC zZiYONl2wH@_RurSMIC%l=yLaMB5aDbH~cpa*x}+~giz7vw`QOg4C@;K z>hS40NAQK`2VZb`7&8COf_7I}-YaAN;JN93_yeYpqklrzR308!o`fP78}~LL=f<4t z8*DJtqpnAi@Z)H!hn_~rgC(HFM><;ns_tY+2#2`$nD*w|yNsRM6_EHNltAcCvT(HJ zIVT+YuuGa}*u!sf^6lasQJ;0BA-4mndhq(~tw*>R%_l=2*OMI7CUtIF2+r5I z=4DK|$!oUVSvcRW0b)Mc3Q_vm4C22ByhXnYln|W?_&M|Yj+r;?(i06h$4H}CS4W+psLC_PKN1WwK)-z17Z2gH>18Tqz~+m+LK(% zvJfOp;O~tumz+M8`pbSWVS)9H41eQef3#88HlJ+o;oqEiV{=cZH+{SaYr=3ims^O* zSQAq^Zz$yo!{1m48`{nVp)W3d?qIgG%E>X5J-o{U*g$96Y^D#tE5g=j2s2zS^7R>W z?|gg&I_<)N+rul`4X-H>)=>M|4+G|*PpLKU9A6@oDy)TGyu<0+XlR%hKE{nuj}Pqg zZS)KIXXnY~=WKl^)1C{|p2ZiO!_XV0&0&RIck2uRZ#e&dLrPj5pY7eBV&?xV-KX#P zw9tLJPxt9QeJ`h9e*4$yp@i?1^jrV<573YOssATE`qKYH4<*zMfXY)h7Kpa%uIA0X z@pwL^&8gFHbksw%Cp;Y)#Z#5=c;B&*o%%9y%W}32viI&zhWLCo-%dr+{#2H@9VYV& z+js9ua%a!%5CXsCO~;TN{gKTC=Y!u}o0qx0-OQ`Z}G zL(~Qz&&df_zKIs3l|MtRJ;fs2{`a%`AI~=W^k({xeTN&dGrw!mWhND zo~ei4VB~3K>p$o9zcPJ$b={pN{ok#h%Vy8EsM&(4zs}^_rd}`OEfS(LHg<=WR@rCl z1GFO>8QvQN;Mwc+|25-(-wW*ee|F4zo&NJ@8~@*A@uO~s6VaQFaXkGc7_(lhJ96e1 zAOC4T$yvRe^MUU96{5T;8CQy$jM#UXZ7^-yP%=EXp~}6>LNm~klzQD8F=g505B10! zQ`0*#Rl4;-WaC>%6_*D9;#Qmgx??wGlJ9K3-nK1R-l7?4n{9FN9?O-+hj!Cm;+7k< zP3ex?zEpNkWS>bwo4QzfqiRgT#!Ja*($DR^DuC`tvi%sZ%HG>l5VcLuf}ZYGZ#J0c`OTgl!uz2IrPB37 z9f*(|yNd@hl`BnO8?U5n6+_!Xr_b3Q8;518PoPZXTdx~otJP*P$Vt=dPB*7L!nV=I zPs+Xkn;ZSq`>fs-q?xF<2l81NvLkO@kFlPjZ*F7%JGZIv<&K0%0;x}pkK6t#-pZzZ zkssT*_dF36P(Go2k}ijHnX`=%n(RT=U3s>BP5b&k>1nhyJ|zS@Nvo3xZKve-oU83u z-|a1yQ`~^(r32#i$XpgtTSMWUwS?L4Yjzi8ZaZdgbtvetE|t{bkm>XQCK~4P1pntr z#t689r~|WJ6V(_mSmqf%&^aBMWrfe=1ENcYq}^KQdC-EsEt+MgT+9T#n&uPMUaqlq zV>@1xfJfa%$K$ez(f^ShUq?MB(0wM^bTRfJibkRW?ERav0fa*nXTQ?^QXAvub|Z4Z zW-cVjT^M)#umqHcK5pnBx7K>uJN+S3efWH*my^zUOxl(q-L^@WF{DR8rWk5AyH$K_ z6|^~IHk`KV^bdCZuPWe4H~iVJa{k|C_vw2o z$uE87ihkrrR{FWG*@F?DVDY1= z{^gav{0m0UfA}vk#pTLRPUT;pNc+M6))V^N@BbOPc>8~DPLZFe&lB+hM|FA-w@+h6 zV9<`RbX~I(oT>ERW~YYhcbtp{0d{LxvL4)wLkRA+cnh%1dx!}&$>f$tPpZQr96CJh z1ojF|0D9KjfEr_JaNIB?1}r}bw!Pp;Mmj%?GsTGhni+IWt z>DYP8t|Cj9^5+Quxb)(4!b{yg@a7Dz!%6!gjb@|PE_-+rGK_(^oCJ?T zA5|Xx=8T04fk|*IjI6crNU*p)FUHA^?hEuC!o0_+{m`KnF6S2L$G!%ed@jY!QBHdL zVm!jmCmlpIhpH$ZNn314lo{En*Z)fJc0+%SGYNgXn7zvOP7~Eo0m6_90@-Bys>M3|dZZfdpXUH3l(Sg*n zH%`Pyf#nsqjpuGhG~a<}TBP1@BZghr@G2f-=o#+1(-tQ}M+VJ-OceGyen)+e^226F z|3bTpI;I@syblNOmkX*KEbViITWama+lR6n1P7fku`QpL&*qUY4c8bk7KMXrX-pGJFVeIHrque@9PC8t@`_7 zZC_lz;&#@W5YW`Zo+U!TU~Pmwkg~yH*TrqHLX*3%${~}7qEm0m`FOStd3JqZ&=(zd z(#BI~7Vw9szUaC|(7(q2^;qSq{WKNK8*AiwOkV2dYE_7x?)h&xb-dIZ~bOBo?5_|w7-y6+>!_SBWZD3iFBzFSIz43$^e)ta9vGDSe;_H#5?q?0Wd` zL^W@V{B@Ya>|-?h;CwjVaQ8)nkGWLQ&X|=OJ0$ zo>ka_Xp4Pcr-N*?lz(CTB8B^>pn+qY2ibwwvwugvBl``1$q*k5`fcjL?ZvRY;SXz` zxSdW^Zh{{~iLpMy^Msx)+23sFWlm*=8_>RUnK@9-&6lOtH`2!-`mwvicz0s$KGA4<73(!-vb{lhh>Lh-PFRqj`Y^&U~5vURLb zTYE|UTG`1CJMZ>n$l%HqvM20A@Xv?OTC1oq9XWP*TNpxsso!%G%p{vb!I+WddPvAo z{u=cA^XwPsIPr0W6IHErj{RRke`=M|P4~MbLbHu=qoLMF&XtA?uD&MRd;^j;4&?gM zN^RXcbvS5-IwblkY@^S!bWXm2t})2WoKi>o_pFDgt&>oj#LKo|cr=&Edo)bXc&AK0 zM?3ewb3Qrzn=stbNCZ_vzpYGFr`W{H~7yrj^(fZaG=;rv=Y2bW*8v6=g zbqP4K^=&#_8V2s|_1F3L{NKKj?}wZ6i+}hL9j@(Iz=H>0pclut=lt>u|HIGI!v}`K z@c81E#2w_@^;3H1fnhAZdj1x@JQ(KJ2pMtoJYT&1nE$aqt9j%v|MZvWhTr!*f1e({ z{fT})UcXD%tA~eJ9?)<7nV+NwU;M*#`SfYRh$+-lL8G z49?x!dlusOBZsM#0U=lr_nMrY3OyYV*)J&J1QU{r3JzTq>NW*vz{fG&}Z6KsJ!bBS+9dBs6{iNp)dI}P=`&j3~MhuAVgfkty z*NMEpAH0?-C*jrw;EqSX4Cq}Klo3Y3AZiI>$UD!E&cml$Nbuy9%dmza*a>~2(7_HZ znBfe_poKHU3FvzZ8}YDYdi>1|TMT!y32$Njgs}VwdyYD*{J$`IMq5<=CU>_%m#>uT zHQ2ukK-MEq=CCgZ*O7L}18v=~{|+tNHfE#kvGC=SwH0VcdZr2G6@*u42PhhRF(p(u zU)7|%9V`NRE+F)ShD!0p=~4gc8*n-%xcnd#twNnYB*F#Q+0H0EF8?(i4M=uQIYfC= zSV#}u!j~Z@qL7CX{s2Qi6k|L8<5~P|Ft$`Y#D(EG?(X;pWmo{>9%ZKNkDP2NXMPM< zfngm&AICz)#dyy*S2YaEwK4MmVL>n{4cL#2GuK1>ZpHi^VnQyJOfTC};eB069V1vOI#Z z8+07Y#E=9zzH^1`unlb54J8x&z6(0@_ERw!YN2M6TeudtX<-9gB%5y9cw{^oG_b>~ z-hN^_qhpi9t&d+0k0g1kaM?E|ZwOVIHc%pLfiY$1EIWj?`WS9!Ftm{Rges$K(AF5X z#t$Rlm`Hx`P0N3W66SO-jb9A@)Y`s4xCe0=Fgl@6Z^dK0QpUU=34LSKSqyW4i(NQ& zT(~ZaFWknWILeR0e9HtcX7rgbXw_A^yX=J+Yv3oy$W)#e&ev}w^cXV zABB>R1%KGsX$fp^!l(Ng6gZycel@DS0?xTJM?I1C*x z9Kv1g+b0!rVlU~r*&!znEx(0-;%Wm`=#7iV))`vQ<}j?aUHP_-`Q4&2WaDqOk=zE1 zk`=Q3##mIk3|X1%{AA9|nC?+q^B>S{Omt z{2@31n5wIOnvWe2iem8d81ViOo{6aZU-SKHqdENZA;0OvNy%sHyCeMO;W`=09`7Pl zezPsmJUoOuLO*DGB*K}weS@(t!ctg&nf)I&>f&9K!?wvZF)RIZr#mh*Fj3R%@C6S~ zBLvk+cfPfxu8sNK=xYr-2` zp+I=EZgmB+jeHyLW(c82Me~pa(9PCrC_*VR932oK4F z6*&iQJ|0zwsE3r{3}Vg|hbophByz*4ry9b^+Z!RGm?Cr<_#q+l7#t6#Bx9d(Q_ zaF=h@I2$Prl8v^+x}W(ltUOn{3Sj{?q}^MSn=!^*4`Hl&^nfx9iuq}#J9aSI_)pMy+W2E{N>OC4^ge}UHE~HqxC_A{-QVB|7~kaulX&$_k#rQ-KYEXU7hqm0zLS3r!oC8-5lwAD*eE}_`B)R z5B#yye}<+j6WBI+C)_T?Gtq`&uK}1ziD|bd09ISAigH^&ZV4bEuU-Y%!92g>EB$a zhTnLiaZ7SN-+VsLnO)})V7ETsug~<7-&mw@(<&AM z>irD^{6Jd+v#vOrD0Vg|Zd zI5tnIKd&3ED3-OD2DgBxg9F?)=dtbHim7UuKH!bZyxpC&he&r?JLlj%nHy2`2d(K7 zY#1Vw>M-TPfBf!`8zgbkNBRG*4QX~UGH#4PCD56Cr~kNR=2*mCXsX`{+OdT~v`=f& zOcZD+(%P|KhRpQDGm$1ABwePkGzsBdp_{-L;j;u+AMfc)YlDE^(Y}d^o0a4@%v*l{ zhqGk6y?g)~*2%shU3p76%H^UdZ|vty#CjJOxPwN9_5d52Z0@x%eJCu4@Eyu_Aw8CV z9&@PCmvl-e4p>?`37wIjm+VcN5dj$dNN8rs6isZ9Qfj_a*g-9`S+e??K!o0w9l4n~n07w- z4~HVTcp75?9zF|w5-EP`A>aDjUjV=4mGuf`gote zNildH4{&Tuoo;=L@(4|%o~P-w9u6s82w%Wx;q2!``;q1`gA~qk)*CjzgMMAnC}p#$ zryfqQ%T(i^-{ziK6G#u?w@dOEc(s-cGHnn45y4m`WWRU5PqK$n;pFC?2Iy+wryN#u$j-EhLJ;lp8qeZTV%vZxmyw} zuw&ZGZJl~}@c1-kduc~spPdH$hpX2NCiR;ygueK|@_h60fsQZjxbELxF6Gbtn3eVF zmT6rq&$zMh)^jW1@jw3q^yIhyD|C4L`}=Li@xMFri$l4_m>V~imvJM)p@ai=diMP1 zQ_OP4pUy=IrhfZWroUdMFy%y{{mxY=E_mdLcA*9%&+b*=?FD@o;2POGIN|fjC!G!6 z3fj2VtU94PeYR?FgQTbf>3D_!;VJn+OP&mtT69>r!>1oR4S7NtCYWtP8qPPu8y>6O zG(&15v{%&2uz(e?V&47*Al75T-2Azf67pDif*~ys;evyK@VoXMx4hYR6TP|!cZ4~Z z-ng^=Q(0(}it4{M-{5k_cj4&MIks6#l+hOaae@f+)ndQ%p+u%ne6yHZkj+Vt*e&A) z7tOie#SjsVX7r+wznj}`u+ZY~azd9<-Uyl(+}c9P(#hJ)ErohG1KLSbyW1iX$OwiT zhyywy8@$)X612wudG;X$i^#y>?WQ>pT(d%lU^SViwa^Oq0v16LR(-O82J>t0qir!V zxak@8uc?qv_)|yfmSpfFby@nYleEV&^9^)xvgi3e*p1e&QaovVo@G1uvQ)YDc`LI? z8{D#Mu=5#`#%($1h~6Nj-bRI%3>(4J^p4g}FTq4Kpb(ob?A;)xlFxAyDnmsC^kX!0 zagouRn~(>}#BsUo5tZSZX#l;WTh4`rjonBhps4t~R-cO7a|YRt@{>hvY(*x3tz)7J ze^C9>!l_8Lmu271YI#RW{^LOpkv%?e|J{88l?OzVq0pS*i5bQS@zdpG(*Fq_H0gAL zbPUHEtpZI7xn(Z@19emwLaxAj$+xa&v(0LoaPuKDqAo8)Z6V$|i(!29-!K%;pfMIV zaa_wyACsBIZDhdDYFm$Kt~5BS!$TiQ0oOXX4KpHqi0^;1!OE5%z_uUvNy* z^1we|kCgMqHvXYcmrmpC-rVhkc+aQm5ecawvc||_ZRwtsIZ73qs+CTzW z9SUCS9Jj&A`3HrY|5}Q7bjbY`SD+ROF0*qCqg5bFTvgvN!WY~A;B<~J3m(` zBoXR-XdyVU8RQ4gBAf`7O`#ZbLVRv(E})S0F;)udqf~f~Be(h%9}8mK4Bc5`?LewM zs#9x?9nqGwa~{?rL-vF}=)MLn!`S+Xbl(fdQ}9_-?jlTMswRUELxh{@jNezZ6`sS_ zoLX2Ds5k6`%ED@Thdzw?Q7ctQm#JML7vP6)d}}X81#6qxFT#zM#%H3Os|r;)8oFnh*3I1} zNJ=*Q- z7K7gd0ZP$~9vwN@*)l1+RnCCZ%C9L`yf8jfVPr}Np;sc!QNq{o z{eO(?aKi6Axe*`Hefo|{O!w(N-KYEXU6kan{?P~Y{G)eHj_3n=cKtD3yk(f7pZsS+ zzx)LoCBOgecj)n@VWr)!-{jx*rxmwUe*PEW-lcfbE{T{C~xR>%J1|H%*1 zdib2>msh8HZPs~MKR&%{*iT3K96kAMU!*Vo)Bhqp`7Qq=I=uB%pCfE|K#+|=9ifQ> z_F11l_UFTSfk`dGmuVOXe;47k6;kz~JAknnKK|U6V|eqa=NAmYU{FypxzF;Y!W&gZb1TejF3S3JLOPW30(p>r49$R$&X_Rt!?}cfQ$7H^^9VOE z99s>6;qc}PUv|6Yzd?CBe;}+zLYJ#2PRWXbiSGPSIr)?b7otX<%l9fA11|JpUE(lu z(}*@47C5oS2?&CoLFu zWPK?KS$qj$S!jQ*(-P_tPBgW@TxT)V3%1c9@vV|qzhcm?qkk_3^u6b|kT>2guxTyf zMk^-Bi*WrdOaM;wxfTQ3=4>vC42Vi9*h7D3r0b8mEsDNI*ngA5MFR5aL0yL;W=2m8;lL0DU!{`u%^?`|e@({+RkZ%pO^W1FC0(yin``9l- z9!B5Tp^`cfgx#E>RfN?xD|GP4a6$Wryl!Ff;Xrk;>jLGZcn~r<+X{mbwtNgB6=%cCb_jHoc?@%)p-CKGJE7FayB}Mq38m-|8?RmY5EdK9xC$L23dx+|lLD6i^jsk{ z(KbG|=359AYa@>7xTu4&jd^ekIzazo$es)hK;2fbHlMCS*GqTwds-^F2mHNV3$zp!@)pO=TZ!e|d+fqN9M z_AOx58SO2=4!@!djbZ+h7qHT_VQl3OxaHspE)Lou5hq6o^vHBkd36aJZE%=W^=oab z!uA|C9}v#Ek5g3k;!tDgjYGueAq>FVE-@l`{JiD;;WfSn)qa>$U{QV(x5=(&1l)k_E3?<2h6^F{e_>ESSRBk^Qg zZawT8Zmys%XGnq}dLAN2ZOtOJ55Ofp5hg%+>oE0rz$AOf55A<2q>QUR5|2Hu?LpLau~Qc;kEyH_7Bw8mTm{89^B<86es8ui1kupM>aD>jU6li3fAT$Mc>IwG%37t}sHl+KWm z=Nk}K;2neFuZ>H~W6=8EM{ien9RVo*#dSo7mj}k%@wxTylPI4Y4&7|jit_^KzY0^x z^x$)=g~@O~Vd!%3qx|X$r7`Snz_y$1GDBu-L@EIm79m2$Jm;jrc;&@z{3GYdE6UI< z7ZJi>$g}$%+V2@w(rs9TbIp(kTy}}2g+!s-Iy}Nx<+Bajx%k*W#xfhDsU6NJS+#Id zE&Kr)KLtWEsqA?;3B5RUfbqi*mk^Azx8I63T8NzSu5L2Fq73h{`1(LUG=gEvsD*tD z|3^areB|LFsE3Wn`2|nyf$(P$f=d1WIi#Sc42y6oQ|HT{diye*fq;L=Hh$gI7#e=N z%@>SsDGy8)eu4=6G=}lT=Jhd-DF0)f;ZsUqLfhl^_7LHIgggY5wbAT@+3o9-uC33_ z&(v^ikKzC@`@ilRzbJn)^fW?S3h-6w8T}AzL=c)M*MEl0KdW&E4{o%Mhp|p3j=z^kt}f zeBC`@ajvYta9Tq99dw_*qtZQM!hO0=_vyPL*@1+ccj)D(4|>e=?~(L_f9QA9<1hUm zPF~LU!QvAv3~K5QLkz~7n~3`3`9Xbmn3gIhAEl$g{bhKB&PYN}64Bc zr-ygx0G=}Ti6iPiL%}Kbk#~pqJW2hT(pLZXp5vn~Gh3O$viMC2I@6}8LkNzEh5qNe zXY~Jdb$=H9e{HHsxJ>##FOs}TncuGd7c}!;bR`3q&sIsJcaeLMAh79N5M2#)_CZ!$f~s-^$U2Icxy z@69bQrN)hXeQo1^)V;W|g#B--5CxWV?xz@zYM2uk^XpmBSL0UFI@$ob89E#PcYbi) z9-{tx^Ea{6oXF;5o#{iP-+7U*bGE+*x5)2(P;6>9!NS*4{=x<|Lf{tcwEY|HpIV;0 zjWY9{Vm_f?kS*GjwU>lu-y-=5w|GhB^6PWniaJ;J+$ZCoxCtsi!lryC6cB`ej+_h9 zpN5jCU4QhB* z4f^hA8Tr0UZvZQz-jIW=H^!eS#eree_^O1#R?L*kWOJgNO|I(SC>GErw*5I1SiB{k zd^y*tmCpNk@trB;yQ7blewmHevZi;#@@z=ul9Ti`TOXm+G6ga$H=oSQK~u8F3N9)b zY1=b$F7qJ+(qtI?mvQKFt+oqxDDTt{HmRZiz^6XP1|47&N~mYuYpnE-=#azjiE=XW z0R5mk1XyShVHaAL3h+j{B+r`BJ|nLLTORM$4??JK_V03s_M}Y@JG?bybEsvL!Z7&lR64B3=_(!^ZV9=nhzg>QDh$JXW56tN0mELc7u#_n5F%$vH zvbX-W%lT6oFb{z6ILSbKc zOX$Tjn?)S1j`ZZlu4O!aO!U%EqkeoNcDu|{ASK8LA1w64|B2=K9bdelk6+ob;unX7 z9)8P1GkjsC-+b${lpi_0_f`9Ndv%(c9qnlF?eUf#9^<+a(Qo?6AEdW_$N!1eoBwv* zJRcR_)}%9TTx0lyfD+F!93V!G@V+6XJ`db3qMV%h`v6aw`Q7ot8*Onjb)+R>c%9am zlw>%GC@5|fQG+^+TQQApgwgVM!ihF#%bb!W77=#ZljWgf1hC9lC*EGJ1$Sx_tW3)^ z@!QTSjplUP>h{qnXF*6w>2%ZV(?L0SLm6gIQ{7zfvUz)uDB#t)1Mj2)ta?ChHV2#c z>*MSmcIZ?}hdpjLmHcoy!Bf2*>K1r1wG%gavcHMixd(%9<;fZ5UgolG3(`g)+YNGC zwt8WiOFmPsi4yLw8f=sgh~l_)pzQ?+VuI@O_UHI}fVzl)Z&Gn3LB>#K1U7j4f-N=L z;=P>994OZY@`fW*gCdlba#17Vg$A#BGCPGlj5g?Bp5?*q8bU;By)hw)#rOIx+JbQ9 z_-;<3)-7q79L|w)L7umHx&B9cch0gVe(U7#>l05 z&82g*&G0&Kkwavs53w$WcNSKg#=u{ERTrdA^^a&pF-{5Q4V!?_sYH_w7ol-erOkg` zUzV_4sc+Q6`n1i5J#-A7$|R}@zU$Y5^fV!y>VUB~cw;v*_USYBsQiV47<7}TTy)1CU7VAN|!i9TW_*i4% zM9@f;ZjYZb{KrJMriBWgSBPlRh2TqHSS{GecDtLZuC&zxvJZY!+HJoD6eu?Wyvaih zEY&{(dNkXz%CcSTQGZ4}FQd82&;|Ny$kl`(gE!z25;orxAnUdIZJ4u8p;k!DjQZ>6qo z%v9bM7=OZ_Y;sLD?gtvh$aCSc0+92lx2l^kcS8gpS*v{Z@ni8D>{^x1-K-Y_F~bU? z^v?%Rj<>L35r=fvXeu|<74$vg#$wXx;hz-M2>Q6nq8w*qj+)~Z%qi4Q9{CpxFCsnM z3>+pxnDK;vk4+{D{z#g?JOJZ;pQ?en=1(nLhL4F-hzz6rWvR_4^o?T^MEe@-74o>D zeZkm0kOdZh0qs~0uR`61-&A|i_m~eNiSum1wTSxFb%wzKZK4m*_LGxC5BOzD%&^`_H5U@poQpeFh z$aCh@+x6&Uu+-SI;3gzE<$+pKekXLY%kJv-W{r0OY991Ri-!;u3#<{v2A#f-K4g@k zAtO0;Geb9pt;o7y_y`(SYoBzn&v){18xsqyWrcOT!QaJpsax2WWm!|-#SbcAnNeA; zdHGm^>w9g?a^8k+<~qg!`kJA9#^;hq0q7Tn?C7B}KvN2vDIxb!2g=ow+uIF>fA$a3 z_J%J3`Wbd!B4oo7KCR_hV0_2Jn;D+NWhvM5!c@cIJRE}rd|nP;0%)AePjnI}E9^P8 z3lFQx5e}r(>4vhgVK<1WHeRL1II4B7v>A~Ph>ZC@&vSZ^-#`rMeZpZbTK_bDxo(8* zR{tf&*^t3jS(njxF2QFuUN9Vx$%D^6C$RkN2R{7lnNah8Mz{+VNj%sBW6CUhsB7lS z$u#zbYyh$TUd#zqBE+6tz_>4tUcom0ERG(=1i@y~o8-5xCe<;~yb-|=Vfj3cDe_OE`$yzX}% zKBQlFFf6CP`s_LXfv+kCnf$^}y-Syl$+)a<)1ynn1AVdn4f?%*=9}XqcMqb<|D~^7 z(aZPDi@5%#<@qgt^j&)J8~>HlBIv(#a{8Ytr03zW6%io@#-OS&uzpYiVSe}Ek#$l8 zu*?RpYIzVMego}`5Z3OzVF-qVl9mqRjJ_I%MF@B_bqoPQTsLI{y6__^*y?j8fW}g2aqdveF}bsfDZRji?R6(Ic&P6Jr2Go>C_jU5$1QD_ z4zH^2EWo?r1}wtVBTo`S9B7=!Z+#aai=)hdg4ce9g-qmChH{AKArCi_&~QjI!fs=V zup@(|7XZP13vk)Xqb?7*S>uq->TqAuZv140d4J}5@wjvWZ-OZ#-2?lqu^zD-W2z8-j5YXQiD1GCzHiG?K z(3h}3aab-F|E5qx*^k%Nj}?2Cjh>^^k0Bc_t1UPtE)ZfP5AjX1-C|k-4{0w91Wn;5 z93EVD+;l`)Qkq{;laKCi zbUK5FN*=`bUe1Iv!Eirl6Iw_q_>67A{OSwsgj&FUB-r3pxJw^L%ucv09ZF77H)9M^ z{`eT3GVlFDAV0(2stmCBp)}6EiyLkhYO31U4mZVO)@?0#=Ykzkg$rT#K`v|)d%dQF z$;HqKM~49)W#7K2JfrU}9L5|&X0#1U<+*(c+0J!G&tn@rzAWiJr2c#(*?)((*kEia z;PffmGT`o&pU(@rcEFix8<=uaWsGeKf9f|g6|VW2eO9<;pe+_k8D$TAA%*rGa?G3% z%0$KEJS^d_W;h0N_*|F&tIx}>>)OGPLB|N@D zsRulGgrX3GOb$5=`1J9HLhe6`h1N7nT2F5BjQ5TvbA$GPXUFkH{rRl7LOk3!9JIFa zEpcdN+ele`gMGOI-|RrSP(R8X;YoCudLRiYY|s6M@S!#()bD+0a)c1^*uM-kA5J`H z@Mq-M`5Xu4LLb!Ma5!rmf648n-o^`?CMN4~_~8aZS`1kJ2y;MFHwT@oLt&4v!)7Gp z{zm}OtZ>KnZbD3x%Xy65ZhtI%%xHrn2)wlXx`E?k{iyBc`Y z3cg%x!!y^95Cf$=5zF!e^xmDucIH?`U86(gzQX6mlD~opLTQH#eT7?G+oZGc?b&9|+bXj`I)Vi-TWCwH0|=B^hto{%hmU-G6+CgZfOQ z&B5hdX}+;B|MCg7gX(XZ1P&pzdF-!o+8j`@r<4s`f0p_1uhD(_HBSrOr~7oD?$dWr zl3)1ph5WyN`U~{?|BFZTy_$aKpZb0D{_pt@P7})inI1|QX}%FV(zyX$cD}{H+un(mOIMGM|?)F;Z*zU9QrnAo%$M2oS$d3 z^qKtp@lEyrt}48ayp@&}Lz}YGuEkJrnQ-`cFIh-)?>H)r%`J@JMK0OPHVR z=s%RTcPp*EyZhp~B%8NFXYSO0-l=C#PNwg>e3t#!-bDYI_up-`gZ_7AJ@Yx2Gx~Qd zuhUY0^VWp_d1L+GNz7+@ZwKAvvyA^)_M43V_|CVZ|4^U4w!;HP{nrOuncPzM8vW-t z)PLFP|CtiuV>Nzv%z{@?9PD?>O)vdPqP(fig7w60Pbrz2bY3>^METxp@jhoUNrz1J zhBV}Dt$1M?H|iB`&!bIgM~FaQCI1)r9(uz&zC#^=5vp5O(rpRooY@U2{>OV&^6*xe^l~x(>@P)IQO=X^!Do=6C=lh(Y)Wqrwu$p*U6Ql;%d;JVw%bR9iKPuK zEffOw-P#Kk+UIk=w<9yHK3Krs@-jA|W&0l1m|{2ek1H|io5K}YC( zl*?e_jdbZUHkFM&+gX2&CY>WH9Y`Kadq-)O$*-GyU<}jI{D~-+tK|G27`++CglOwe zZ|ToEsPv$%n?#$b`FLK-#+R~%{^W5MnXCh~_J~YcY7B%U*gpN>`5t;g>%O~VP zdS*7Bv1vKpQOR_of1GI-&wQks&oOlwYvVtGf3lGWdlrX2#$h16%yc`IaZayf+oq^5 zWfJ5(U)zweBY)>A!e=}5^tKbdcKvVpS5NNPKTr4R*F4>aa=1_T={|kOB>DG#<|#eA zc~4KdfB5o+#E|=o>q;*jXYlIcmabNtHh*h;kDEn5HZt1BaWU@TdGvy{JHB820}K7o zd)ot9U;93xFMQ2DUw>(w47Jhfhu)K-;q>h09Xnq7=yoN%@ED^ihI_#D)?24KFBbX@ zf9MD3?ce&Rh!4LnZ@L6@e#PXY260b)&7nKpuqjiMje0Mxg zN6cnK?o^YViXHhzTl{2tt%WcJjAu|2r|oHhJlvg#Y%45C1pJi#?l58v#veTRI1G(& z@m099X7!g~hp~qw==>}ASK2H*6v6^ej&qf7ZLrFV-)HKeDk~>~vAVEugDs_)SS940 zw*eM819DQ`P=vVj%nd^SSbXSlx(c2T7i0zN@Ot88Z=P}HHUXYM1!Rsb8^qd5 zHPMS()PN=skeyb3Z*NVOCUAcClg9v&tD>+LFM}REf$TgTw}6&dh7d}4J>_|{1>xYv z-?%+a)Zh?F;^qU@tu6M?A%}5O4$&@(wB&EeB7+QbW^N%fRyb{qhd1Chb;txxL~y}Q zWnkuMOFT_tnDCa>M)s5*E_*>$SK7pA9xmE@Oc#ag#^R&Df>uE-cwZrOaM)R{vRdeqrOT)NRSptJ0@2J*HzIMPuR|*X zCOoQH{Irgz5byjT%rQXFD8vjyt4KQW;i+toR z8{<8@3D1P~yva$15`bF`A=}c+#xWrH-TKSNf91~wHh|h#9y-!u&q6uf4pd)cUoTXf zbZ~>CbQ;Ega4{g!P8OjcwE!KazL_Yoix`c!;72~q&l2_wnkQ8qhqV3@}N*Gd$7cHfK zOntoZwr0O$XabRhc||*qo4z_ETogTp7IdmpZ>6psrU$RI}*l|2`r5ncKSAfjv>j6_NB`qosx9oM3 zI=9)MfsBy!RhcEXU?^RtkfVs!q1g_#=(&c^Aa*lTZ=O0#u5xit=PsLTIf04h4o z%%txL(VWdFWDKQdMY|ep=lo6oLOCrg&I0= zMq68Plq|nNBoFyqO_ucY;KzVU7h5b=SaF<}fj-kB=_k~Ju-l`6F%BC#{#PBb7tqEL zfox0NFsB^Lxb>kfd$b8?{M~G+)SUo@9Ro%@L!X!eqpq9gI1bmIJj~i>vJ5+DdFOae^Ns8;jt$#zm z`bZw)kT&zvKy+#_^$XZr(DB?GZ7i}-ONY3b?>D`9-dq$)0m?%G5^D-s7=@>%`sj8l zWHst8ICshN7q@j<6?4CVA7iNcTJ9}IegQk;eWszrVjcxP$a*ZEhkh-&QCxnIgy8t+ zw1wiWk1|r0U6(*slm3H^ZQ-Y-t#SDwBG|Cyq$T8N@s3keqcmvHH7KKP9f+}$p+5^r zXRIZp`~W?pVHu7=s=eYUg!*ovBQ3@EAOY7dA`DPDKR~;fY#Vx1%{Ps9A&F4;&yeC@% zp8qnv^Z8Ho^WXY}>48HazJ2oiU%n@s{LAtC=ytJC5x48#;NSaaKGb6V`cJ(}A3wBu z*Xw%kbHDRF`rLo-57Fh5pE)@kZ>PhLaNh&c3E|h$u?3joBA6QdvO$N@#_5cHKmX zkm2lJayvZ2IO*~gsL}R-v^B_g<7ML&$ny<^6+me81wwux-1i8>k+K9lJQo%upjqb= zvIfQPvH@~saD|rnI786;WFBP8Zd6J~D!PGMJZV{?4QPzxxxD)Se>=bexWXcMx3g>o`B>Io=vZ5MaOwr*;XGAh1&n-jAB2I|EyOzNKiBz4B69=^kBdW10DH&eK- z0kJu1ycy$5=-pa2Q=v-L4e>(~;Ug&oD6GM_Qfr8T;E&6&9!dbHc zEuJ=IWJ5rPq=$&Lbfd zd_uB|l|8+ETLm4mq5Dz|k8$L;eq!#fRwc7^hU0uun%`pVyc?8=bcbeQY{DV8Gd-kJToJ zZ}1rABPaR?H2+9fCq7&}?L3g^y9j51AtaU>3mUH?RKei$fa$)mIakvO?^>;_?-bAv4x4wW~PR0Q@zE62b!=;s+T5DM7pewotnrUydF+L5m6* zAd^qRLknS2V#6eSGoc}(w z{!m!*2(dS4yD}MSc7Qu=mmd0IjBU3cQ^L#ZrrnX%UaNk&UIetfj*p*khCg`l{%SD$ zVmcafD9761QrjMLCJLB1#t5Ui{JOB}z{9PR6FCFND5j1aK5qaVMm@XDuEil=Yi>zLCxEMjlR0T!SJMooyH?k9gHD8 z9Lk3pfAf&O&tFVOSov`A#0;N?fbq}(xaKp)j*CaD>FqJbdYG?_E<4$W+iN`)qfqb{ zZ$KO7|zdj0gb}eo~|e0TcCV_$u$qcwK$!i_+;h zJ~(*XHl<^20YW@TgfCP-yvB+3ncKHX{fE3ncqz>@hntZeg2(1uejpCQ^OcUV&h>Oo zAtuB8fT-U@=N96Jd`(H>U>Y2NS>5K3IaGuj!>|G-*OWBxg&f7YHPq~-G`TP|sPE1anNRn9lU71!vN3@5=JvaR?NHX%cIId3E6 zL>^XP^G_peP}nP7=X4%YciY;U42BFQpEqcKfj_BpL-m>2LQ+1r^5urp|GWzSmTY{N zSTldOWbc)aHN(Hs=MPPmFa(Cf%Bb)4;5{La0^R4rum*VJ@|>3qe4c<|nsr_Ay7NKT z-aU+?x=0v;%|o{30S)URD?j`YT-SmFgEK+s z_$=vp>QlD4@}`z};WqE@-v3=pc{A^&Q-hN%Dn~7#tM+a}oUdKptp6epIBnUeadVA6lQ9lNRLbx-)*mwo*g zkU#zm`Y)e>?^**(*&r_c=Slzhjf<5xvK5&nZSCFqpQZccu(Ob1vbrroCjCEG50;z! z$6V(oq<*_`qI8?B*}OZmrk-tRg_HU_gw*TO3*$G@e}0YrqYS>LkL5Fr|IBy%{)YPB z=oQQ}7f4n;QSlp#b2=#`0Lmp?bfOMbh4vStex1_TvH&v^#G_@8rd1w%eDD4zQI% zP_HCyd1Eb}1sPF+{w_#b@iFRkS!_2bfqp^SXLh%Zhw2~84}GQ8&3)GOpZJVnqMg1z zBa$-9cw_2ZAN&;WPBiGZ)t`ect1+{eEfX=@8&D{BvAHZwhPbs?&bAdRWXPW;lcmXf zlq*PzRR6?pB8}9h%bRvjU1@SdS!T%uRh!^h-B+uB()WhC;ynWS$LjMl^1B(KDkuDR z<&VfD*2N>iGN|)6|3{w<+sV>++44k}bG50DeFD6Y#^cbhxP3`LZR#u7H$!ESMqlW3 z+K*tqQ1U+cFw-CT8oQ(pBPZi1k=Ur!LsSPfI=>V}gk-}n-uCi_rbMZrsc!7qB!DTA zAi_50p#rq+k~XsZQzGgTKyjOsr!kEt+6DWxrQW_iX2@n!GVS4RIely@K6H6WJH1Z) zPTNCWE?XN=1N(Eg0F9o?#APS8-DuC(xx&o{iazThV7z~^3sAP_GLnwTf01o}$y}aH zWkI2#-ze){c4%_V!cNHMN15JBy%Na3X;5zmhK|@|kN;O{zL!w%&+@gltrq+w()7If z0wDX#Kyo4}x`LnF`!D#2KG4k9y=LtVP_cQ3ZYZKm)F`l{5B?ZgE5N_mhfESgXor#} zj+|{F;2BN*SZye}dbt=Ti8i!M`Y7OU6Mm+N1zW!qq4i~&|Mk2=6Z!dkmqwyVSEHR$ zvP07RVxHy2=@Wagw`nfAwRalB_ww()ILTWYiY_1iDF2nJY^U70bZO>v)~WI#_HCUM zV|yF-ngy>4&11IDPvhOVkg>6`_BrM%))&(}LTb)J1oIj(e+-pWXh$dRNVBcQ4#0Iu z)Hz`1uD>(;APlk??VD=LFZ%stUyrwO;1b3YDj%fzQ*%F_ZFlg`|CK3!2^xH#Uaf`{~{n z>w$j#H_b-<;_n(^66~1aKmK*5ugA^gE1~P7%~~%$VflqGTmIz{)3wiLzsN_rxVF#V zT#l#V>1IRJ_2YHCub;nvoId?~Hi-Sz-(23skN(|-{@#1`d0eDBEOuP?it8%iN2mXf zUhi-G{a>Q@fBat~zWDJx={geoX@C1dK8f`kE@6_wx{89ZHz22R-AIe?V}K zhr9mpkqW&HPhB5_VU)0TdCF6YW|~%c_1VIGqV3Gk6X|eroxKwTyB9-DjOuQ|X*^)D zetLdyA_DYSnUpvDk%tAdD0+J(p!vpc3s1a__8qF|_8j5pqu;o^VC(m|p-2grpLMpn zW}Z;Owxjc|uLQ&@z(H%4L(ov=vs6D~Xp$xA7pln`H=RiNqjI{F%^dJYAO`-!=9mP$bhJI7)mPWA;4AWCNB0FGY@;sUpl)rE`ahmW8{%co2Gni*DzoZW zd$Dp~aIRN9KW`@l!27+$0-QVv(1vo-pI9G({7Y}MPS)1m^@Q{-loQH4*`JK_ z?

- - - + + + ) } diff --git a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/EmailReflectionCard.tsx b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/EmailReflectionCard.tsx index dfb3bd67f4c..44fc7089dcb 100644 --- a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/EmailReflectionCard.tsx +++ b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/EmailReflectionCard.tsx @@ -18,6 +18,7 @@ const contentStyle = { borderStyle: 'solid', borderWidth: '1px', boxSizing: 'content-box', + breakInside: 'avoid', color: PALETTE.SLATE_700, fontFamily: FONT_FAMILY.SANS_SERIF, fontSize: '14px', diff --git a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx index b5b46325d23..4e836b0cf75 100644 --- a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx +++ b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx @@ -169,7 +169,7 @@ const RetroTopic = (props: Props) => { + + + + ) } From 968452e28003b188f6706f10b005d84508e11634 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Wed, 4 Sep 2024 08:53:39 -0700 Subject: [PATCH 439/529] feat(orgAdmin): search in org members page (#10187) --- .../components/OrgMembers/OrgMembers.tsx | 44 +++++++++++++++---- 1 file changed, 35 insertions(+), 9 deletions(-) diff --git a/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx b/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx index 0e3e0b19a4f..8fc04aab8c9 100644 --- a/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx +++ b/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx @@ -1,7 +1,7 @@ import graphql from 'babel-plugin-relay/macro' import type {Parser as JSON2CSVParser} from 'json2csv' import Parser from 'json2csv/lib/JSON2CSVParser' // only grab the sync parser -import React, {useMemo, useState} from 'react' +import React, {useCallback, useMemo, useState} from 'react' import {PreloadedQuery, usePaginationFragment, usePreloadedQuery} from 'react-relay' import {OrgMembersPaginationQuery} from '~/__generated__/OrgMembersPaginationQuery.graphql' import {OrgMembersQuery} from '~/__generated__/OrgMembersQuery.graphql' @@ -76,23 +76,39 @@ const OrgMembers = (props: Props) => { ) const [sortBy, setSortBy] = useState('lastSeenAt') const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc') + const [searchInput, setSearchInput] = useState('') - const sortedOrganizationUsers = useMemo(() => { - return [...organizationUsers.edges].sort((a, b) => { + const handleSearchChange = useCallback((e: React.ChangeEvent) => { + setSearchInput(e.target.value) + }, []) + + const filteredOrgUsers = useMemo(() => { + const cleanedSearchInput = searchInput.toLowerCase().trim() + return organizationUsers.edges + .map(({node}) => node) + .filter( + (user) => + user.user.preferredName.toLowerCase().includes(cleanedSearchInput) || + user.user.email.toLowerCase().includes(cleanedSearchInput) + ) + }, [organizationUsers.edges, searchInput]) + + const finalOrgUsers = useMemo(() => { + return [...filteredOrgUsers].sort((a, b) => { if (sortBy === 'lastSeenAt') { - const aDate = a.node.user.lastSeenAt ? new Date(a.node.user.lastSeenAt) : new Date(0) - const bDate = b.node.user.lastSeenAt ? new Date(b.node.user.lastSeenAt) : new Date(0) + const aDate = a.user.lastSeenAt ? new Date(a.user.lastSeenAt) : new Date(0) + const bDate = b.user.lastSeenAt ? new Date(b.user.lastSeenAt) : new Date(0) return sortDirection === 'asc' ? aDate.getTime() - bDate.getTime() : bDate.getTime() - aDate.getTime() } else if (sortBy === 'preferredName') { return sortDirection === 'asc' - ? a.node.user.preferredName.localeCompare(b.node.user.preferredName) - : b.node.user.preferredName.localeCompare(a.node.user.preferredName) + ? a.user.preferredName.localeCompare(b.user.preferredName) + : b.user.preferredName.localeCompare(a.user.preferredName) } return 0 }) - }, [organizationUsers.edges, sortBy, sortDirection]) + }, [filteredOrgUsers, sortBy, sortDirection]) const handleSort = (column: keyof User) => { if (sortBy === column) { @@ -143,6 +159,16 @@ const OrgMembers = (props: Props) => { +
+ +
+
@@ -173,7 +199,7 @@ const OrgMembers = (props: Props) => {
- {sortedOrganizationUsers.map(({node: organizationUser}) => ( + {finalOrgUsers.map((organizationUser) => ( Date: Thu, 5 Sep 2024 07:53:24 +0200 Subject: [PATCH 440/529] chore(release): release v7.46.0 (#10182) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index dcfe77c1e9a..0e19973322f 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.45.2" + ".": "7.46.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index aaa80d4c0cc..5fe32e47683 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.46.0](https://github.com/ParabolInc/parabol/compare/v7.45.2...v7.46.0) (2024-09-04) + + +### Added + +* **orgAdmin:** search in org members page ([#10187](https://github.com/ParabolInc/parabol/issues/10187)) ([968452e](https://github.com/ParabolInc/parabol/commit/968452e28003b188f6706f10b005d84508e11634)) +* **orgAdmins:** Make org members view sortable ([#10146](https://github.com/ParabolInc/parabol/issues/10146)) ([97bb948](https://github.com/ParabolInc/parabol/commit/97bb948330e1e57a331113e267ec5965a84ea6e4)) + + +### Changed + +* **deps:** bump micromatch from 4.0.5 to 4.0.8 ([#10164](https://github.com/ParabolInc/parabol/issues/10164)) ([70f69ce](https://github.com/ParabolInc/parabol/commit/70f69ce039f9550c52f391c0f556919a7fe4589b)) + ## [7.45.2](https://github.com/ParabolInc/parabol/compare/v7.45.1...v7.45.2) (2024-08-29) diff --git a/package.json b/package.json index 872a9104293..d3864155421 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.45.2", + "version": "7.46.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 83fdd61db90..60f90f1391d 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.45.2", + "version": "7.46.0", "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.45.2" + "parabol-server": "7.46.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 0304016588a..9c7eecb93f3 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.45.2", + "version": "7.46.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 4ad30a514da..833139fa5a0 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.45.2", + "version": "7.46.0", "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 610357c65da..488642e3f6f 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.45.2", + "version": "7.46.0", "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.45.2", - "parabol-server": "7.45.2", + "parabol-client": "7.46.0", + "parabol-server": "7.46.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 4b7caae9866..6c027507821 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.45.2", + "version": "7.46.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index b12149d0c45..b540cb84d97 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.45.2", + "version": "7.46.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.45.2", + "parabol-client": "7.46.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 91482054870809d133fbc70af078b033d55c6ace Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Fri, 6 Sep 2024 13:38:14 -0700 Subject: [PATCH 441/529] chore(rethinkdb): Comment: Phase 2 (#10180) Signed-off-by: Matt Krick --- .../1724884922936_Comment-phase2.ts | 140 ++++++++++++++++++ 1 file changed, 140 insertions(+) create mode 100644 packages/server/postgres/migrations/1724884922936_Comment-phase2.ts diff --git a/packages/server/postgres/migrations/1724884922936_Comment-phase2.ts b/packages/server/postgres/migrations/1724884922936_Comment-phase2.ts new file mode 100644 index 00000000000..896774f9f81 --- /dev/null +++ b/packages/server/postgres/migrations/1724884922936_Comment-phase2.ts @@ -0,0 +1,140 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + try { + console.log('Adding index') + await r + .table('Comment') + .indexCreate('updatedAtId', (row: any) => [row('updatedAt'), row('id')]) + .run() + await r.table('Comment').indexWait().run() + } catch { + // index already exists + } + + console.log('Adding index complete') + + // must truncate because some rows didn't have a threadParentId + await sql`TRUNCATE TABLE "Comment"`.execute(pg) + + const MAX_PG_PARAMS = 65545 + const PG_COLS = [ + 'id', + 'createdAt', + 'updatedAt', + 'isActive', + 'isAnonymous', + 'threadParentId', + 'reactjis', + 'content', + 'createdBy', + 'plaintextContent', + 'discussionId', + 'threadSortOrder' + ] as const + type Comment = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + + let curUpdatedAt = r.minval + let curId = r.minval + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, String(curUpdatedAt), String(curId)) + const rawRowsToInsert = (await r + .table('Comment') + .between([curUpdatedAt, curId], [r.maxval, r.maxval], { + index: 'updatedAtId', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'updatedAtId'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as Comment[] + + const rowsToInsert = rawRowsToInsert + .map((row) => { + const {plaintextContent, threadSortOrder, reactjis, ...rest} = row as any + return { + ...rest, + plaintextContent: plaintextContent.slice(0, 2000), + threadSortOrder: threadSortOrder ? Math.trunc(threadSortOrder) : 0, + reactjis: reactjis?.map((r: any) => `(${r.id},${r.userId})`) ?? [] + } + }) + .filter((row) => row.discussionId) + + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curUpdatedAt = lastRow.updatedAt + curId = lastRow.id + await Promise.all( + rowsToInsert.map(async (row) => { + try { + await pg + .insertInto('Comment') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_createdBy') { + await pg + .insertInto('Comment') + .values({...row, createdBy: null}) + .onConflict((oc) => oc.doNothing()) + .execute() + return + } + if (e.constraint === 'fk_discussionId') { + console.log(`Skipping ${row.id} because it has no discussion`) + return + } + console.log(e, row) + } + }) + ) + } + + // if the threadParentId references an id that does not exist, set it to null + console.log('adding threadParentId constraint') + await pg + .updateTable('Comment') + .set({threadParentId: null}) + .where(({eb, selectFrom}) => + eb( + 'id', + 'in', + selectFrom('Comment as child') + .select('child.id') + .leftJoin('Comment as parent', 'child.threadParentId', 'parent.id') + .where('parent.id', 'is', null) + .where('child.threadParentId', 'is not', null) + ) + ) + .execute() + await pg.schema + .alterTable('Comment') + .addForeignKeyConstraint('fk_threadParentId', ['threadParentId'], 'Comment', ['id']) + .onDelete('set null') + .execute() +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql`TRUNCATE TABLE "Comment" CASCADE`.execute(pg) +} From c4444ef9814a30bb2659427d17f639dbf151f46e Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Fri, 6 Sep 2024 13:38:40 -0700 Subject: [PATCH 442/529] fix: multiple slack notifications (#10190) Signed-off-by: Matt Krick --- packages/server/dataloader/integrationAuthLoaders.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/packages/server/dataloader/integrationAuthLoaders.ts b/packages/server/dataloader/integrationAuthLoaders.ts index 6fda9b74f56..6ae61e334be 100644 --- a/packages/server/dataloader/integrationAuthLoaders.ts +++ b/packages/server/dataloader/integrationAuthLoaders.ts @@ -187,6 +187,7 @@ export const slackNotificationsByTeamIdAndEvent = (parent: RootDataLoader) => { .flat() return keys.map((key) => { + const usedChannelIds = new Set() return res .filter((doc) => doc.teamId === key.teamId && doc.event === key.event) .map((notification) => { @@ -200,6 +201,11 @@ export const slackNotificationsByTeamIdAndEvent = (parent: RootDataLoader) => { } }) .filter(isValid) + .filter(({channelId}) => { + if (!channelId || usedChannelIds.has(channelId)) return false + usedChannelIds.add(channelId) + return true + }) }) }) } From 724092919768ea0ee46c94947be7e0765145994f Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 13:44:22 -0700 Subject: [PATCH 443/529] chore(release): release v7.46.1 (#10191) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0e19973322f..c8382936cbf 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.46.0" + ".": "7.46.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 5fe32e47683..9ba2ccc73c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.46.1](https://github.com/ParabolInc/parabol/compare/v7.46.0...v7.46.1) (2024-09-06) + + +### Fixed + +* multiple slack notifications ([#10190](https://github.com/ParabolInc/parabol/issues/10190)) ([c4444ef](https://github.com/ParabolInc/parabol/commit/c4444ef9814a30bb2659427d17f639dbf151f46e)) + + +### Changed + +* **rethinkdb:** Comment: Phase 2 ([#10180](https://github.com/ParabolInc/parabol/issues/10180)) ([9148205](https://github.com/ParabolInc/parabol/commit/91482054870809d133fbc70af078b033d55c6ace)) + ## [7.46.0](https://github.com/ParabolInc/parabol/compare/v7.45.2...v7.46.0) (2024-09-04) diff --git a/package.json b/package.json index d3864155421..e400e0588ab 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.46.0", + "version": "7.46.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 60f90f1391d..f6824bc2865 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.46.0", + "version": "7.46.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.46.0" + "parabol-server": "7.46.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index 9c7eecb93f3..411b8ad2710 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.46.0", + "version": "7.46.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 833139fa5a0..02c3a9a08d9 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.46.0", + "version": "7.46.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 488642e3f6f..181bf194d3a 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.46.0", + "version": "7.46.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.46.0", - "parabol-server": "7.46.0", + "parabol-client": "7.46.1", + "parabol-server": "7.46.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 6c027507821..36efbeb68df 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.46.0", + "version": "7.46.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index b540cb84d97..6830512698a 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.46.0", + "version": "7.46.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.46.0", + "parabol-client": "7.46.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 724a340e5872fc13963cad278bb13017f0ec1270 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Fri, 6 Sep 2024 16:00:12 -0700 Subject: [PATCH 444/529] fix: insert discussion before comment (#10194) Signed-off-by: Matt Krick --- .../addAgendaItemToActiveActionMeeting.ts | 25 +++++---- .../helpers/createNewMeetingPhases.ts | 52 ++++++++++++------- .../mutations/helpers/handleCompletedStage.ts | 8 +-- .../server/graphql/mutations/joinMeeting.ts | 13 ++--- .../graphql/mutations/updatePokerScope.ts | 9 ++-- .../postgres/queries/insertDiscussions.ts | 15 ------ .../queries/src/insertDiscussionsQuery.sql | 6 --- 7 files changed, 63 insertions(+), 65 deletions(-) delete mode 100644 packages/server/postgres/queries/insertDiscussions.ts delete mode 100644 packages/server/postgres/queries/src/insertDiscussionsQuery.sql diff --git a/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts b/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts index 4ceb9ab23c9..735ba4be831 100644 --- a/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts +++ b/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts @@ -2,7 +2,6 @@ import getRethink from '../../../database/rethinkDriver' import AgendaItemsStage from '../../../database/types/AgendaItemsStage' import MeetingAction from '../../../database/types/MeetingAction' import getKysely from '../../../postgres/getKysely' -import insertDiscussions from '../../../postgres/queries/insertDiscussions' import getPhase from '../../../utils/getPhase' import {DataLoaderWorker} from '../../graphql' @@ -47,16 +46,20 @@ const addAgendaItemToActiveActionMeeting = async ( updatedAt: now }) .run(), - getKysely().updateTable('AgendaItem').set({meetingId}).where('id', '=', agendaItemId).execute(), - insertDiscussions([ - { - id: discussionId, - teamId, - meetingId, - discussionTopicType: 'agendaItem' as const, - discussionTopicId: agendaItemId - } - ]) + getKysely() + .with('InsertDiscussion', (qb) => + qb.insertInto('Discussion').values({ + id: discussionId, + teamId, + meetingId, + discussionTopicType: 'agendaItem', + discussionTopicId: agendaItemId + }) + ) + .updateTable('AgendaItem') + .set({meetingId}) + .where('id', '=', agendaItemId) + .execute() ]) return meetingId diff --git a/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts b/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts index 56d4115ef63..098392fc6a6 100644 --- a/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts +++ b/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts @@ -23,7 +23,7 @@ import TeamHealthPhase from '../../../database/types/TeamHealthPhase' import TeamHealthStage from '../../../database/types/TeamHealthStage' import UpdatesPhase from '../../../database/types/UpdatesPhase' import UpdatesStage from '../../../database/types/UpdatesStage' -import insertDiscussions from '../../../postgres/queries/insertDiscussions' +import getKysely from '../../../postgres/getKysely' import {MeetingTypeEnum} from '../../../postgres/types/Meeting' import isPhaseAvailable from '../../../utils/isPhaseAvailable' import {DataLoaderWorker} from '../../graphql' @@ -77,6 +77,7 @@ const createNewMeetingPhases = async ( meetingType: MeetingTypeEnum, dataLoader: DataLoaderWorker ) => { + const pg = getKysely() const [meetingSettings, stageDurations, team] = await Promise.all([ dataLoader.get('meetingSettingsByType').load({teamId, meetingType}), getPastStageDurations(teamId), @@ -108,17 +109,22 @@ const createNewMeetingPhases = async ( case DISCUSS: const discussPhase = new DiscussPhase(durations) const discussStages = discussPhase.stages.filter((stage) => stage.reflectionGroupId) - asyncSideEffects.push( - insertDiscussions( - discussStages.map((stage) => ({ - id: stage.discussionId, - teamId, - meetingId, - discussionTopicId: stage.reflectionGroupId, - discussionTopicType: 'reflectionGroup' as const - })) + if (discussStages.length > 0) { + asyncSideEffects.push( + pg + .insertInto('Discussion') + .values( + discussStages.map((stage) => ({ + id: stage.discussionId, + teamId, + meetingId, + discussionTopicId: stage.reflectionGroupId, + discussionTopicType: 'reflectionGroup' + })) + ) + .execute() ) - ) + } return discussPhase case UPDATES: return new UpdatesPhase({durations, stages: [new UpdatesStage(facilitatorTeamMemberId)]}) @@ -127,14 +133,22 @@ const createNewMeetingPhases = async ( const agendaItemIds = agendaItems.map(({id}) => id) const agendaItemPhase = new AgendaItemsPhase(agendaItemIds, durations) const {stages} = agendaItemPhase - const discussions = stages.map((stage) => ({ - id: stage.discussionId, - teamId, - meetingId, - discussionTopicId: stage.agendaItemId, - discussionTopicType: 'agendaItem' as const - })) - asyncSideEffects.push(insertDiscussions(discussions)) + if (stages.length > 0) { + asyncSideEffects.push( + pg + .insertInto('Discussion') + .values( + stages.map((stage) => ({ + id: stage.discussionId, + teamId, + meetingId, + discussionTopicId: stage.agendaItemId, + discussionTopicType: 'agendaItem' + })) + ) + .execute() + ) + } return agendaItemPhase case 'ESTIMATE': return new EstimatePhase() diff --git a/packages/server/graphql/mutations/helpers/handleCompletedStage.ts b/packages/server/graphql/mutations/helpers/handleCompletedStage.ts index 7bd65438355..a6aa7267f88 100644 --- a/packages/server/graphql/mutations/helpers/handleCompletedStage.ts +++ b/packages/server/graphql/mutations/helpers/handleCompletedStage.ts @@ -6,7 +6,6 @@ import DiscussStage from '../../../database/types/DiscussStage' import GenericMeetingStage from '../../../database/types/GenericMeetingStage' import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import getKysely from '../../../postgres/getKysely' -import insertDiscussions from '../../../postgres/queries/insertDiscussions' import {AnyMeeting} from '../../../postgres/types/Meeting' import {DataLoaderWorker} from '../../graphql' import addAIGeneratedContentToThreads from './addAIGeneratedContentToThreads' @@ -28,12 +27,11 @@ const handleCompletedRetrospectiveStage = async ( meeting: MeetingRetrospective, dataLoader: DataLoaderWorker ) => { + const pg = getKysely() if (stage.phaseType === REFLECT || stage.phaseType === GROUP) { const data: Record = await removeEmptyReflections(meeting, dataLoader) if (stage.phaseType === REFLECT) { - const pg = getKysely() - const [reflectionGroups, unsortedReflections] = await Promise.all([ dataLoader.get('retroReflectionGroupsByMeetingId').load(meeting.id), dataLoader.get('retroReflectionsByMeetingId').load(meeting.id) @@ -93,7 +91,9 @@ const handleCompletedRetrospectiveStage = async ( discussionTopicId: stage.reflectionGroupId })) // discussions must exist before we can add comments to them! - await insertDiscussions(discussions) + if (discussions.length > 0) { + await pg.insertInto('Discussion').values(discussions).execute() + } await Promise.all([ addAIGeneratedContentToThreads(discussPhaseStages, meetingId, dataLoader), publishToEmbedder({jobType: 'relatedDiscussions:start', data: {meetingId}, priority: 0}) diff --git a/packages/server/graphql/mutations/joinMeeting.ts b/packages/server/graphql/mutations/joinMeeting.ts index 7be33d39e2f..c95582aeea1 100644 --- a/packages/server/graphql/mutations/joinMeeting.ts +++ b/packages/server/graphql/mutations/joinMeeting.ts @@ -13,7 +13,7 @@ import RetroMeetingMember from '../../database/types/RetroMeetingMember' import TeamPromptMeetingMember from '../../database/types/TeamPromptMeetingMember' import TeamPromptResponseStage from '../../database/types/TeamPromptResponseStage' import UpdatesStage from '../../database/types/UpdatesStage' -import insertDiscussions from '../../postgres/queries/insertDiscussions' +import getKysely from '../../postgres/getKysely' import {TeamMember} from '../../postgres/types' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' @@ -145,15 +145,16 @@ const joinMeeting = { // only add a new stage for the new users (ie. invited to the team after the meeting was started) if (teamMemberResponseStage) return const responsesStage = new TeamPromptResponseStage({teamMemberId}) - await insertDiscussions([ - { + await getKysely() + .insertInto('Discussion') + .values({ id: responsesStage.discussionId, teamId, meetingId, discussionTopicId: teamMemberId, - discussionTopicType: 'teamPromptResponse' as const - } - ]) + discussionTopicType: 'teamPromptResponse' + }) + .execute() return addStageToPhase(responsesStage, 'RESPONSES') } diff --git a/packages/server/graphql/mutations/updatePokerScope.ts b/packages/server/graphql/mutations/updatePokerScope.ts index 0300d2f9832..823a6381383 100644 --- a/packages/server/graphql/mutations/updatePokerScope.ts +++ b/packages/server/graphql/mutations/updatePokerScope.ts @@ -1,12 +1,13 @@ import {GraphQLID, GraphQLList, GraphQLNonNull} from 'graphql' +import {Insertable} from 'kysely' import {SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' -import {Writeable} from '../../../client/types/generics' import {ESTIMATE_TASK_SORT_ORDER} from '../../../client/utils/constants' import getRethink from '../../database/rethinkDriver' import EstimateStage from '../../database/types/EstimateStage' import MeetingPoker from '../../database/types/MeetingPoker' import {TaskServiceEnum} from '../../database/types/Task' -import insertDiscussions, {InputDiscussions} from '../../postgres/queries/insertDiscussions' +import getKysely from '../../postgres/getKysely' +import {Discussion} from '../../postgres/pg' import RedisLockQueue from '../../utils/RedisLockQueue' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' @@ -112,7 +113,7 @@ const updatePokerScope = { // add stages const templateRef = await dataLoader.get('templateRefs').loadNonNull(templateRefId) const {dimensions} = templateRef - const newDiscussions = [] as Writeable + const newDiscussions = [] as Insertable[] const additiveUpdates = updates.filter((update) => { const {action, serviceTaskId} = update return action === 'ADD' && !stages.find((stage) => stage.serviceTaskId === serviceTaskId) @@ -168,7 +169,7 @@ const updatePokerScope = { }) .run() if (newDiscussions.length > 0) { - await insertDiscussions(newDiscussions) + await getKysely().insertInto('Discussion').values(newDiscussions).execute() } const data = {meetingId, newStageIds} publish(SubscriptionChannel.MEETING, meetingId, 'UpdatePokerScopeSuccess', data, subOptions) diff --git a/packages/server/postgres/queries/insertDiscussions.ts b/packages/server/postgres/queries/insertDiscussions.ts deleted file mode 100644 index 6e473a40bb8..00000000000 --- a/packages/server/postgres/queries/insertDiscussions.ts +++ /dev/null @@ -1,15 +0,0 @@ -import {DeepNonNullable} from '../../../client/types/generics' -import getPg from '../getPg' -import { - IInsertDiscussionsQueryParams, - insertDiscussionsQuery -} from './generated/insertDiscussionsQuery' - -export type InputDiscussions = DeepNonNullable - -const insertDiscussions = async (discussions: InputDiscussions) => { - if (discussions.length === 0) return - insertDiscussionsQuery.run({discussions} as any, getPg()) -} - -export default insertDiscussions diff --git a/packages/server/postgres/queries/src/insertDiscussionsQuery.sql b/packages/server/postgres/queries/src/insertDiscussionsQuery.sql deleted file mode 100644 index 787f675b0ed..00000000000 --- a/packages/server/postgres/queries/src/insertDiscussionsQuery.sql +++ /dev/null @@ -1,6 +0,0 @@ -/* - @name insertDiscussionsQuery - @param discussions -> ((id, teamId, meetingId, discussionTopicId, discussionTopicType)...) -*/ -INSERT INTO "Discussion" ("id", "teamId", "meetingId", "discussionTopicId", "discussionTopicType") -VALUES :discussions; From 4c30f72d23e7b534465815a6b3b05948b321d1c9 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Fri, 6 Sep 2024 16:14:07 -0700 Subject: [PATCH 445/529] chore(release): release v7.46.2 (#10195) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c8382936cbf..38db86c7257 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.46.1" + ".": "7.46.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9ba2ccc73c2..bb42db53b33 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.46.2](https://github.com/ParabolInc/parabol/compare/v7.46.1...v7.46.2) (2024-09-06) + + +### Fixed + +* insert discussion before comment ([#10194](https://github.com/ParabolInc/parabol/issues/10194)) ([724a340](https://github.com/ParabolInc/parabol/commit/724a340e5872fc13963cad278bb13017f0ec1270)) + ## [7.46.1](https://github.com/ParabolInc/parabol/compare/v7.46.0...v7.46.1) (2024-09-06) diff --git a/package.json b/package.json index e400e0588ab..7178b32572c 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.46.1", + "version": "7.46.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index f6824bc2865..459a90d9a9a 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.46.1", + "version": "7.46.2", "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.46.1" + "parabol-server": "7.46.2" } } diff --git a/packages/client/package.json b/packages/client/package.json index 411b8ad2710..cf308db6ef6 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.46.1", + "version": "7.46.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 02c3a9a08d9..b86bff53eb1 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.46.1", + "version": "7.46.2", "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 181bf194d3a..87ebf8e5828 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.46.1", + "version": "7.46.2", "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.46.1", - "parabol-server": "7.46.1", + "parabol-client": "7.46.2", + "parabol-server": "7.46.2", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 36efbeb68df..cb1fc4cc465 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.46.1", + "version": "7.46.2", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 6830512698a..80a7fcc66b2 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.46.1", + "version": "7.46.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.46.1", + "parabol-client": "7.46.2", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From be5d28a05d3d839ead0924fe79b736bd49abbce8 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Mon, 9 Sep 2024 11:09:45 -0700 Subject: [PATCH 446/529] chore(metrics): Only track 'Loaded a Page' event to Amplitude when userId is known (#9193) --- packages/client/components/AnalyticsPage.tsx | 33 +++++++++++--------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/packages/client/components/AnalyticsPage.tsx b/packages/client/components/AnalyticsPage.tsx index bf18f09dd94..0b1cbcc4910 100644 --- a/packages/client/components/AnalyticsPage.tsx +++ b/packages/client/components/AnalyticsPage.tsx @@ -171,21 +171,24 @@ const AnalyticsPage = () => { const translated = !!document.querySelector( 'html.translated-ltr, html.translated-rtl, ya-tr-span, *[_msttexthash], *[x-bergamot-translated]' ) - amplitude.track( - 'Loaded a Page', - { - name: pageName, - referrer: document.referrer, - title, - path: pathname, - url: href, - translated, - search: location.search - }, - { - user_id: atmosphere.viewerId - } - ) + const userId = atmosphere.viewerId + if (!!userId) { + amplitude.track( + 'Loaded a Page', + { + name: pageName, + referrer: document.referrer, + title, + path: pathname, + url: href, + translated, + search: location.search + }, + { + user_id: userId + } + ) + } }, TIME_TO_RENDER_TREE) }, [pathname, location.search, atmosphere.viewerId]) From 22c3b5bfa392d0bcaeed997ce7698a72edf9b23c Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 9 Sep 2024 11:38:18 -0700 Subject: [PATCH 447/529] chore(rethinkdb): Comment: Phase 3 (#10172) Signed-off-by: Matt Krick --- codegen.json | 2 +- .../indexing/retrospectiveDiscussionTopic.ts | 4 +- .../helpers/publishSimilarRetroTopics.ts | 3 - packages/server/database/rethinkDriver.ts | 5 -- packages/server/database/types/Comment.ts | 63 ------------------- packages/server/database/types/Reactji.ts | 15 ----- .../dataloader/foreignKeyLoaderMakers.ts | 4 +- .../dataloader/primaryKeyLoaderMakers.ts | 2 +- .../rethinkForeignKeyLoaderMakers.ts | 17 ----- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../helpers/addAIGeneratedContentToThreads.ts | 39 +++++------- .../mutations/helpers/calculateEngagement.ts | 2 +- .../helpers/notifications/SlackNotifier.ts | 2 +- .../resetRetroMeetingToGroupStage.ts | 5 -- .../mutations/generateMeetingSummary.ts | 12 ++-- .../private/mutations/hardDeleteUser.ts | 12 +--- .../graphql/public/mutations/addComment.ts | 26 ++++---- .../public/mutations/addReactjiToReactable.ts | 36 +---------- .../graphql/public/mutations/deleteComment.ts | 3 - .../public/mutations/helpers/getTopics.ts | 10 +-- .../public/mutations/updateCommentContent.ts | 8 --- .../graphql/public/types/AddCommentSuccess.ts | 2 +- .../server/graphql/public/types/Comment.ts | 6 +- .../public/types/DeleteCommentSuccess.ts | 2 +- .../public/types/NotifyDiscussionMentioned.ts | 4 +- .../public/types/NotifyResponseReplied.ts | 4 +- .../types/UpdateCommentContentSuccess.ts | 2 +- .../graphql/resolvers/resolveReactjis.ts | 4 +- .../resolvers/resolveThreadableConnection.ts | 2 +- packages/server/postgres/select.ts | 2 +- packages/server/postgres/types/index.d.ts | 5 +- packages/server/utils/analytics/analytics.ts | 2 +- packages/server/utils/getGroupedReactjis.ts | 4 +- 33 files changed, 73 insertions(+), 237 deletions(-) delete mode 100644 packages/server/database/types/Comment.ts delete mode 100644 packages/server/database/types/Reactji.ts diff --git a/codegen.json b/codegen.json index 50e40fa3bbe..bb7e83c6aa9 100644 --- a/codegen.json +++ b/codegen.json @@ -84,7 +84,7 @@ "AzureDevOpsRemoteProject": "./types/AzureDevOpsRemoteProject#AzureDevOpsRemoteProjectSource", "AzureDevOpsWorkItem": "../../dataloader/azureDevOpsLoaders#AzureDevOpsWorkItem", "BatchArchiveTasksSuccess": "./types/BatchArchiveTasksSuccess#BatchArchiveTasksSuccessSource", - "Comment": "../../database/types/Comment#default as CommentDB", + "Comment": "../../postgres/types/index#Comment as CommentDB", "Company": "./types/Company#CompanySource", "CreateGcalEventInput": "./types/CreateGcalEventInput#default", "CreateImposterTokenPayload": "./types/CreateImposterTokenPayload#CreateImposterTokenPayloadSource", diff --git a/packages/embedder/indexing/retrospectiveDiscussionTopic.ts b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts index ac49248abfd..36eca506754 100644 --- a/packages/embedder/indexing/retrospectiveDiscussionTopic.ts +++ b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts @@ -1,7 +1,7 @@ -import Comment from 'parabol-server/database/types/Comment' import {isMeetingRetrospective} from 'parabol-server/database/types/MeetingRetrospective' import {DataLoaderInstance} from 'parabol-server/dataloader/RootDataLoader' import prettier from 'prettier' +import {Comment} from '../../server/postgres/types' import {inferLanguage} from '../inferLanguage' import {ISO6391} from '../iso6393To1' @@ -154,7 +154,7 @@ export const createTextFromRetrospectiveDiscussionTopic = async ( }) as Comment[] const filteredComments = sortedComments.filter( - (c) => !IGNORE_COMMENT_USER_IDS.includes(c.createdBy) + (c) => !IGNORE_COMMENT_USER_IDS.includes(c.createdBy!) ) if (filteredComments.length) { markdown += `Further discussion was made:\n` diff --git a/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts b/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts index 8490667c5eb..217ee63073b 100644 --- a/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts +++ b/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts @@ -1,7 +1,6 @@ import {SubscriptionChannel} from '../../../client/types/constEnums' import makeAppURL from '../../../client/utils/makeAppURL' import appOrigin from '../../../server/appOrigin' -import getRethink from '../../../server/database/rethinkDriver' import {DataLoaderInstance} from '../../../server/dataloader/RootDataLoader' import {isRetroMeeting} from '../../../server/graphql/meetingTypePredicates' import { @@ -54,7 +53,6 @@ export const publishSimilarRetroTopics = async ( similarEmbeddings: {embeddingsMetadataId: number; similarity: number}[], dataLoader: DataLoaderInstance ) => { - const r = await getRethink() const pg = getKysely() const links = await Promise.all( similarEmbeddings.map((se) => makeSimilarDiscussionLink(se, dataLoader)) @@ -69,7 +67,6 @@ export const publishSimilarRetroTopics = async ( buildCommentContentBlock('🤖 Related Discussions', `
    ${listItems}
`), 2 ) - await r.table('Comment').insert(relatedDiscussionsComment).run() await pg .insertInto('Comment') .values({ diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index b45f322d350..055b5800142 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -3,7 +3,6 @@ import TeamInvitation from '../database/types/TeamInvitation' import {AnyMeeting, AnyMeetingTeamMember} from '../postgres/types/Meeting' import getRethinkConfig from './getRethinkConfig' import {R} from './stricterR' -import Comment from './types/Comment' import MassInvitation from './types/MassInvitation' import NotificationKickedOut from './types/NotificationKickedOut' import NotificationMeetingStageTimeLimitEnd from './types/NotificationMeetingStageTimeLimitEnd' @@ -21,10 +20,6 @@ import RetrospectivePrompt from './types/RetrospectivePrompt' import Task from './types/Task' export type RethinkSchema = { - Comment: { - type: Comment - index: 'discussionId' - } ReflectPrompt: { type: RetrospectivePrompt index: 'teamId' | 'templateId' diff --git a/packages/server/database/types/Comment.ts b/packages/server/database/types/Comment.ts deleted file mode 100644 index 5fee86fdade..00000000000 --- a/packages/server/database/types/Comment.ts +++ /dev/null @@ -1,63 +0,0 @@ -import extractTextFromDraftString from 'parabol-client/utils/draftjs/extractTextFromDraftString' -import generateUID from '../../generateUID' -import Reactji from './Reactji' - -interface CommentInput { - id?: string - createdAt?: Date | null - isActive?: boolean | null - isAnonymous?: boolean | null - threadParentId?: string | null - reactjis?: Reactji[] | null - updatedAt?: Date | null - content: string - createdBy: string - plaintextContent?: string // the plaintext version of content - discussionId: string - threadSortOrder: number -} - -export default class Comment { - id: string - createdAt: Date - isActive: boolean - isAnonymous: boolean - threadParentId?: string - reactjis: Reactji[] - updatedAt: Date - content: string - // userId of the creator - createdBy: string - plaintextContent: string - discussionId: string - threadSortOrder: number - - constructor(input: CommentInput) { - const { - id, - content, - createdAt, - createdBy, - plaintextContent, - threadParentId, - threadSortOrder, - updatedAt, - discussionId, - isActive, - isAnonymous, - reactjis - } = input - this.id = id || generateUID() - this.content = content - this.createdAt = createdAt || new Date() - this.createdBy = createdBy - this.plaintextContent = plaintextContent || extractTextFromDraftString(content) - this.threadSortOrder = threadSortOrder - this.threadParentId = threadParentId || undefined - this.updatedAt = updatedAt || new Date() - this.discussionId = discussionId - this.isActive = isActive ?? true - this.isAnonymous = isAnonymous ?? false - this.reactjis = reactjis ?? [] - } -} diff --git a/packages/server/database/types/Reactji.ts b/packages/server/database/types/Reactji.ts deleted file mode 100644 index fa36d325351..00000000000 --- a/packages/server/database/types/Reactji.ts +++ /dev/null @@ -1,15 +0,0 @@ -interface Input { - userId: string - id: string -} - -export default class Reactji { - userId: string - id: string - constructor(input: Input) { - const {id, userId} = input - this.userId = userId - // not a GUID, the client will need a GUID - this.id = id - } -} diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index d65ab6b9385..0f08e21d5a6 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -207,8 +207,8 @@ export const slackNotificationsByTeamId = foreignKeyLoaderMaker( } ) -export const _pgcommentsByDiscussionId = foreignKeyLoaderMaker( - '_pgcomments', +export const commentsByDiscussionId = foreignKeyLoaderMaker( + 'comments', 'discussionId', async (discussionIds) => { // include deleted comments so we can replace them with tombstones diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index c57d6b9adab..adeee70820a 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -107,6 +107,6 @@ export const slackNotifications = primaryKeyLoaderMaker((ids: readonly string[]) return selectSlackNotifications().where('id', 'in', ids).execute() }) -export const _pgcomments = primaryKeyLoaderMaker((ids: readonly string[]) => { +export const comments = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectComments().where('id', 'in', ids).execute() }) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index 3f9392197a6..b0bf83cf10a 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -15,23 +15,6 @@ export const activeMeetingsByTeamId = new RethinkForeignKeyLoaderMaker( .run() } ) - -export const commentsByDiscussionId = new RethinkForeignKeyLoaderMaker( - 'comments', - 'discussionId', - async (discussionIds) => { - const r = await getRethink() - return ( - r - .table('Comment') - .getAll(r.args(discussionIds), {index: 'discussionId'}) - // include deleted comments so we can replace them with tombstones - // .filter({isActive: true}) - .run() - ) - } -) - export const completedMeetingsByTeamId = new RethinkForeignKeyLoaderMaker( 'newMeetings', 'teamId', diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index 48019c4fcd3..742b1b9ea39 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -3,7 +3,6 @@ import RethinkPrimaryKeyLoaderMaker from './RethinkPrimaryKeyLoaderMaker' /** * all rethink dataloader types which also must exist in {@link rethinkDriver/RethinkSchema} */ -export const comments = new RethinkPrimaryKeyLoaderMaker('Comment') export const reflectPrompts = new RethinkPrimaryKeyLoaderMaker('ReflectPrompt') export const massInvitations = new RethinkPrimaryKeyLoaderMaker('MassInvitation') export const meetingMembers = new RethinkPrimaryKeyLoaderMaker('MeetingMember') diff --git a/packages/server/graphql/mutations/helpers/addAIGeneratedContentToThreads.ts b/packages/server/graphql/mutations/helpers/addAIGeneratedContentToThreads.ts index 5eae10abdb7..6b18a33ff8b 100644 --- a/packages/server/graphql/mutations/helpers/addAIGeneratedContentToThreads.ts +++ b/packages/server/graphql/mutations/helpers/addAIGeneratedContentToThreads.ts @@ -1,8 +1,10 @@ +import {Insertable} from 'kysely' import {PARABOL_AI_USER_ID} from '../../../../client/utils/constants' -import getRethink from '../../../database/rethinkDriver' -import Comment from '../../../database/types/Comment' +import extractTextFromDraftString from '../../../../client/utils/draftjs/extractTextFromDraftString' import DiscussStage from '../../../database/types/DiscussStage' +import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' +import {Comment} from '../../../postgres/pg' import {convertHtmlToTaskContent} from '../../../utils/draftjs/convertHtmlToTaskContent' import {DataLoaderWorker} from '../../graphql' @@ -16,27 +18,25 @@ export const buildCommentContentBlock = ( return convertHtmlToTaskContent(html) } -export const createAIComment = (discussionId: string, content: string, order: number) => - new Comment({ - discussionId, - content, - threadSortOrder: order, - createdBy: PARABOL_AI_USER_ID - }) +export const createAIComment = (discussionId: string, content: string, order: number) => ({ + id: generateUID(), + discussionId, + content, + plaintextContent: extractTextFromDraftString(content), + threadSortOrder: order, + createdBy: PARABOL_AI_USER_ID +}) const addAIGeneratedContentToThreads = async ( stages: DiscussStage[], meetingId: string, dataLoader: DataLoaderWorker ) => { - const [r, groups] = await Promise.all([ - getRethink(), - dataLoader.get('retroReflectionGroupsByMeetingId').load(meetingId) - ]) + const groups = await dataLoader.get('retroReflectionGroupsByMeetingId').load(meetingId) const commentPromises = stages.map(async ({discussionId, reflectionGroupId}) => { const group = groups.find((group) => group.id === reflectionGroupId) if (!group?.discussionPromptQuestion) return - const comments: Comment[] = [] + const comments: Insertable[] = [] if (group.discussionPromptQuestion) { const topicSummaryComment = createAIComment( @@ -46,16 +46,7 @@ const addAIGeneratedContentToThreads = async ( ) comments.push(topicSummaryComment) } - const pgComments = comments.map((comment) => ({ - id: comment.id, - content: comment.content, - plaintextContent: comment.plaintextContent, - createdBy: comment.createdBy, - threadSortOrder: comment.threadSortOrder, - discussionId: comment.discussionId - })) - await getKysely().insertInto('Comment').values(pgComments).execute() - return r.table('Comment').insert(comments).run() + await getKysely().insertInto('Comment').values(comments).execute() }) await Promise.all(commentPromises) } diff --git a/packages/server/graphql/mutations/helpers/calculateEngagement.ts b/packages/server/graphql/mutations/helpers/calculateEngagement.ts index e4faf3d4d25..bed551a8130 100644 --- a/packages/server/graphql/mutations/helpers/calculateEngagement.ts +++ b/packages/server/graphql/mutations/helpers/calculateEngagement.ts @@ -88,7 +88,7 @@ const calculateEngagement = async (meeting: Meeting, dataLoader: DataLoaderWorke ]) const threadables = [...discussions.flat(), ...tasks.flat()] threadables.forEach(({createdBy}) => { - passiveMembers.delete(createdBy) + createdBy && passiveMembers.delete(createdBy) }) discussions.forEach((comments) => { diff --git a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts index 0e73db46043..457220ef98a 100644 --- a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts @@ -257,7 +257,7 @@ const getSlackMessageForNotification = async ( return null } const author = await dataLoader.get('users').loadNonNull(notification.authorId) - const comment = await dataLoader.get('comments').load(notification.commentId) + const comment = await dataLoader.get('comments').loadNonNull(notification.commentId) const authorName = comment.isAnonymous ? 'Anonymous' : author.preferredName diff --git a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts index 0e775bc2dc2..259a838413a 100644 --- a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts +++ b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts @@ -104,11 +104,6 @@ const resetRetroMeetingToGroupStage = { reflectionGroups.forEach((rg) => (rg.voterIds = [])) await Promise.all([ - r - .table('Comment') - .getAll(r.args(discussionIdsToDelete), {index: 'discussionId'}) - .delete() - .run(), pg.deleteFrom('Comment').where('discussionId', 'in', discussionIdsToDelete).execute(), r.table('Task').getAll(r.args(discussionIdsToDelete), {index: 'discussionId'}).delete().run(), pg diff --git a/packages/server/graphql/private/mutations/generateMeetingSummary.ts b/packages/server/graphql/private/mutations/generateMeetingSummary.ts index 6c82273ef1a..7be80c0d07b 100644 --- a/packages/server/graphql/private/mutations/generateMeetingSummary.ts +++ b/packages/server/graphql/private/mutations/generateMeetingSummary.ts @@ -45,7 +45,7 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn if (!discussion) return null const {id: discussionId} = discussion const rawComments = await dataLoader.get('commentsByDiscussionId').load(discussionId) - const humanComments = rawComments.filter((c) => !IGNORE_COMMENT_USER_IDS.includes(c.createdBy)) + const humanComments = rawComments.filter((c) => !IGNORE_COMMENT_USER_IDS.includes(c.createdBy!)) const rootComments = humanComments.filter((c) => !c.threadParentId) rootComments.sort((a, b) => { return a.createdAt.getTime() < b.createdAt.getTime() ? -1 : 1 @@ -53,8 +53,8 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn const comments = await Promise.all( rootComments.map(async (comment) => { const {createdBy, isAnonymous, plaintextContent} = comment - const creator = await dataLoader.get('users').loadNonNull(createdBy) - const commentAuthor = isAnonymous ? 'Anonymous' : creator.preferredName + const creator = createdBy ? await dataLoader.get('users').loadNonNull(createdBy) : null + const commentAuthor = isAnonymous || !creator ? 'Anonymous' : creator.preferredName const commentReplies = await Promise.all( humanComments .filter((c) => c.threadParentId === comment.id) @@ -63,8 +63,10 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn }) .map(async (reply) => { const {createdBy, isAnonymous, plaintextContent} = reply - const creator = await dataLoader.get('users').loadNonNull(createdBy) - const replyAuthor = isAnonymous ? 'Anonymous' : creator.preferredName + const creator = createdBy + ? await dataLoader.get('users').loadNonNull(createdBy) + : null + const replyAuthor = isAnonymous || !creator ? 'Anonymous' : creator.preferredName return { text: plaintextContent, author: replyAuthor diff --git a/packages/server/graphql/private/mutations/hardDeleteUser.ts b/packages/server/graphql/private/mutations/hardDeleteUser.ts index 0771f18b95c..6c19edc395b 100644 --- a/packages/server/graphql/private/mutations/hardDeleteUser.ts +++ b/packages/server/graphql/private/mutations/hardDeleteUser.ts @@ -75,7 +75,7 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( const teamDiscussionIds = discussions.rows.map(({id}) => id) // soft delete first for side effects - const tombstoneId = await softDeleteUser(userIdToDelete, dataLoader) + await softDeleteUser(userIdToDelete, dataLoader) // all other writes await setFacilitatedUserIdOrDelete(userIdToDelete, teamIds, dataLoader) @@ -103,15 +103,7 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( .table('TeamInvitation') .getAll(r.args(teamIds), {index: 'teamId'}) .filter((row: RValue) => row('acceptedBy').eq(userIdToDelete)) - .update({acceptedBy: ''}), - comment: r - .table('Comment') - .getAll(r.args(teamDiscussionIds), {index: 'discussionId'}) - .filter((row: RValue) => row('createdBy').eq(userIdToDelete)) - .update({ - createdBy: tombstoneId, - isAnonymous: true - }) + .update({acceptedBy: ''}) }).run() // now postgres, after FKs are added then triggers should take care of children diff --git a/packages/server/graphql/public/mutations/addComment.ts b/packages/server/graphql/public/mutations/addComment.ts index 99c0aa07a82..6ccf9128c89 100644 --- a/packages/server/graphql/public/mutations/addComment.ts +++ b/packages/server/graphql/public/mutations/addComment.ts @@ -2,15 +2,16 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' import MeetingMemberId from '../../../../client/shared/gqlIds/MeetingMemberId' import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' +import extractTextFromDraftString from '../../../../client/utils/draftjs/extractTextFromDraftString' import getTypeFromEntityMap from '../../../../client/utils/draftjs/getTypeFromEntityMap' import getRethink from '../../../database/rethinkDriver' -import Comment from '../../../database/types/Comment' import GenericMeetingPhase, { NewMeetingPhaseTypeEnum } from '../../../database/types/GenericMeetingPhase' import GenericMeetingStage from '../../../database/types/GenericMeetingStage' import NotificationDiscussionMentioned from '../../../database/types/NotificationDiscussionMentioned' import NotificationResponseReplied from '../../../database/types/NotificationResponseReplied' +import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' import {IGetDiscussionsByIdsQueryResult} from '../../../postgres/queries/generated/getDiscussionsByIdsQuery' import {analytics} from '../../../utils/analytics/analytics' @@ -78,7 +79,7 @@ const addComment: MutationResolvers['addComment'] = async ( const subOptions = {mutatorId, operationId} //AUTH - const {discussionId} = comment + const {discussionId, threadSortOrder, isAnonymous, threadParentId} = comment const discussion = await dataLoader.get('discussions').load(discussionId) if (!discussion) { return {error: {message: 'Invalid discussion thread'}} @@ -101,20 +102,19 @@ const addComment: MutationResolvers['addComment'] = async ( // VALIDATION const content = normalizeRawDraftJS(comment.content) - const dbComment = new Comment({...comment, content, createdBy: viewerId}) - const {id: commentId, isAnonymous, threadParentId} = dbComment - await r.table('Comment').insert(dbComment).run() + const commentId = generateUID() await getKysely() .insertInto('Comment') .values({ - id: dbComment.id, - content: dbComment.content, - plaintextContent: dbComment.plaintextContent, - createdBy: dbComment.createdBy, - threadParentId: dbComment.threadParentId, - threadSortOrder: dbComment.threadSortOrder, - discussionId: dbComment.discussionId + id: commentId, + content, + plaintextContent: extractTextFromDraftString(content), + createdBy: viewerId, + threadSortOrder, + threadParentId, + discussionId }) + .returning('id') .execute() if (discussion.discussionTopicType === 'teamPromptResponse') { @@ -163,7 +163,7 @@ const addComment: MutationResolvers['addComment'] = async ( )! const {stages} = containsThreadablePhase const isAsync = stages.some((stage: GenericMeetingStage) => stage.isAsync) - analytics.commentAdded(viewer, meeting, isAnonymous, isAsync, !!threadParentId) + analytics.commentAdded(viewer, meeting, !!isAnonymous, isAsync, !!threadParentId) publish(SubscriptionChannel.MEETING, meetingId, 'AddCommentSuccess', data, subOptions) return data } diff --git a/packages/server/graphql/public/mutations/addReactjiToReactable.ts b/packages/server/graphql/public/mutations/addReactjiToReactable.ts index 93225e8837e..2debd57a3ac 100644 --- a/packages/server/graphql/public/mutations/addReactjiToReactable.ts +++ b/packages/server/graphql/public/mutations/addReactjiToReactable.ts @@ -3,9 +3,6 @@ import TeamPromptResponseId from 'parabol-client/shared/gqlIds/TeamPromptRespons import {SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import {ValueOf} from '../../../../client/types/generics' -import getRethink from '../../../database/rethinkDriver' -import {RDatum} from '../../../database/stricterR' -import Comment from '../../../database/types/Comment' import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' import getKysely from '../../../postgres/getKysely' import {analytics} from '../../../utils/analytics/analytics' @@ -42,10 +39,8 @@ const addReactjiToReactable: MutationResolvers['addReactjiToReactable'] = async {reactableId, reactableType, reactji, isRemove, meetingId}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { - const r = await getRethink() const pg = getKysely() const viewerId = getUserId(authToken) - const now = new Date() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -87,7 +82,6 @@ const addReactjiToReactable: MutationResolvers['addReactjiToReactable'] = async } // RESOLUTION - const subDoc = {id: reactji, userId: viewerId} const tableName = tableLookup[reactableType] const dbId = tableName === 'TeamPromptResponse' ? TeamPromptResponseId.split(reactableId) : reactableId @@ -110,37 +104,9 @@ const addReactjiToReactable: MutationResolvers['addReactjiToReactable'] = async } } - const updateRethink = async (rethinkDbTable: ValueOf) => { - if (rethinkDbTable === 'TeamPromptResponse' || rethinkDbTable === 'RetroReflection') return - if (isRemove) { - await r - .table(rethinkDbTable) - .get(dbId) - .update((row: RDatum) => ({ - reactjis: row('reactjis').difference([subDoc]), - updatedAt: now - })) - .run() - } else { - await r - .table(rethinkDbTable) - .get(dbId) - .update((row: RDatum) => ({ - reactjis: r.branch( - row('reactjis').contains(subDoc), - row('reactjis'), - // don't use distinct, it sorts the fields - row('reactjis').append(subDoc) - ), - updatedAt: now - })) - .run() - } - } const [meeting] = await Promise.all([ dataLoader.get('newMeetings').load(meetingId), - updatePG(tableName), - updateRethink(tableName) + updatePG(tableName) ]) dataLoader.clearAll(['comments', 'teamPromptResponses', 'retroReflections']) diff --git a/packages/server/graphql/public/mutations/deleteComment.ts b/packages/server/graphql/public/mutations/deleteComment.ts index 957cbda541a..a63b4623bdc 100644 --- a/packages/server/graphql/public/mutations/deleteComment.ts +++ b/packages/server/graphql/public/mutations/deleteComment.ts @@ -1,7 +1,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import {PARABOL_AI_USER_ID} from '../../../../client/utils/constants' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {getUserId} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -12,7 +11,6 @@ const deleteComment: MutationResolvers['deleteComment'] = async ( {commentId, meetingId}, {authToken, dataLoader, socketId: mutatorId} ) => { - const r = await getRethink() const viewerId = getUserId(authToken) const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -40,7 +38,6 @@ const deleteComment: MutationResolvers['deleteComment'] = async ( return {error: {message: 'Can only delete your own comment or Parabol AI comments'}} } - await r.table('Comment').get(commentId).update({isActive: false, updatedAt: now}).run() await getKysely() .updateTable('Comment') .set({updatedAt: now}) diff --git a/packages/server/graphql/public/mutations/helpers/getTopics.ts b/packages/server/graphql/public/mutations/helpers/getTopics.ts index bca0f2a4cec..94d23e228d9 100644 --- a/packages/server/graphql/public/mutations/helpers/getTopics.ts +++ b/packages/server/graphql/public/mutations/helpers/getTopics.ts @@ -19,7 +19,7 @@ const getComments = async (reflectionGroupId: string, dataLoader: DataLoaderWork if (!discussion) return null const {id: discussionId} = discussion const rawComments = await dataLoader.get('commentsByDiscussionId').load(discussionId) - const humanComments = rawComments.filter((c) => !IGNORE_COMMENT_USER_IDS.includes(c.createdBy)) + const humanComments = rawComments.filter((c) => !IGNORE_COMMENT_USER_IDS.includes(c.createdBy!)) const rootComments = humanComments.filter((c) => !c.threadParentId) rootComments.sort((a, b) => { return a.createdAt.getTime() < b.createdAt.getTime() ? -1 : 1 @@ -27,8 +27,8 @@ const getComments = async (reflectionGroupId: string, dataLoader: DataLoaderWork const comments = await Promise.all( rootComments.map(async (comment) => { const {createdBy, isAnonymous, plaintextContent} = comment - const creator = await dataLoader.get('users').loadNonNull(createdBy) - const commentAuthor = isAnonymous ? 'Anonymous' : creator.preferredName + const creator = createdBy ? await dataLoader.get('users').loadNonNull(createdBy) : null + const commentAuthor = isAnonymous || !creator ? 'Anonymous' : creator.preferredName const commentReplies = await Promise.all( humanComments .filter((c) => c.threadParentId === comment.id) @@ -37,8 +37,8 @@ const getComments = async (reflectionGroupId: string, dataLoader: DataLoaderWork }) .map(async (reply) => { const {createdBy, isAnonymous, plaintextContent} = reply - const creator = await dataLoader.get('users').loadNonNull(createdBy) - const replyAuthor = isAnonymous ? 'Anonymous' : creator.preferredName + const creator = createdBy ? await dataLoader.get('users').loadNonNull(createdBy) : null + const replyAuthor = isAnonymous || !creator ? 'Anonymous' : creator.preferredName return { text: plaintextContent, author: replyAuthor diff --git a/packages/server/graphql/public/mutations/updateCommentContent.ts b/packages/server/graphql/public/mutations/updateCommentContent.ts index 3f11febcc72..9ab6cb85b45 100644 --- a/packages/server/graphql/public/mutations/updateCommentContent.ts +++ b/packages/server/graphql/public/mutations/updateCommentContent.ts @@ -3,7 +3,6 @@ import extractTextFromDraftString from 'parabol-client/utils/draftjs/extractText import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' import {PARABOL_AI_USER_ID} from '../../../../client/utils/constants' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {getUserId} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -15,9 +14,7 @@ const updateCommentContent: MutationResolvers['updateCommentContent'] = async ( {commentId, content, meetingId}, {authToken, dataLoader, socketId: mutatorId} ) => { - const r = await getRethink() const operationId = dataLoader.share() - const now = new Date() const subOptions = {operationId, mutatorId} // AUTH @@ -47,11 +44,6 @@ const updateCommentContent: MutationResolvers['updateCommentContent'] = async ( // RESOLUTION const plaintextContent = extractTextFromDraftString(normalizedContent) - await r - .table('Comment') - .get(commentId) - .update({content: normalizedContent, plaintextContent, updatedAt: now}) - .run() await getKysely() .updateTable('Comment') .set({content: normalizedContent, plaintextContent}) diff --git a/packages/server/graphql/public/types/AddCommentSuccess.ts b/packages/server/graphql/public/types/AddCommentSuccess.ts index 5f946d07872..eb620afe185 100644 --- a/packages/server/graphql/public/types/AddCommentSuccess.ts +++ b/packages/server/graphql/public/types/AddCommentSuccess.ts @@ -7,7 +7,7 @@ export type AddCommentSuccessSource = { const AddCommentSuccess: AddCommentSuccessResolvers = { comment: async ({commentId}, _args, {dataLoader}) => { - return dataLoader.get('comments').load(commentId) + return dataLoader.get('comments').loadNonNull(commentId) } } diff --git a/packages/server/graphql/public/types/Comment.ts b/packages/server/graphql/public/types/Comment.ts index 252fd124215..bede0ac3a81 100644 --- a/packages/server/graphql/public/types/Comment.ts +++ b/packages/server/graphql/public/types/Comment.ts @@ -7,7 +7,7 @@ const TOMBSTONE = convertToTaskContent('[deleted]') const Comment: CommentResolvers = { content: ({isActive, content}) => { - return isActive ? content : TOMBSTONE + return isActive ? JSON.stringify(content) : TOMBSTONE }, createdBy: ({createdBy, isAnonymous}) => { @@ -15,7 +15,9 @@ const Comment: CommentResolvers = { }, createdByUser: ({createdBy, isActive, isAnonymous}, _args, {dataLoader}) => { - return isAnonymous || !isActive ? null : dataLoader.get('users').loadNonNull(createdBy) + return isAnonymous || !isActive || !createdBy + ? null + : dataLoader.get('users').loadNonNull(createdBy) }, isActive: ({isActive}) => !!isActive, diff --git a/packages/server/graphql/public/types/DeleteCommentSuccess.ts b/packages/server/graphql/public/types/DeleteCommentSuccess.ts index b9f2c86f76d..68f28c5117c 100644 --- a/packages/server/graphql/public/types/DeleteCommentSuccess.ts +++ b/packages/server/graphql/public/types/DeleteCommentSuccess.ts @@ -6,7 +6,7 @@ export type DeleteCommentSuccessSource = { const DeleteCommentSuccess: DeleteCommentSuccessResolvers = { comment: async ({commentId}, _args, {dataLoader}) => { - return dataLoader.get('comments').load(commentId) + return dataLoader.get('comments').loadNonNull(commentId) } } diff --git a/packages/server/graphql/public/types/NotifyDiscussionMentioned.ts b/packages/server/graphql/public/types/NotifyDiscussionMentioned.ts index 1ab5dfd001d..d87b5bfefda 100644 --- a/packages/server/graphql/public/types/NotifyDiscussionMentioned.ts +++ b/packages/server/graphql/public/types/NotifyDiscussionMentioned.ts @@ -7,13 +7,13 @@ const NotifyDiscussionMentioned: NotifyDiscussionMentionedResolvers = { return meeting }, author: async ({authorId, commentId}, _args: unknown, {dataLoader}) => { - const comment = await dataLoader.get('comments').load(commentId) + const comment = await dataLoader.get('comments').loadNonNull(commentId) if (comment.isAnonymous) return null return dataLoader.get('users').loadNonNull(authorId) }, comment: ({commentId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('comments').load(commentId) + return dataLoader.get('comments').loadNonNull(commentId) }, discussion: ({discussionId}, _args: unknown, {dataLoader}) => { return dataLoader.get('discussions').loadNonNull(discussionId) diff --git a/packages/server/graphql/public/types/NotifyResponseReplied.ts b/packages/server/graphql/public/types/NotifyResponseReplied.ts index 53dd275becf..883a71d93bb 100644 --- a/packages/server/graphql/public/types/NotifyResponseReplied.ts +++ b/packages/server/graphql/public/types/NotifyResponseReplied.ts @@ -14,13 +14,13 @@ const NotifyResponseReplied: NotifyResponseRepliedResolvers = { return responses.find(({userId: responseUserId}) => responseUserId === userId)! }, author: async ({authorId, commentId}, _args: unknown, {dataLoader}) => { - const comment = await dataLoader.get('comments').load(commentId) + const comment = await dataLoader.get('comments').loadNonNull(commentId) if (comment.isAnonymous) return null return dataLoader.get('users').loadNonNull(authorId) }, comment: ({commentId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('comments').load(commentId) + return dataLoader.get('comments').loadNonNull(commentId) } } diff --git a/packages/server/graphql/public/types/UpdateCommentContentSuccess.ts b/packages/server/graphql/public/types/UpdateCommentContentSuccess.ts index 62c7382325b..53a55fea1cc 100644 --- a/packages/server/graphql/public/types/UpdateCommentContentSuccess.ts +++ b/packages/server/graphql/public/types/UpdateCommentContentSuccess.ts @@ -6,7 +6,7 @@ export type UpdateCommentContentSuccessSource = { const UpdateCommentContentSuccess: UpdateCommentContentSuccessResolvers = { comment: async ({commentId}, _args, {dataLoader}) => { - return dataLoader.get('comments').load(commentId) + return dataLoader.get('comments').loadNonNull(commentId) } } diff --git a/packages/server/graphql/resolvers/resolveReactjis.ts b/packages/server/graphql/resolvers/resolveReactjis.ts index d29cdedafea..9695a0e5487 100644 --- a/packages/server/graphql/resolvers/resolveReactjis.ts +++ b/packages/server/graphql/resolvers/resolveReactjis.ts @@ -1,10 +1,10 @@ -import Reactji from '../../database/types/Reactji' +import {ReactjiDB} from '../../postgres/types' import {getUserId} from '../../utils/authorization' import getGroupedReactjis from '../../utils/getGroupedReactjis' import {GQLContext} from './../graphql' const resolveReactjis = ( - {reactjis, id}: {reactjis: Reactji[]; id: string}, + {reactjis, id}: {reactjis: ReactjiDB[]; id: string}, _args: unknown, {authToken}: GQLContext ) => { diff --git a/packages/server/graphql/resolvers/resolveThreadableConnection.ts b/packages/server/graphql/resolvers/resolveThreadableConnection.ts index 180bcae6e2c..f553886f470 100644 --- a/packages/server/graphql/resolvers/resolveThreadableConnection.ts +++ b/packages/server/graphql/resolvers/resolveThreadableConnection.ts @@ -1,5 +1,5 @@ -import Comment from '../../database/types/Comment' import TaskDB from '../../database/types/Task' +import {Comment} from '../../postgres/types' import {ThreadableSource} from '../public/types/Threadable' import {DataLoaderWorker} from './../graphql' diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index 9a610b1de19..f97275eb80b 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -2,6 +2,7 @@ import type {JSONContent} from '@tiptap/core' import {NotNull, sql} from 'kysely' import {NewMeetingPhaseTypeEnum} from '../graphql/public/resolverTypes' import getKysely from './getKysely' +import {ReactjiDB} from './types' export const selectTimelineEvent = () => { return getKysely().selectFrom('TimelineEvent').selectAll().$narrowType< @@ -101,7 +102,6 @@ export const selectTeams = () => >('to_json', ['jiraDimensionFields']).as('jiraDimensionFields') ]) -export type ReactjiDB = {id: string; userId: string} export const selectRetroReflections = () => getKysely() .selectFrom('RetroReflection') diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index a3abdf24161..48fb2419996 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -1,5 +1,4 @@ import {SelectQueryBuilder, Selectable} from 'kysely' -import type Comment from '../../database/types/Comment' import { Discussion as DiscussionPG, OrganizationUser as OrganizationUserPG, @@ -7,6 +6,7 @@ import { } from '../pg.d' import { selectAgendaItems, + selectComments, selectMeetingSettings, selectOrganizations, selectRetroReflections, @@ -23,6 +23,7 @@ type ExtractTypeFromQueryBuilderSelect any> = ReturnType extends SelectQueryBuilder<_, _, infer X> ? X : never export type Discussion = Selectable +export type ReactjiDB = {id: string; userId: string} export interface Organization extends ExtractTypeFromQueryBuilderSelect {} @@ -53,3 +54,5 @@ export type AgendaItem = ExtractTypeFromQueryBuilderSelect export type SlackNotification = ExtractTypeFromQueryBuilderSelect + +export type Comment = ExtractTypeFromQueryBuilderSelect diff --git a/packages/server/utils/analytics/analytics.ts b/packages/server/utils/analytics/analytics.ts index 0476981a555..00b37a3fbc3 100644 --- a/packages/server/utils/analytics/analytics.ts +++ b/packages/server/utils/analytics/analytics.ts @@ -358,7 +358,7 @@ class Analytics { user: AnalyticsUser, meetingId: string, meetingType: MeetingTypeEnum, - reactable: {createdBy?: string; id: string}, + reactable: {createdBy?: string | null; id: string}, reactableType: ReactableEnum, reactji: string, isRemove: boolean diff --git a/packages/server/utils/getGroupedReactjis.ts b/packages/server/utils/getGroupedReactjis.ts index 6eda683da36..06448b744e1 100644 --- a/packages/server/utils/getGroupedReactjis.ts +++ b/packages/server/utils/getGroupedReactjis.ts @@ -1,8 +1,8 @@ import ReactjiId from 'parabol-client/shared/gqlIds/ReactjiId' -import Reactji from '../database/types/Reactji' import {ReactjiSource} from '../graphql/public/types/Reactji' +import {ReactjiDB} from '../postgres/types' -const getGroupedReactjis = (reactjis: Reactji[], viewerId: string, idPrefix: string) => { +const getGroupedReactjis = (reactjis: ReactjiDB[], viewerId: string, idPrefix: string) => { const agg = {} as {[key: string]: ReactjiSource} reactjis.forEach((reactji) => { const {id, userId} = reactji From 02544f9cfc49386165d3ff69b3842d6d1b64183c Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Mon, 9 Sep 2024 13:31:42 -0700 Subject: [PATCH 448/529] chore(release): release v7.46.3 (#10199) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 38db86c7257..e381287fd82 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.46.2" + ".": "7.46.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index bb42db53b33..dc98cd33f0a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.46.3](https://github.com/ParabolInc/parabol/compare/v7.46.2...v7.46.3) (2024-09-09) + + +### Changed + +* **metrics:** Only track 'Loaded a Page' event to Amplitude when userId is known ([#9193](https://github.com/ParabolInc/parabol/issues/9193)) ([be5d28a](https://github.com/ParabolInc/parabol/commit/be5d28a05d3d839ead0924fe79b736bd49abbce8)) +* **rethinkdb:** Comment: Phase 3 ([#10172](https://github.com/ParabolInc/parabol/issues/10172)) ([22c3b5b](https://github.com/ParabolInc/parabol/commit/22c3b5bfa392d0bcaeed997ce7698a72edf9b23c)) + ## [7.46.2](https://github.com/ParabolInc/parabol/compare/v7.46.1...v7.46.2) (2024-09-06) diff --git a/package.json b/package.json index 7178b32572c..66631ee75c0 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.46.2", + "version": "7.46.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 459a90d9a9a..1469979b198 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.46.2", + "version": "7.46.3", "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.46.2" + "parabol-server": "7.46.3" } } diff --git a/packages/client/package.json b/packages/client/package.json index cf308db6ef6..d8ecd4195f5 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.46.2", + "version": "7.46.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index b86bff53eb1..34fe59960ba 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.46.2", + "version": "7.46.3", "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 87ebf8e5828..ed6e3406ee5 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.46.2", + "version": "7.46.3", "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.46.2", - "parabol-server": "7.46.2", + "parabol-client": "7.46.3", + "parabol-server": "7.46.3", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index cb1fc4cc465..a5edf8dc531 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.46.2", + "version": "7.46.3", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 80a7fcc66b2..b1a7940956a 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.46.2", + "version": "7.46.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.46.2", + "parabol-client": "7.46.3", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 4368c0bc6af2310f62133ae07c331912bb048628 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Mon, 9 Sep 2024 14:37:50 -0700 Subject: [PATCH 449/529] fix(orgAdmin): user should be able to remove themselves from the org (#10201) --- packages/client/components/OrgAdminActionMenu.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/components/OrgAdminActionMenu.tsx b/packages/client/components/OrgAdminActionMenu.tsx index c61a4679d15..d4118c32e3f 100644 --- a/packages/client/components/OrgAdminActionMenu.tsx +++ b/packages/client/components/OrgAdminActionMenu.tsx @@ -62,10 +62,11 @@ export const OrgAdminActionMenu = (props: Props) => { const {viewerId} = atmosphere const {role, user} = organizationUser const {id: userId} = user + const isSelf = viewerId === userId const orgAdminCount = billingLeaders.filter( (billingLeader) => billingLeader.role === 'ORG_ADMIN' ).length - const canEdit = isViewerOrgAdmin || (isViewerBillingLeaderPlus && role !== 'ORG_ADMIN') + const canEdit = isSelf || isViewerOrgAdmin || (isViewerBillingLeaderPlus && role !== 'ORG_ADMIN') const isViewerLastOrgAdmin = isViewerOrgAdmin && orgAdminCount === 1 const isViewerLastRole = isViewerBillingLeaderPlus && billingLeaders.length === 1 @@ -80,7 +81,6 @@ export const OrgAdminActionMenu = (props: Props) => { const isOrgAdmin = role === 'ORG_ADMIN' const isBillingLeader = role === 'BILLING_LEADER' - const isSelf = viewerId === userId const roleName = role === 'ORG_ADMIN' ? 'Org Admin' : 'Billing Leader' const canRemoveRole = role && From 880683996e4afb117ed21918a2c91574b649d4d9 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 10 Sep 2024 13:59:11 +0200 Subject: [PATCH 450/529] feat: Enable connecting to different GitLab integration providers (#10025) --- codegen.json | 2 + .../components/Dashboard/DashSidebar.tsx | 6 + .../components/Dashboard/LeftDashNavItem.tsx | 2 + .../Dashboard/MobileDashSidebar.tsx | 7 + .../components/Settings/SettingsWrapper.tsx | 3 +- .../ProviderRow/GitLabProviderRow.tsx | 98 +++++++++-- .../components/ProviderRow/MSTeamsPanel.tsx | 8 +- .../ProviderRow/MSTeamsProviderRow.tsx | 16 +- .../ProviderRow/MattermostPanel.tsx | 8 +- .../ProviderRow/MattermostProviderRow.tsx | 16 +- .../ProviderRow/ProviderRowBase.tsx | 147 +++------------- .../OrgBilling/OrgBillingDangerZone.tsx | 11 +- .../components/OrgBilling/Organization.tsx | 12 ++ .../AddGitLabProviderDialog.tsx | 62 +++++++ .../EditGitLabProviderDialog.tsx | 76 +++++++++ .../OrgIntegrations/GitLabProviderRow.tsx | 77 +++++++++ .../OrgIntegrations/GitLabProviders.tsx | 79 +++++++++ .../OrgIntegrations/OrgIntegrations.tsx | 55 ++++++ .../RemoveIntegrationProviderDialog.tsx | 75 +++++++++ .../UpsertGitLabProviderDialog.tsx | 157 ++++++++++++++++++ .../AddIntegrationProviderMutation.ts | 13 +- .../AddTeamMemberIntegrationAuthMutation.ts | 6 +- .../RemoveIntegrationProviderMutation.ts | 14 +- ...RemoveTeamMemberIntegrationAuthMutation.ts | 6 +- .../gqlIds/OrgIntegrationProvidersId.ts | 6 + .../subscriptions/OrganizationSubscription.ts | 8 +- .../client/subscriptions/TeamSubscription.ts | 3 - packages/client/tailwindTheme.ts | 6 +- packages/client/types/constEnums.ts | 15 +- .../ui/AlertDialog/AlertDialogContent.tsx | 1 + .../ui/AlertDialog/AlertDialogTitle.tsx | 1 + packages/client/ui/Dialog/Dialog.tsx | 6 +- packages/client/ui/Dialog/DialogTrigger.tsx | 11 ++ packages/client/utils/constants.ts | 1 + .../dataloader/integrationAuthLoaders.ts | 4 +- .../mutations/addIntegrationProvider.ts | 13 +- .../mutations/removeIntegrationProvider.ts | 29 +++- .../mutations/updateIntegrationProvider.ts | 75 +++++---- .../AddIntegrationProviderSuccess.graphql | 10 ++ ...grationProviderMetadataInputOAuth2.graphql | 2 +- .../typeDefs/OrgIntegrationProviders.graphql | 14 ++ .../public/typeDefs/Organization.graphql | 5 + .../OrganizationSubscriptionPayload.graphql | 10 +- .../RemoveIntegrationProviderSuccess.graphql | 13 +- .../typeDefs/TeamSubscriptionPayload.graphql | 1 - .../UpdateIntegrationProviderSuccess.graphql | 5 - .../types/AddIntegrationProviderSuccess.ts | 24 +++ .../graphql/public/types/GitLabIntegration.ts | 3 +- .../public/types/OrgIntegrationProviders.ts | 21 +++ .../graphql/public/types/Organization.ts | 3 +- .../types/UpdateIntegrationProviderSuccess.ts | 4 - .../IntegrationProviderMetadataInputOAuth2.ts | 2 +- .../types/RemoveIntegrationProviderPayload.ts | 10 -- .../queries/upsertIntegrationProvider.ts | 2 +- tailwind.config.js | 3 + 55 files changed, 1008 insertions(+), 259 deletions(-) create mode 100644 packages/client/modules/userDashboard/components/OrgIntegrations/AddGitLabProviderDialog.tsx create mode 100644 packages/client/modules/userDashboard/components/OrgIntegrations/EditGitLabProviderDialog.tsx create mode 100644 packages/client/modules/userDashboard/components/OrgIntegrations/GitLabProviderRow.tsx create mode 100644 packages/client/modules/userDashboard/components/OrgIntegrations/GitLabProviders.tsx create mode 100644 packages/client/modules/userDashboard/components/OrgIntegrations/OrgIntegrations.tsx create mode 100644 packages/client/modules/userDashboard/components/OrgIntegrations/RemoveIntegrationProviderDialog.tsx create mode 100644 packages/client/modules/userDashboard/components/OrgIntegrations/UpsertGitLabProviderDialog.tsx create mode 100644 packages/client/shared/gqlIds/OrgIntegrationProvidersId.ts create mode 100644 packages/client/ui/Dialog/DialogTrigger.tsx create mode 100644 packages/server/graphql/public/typeDefs/OrgIntegrationProviders.graphql create mode 100644 packages/server/graphql/public/types/AddIntegrationProviderSuccess.ts create mode 100644 packages/server/graphql/public/types/OrgIntegrationProviders.ts diff --git a/codegen.json b/codegen.json index bb7e83c6aa9..999ac8008ba 100644 --- a/codegen.json +++ b/codegen.json @@ -49,6 +49,7 @@ "SetSlackNotificationPayload": "./types/SetSlackNotificationPayload#SetSlackNotificationPayloadSource", "SetDefaultSlackChannelSuccess": "./types/SetDefaultSlackChannelSuccess#SetDefaultSlackChannelSuccessSource", "AddCommentSuccess": "./types/AddCommentSuccess#AddCommentSuccessSource", + "AddIntegrationProviderSuccess": "./types/AddIntegrationProviderSuccess#AddIntegrationProviderSuccessSource", "DeleteCommentSuccess": "./types/DeleteCommentSuccess#DeleteCommentSuccessSource", "UpdateCommentContentSuccess": "./types/UpdateCommentContentSuccess#UpdateCommentContentSuccessSource", "AddSlackAuthPayload": "./types/AddSlackAuthPayload#AddSlackAuthPayloadSource", @@ -134,6 +135,7 @@ "TemplateScale": "../../postgres/types/index#TemplateScale as TemplateScaleDB", "TemplateScaleRef": "../../postgres/types/index#TemplateScaleRef as TemplateScaleRefDB", "Threadable": "./types/Threadable#ThreadableSource", + "OrgIntegrationProviders": "./types/OrgIntegrationProviders#OrgIntegrationProvidersSource", "OrganizationUser": "../../postgres/types/index#OrganizationUser as OrganizationUserDB", "PokerMeeting": "../../database/types/MeetingPoker#default as MeetingPoker", "PokerMeetingMember": "../../database/types/MeetingPokerMeetingMember#default as PokerMeetingMemberDB", diff --git a/packages/client/components/Dashboard/DashSidebar.tsx b/packages/client/components/Dashboard/DashSidebar.tsx index fb7ee5afdd9..4979587e1c4 100644 --- a/packages/client/components/Dashboard/DashSidebar.tsx +++ b/packages/client/components/Dashboard/DashSidebar.tsx @@ -9,6 +9,7 @@ import { AUTHENTICATION_PAGE, BILLING_PAGE, MEMBERS_PAGE, + ORG_INTEGRATIONS_PAGE, ORG_SETTINGS_PAGE, TEAMS_PAGE } from '../../utils/constants' @@ -123,6 +124,11 @@ const DashSidebar = (props: Props) => { href={`/me/organizations/${orgId}/${ORG_SETTINGS_PAGE}`} label={'Organization Settings'} /> + , warning: , work: , + appRegistration: , timeline: , key: } diff --git a/packages/client/components/Dashboard/MobileDashSidebar.tsx b/packages/client/components/Dashboard/MobileDashSidebar.tsx index 0b097cafb1e..1a0898d2932 100644 --- a/packages/client/components/Dashboard/MobileDashSidebar.tsx +++ b/packages/client/components/Dashboard/MobileDashSidebar.tsx @@ -10,6 +10,7 @@ import { AUTHENTICATION_PAGE, BILLING_PAGE, MEMBERS_PAGE, + ORG_INTEGRATIONS_PAGE, ORG_SETTINGS_PAGE, TEAMS_PAGE } from '../../utils/constants' @@ -169,6 +170,12 @@ const MobileDashSidebar = (props: Props) => { href={`/me/organizations/${orgId}/${ORG_SETTINGS_PAGE}`} label={'Organization Settings'} /> + (({narrow}) => ({ display: 'flex', flexDirection: 'column', margin: '0 auto', - maxWidth: narrow ? 644 : 768, + maxWidth: narrow ? Layout.SETTINGS_NARROW_MAX_WIDTH : Layout.SETTINGS_MAX_WIDTH, width: '100%' })) diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/GitLabProviderRow.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/GitLabProviderRow.tsx index 187e453ac1e..806c4fcf077 100644 --- a/packages/client/modules/teamDashboard/components/ProviderRow/GitLabProviderRow.tsx +++ b/packages/client/modules/teamDashboard/components/ProviderRow/GitLabProviderRow.tsx @@ -1,15 +1,22 @@ +import {Done as DoneIcon, MoreVert as MoreVertIcon} from '@mui/icons-material' import graphql from 'babel-plugin-relay/macro' import React from 'react' import {useFragment} from 'react-relay' import {GitLabProviderRow_viewer$key} from '../../../../__generated__/GitLabProviderRow_viewer.graphql' +import FlatButton from '../../../../components/FlatButton' import GitLabProviderLogo from '../../../../components/GitLabProviderLogo' +import ProviderActions from '../../../../components/ProviderActions' +import RowInfo from '../../../../components/Row/RowInfo' +import RowInfoCopy from '../../../../components/Row/RowInfoCopy' import useAtmosphere from '../../../../hooks/useAtmosphere' +import useBreakpoint from '../../../../hooks/useBreakpoint' import {MenuPosition} from '../../../../hooks/useCoords' import useMenu from '../../../../hooks/useMenu' import useMutationProps from '../../../../hooks/useMutationProps' +import {Breakpoint} from '../../../../types/constEnums' import GitLabClientManager from '../../../../utils/GitLabClientManager' +import ConnectButton from './ConnectButton' import GitLabConfigMenu from './GitLabConfigMenu' -import ProviderRow from './ProviderRow' interface Props { teamId: string @@ -22,6 +29,7 @@ graphql` gitlab { auth { provider { + id scope } } @@ -30,6 +38,11 @@ graphql` clientId serverBaseUrl } + sharedProviders { + id + clientId + serverBaseUrl + } } } } @@ -56,6 +69,7 @@ const GitLabProviderRow = (props: Props) => { menuProps, togglePortal } = useMenu(MenuPosition.UPPER_RIGHT) + const isDesktop = useBreakpoint(Breakpoint.SIDEBAR_LEFT) const viewer = useFragment( graphql` @@ -70,22 +84,78 @@ const GitLabProviderRow = (props: Props) => { const {teamMember} = viewer const {integrations} = teamMember! const {gitlab} = integrations - const {auth, cloudProvider} = gitlab - if (!cloudProvider) return null - const {clientId, id: cloudProviderId, serverBaseUrl} = cloudProvider + const {auth, cloudProvider, sharedProviders} = gitlab + const connected = !!auth + const connectedProviderId = auth?.provider?.id + const availableProviders = [...(cloudProvider ? [cloudProvider] : []), ...sharedProviders] + + if (availableProviders.length === 0) return null return ( <> - openOAuth(cloudProviderId, clientId, serverBaseUrl)} - submitting={submitting} - togglePortal={togglePortal} - menuRef={menuRef} - providerName={'GitLab'} - providerDescription={'Use GitLab Issues from within Parabol'} - providerLogo={} - /> +
+
+ +
+ {availableProviders.map(({id, serverBaseUrl, clientId}) => { + const showProvider = !connected || id === connectedProviderId + const isCloudProvider = cloudProvider?.id === id + if (!showProvider) return null + return ( +
+ +
+ {isCloudProvider ? 'GitLab' : serverBaseUrl.replace(/https:\/\//, '')} +
+ + {isCloudProvider + ? 'Use GitLab Issues from within Parabol.' + : 'Connect to your own GitLab server.'} + +
+ + {!connected && ( + openOAuth(id, clientId, serverBaseUrl)} + submitting={submitting} + /> + )} + {id === connectedProviderId && ( + <> + {isDesktop ? ( + <> +
+ +
+ Connected +
+
+ + + + + ) : ( + + + + )} + + )} +
+
+ ) + })} +
+
+
{menuPortal( )} diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsPanel.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsPanel.tsx index fe0d190d2f2..61bae87c02b 100644 --- a/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsPanel.tsx +++ b/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsPanel.tsx @@ -1,6 +1,6 @@ import styled from '@emotion/styled' import graphql from 'babel-plugin-relay/macro' -import React, {FormEvent} from 'react' +import React, {FormEvent, useEffect} from 'react' import {useFragment} from 'react-relay' import {MSTeamsPanel_viewer$key} from '~/__generated__/MSTeamsPanel_viewer.graphql' import {MenuPosition} from '~/hooks/useCoords' @@ -105,6 +105,12 @@ const MSTeamsPanel = (props: Props) => { } } }) + // because we render this panel also when isConnectClicked, we cannot guarantee the serverWebhookUrl is correct on first render + useEffect(() => { + if (serverWebhookUrl && !fields.webhookUrl.value) { + fields.webhookUrl.resetValue(serverWebhookUrl) + } + }, [serverWebhookUrl]) const { submitting, diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsProviderRow.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsProviderRow.tsx index e8611cc853c..e22cdc5a9d3 100644 --- a/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsProviderRow.tsx +++ b/packages/client/modules/teamDashboard/components/ProviderRow/MSTeamsProviderRow.tsx @@ -18,13 +18,11 @@ interface Props { } graphql` - fragment MSTeamsProviderRowTeamMember on TeamMember { - integrations { - msTeams { - auth { - provider { - id - } + fragment MSTeamsProviderRowTeamMemberIntegrations on TeamMemberIntegrations { + msTeams { + auth { + provider { + id } } } @@ -37,7 +35,9 @@ const MSTeamsProviderRow = (props: Props) => { fragment MSTeamsProviderRow_viewer on User { ...MSTeamsPanel_viewer teamMember(teamId: $teamId) { - ...MSTeamsProviderRowTeamMember @relay(mask: false) + integrations { + ...MSTeamsProviderRowTeamMemberIntegrations @relay(mask: false) + } } } `, diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/MattermostPanel.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/MattermostPanel.tsx index 6b728ea5cd7..e2075426a5b 100644 --- a/packages/client/modules/teamDashboard/components/ProviderRow/MattermostPanel.tsx +++ b/packages/client/modules/teamDashboard/components/ProviderRow/MattermostPanel.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled' import {Info as InfoIcon} from '@mui/icons-material' import graphql from 'babel-plugin-relay/macro' -import React, {FormEvent} from 'react' +import React, {FormEvent, useEffect} from 'react' import {useFragment} from 'react-relay' import {MattermostPanel_viewer$key} from '~/__generated__/MattermostPanel_viewer.graphql' import {MenuPosition} from '~/hooks/useCoords' @@ -119,6 +119,12 @@ const MattermostPanel = (props: Props) => { } } }) + // because we render this panel also when isConnectClicked, we cannot guarantee the serverWebhookUrl is correct on first render + useEffect(() => { + if (serverWebhookUrl && !fields.webhookUrl.value) { + fields.webhookUrl.resetValue(serverWebhookUrl) + } + }, [serverWebhookUrl]) const { submitting, diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/MattermostProviderRow.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/MattermostProviderRow.tsx index fa6d39c9e8e..baf902c80f0 100644 --- a/packages/client/modules/teamDashboard/components/ProviderRow/MattermostProviderRow.tsx +++ b/packages/client/modules/teamDashboard/components/ProviderRow/MattermostProviderRow.tsx @@ -18,13 +18,11 @@ interface Props { } graphql` - fragment MattermostProviderRowTeamMember on TeamMember { - integrations { - mattermost { - auth { - provider { - id - } + fragment MattermostProviderRowTeamMemberIntegrations on TeamMemberIntegrations { + mattermost { + auth { + provider { + id } } } @@ -37,7 +35,9 @@ const MattermostProviderRow = (props: Props) => { fragment MattermostProviderRow_viewer on User { ...MattermostPanel_viewer teamMember(teamId: $teamId) { - ...MattermostProviderRowTeamMember @relay(mask: false) + integrations { + ...MattermostProviderRowTeamMemberIntegrations @relay(mask: false) + } } } `, diff --git a/packages/client/modules/teamDashboard/components/ProviderRow/ProviderRowBase.tsx b/packages/client/modules/teamDashboard/components/ProviderRow/ProviderRowBase.tsx index f604c80e001..7309692cab6 100644 --- a/packages/client/modules/teamDashboard/components/ProviderRow/ProviderRowBase.tsx +++ b/packages/client/modules/teamDashboard/components/ProviderRow/ProviderRowBase.tsx @@ -1,4 +1,3 @@ -import styled from '@emotion/styled' import {Done as DoneIcon, MoreVert as MoreVertIcon} from '@mui/icons-material' import React from 'react' import FlatButton from '../../../../components/FlatButton' @@ -6,103 +5,16 @@ import ProviderActions from '../../../../components/ProviderActions' import RowInfo from '../../../../components/Row/RowInfo' import RowInfoCopy from '../../../../components/Row/RowInfoCopy' import useBreakpoint from '../../../../hooks/useBreakpoint' -import {cardShadow} from '../../../../styles/elevation' -import {PALETTE} from '../../../../styles/paletteV3' -import {Breakpoint, Layout} from '../../../../types/constEnums' - -const MenuButton = styled(FlatButton)({ - borderColor: PALETTE.SLATE_400, - color: PALETTE.SLATE_700, - fontSize: 14, - fontWeight: 600, - minWidth: 36, - paddingLeft: 0, - paddingRight: 0 -}) - -const SmallMenuButton = styled(MenuButton)({ - minWidth: 30 -}) - -const StatusWrapper = styled('div')({ - display: 'flex', - alignItems: 'center', - paddingRight: 25 -}) - -const StatusLabel = styled('div')({ - color: PALETTE.SLATE_700, - fontSize: 14, - fontWeight: 600, - paddingLeft: 6 -}) - -const StatusIcon = styled('div')({ - svg: { - fontSize: 18 - }, - width: 18, - height: 18, - color: PALETTE.SUCCESS_LIGHT -}) - -const MenuSmallIcon = styled(MoreVertIcon)({ - svg: { - fontSize: 18 - }, - width: 18, - height: 18 -}) - -const ProviderName = styled('div')({ - color: PALETTE.SLATE_700, - fontSize: 16, - fontWeight: 600, - lineHeight: '24px', - alignItems: 'center', - display: 'flex', - marginRight: 16, - verticalAlign: 'middle' -}) - -const ProviderCard = styled('div')({ - display: 'flex', - flexDirection: 'column', - backgroundColor: 'white', - borderRadius: 4, - boxShadow: cardShadow, - flexShrink: 0, - justifyContent: 'flex-start', - margin: '16px 0', - padding: 0, - position: 'relative', - width: '100%' -}) - -const CardTop = styled('div')({ - display: 'flex', - justifyContent: 'flex-start', - padding: Layout.ROW_GUTTER -}) - -const HowItWorksLink = styled('a')({ - color: PALETTE.SKY_500, - cursor: 'pointer', - textDecoration: 'underline', - ':hover, :focus, :active': { - color: PALETTE.SKY_600 - } -}) +import {Breakpoint} from '../../../../types/constEnums' export interface ProviderRowBaseProps { connected: boolean togglePortal: () => void menuRef: React.MutableRefObject // TODO: make generic menu component providerName: string - providerDescription: string + providerDescription: React.ReactElement | string providerLogo: React.ReactElement children?: React.ReactElement | false - seeHowItWorksUrl?: string connectButton: React.ReactElement error?: React.ReactElement | string } @@ -117,27 +29,18 @@ const ProviderRowBase = (props: ProviderRowBaseProps) => { providerName, providerDescription, providerLogo, - seeHowItWorksUrl, children } = props const isDesktop = useBreakpoint(Breakpoint.SIDEBAR_LEFT) return ( - - +
+
{providerLogo} - {providerName} - - {providerDescription}.{' '} - {seeHowItWorksUrl && ( - <> - - See how it works - - . - - )} - +
+ {providerName} +
+ {providerDescription} {!!error && (
{error} @@ -150,29 +53,33 @@ const ProviderRowBase = (props: ProviderRowBaseProps) => { <> {isDesktop ? ( <> - - - - - Connected - - - - - - +
+ +
Connected
+
+ + + ) : ( - + - + )} )} - +
{children} - +
) } diff --git a/packages/client/modules/userDashboard/components/OrgBilling/OrgBillingDangerZone.tsx b/packages/client/modules/userDashboard/components/OrgBilling/OrgBillingDangerZone.tsx index 6b9761f088a..ac4385185c7 100644 --- a/packages/client/modules/userDashboard/components/OrgBilling/OrgBillingDangerZone.tsx +++ b/packages/client/modules/userDashboard/components/OrgBilling/OrgBillingDangerZone.tsx @@ -49,7 +49,16 @@ const OrgBillingDangerZone = (props: Props) => { ) const {history} = useRouter() const {id, isBillingLeader, billingTier} = organization - if (!isBillingLeader) return null + if (!isBillingLeader) + return ( + + +
+ {'Only the billing leader can manage this organization'} +
+
+
+ ) const isStarter = billingTier === 'starter' const isTeam = billingTier === 'team' diff --git a/packages/client/modules/userDashboard/components/OrgBilling/Organization.tsx b/packages/client/modules/userDashboard/components/OrgBilling/Organization.tsx index 5d2d9bf6c5d..6f37d620d23 100644 --- a/packages/client/modules/userDashboard/components/OrgBilling/Organization.tsx +++ b/packages/client/modules/userDashboard/components/OrgBilling/Organization.tsx @@ -7,6 +7,7 @@ import { AUTHENTICATION_PAGE, BILLING_PAGE, MEMBERS_PAGE, + ORG_INTEGRATIONS_PAGE, ORG_SETTINGS_PAGE, TEAMS_PAGE } from '../../../../utils/constants' @@ -26,6 +27,11 @@ const OrgTeamMembers = lazy( ) const OrgDetails = lazy(() => import(/* webpackChunkName: 'OrgDetails' */ './OrgDetails')) + +const OrgIntegrations = lazy( + () => import(/* webpackChunkName: 'OrgIntegrations' */ '../OrgIntegrations/OrgIntegrations') +) + const Authentication = lazy( () => import( @@ -46,6 +52,7 @@ const query = graphql` ...OrgPlansAndBillingRoot_organization ...OrgDetails_organization ...OrgTeams_organization + ...OrgIntegrations_organization isBillingLeader } } @@ -85,6 +92,11 @@ const Organization = (props: Props) => { path={`${match.url}/${ORG_SETTINGS_PAGE}`} render={(p) => } /> + } + /> void +} + +const AddGitLabProviderDialog = (props: Props) => { + const {orgId, isOpen, onClose} = props + const atmosphere = useAtmosphere() + const {onError, onCompleted, submitting, submitMutation, error} = useMutationProps() + + const onUpsert = (serverBaseUrl: string, clientId: string, clientSecret: string) => { + if (submitting) return + + submitMutation() + AddIntegrationProviderMutation( + atmosphere, + { + input: { + orgId, + service: 'gitlab', + authStrategy: 'oauth2', + scope: 'org', + oAuth2ProviderMetadataInput: { + serverBaseUrl, + clientId, + clientSecret + } + } + }, + { + onError, + onCompleted: () => { + onCompleted() + onClose() + } + } + ) + } + + return ( + + ) +} + +export default AddGitLabProviderDialog diff --git a/packages/client/modules/userDashboard/components/OrgIntegrations/EditGitLabProviderDialog.tsx b/packages/client/modules/userDashboard/components/OrgIntegrations/EditGitLabProviderDialog.tsx new file mode 100644 index 00000000000..6ea390e5753 --- /dev/null +++ b/packages/client/modules/userDashboard/components/OrgIntegrations/EditGitLabProviderDialog.tsx @@ -0,0 +1,76 @@ +import graphql from 'babel-plugin-relay/macro' +import React from 'react' +import {useFragment} from 'react-relay' +import {EditGitLabProviderDialog_integrationProvider$key} from '../../../../__generated__/EditGitLabProviderDialog_integrationProvider.graphql' +import useAtmosphere from '../../../../hooks/useAtmosphere' +import useMutationProps from '../../../../hooks/useMutationProps' +import UpdateIntegrationProviderMutation from '../../../../mutations/UpdateIntegrationProviderMutation' +import UpsertGitLabProviderDialog from './UpsertGitLabProviderDialog' + +type Props = { + isOpen: boolean + onClose: () => void + integrationProviderRef: EditGitLabProviderDialog_integrationProvider$key +} + +const EditGitLabProviderDialog = (props: Props) => { + const {isOpen, onClose, integrationProviderRef} = props + + const IntegrationProvider = useFragment( + graphql` + fragment EditGitLabProviderDialog_integrationProvider on IntegrationProviderOAuth2 { + id + serverBaseUrl + clientId + } + `, + integrationProviderRef + ) + const {id, serverBaseUrl, clientId} = IntegrationProvider + + const atmosphere = useAtmosphere() + + const {onError, onCompleted, submitting, submitMutation, error} = useMutationProps() + + const onUpsert = (serverBaseUrl: string, clientId: string, clientSecret: string) => { + if (submitting) return + + submitMutation() + UpdateIntegrationProviderMutation( + atmosphere, + { + provider: { + id, + oAuth2ProviderMetadataInput: { + serverBaseUrl, + clientId, + clientSecret + } + } + }, + { + onError, + onCompleted: () => { + onCompleted() + onClose() + } + } + ) + } + + return ( + + ) +} + +export default EditGitLabProviderDialog diff --git a/packages/client/modules/userDashboard/components/OrgIntegrations/GitLabProviderRow.tsx b/packages/client/modules/userDashboard/components/OrgIntegrations/GitLabProviderRow.tsx new file mode 100644 index 00000000000..cf8199bd709 --- /dev/null +++ b/packages/client/modules/userDashboard/components/OrgIntegrations/GitLabProviderRow.tsx @@ -0,0 +1,77 @@ +import {MoreVert as MoreVertIcon} from '@mui/icons-material' +import graphql from 'babel-plugin-relay/macro' +import React from 'react' +import {useFragment} from 'react-relay' +import {GitLabProviderRow_integrationProvider$key} from '../../../../__generated__/GitLabProviderRow_integrationProvider.graphql' +import FlatButton from '../../../../components/FlatButton' +import ProviderActions from '../../../../components/ProviderActions' +import RowInfoCopy from '../../../../components/Row/RowInfoCopy' +import {useDialogState} from '../../../../ui/Dialog/useDialogState' +import {Menu} from '../../../../ui/Menu/Menu' +import {MenuContent} from '../../../../ui/Menu/MenuContent' +import {MenuItem} from '../../../../ui/Menu/MenuItem' +import EditGitLabProviderDialog from './EditGitLabProviderDialog' +import RemoveIntegrationProviderDialog from './RemoveIntegrationProviderDialog' + +type Props = { + integrationProviderRef: GitLabProviderRow_integrationProvider$key +} + +const GitLabProviderRow = (props: Props) => { + const {integrationProviderRef} = props + const integrationProvider = useFragment( + graphql` + fragment GitLabProviderRow_integrationProvider on IntegrationProviderOAuth2 { + id + serverBaseUrl + ...EditGitLabProviderDialog_integrationProvider + ...RemoveIntegrationProviderDialog_integrationProvider + } + `, + integrationProviderRef + ) + const {id, serverBaseUrl} = integrationProvider + + const {isOpen: isEditOpen, open: openEdit, close: closeEdit} = useDialogState() + const {isOpen: isDeleteOpen, open: openDelete, close: closeDelete} = useDialogState() + + return ( + <> +
+
+
+ {serverBaseUrl.replace(/https:\/\//, '')} +
+ +
+ + + + + } + > + + Edit + Delete + + + +
+ + + + ) +} + +export default GitLabProviderRow diff --git a/packages/client/modules/userDashboard/components/OrgIntegrations/GitLabProviders.tsx b/packages/client/modules/userDashboard/components/OrgIntegrations/GitLabProviders.tsx new file mode 100644 index 00000000000..04adca53e2c --- /dev/null +++ b/packages/client/modules/userDashboard/components/OrgIntegrations/GitLabProviders.tsx @@ -0,0 +1,79 @@ +import {Add as AddIcon} from '@mui/icons-material' +import graphql from 'babel-plugin-relay/macro' +import React from 'react' +import {useFragment} from 'react-relay' +import {GitLabProviders_organization$key} from '../../../../__generated__/GitLabProviders_organization.graphql' +import GitLabProviderLogo from '../../../../components/GitLabProviderLogo' +import ProviderActions from '../../../../components/ProviderActions' +import RowInfoCopy from '../../../../components/Row/RowInfoCopy' +import useBreakpoint from '../../../../hooks/useBreakpoint' +import {Breakpoint} from '../../../../types/constEnums' +import {useDialogState} from '../../../../ui/Dialog/useDialogState' +import ProviderRowActionButton from '../../../teamDashboard/components/ProviderRow/ProviderRowActionButton' +import AddGitLabProviderDialog from './AddGitLabProviderDialog' +import GitLabProviderRow from './GitLabProviderRow' + +type Props = { + organizationRef: GitLabProviders_organization$key +} + +graphql` + fragment GitLabProviders_orgIntegrationProviders on OrgIntegrationProviders { + gitlab { + ...GitLabProviderRow_integrationProvider + id + serverBaseUrl + clientId + } + } +` + +const GitLabProviders = (props: Props) => { + const {organizationRef} = props + const organization = useFragment( + graphql` + fragment GitLabProviders_organization on Organization { + orgId: id + viewerOrganizationUser { + id + role + } + integrationProviders { + ...GitLabProviders_orgIntegrationProviders @relay(mask: false) + } + } + `, + organizationRef + ) + const {orgId, viewerOrganizationUser} = organization + const isOrgAdmin = viewerOrganizationUser?.role === 'ORG_ADMIN' + const {isOpen, open, close} = useDialogState() + + const isDesktop = useBreakpoint(Breakpoint.SIDEBAR_LEFT) + + return ( + <> +
+
+ +
+
GitLab
+ Add private servers for use by your teams. +
+ + + {isDesktop ? 'Add Server' : } + + +
+ {isOrgAdmin && + organization.integrationProviders.gitlab.map((provider) => ( + + ))} +
+ + + ) +} + +export default GitLabProviders diff --git a/packages/client/modules/userDashboard/components/OrgIntegrations/OrgIntegrations.tsx b/packages/client/modules/userDashboard/components/OrgIntegrations/OrgIntegrations.tsx new file mode 100644 index 00000000000..8268ff292ea --- /dev/null +++ b/packages/client/modules/userDashboard/components/OrgIntegrations/OrgIntegrations.tsx @@ -0,0 +1,55 @@ +import graphql from 'babel-plugin-relay/macro' +import React, {Suspense} from 'react' +import {useFragment} from 'react-relay' +import {OrgIntegrations_organization$key} from '../../../../__generated__/OrgIntegrations_organization.graphql' +import {Loader} from '../../../../utils/relay/renderLoader' +import GitLabProviders from './GitLabProviders' + +type Props = { + organizationRef: OrgIntegrations_organization$key +} + +const OrgIntegrations = (props: Props) => { + const {organizationRef} = props + const organization = useFragment( + graphql` + fragment OrgIntegrations_organization on Organization { + id + ...GitLabProviders_organization + createdAt + tier + viewerOrganizationUser { + id + role + } + } + `, + organizationRef + ) + + const {viewerOrganizationUser} = organization + const isOrgAdmin = viewerOrganizationUser?.role === 'ORG_ADMIN' + + return ( + }> +
+
+

Integration Settings

+ {isOrgAdmin ? ( +
+ Configure organization-level integrations that can be used by teams across your + organization. +
+ See the team integration tab for team-level connections. +
+ ) : ( +
Only organization admins can manage integrations.
+ )} + +
+
+
+ ) +} + +export default OrgIntegrations diff --git a/packages/client/modules/userDashboard/components/OrgIntegrations/RemoveIntegrationProviderDialog.tsx b/packages/client/modules/userDashboard/components/OrgIntegrations/RemoveIntegrationProviderDialog.tsx new file mode 100644 index 00000000000..d4b26dcfb6d --- /dev/null +++ b/packages/client/modules/userDashboard/components/OrgIntegrations/RemoveIntegrationProviderDialog.tsx @@ -0,0 +1,75 @@ +import graphql from 'babel-plugin-relay/macro' +import React from 'react' +import {useFragment} from 'react-relay' +import useAtmosphere from '~/hooks/useAtmosphere' +import {RemoveIntegrationProviderDialog_integrationProvider$key} from '../../../../__generated__/RemoveIntegrationProviderDialog_integrationProvider.graphql' +import PrimaryButton from '../../../../components/PrimaryButton' +import useMutationProps from '../../../../hooks/useMutationProps' +import RemoveIntegrationProviderMutation from '../../../../mutations/RemoveIntegrationProviderMutation' +import {Dialog} from '../../../../ui/Dialog/Dialog' +import {DialogContent} from '../../../../ui/Dialog/DialogContent' +import {DialogTitle} from '../../../../ui/Dialog/DialogTitle' + +interface Props { + isOpen: boolean + onClose: () => void + integrationProviderRef: RemoveIntegrationProviderDialog_integrationProvider$key +} + +const prettifyService = (service: string) => { + switch (service) { + case 'gitlab': + return 'GitLab' + case 'github': + return 'GitHub' + default: + return service.charAt(0).toUpperCase() + service.slice(1) + } +} + +const RemoveIntegrationProviderDialog = (props: Props) => { + const atmosphere = useAtmosphere() + const {isOpen, onClose, integrationProviderRef} = props + const integrationProvider = useFragment( + graphql` + fragment RemoveIntegrationProviderDialog_integrationProvider on IntegrationProvider { + id + service + ... on IntegrationProviderOAuth2 { + serverBaseUrl + } + } + `, + integrationProviderRef + ) + const {id: providerId, service, serverBaseUrl} = integrationProvider + const {onCompleted, onError} = useMutationProps() + + const handleClick = () => { + onClose() + RemoveIntegrationProviderMutation(atmosphere, {providerId}, {onCompleted, onError}) + } + const prettyService = prettifyService(service) + return ( + + + Are you sure? +
+ If you remove this{' '} + + {prettyService} + {serverBaseUrl && ` (${serverBaseUrl.replace(/https:\/\//, '')})`} + {' '} + integration, it will no longer be available to any teams. +
+
+ + Remove {prettyService} integration + +
+
+
+ ) +} + +export default RemoveIntegrationProviderDialog diff --git a/packages/client/modules/userDashboard/components/OrgIntegrations/UpsertGitLabProviderDialog.tsx b/packages/client/modules/userDashboard/components/OrgIntegrations/UpsertGitLabProviderDialog.tsx new file mode 100644 index 00000000000..360b294a9c8 --- /dev/null +++ b/packages/client/modules/userDashboard/components/OrgIntegrations/UpsertGitLabProviderDialog.tsx @@ -0,0 +1,157 @@ +import React from 'react' +import ErrorAlert from '../../../../components/ErrorAlert/ErrorAlert' +import GitLabProviderLogo from '../../../../components/GitLabProviderLogo' +import BasicInput from '../../../../components/InputField/BasicInput' +import PrimaryButton from '../../../../components/PrimaryButton' +import useForm from '../../../../hooks/useForm' +import {Dialog} from '../../../../ui/Dialog/Dialog' +import {DialogContent} from '../../../../ui/Dialog/DialogContent' +import {DialogTitle} from '../../../../ui/Dialog/DialogTitle' +import linkify from '../../../../utils/linkify' +import makeAppURL from '../../../../utils/makeAppURL' +import Legitity from '../../../../validation/Legitity' +import CopyShortLink from '../../../meeting/components/CopyShortLink/CopyShortLink' + +type Props = { + isOpen: boolean + onClose: () => void + defaults: { + serverBaseUrl: string + clientId: string + clientSecret: string + } + onUpsert: (serverBaseUrl: string, clientId: string, clientSecret: string) => void + error?: {message: string} +} + +const UpsertGitLabProviderDialog = (props: Props) => { + const {isOpen, onClose, defaults, onUpsert, error} = props + + const redirectUri = makeAppURL(window.location.origin, 'auth/gitlab') + const {fields, onChange, validateField} = useForm({ + serverBaseUrl: { + getDefault: () => defaults.serverBaseUrl, + validate: (rawInput: string) => { + return new Legitity(rawInput).test((maybeUrl) => { + if (!maybeUrl) return 'Please enter a server base URL' + const links = linkify.match(maybeUrl) + return !links ? 'Not looking too linky' : '' + }) + } + }, + clientId: { + getDefault: () => defaults.clientId, + validate: (clientId: string) => { + return new Legitity(clientId).required('Please enter a client ID') + } + }, + clientSecret: { + getDefault: () => defaults.clientSecret, + validate: (clientSecret: string) => { + return new Legitity(clientSecret).required('Please enter a client secret') + } + } + }) + + const onSubmit = () => { + const validation = validateField() + if (Object.values(validation).some(({error}) => error)) return + onUpsert(fields.serverBaseUrl.value, fields.clientId.value, fields.clientSecret.value) + onClose?.() + } + + return ( + + + + +
Add GitLab Server
+
+
{ + e.preventDefault() + onSubmit() + }} + > +
+ In the Admin Area, Applications add a New application with the + following settings +
+
+
Name
+
Parabol
+
+
+
Redirect URI
+ +
+
+
Trusted
+
Yes or No
+
+
+
Confidential
+
Yes
+
+
+
Scopes
+
+ api (Access the authenticated user's API) +
+
+
+ Press Save application and enter the Application ID and Secret{' '} + below +
+
+
Server base URL
+
+ +
+
+
+
Application ID
+
+ +
+
+
+
Secret
+
+ +
+
+ {error && } +
+ + Save + +
+ +
+
+ ) +} + +export default UpsertGitLabProviderDialog diff --git a/packages/client/mutations/AddIntegrationProviderMutation.ts b/packages/client/mutations/AddIntegrationProviderMutation.ts index 3e4d831de08..28f3d1c1c89 100644 --- a/packages/client/mutations/AddIntegrationProviderMutation.ts +++ b/packages/client/mutations/AddIntegrationProviderMutation.ts @@ -4,9 +4,11 @@ import {AddIntegrationProviderMutation as TAddIntegrationProviderMutation} from import {StandardMutation} from '../types/relayMutations' graphql` - fragment AddIntegrationProviderMutation_team on AddIntegrationProviderSuccess { + fragment AddIntegrationProviderMutation_organization on AddIntegrationProviderSuccess { provider { id + teamId + orgId ... on IntegrationProviderWebhook { webhookUrl } @@ -16,6 +18,13 @@ graphql` tenantId } } + teamMemberIntegrations { + ...MattermostProviderRowTeamMemberIntegrations @relay(mask: false) + ...MSTeamsProviderRowTeamMemberIntegrations @relay(mask: false) + } + orgIntegrationProviders { + ...GitLabProviders_orgIntegrationProviders @relay(mask: false) + } } ` @@ -27,7 +36,7 @@ const mutation = graphql` message } } - ...AddIntegrationProviderMutation_team @relay(mask: false) + ...AddIntegrationProviderMutation_organization @relay(mask: false) } } ` diff --git a/packages/client/mutations/AddTeamMemberIntegrationAuthMutation.ts b/packages/client/mutations/AddTeamMemberIntegrationAuthMutation.ts index fbf0847e219..a44d209ce16 100644 --- a/packages/client/mutations/AddTeamMemberIntegrationAuthMutation.ts +++ b/packages/client/mutations/AddTeamMemberIntegrationAuthMutation.ts @@ -27,11 +27,13 @@ const mutation = graphql` teamMember { ...GitLabProviderRowTeamMember ...ScopePhaseAreaGitLab_teamMember - ...MattermostProviderRowTeamMember ...JiraServerProviderRowTeamMember ...AzureDevOpsProviderRowTeamMember - ...MSTeamsProviderRowTeamMember ...GcalProviderRowTeamMember + integrations { + ...MattermostProviderRowTeamMemberIntegrations + ...MSTeamsProviderRowTeamMemberIntegrations + } } } } diff --git a/packages/client/mutations/RemoveIntegrationProviderMutation.ts b/packages/client/mutations/RemoveIntegrationProviderMutation.ts index 26acc7035fe..c7026dbf7a0 100644 --- a/packages/client/mutations/RemoveIntegrationProviderMutation.ts +++ b/packages/client/mutations/RemoveIntegrationProviderMutation.ts @@ -4,11 +4,13 @@ import {RemoveIntegrationProviderMutation as TRemoveIntegrationProviderMutation} import {StandardMutation} from '../types/relayMutations' graphql` - fragment RemoveIntegrationProviderMutation_team on RemoveIntegrationProviderSuccess { - teamMember { - ...MattermostProviderRowTeamMember @relay(mask: false) - ...GitLabProviderRowTeamMember @relay(mask: false) - ...MSTeamsProviderRowTeamMember @relay(mask: false) + fragment RemoveIntegrationProviderMutation_organization on RemoveIntegrationProviderSuccess { + teamMemberIntegrations { + ...MattermostProviderRowTeamMemberIntegrations @relay(mask: false) + ...MSTeamsProviderRowTeamMemberIntegrations @relay(mask: false) + } + orgIntegrationProviders { + ...GitLabProviders_orgIntegrationProviders @relay(mask: false) } } ` @@ -16,12 +18,12 @@ graphql` const mutation = graphql` mutation RemoveIntegrationProviderMutation($providerId: ID!) { removeIntegrationProvider(providerId: $providerId) { + ...RemoveIntegrationProviderMutation_organization ... on ErrorPayload { error { message } } - ...RemoveIntegrationProviderMutation_team @relay(mask: false) } } ` diff --git a/packages/client/mutations/RemoveTeamMemberIntegrationAuthMutation.ts b/packages/client/mutations/RemoveTeamMemberIntegrationAuthMutation.ts index fcec82fdcfc..f2b1307e7dc 100644 --- a/packages/client/mutations/RemoveTeamMemberIntegrationAuthMutation.ts +++ b/packages/client/mutations/RemoveTeamMemberIntegrationAuthMutation.ts @@ -7,11 +7,13 @@ graphql` fragment RemoveTeamMemberIntegrationAuthMutation_team on RemoveTeamMemberIntegrationAuthSuccess { teamMember { ...GitLabProviderRowTeamMember - ...MattermostProviderRowTeamMember ...JiraServerProviderRowTeamMember ...AzureDevOpsProviderRowTeamMember - ...MSTeamsProviderRowTeamMember ...GcalProviderRowTeamMember + integrations { + ...MattermostProviderRowTeamMemberIntegrations + ...MSTeamsProviderRowTeamMemberIntegrations + } } } ` diff --git a/packages/client/shared/gqlIds/OrgIntegrationProvidersId.ts b/packages/client/shared/gqlIds/OrgIntegrationProvidersId.ts new file mode 100644 index 00000000000..4630e888c27 --- /dev/null +++ b/packages/client/shared/gqlIds/OrgIntegrationProvidersId.ts @@ -0,0 +1,6 @@ +const OrgIntegrationProvidersId = { + join: (orgId: string) => `orgIntegrationProviders:${orgId}`, + split: (id: string) => id.split(':')[1]! +} + +export default OrgIntegrationProvidersId diff --git a/packages/client/subscriptions/OrganizationSubscription.ts b/packages/client/subscriptions/OrganizationSubscription.ts index cc9fb3bae44..7cb8b3c6bab 100644 --- a/packages/client/subscriptions/OrganizationSubscription.ts +++ b/packages/client/subscriptions/OrganizationSubscription.ts @@ -27,6 +27,9 @@ const subscription = graphql` subscription OrganizationSubscription { organizationSubscription { fieldName + AddIntegrationProviderSuccess { + ...AddIntegrationProviderMutation_organization @relay(mask: false) + } AddOrgPayload { ...AddOrgMutation_organization @relay(mask: false) } @@ -48,6 +51,9 @@ const subscription = graphql` UpgradeToTeamTierSuccess { ...UpgradeToTeamTierFrag_organization @relay(mask: false) } + RemoveIntegrationProviderSuccess { + ...RemoveIntegrationProviderMutation_organization @relay(mask: false) + } RemoveOrgUserPayload { ...RemoveOrgUserMutation_organization @relay(mask: false) } @@ -73,8 +79,8 @@ const onNextHandlers = { const updateHandlers = { AddOrgPayload: addOrgMutationOrganizationUpdater, ArchiveOrganizationPayload: archiveOrganizationOrganizationUpdater, - SetOrgUserRoleSuccess: setOrgUserRoleAddedOrganizationUpdater, RemoveOrgUserPayload: removeOrgUserOrganizationUpdater, + SetOrgUserRoleSuccess: setOrgUserRoleAddedOrganizationUpdater, UpdateTemplateScopeSuccess: updateTemplateScopeOrganizationUpdater } as const diff --git a/packages/client/subscriptions/TeamSubscription.ts b/packages/client/subscriptions/TeamSubscription.ts index 6e142aea6b1..17527fdee9b 100644 --- a/packages/client/subscriptions/TeamSubscription.ts +++ b/packages/client/subscriptions/TeamSubscription.ts @@ -181,9 +181,6 @@ const subscription = graphql` OldUpgradeToTeamTierPayload { ...OldUpgradeToTeamTierMutation_team @relay(mask: false) } - AddIntegrationProviderSuccess { - ...AddIntegrationProviderMutation_team @relay(mask: false) - } } } ` diff --git a/packages/client/tailwindTheme.ts b/packages/client/tailwindTheme.ts index ad30c0d94b7..bd4b95d4304 100644 --- a/packages/client/tailwindTheme.ts +++ b/packages/client/tailwindTheme.ts @@ -153,7 +153,11 @@ export default { '700': '#444258', '800': '#2D2D39', '900': '#1C1C21' - } + }, + 'success-light': '#2db553', + starter: '#F2E1F7', + team: '#CBECF0', + enterprise: '#FFE2E0' }, keyframes: { overlayShow: { diff --git a/packages/client/types/constEnums.ts b/packages/client/types/constEnums.ts index 9397a89a34e..366b1b5aa3e 100644 --- a/packages/client/types/constEnums.ts +++ b/packages/client/types/constEnums.ts @@ -193,6 +193,7 @@ export const enum Layout { LAYOUT_GRID = 8, // 1x ROW_GUTTER = 16, // 2x SETTINGS_MAX_WIDTH = 768, + SETTINGS_NARROW_MAX_WIDTH = 644, TASK_COLUMNS_MAX_WIDTH = 1360 } @@ -293,21 +294,21 @@ export const enum Pricing { export const enum Providers { ATLASSIAN_NAME = 'Atlassian', - ATLASSIAN_DESC = 'Use Jira Cloud Issues from within Parabol', + ATLASSIAN_DESC = 'Use Jira Cloud Issues from within Parabol.', JIRA_SERVER_NAME = 'Jira Server', - JIRA_SERVER_DESC = 'Use Jira Server Issues from within Parabol', + JIRA_SERVER_DESC = 'Use Jira Server Issues from within Parabol.', GITHUB_NAME = 'GitHub', GCAL_NAME = 'Google Calendar', - GCAL_DESC = 'Create Google Calendar events from within Parabol', - GITHUB_DESC = 'Use GitHub Issues from within Parabol', + GCAL_DESC = 'Create Google Calendar events from within Parabol.', + GITHUB_DESC = 'Use GitHub Issues from within Parabol.', GITHUB_SCOPE = 'read:org,repo', GITLAB_SCOPE = 'api', MATTERMOST_NAME = 'Mattermost', - MATTERMOST_DESC = 'Push notifications to Mattermost', + MATTERMOST_DESC = 'Push notifications to Mattermost.', SLACK_NAME = 'Slack', - SLACK_DESC = 'Push notifications to Slack', + SLACK_DESC = 'Push notifications to Slack.', AZUREDEVOPS_NAME = 'Azure DevOps', - AZUREDEVOPS_DESC = 'Use Azure DevOps Issues from within Parabol', + AZUREDEVOPS_DESC = 'Use Azure DevOps Issues from within Parabol.', MSTEAMS_NAME = 'Microsoft Teams', MSTEAMS_DESC = 'Push notifications to Microsoft Teams' } diff --git a/packages/client/ui/AlertDialog/AlertDialogContent.tsx b/packages/client/ui/AlertDialog/AlertDialogContent.tsx index 12d5a79355c..83c0f86e3ec 100644 --- a/packages/client/ui/AlertDialog/AlertDialogContent.tsx +++ b/packages/client/ui/AlertDialog/AlertDialogContent.tsx @@ -21,3 +21,4 @@ const AlertDialogContent = React.forwardRef< )) AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName +export {AlertDialogContent} diff --git a/packages/client/ui/AlertDialog/AlertDialogTitle.tsx b/packages/client/ui/AlertDialog/AlertDialogTitle.tsx index cd4c9a9b0a8..f9a6f16afea 100644 --- a/packages/client/ui/AlertDialog/AlertDialogTitle.tsx +++ b/packages/client/ui/AlertDialog/AlertDialogTitle.tsx @@ -13,3 +13,4 @@ const AlertDialogTitle = React.forwardRef< /> )) AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName +export {AlertDialogTitle} diff --git a/packages/client/ui/Dialog/Dialog.tsx b/packages/client/ui/Dialog/Dialog.tsx index 633e5e7c74f..442e9c04d1a 100644 --- a/packages/client/ui/Dialog/Dialog.tsx +++ b/packages/client/ui/Dialog/Dialog.tsx @@ -2,8 +2,8 @@ import * as RadixDialog from '@radix-ui/react-dialog' import * as React from 'react' interface DialogProps extends React.ComponentPropsWithoutRef { - isOpen: boolean - onClose: () => void + isOpen?: boolean + onClose?: () => void children: React.ReactNode } @@ -14,7 +14,7 @@ export const Dialog = (props: DialogProps) => { open={isOpen} onOpenChange={(newOpen) => { if (!newOpen) { - onClose() + onClose?.() } }} {...other} diff --git a/packages/client/ui/Dialog/DialogTrigger.tsx b/packages/client/ui/Dialog/DialogTrigger.tsx new file mode 100644 index 00000000000..5fa0ae42530 --- /dev/null +++ b/packages/client/ui/Dialog/DialogTrigger.tsx @@ -0,0 +1,11 @@ +import * as RadixDialog from '@radix-ui/react-dialog' +import React from 'react' + +export const DialogTrigger = React.forwardRef< + HTMLButtonElement, + React.ComponentPropsWithoutRef +>(({className, children, ...props}, ref) => ( + + {children} + +)) diff --git a/packages/client/utils/constants.ts b/packages/client/utils/constants.ts index b4c1b3efd4c..e2e36135f53 100644 --- a/packages/client/utils/constants.ts +++ b/packages/client/utils/constants.ts @@ -96,6 +96,7 @@ export const BILLING_PAGE = 'billing' export const MEMBERS_PAGE = 'members' export const TEAMS_PAGE = 'teams' export const ORG_SETTINGS_PAGE = 'settings' +export const ORG_INTEGRATIONS_PAGE = 'integrations' export const AUTHENTICATION_PAGE = 'authentication' /* Stripe */ diff --git a/packages/server/dataloader/integrationAuthLoaders.ts b/packages/server/dataloader/integrationAuthLoaders.ts index 6ae61e334be..2cb7083383b 100644 --- a/packages/server/dataloader/integrationAuthLoaders.ts +++ b/packages/server/dataloader/integrationAuthLoaders.ts @@ -82,7 +82,9 @@ export const sharedIntegrationProviders = (parent: RootDataLoader) => { ) as TIntegrationProvider[][] }, { - ...parent.dataLoaderOptions + ...parent.dataLoaderOptions, + cacheKeyFn: ({service, orgIds, teamIds}) => + `${service}-${orgIds.toSorted().join(',')}-${teamIds.toSorted().join(',')}` } ) } diff --git a/packages/server/graphql/mutations/addIntegrationProvider.ts b/packages/server/graphql/mutations/addIntegrationProvider.ts index b0618b8dd74..1333b899cb4 100644 --- a/packages/server/graphql/mutations/addIntegrationProvider.ts +++ b/packages/server/graphql/mutations/addIntegrationProvider.ts @@ -92,9 +92,6 @@ const addIntegrationProvider = { return {error: {message: 'Exactly 1 metadata provider is expected'}} } - const resolvedOrgId = - orgId || (teamId ? (await dataLoader.get('teams').loadNonNull(teamId)).orgId : null) - // RESOLUTION const providerId = await upsertIntegrationProvider({ authStrategy, @@ -109,11 +106,15 @@ const addIntegrationProvider = { : {orgId: null, teamId}) }) - const data = {providerId} - if (resolvedOrgId) { + const data = { + providerId, + orgId, + teamId + } + if (orgId) { publish( SubscriptionChannel.ORGANIZATION, - resolvedOrgId, + orgId, 'AddIntegrationProviderSuccess', data, subOptions diff --git a/packages/server/graphql/mutations/removeIntegrationProvider.ts b/packages/server/graphql/mutations/removeIntegrationProvider.ts index b53d5ab3fb3..e31ea4b8e0b 100644 --- a/packages/server/graphql/mutations/removeIntegrationProvider.ts +++ b/packages/server/graphql/mutations/removeIntegrationProvider.ts @@ -1,13 +1,15 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import IntegrationProviderId from 'parabol-client/shared/gqlIds/IntegrationProviderId' +import {SubscriptionChannel} from 'parabol-client/types/constEnums' import removeIntegrationProviderQuery from '../../postgres/queries/removeIntegrationProvider' import {getUserId, isSuperUser, isTeamMember, isUserOrgAdmin} from '../../utils/authorization' +import publish from '../../utils/publish' import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' import RemoveIntegrationProviderPayload from '../types/RemoveIntegrationProviderPayload' const removeIntegrationProvider = { - name: 'RemoveIntegrationProvider', + name: 'removeIntegrationProvider', type: new GraphQLNonNull(RemoveIntegrationProviderPayload), description: 'Remove an Integration Provider, and any associated tokens', args: { @@ -19,9 +21,11 @@ const removeIntegrationProvider = { resolve: async ( _source: unknown, {providerId}: {providerId: string}, - {authToken, dataLoader}: GQLContext + {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { const viewerId = getUserId(authToken) + const operationId = dataLoader.share() + const subOptions = {mutatorId, operationId} // AUTH const providerDbId = IntegrationProviderId.split(providerId) @@ -37,14 +41,31 @@ const removeIntegrationProvider = { return {error: {message: 'Must be a member of the organization that created the provider'}} } if (scope === 'team' && !isTeamMember(authToken, teamId!)) { - return {error: {message: 'Must be on the team that created the provider'}} + const team = await dataLoader.get('teams').load(teamId!) + if (!team || !isUserOrgAdmin(viewerId, team.orgId, dataLoader)) { + return {error: {message: 'Must be on the team that created the provider'}} + } } } // RESOLUTION await removeIntegrationProviderQuery(providerDbId) - const data = {userId: viewerId, teamId} + const data = { + providerId, + orgIntegrationProviders: orgId ? {orgId} : null, + teamMemberIntegrations: teamId ? {teamId, userId: viewerId} : null + } + + if (orgId) { + publish( + SubscriptionChannel.ORGANIZATION, + orgId, + 'RemoveIntegrationProviderSuccess', + data, + subOptions + ) + } return data } } diff --git a/packages/server/graphql/public/mutations/updateIntegrationProvider.ts b/packages/server/graphql/public/mutations/updateIntegrationProvider.ts index 916ebc106f6..05ed8184256 100644 --- a/packages/server/graphql/public/mutations/updateIntegrationProvider.ts +++ b/packages/server/graphql/public/mutations/updateIntegrationProvider.ts @@ -1,6 +1,6 @@ import IntegrationProviderId from 'parabol-client/shared/gqlIds/IntegrationProviderId' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import upsertIntegrationProvider from '../../../postgres/queries/upsertIntegrationProvider' +import getKysely from '../../../postgres/getKysely' import {getUserId, isSuperUser, isTeamMember, isUserOrgAdmin} from '../../../utils/authorization' import publish from '../../../utils/publish' import {MSTeamsNotifier} from '../../mutations/helpers/notifications/MSTeamsNotifier' @@ -16,6 +16,7 @@ const updateIntegrationProvider: MutationResolvers['updateIntegrationProvider'] const viewerId = getUserId(authToken) const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} + const pg = getKysely() const { id: providerId, @@ -28,18 +29,15 @@ const updateIntegrationProvider: MutationResolvers['updateIntegrationProvider'] // INPUT VALIDATION const providerDbId = IntegrationProviderId.split(providerId) - const currentProvider = await dataLoader.get('integrationProviders').load(providerDbId) - dataLoader.get('integrationProviders').clear(providerDbId) + const currentProvider = await pg + .selectFrom('IntegrationProvider') + .selectAll() + .where('id', '=', providerDbId) + .executeTakeFirst() if (!currentProvider) { return {error: {message: 'Invalid provider ID'}} } - const { - teamId: oldTeamId, - orgId: oldOrgId, - scope: oldScope, - service, - authStrategy - } = currentProvider + const {teamId: oldTeamId, orgId: oldOrgId, scope: oldScope, authStrategy} = currentProvider const [oldTeam, newTeam] = await Promise.all([ oldTeamId ? dataLoader.get('teams').load(oldTeamId) : null, @@ -88,49 +86,58 @@ const updateIntegrationProvider: MutationResolvers['updateIntegrationProvider'] if (oAuth2ProviderMetadataInput && webhookProviderMetadataInput) { return {error: {message: 'Provided 2 metadata types, expected 1'}} } - if (!oAuth2ProviderMetadataInput && !webhookProviderMetadataInput) { - return {error: {message: 'Provided 0 metadata types, expected 1'}} + if (authStrategy === 'webhook') { + if (!webhookProviderMetadataInput) { + return {error: {message: 'Expected webhook metadata'}} + } + } else if (authStrategy === 'oauth2') { + if (!oAuth2ProviderMetadataInput) { + return {error: {message: 'Expected OAuth2 metadata'}} + } + } else { + return {error: {message: 'Unsupported authStrategy'}} } - const resolvedOrgId = - newOrgId || (newTeamId ? (await dataLoader.get('teams').loadNonNull(newTeamId)).orgId : null) - const scope = newScope || oldScope + const teamId = newTeamId || oldTeamId + const orgId = newOrgId || oldOrgId + const scope = newScope ?? oldScope // RESOLUTION - await upsertIntegrationProvider({ - ...oAuth2ProviderMetadataInput, - ...webhookProviderMetadataInput, - service, - authStrategy, - scope: newScope, - ...(scope === 'global' - ? {orgId: null, teamId: null} - : scope === 'org' - ? {orgId: newOrgId!, teamId: null} - : {orgId: null, teamId: newTeamId!}) - }) - + await pg + .updateTable('IntegrationProvider') + .set({ + ...oAuth2ProviderMetadataInput, + ...webhookProviderMetadataInput, + scope, + ...(scope === 'global' + ? {orgId: null, teamId: null} + : scope === 'org' + ? {orgId, teamId: null} + : {orgId: null, teamId}) + }) + .where('id', '=', providerDbId) + .execute() if (currentProvider.service === 'mattermost') { const {webhookUrl} = currentProvider const newWebhookUrl = webhookProviderMetadataInput?.webhookUrl - if (newTeamId && newWebhookUrl && newWebhookUrl !== webhookUrl) { + if (teamId && newWebhookUrl && newWebhookUrl !== webhookUrl) { Object.assign(currentProvider, webhookProviderMetadataInput) - await MattermostNotifier.integrationUpdated(dataLoader, newTeamId, viewerId) + await MattermostNotifier.integrationUpdated(dataLoader, teamId, viewerId) } } if (currentProvider.service === 'msTeams') { const {webhookUrl} = currentProvider const newWebhookUrl = webhookProviderMetadataInput?.webhookUrl - if (newTeamId && newWebhookUrl && newWebhookUrl !== webhookUrl) { + if (teamId && newWebhookUrl && newWebhookUrl !== webhookUrl) { Object.assign(currentProvider, webhookProviderMetadataInput) - await MSTeamsNotifier.integrationUpdated(dataLoader, newTeamId, viewerId) + await MSTeamsNotifier.integrationUpdated(dataLoader, teamId, viewerId) } } const data = {userId: viewerId, providerId: providerDbId} - if (resolvedOrgId) { + if (orgId) { publish( SubscriptionChannel.ORGANIZATION, - resolvedOrgId, + orgId, 'UpdateIntegrationProviderSuccess', data, subOptions diff --git a/packages/server/graphql/public/typeDefs/AddIntegrationProviderSuccess.graphql b/packages/server/graphql/public/typeDefs/AddIntegrationProviderSuccess.graphql index 4d2bf05cb2d..703bdd90fab 100644 --- a/packages/server/graphql/public/typeDefs/AddIntegrationProviderSuccess.graphql +++ b/packages/server/graphql/public/typeDefs/AddIntegrationProviderSuccess.graphql @@ -3,4 +3,14 @@ type AddIntegrationProviderSuccess { The provider that was added """ provider: IntegrationProvider! + + """ + The updated set of organization integration providers if there were changes + """ + orgIntegrationProviders: OrgIntegrationProviders + + """ + Updated team member integrations if there were changes + """ + teamMemberIntegrations: TeamMemberIntegrations } diff --git a/packages/server/graphql/public/typeDefs/IntegrationProviderMetadataInputOAuth2.graphql b/packages/server/graphql/public/typeDefs/IntegrationProviderMetadataInputOAuth2.graphql index 883a150c01e..3cb13924e46 100644 --- a/packages/server/graphql/public/typeDefs/IntegrationProviderMetadataInputOAuth2.graphql +++ b/packages/server/graphql/public/typeDefs/IntegrationProviderMetadataInputOAuth2.graphql @@ -20,5 +20,5 @@ input IntegrationProviderMetadataInputOAuth2 { """ The tenant id to give to the provider """ - tenantId: String! + tenantId: String } diff --git a/packages/server/graphql/public/typeDefs/OrgIntegrationProviders.graphql b/packages/server/graphql/public/typeDefs/OrgIntegrationProviders.graphql new file mode 100644 index 00000000000..a2c3b2205ba --- /dev/null +++ b/packages/server/graphql/public/typeDefs/OrgIntegrationProviders.graphql @@ -0,0 +1,14 @@ +""" +Custom integration providers for this organization +""" +type OrgIntegrationProviders { + """ + composite + """ + id: ID! + + """ + Organization specific GitLab integrations + """ + gitlab: [IntegrationProviderOAuth2!]! +} diff --git a/packages/server/graphql/public/typeDefs/Organization.graphql b/packages/server/graphql/public/typeDefs/Organization.graphql index 775fac7a035..4a08189c7f2 100644 --- a/packages/server/graphql/public/typeDefs/Organization.graphql +++ b/packages/server/graphql/public/typeDefs/Organization.graphql @@ -185,4 +185,9 @@ type Organization { Empty if all domains are allowed """ approvedDomains: [String!]! + + """ + Custom integration providers with organization scope + """ + integrationProviders: OrgIntegrationProviders! } diff --git a/packages/server/graphql/public/typeDefs/OrganizationSubscriptionPayload.graphql b/packages/server/graphql/public/typeDefs/OrganizationSubscriptionPayload.graphql index 2218c2867fa..259e614cf52 100644 --- a/packages/server/graphql/public/typeDefs/OrganizationSubscriptionPayload.graphql +++ b/packages/server/graphql/public/typeDefs/OrganizationSubscriptionPayload.graphql @@ -1,16 +1,18 @@ type OrganizationSubscriptionPayload { fieldName: String! + AddIntegrationProviderSuccess: AddIntegrationProviderSuccess AddOrgPayload: AddOrgPayload ArchiveOrganizationPayload: ArchiveOrganizationPayload DowngradeToStarterPayload: DowngradeToStarterPayload + OldUpdateCreditCardPayload: OldUpdateCreditCardPayload + OldUpgradeToTeamTierPayload: OldUpgradeToTeamTierPayload PayLaterPayload: PayLaterPayload + RemoveIntegrationProviderSuccess: RemoveIntegrationProviderSuccess RemoveOrgUserPayload: RemoveOrgUserPayload SetOrgUserRoleSuccess: SetOrgUserRoleSuccess - OldUpdateCreditCardPayload: OldUpdateCreditCardPayload UpdateCreditCardPayload: UpdateCreditCardPayload - UpdateOrgPayload: UpdateOrgPayload - OldUpgradeToTeamTierPayload: OldUpgradeToTeamTierPayload - UpgradeToTeamTierSuccess: UpgradeToTeamTierSuccess UpdateIntegrationProviderSuccess: UpdateIntegrationProviderSuccess + UpdateOrgPayload: UpdateOrgPayload UpdateTemplateScopeSuccess: UpdateTemplateScopeSuccess + UpgradeToTeamTierSuccess: UpgradeToTeamTierSuccess } diff --git a/packages/server/graphql/public/typeDefs/RemoveIntegrationProviderSuccess.graphql b/packages/server/graphql/public/typeDefs/RemoveIntegrationProviderSuccess.graphql index 21a48092f74..a50745b33a9 100644 --- a/packages/server/graphql/public/typeDefs/RemoveIntegrationProviderSuccess.graphql +++ b/packages/server/graphql/public/typeDefs/RemoveIntegrationProviderSuccess.graphql @@ -1,11 +1,16 @@ type RemoveIntegrationProviderSuccess { """ - The team member with the updated auth + The ID of the integration provider that was removed """ - teamMember: TeamMember! + providerId: ID! """ - The user who updated TeamMemberIntegrationAuth object + The updated set of organization integration providers if there were changes """ - user: User! + orgIntegrationProviders: OrgIntegrationProviders + + """ + Updated team member integrations if there were changes + """ + teamMemberIntegrations: TeamMemberIntegrations } diff --git a/packages/server/graphql/public/typeDefs/TeamSubscriptionPayload.graphql b/packages/server/graphql/public/typeDefs/TeamSubscriptionPayload.graphql index d7860449a7c..98cafd8758b 100644 --- a/packages/server/graphql/public/typeDefs/TeamSubscriptionPayload.graphql +++ b/packages/server/graphql/public/typeDefs/TeamSubscriptionPayload.graphql @@ -4,7 +4,6 @@ type TeamSubscriptionPayload { AddAgendaItemPayload: AddAgendaItemPayload AddAtlassianAuthPayload: AddAtlassianAuthPayload AddGitHubAuthPayload: AddGitHubAuthPayload - AddIntegrationProviderSuccess: AddIntegrationProviderSuccess AddSlackAuthPayload: AddSlackAuthPayload AddTeamPayload: AddTeamPayload ArchiveTeamPayload: ArchiveTeamPayload diff --git a/packages/server/graphql/public/typeDefs/UpdateIntegrationProviderSuccess.graphql b/packages/server/graphql/public/typeDefs/UpdateIntegrationProviderSuccess.graphql index a94f639f693..2cf6103e093 100644 --- a/packages/server/graphql/public/typeDefs/UpdateIntegrationProviderSuccess.graphql +++ b/packages/server/graphql/public/typeDefs/UpdateIntegrationProviderSuccess.graphql @@ -3,9 +3,4 @@ type UpdateIntegrationProviderSuccess { The provider that was updated """ provider: IntegrationProvider! - - """ - The user who updated IntegrationProvider object - """ - user: User! } diff --git a/packages/server/graphql/public/types/AddIntegrationProviderSuccess.ts b/packages/server/graphql/public/types/AddIntegrationProviderSuccess.ts new file mode 100644 index 00000000000..5ee60687621 --- /dev/null +++ b/packages/server/graphql/public/types/AddIntegrationProviderSuccess.ts @@ -0,0 +1,24 @@ +import {getUserId} from '../../../utils/authorization' +import {AddIntegrationProviderSuccessResolvers} from '../resolverTypes' + +export type AddIntegrationProviderSuccessSource = { + providerId: number + orgId?: string + teamId?: string +} + +const AddIntegrationProviderSuccess: AddIntegrationProviderSuccessResolvers = { + provider: async ({providerId}, _args, {dataLoader}) => { + return dataLoader.get('integrationProviders').loadNonNull(providerId) + }, + orgIntegrationProviders: ({orgId}) => { + return orgId ? {orgId} : null + }, + teamMemberIntegrations: async ({teamId}, _args, {authToken}) => { + if (!teamId) return null + const viewerId = getUserId(authToken) + return {teamId, userId: viewerId} + } +} + +export default AddIntegrationProviderSuccess diff --git a/packages/server/graphql/public/types/GitLabIntegration.ts b/packages/server/graphql/public/types/GitLabIntegration.ts index f5093a5b329..14d7052f067 100644 --- a/packages/server/graphql/public/types/GitLabIntegration.ts +++ b/packages/server/graphql/public/types/GitLabIntegration.ts @@ -36,9 +36,10 @@ const GitLabIntegration: GitLabIntegrationResolvers = { sharedProviders: async ({teamId}, _args, {dataLoader}) => { const team = await dataLoader.get('teams').loadNonNull(teamId) const {orgId} = team - return dataLoader + const sharedProviders = await dataLoader .get('sharedIntegrationProviders') .load({service: 'gitlab', orgIds: [orgId], teamIds: [teamId]}) + return sharedProviders.filter(({scope}) => scope !== 'global') }, gitlabSearchQueries: async () => [], diff --git a/packages/server/graphql/public/types/OrgIntegrationProviders.ts b/packages/server/graphql/public/types/OrgIntegrationProviders.ts new file mode 100644 index 00000000000..7631550c714 --- /dev/null +++ b/packages/server/graphql/public/types/OrgIntegrationProviders.ts @@ -0,0 +1,21 @@ +import OrgIntegrationProvidersId from '../../../../client/shared/gqlIds/OrgIntegrationProvidersId' +import {getUserId, isUserInOrg} from '../../../utils/authorization' +import {OrgIntegrationProvidersResolvers} from '../resolverTypes' + +export type OrgIntegrationProvidersSource = { + orgId: string +} +const OrgIntegrationProviders: OrgIntegrationProvidersResolvers = { + id: ({orgId}) => OrgIntegrationProvidersId.join(orgId), + + gitlab: async ({orgId}, _args, {authToken, dataLoader}) => { + const viewerId = getUserId(authToken) + if (!isUserInOrg(viewerId, orgId, dataLoader)) return [] + const providers = await dataLoader + .get('sharedIntegrationProviders') + .load({service: 'gitlab', orgIds: [orgId], teamIds: []}) + return providers.filter((provider) => provider.scope === 'org') + } +} + +export default OrgIntegrationProviders diff --git a/packages/server/graphql/public/types/Organization.ts b/packages/server/graphql/public/types/Organization.ts index 216634df8b8..9383e193f7d 100644 --- a/packages/server/graphql/public/types/Organization.ts +++ b/packages/server/graphql/public/types/Organization.ts @@ -129,7 +129,8 @@ const Organization: OrganizationResolvers = { (organizationUser) => organizationUser.role === 'BILLING_LEADER' || organizationUser.role === 'ORG_ADMIN' ) - } + }, + integrationProviders: ({id: orgId}) => ({orgId}) } export default Organization diff --git a/packages/server/graphql/public/types/UpdateIntegrationProviderSuccess.ts b/packages/server/graphql/public/types/UpdateIntegrationProviderSuccess.ts index 858306b6ae2..12fbe78848e 100644 --- a/packages/server/graphql/public/types/UpdateIntegrationProviderSuccess.ts +++ b/packages/server/graphql/public/types/UpdateIntegrationProviderSuccess.ts @@ -2,15 +2,11 @@ import {UpdateIntegrationProviderSuccessResolvers} from '../resolverTypes' export type UpdateIntegrationProviderSuccessSource = { providerId: number - userId: string } const UpdateIntegrationProviderSuccess: UpdateIntegrationProviderSuccessResolvers = { provider: async ({providerId}, _args, {dataLoader}) => { return dataLoader.get('integrationProviders').loadNonNull(providerId) - }, - user: async ({userId}, _args, {dataLoader}) => { - return dataLoader.get('users').loadNonNull(userId) } } diff --git a/packages/server/graphql/types/IntegrationProviderMetadataInputOAuth2.ts b/packages/server/graphql/types/IntegrationProviderMetadataInputOAuth2.ts index afc9035c4d0..5a6622a93ca 100644 --- a/packages/server/graphql/types/IntegrationProviderMetadataInputOAuth2.ts +++ b/packages/server/graphql/types/IntegrationProviderMetadataInputOAuth2.ts @@ -24,7 +24,7 @@ export const IntegrationProviderMetadataInputOAuth2 = new GraphQLInputObjectType description: 'The client secret to give to the provider' }, tenantId: { - type: new GraphQLNonNull(GraphQLString), + type: GraphQLString, description: 'The tenant id to give to the provider' } }) diff --git a/packages/server/graphql/types/RemoveIntegrationProviderPayload.ts b/packages/server/graphql/types/RemoveIntegrationProviderPayload.ts index 2d2ec6f67e8..81fa490b297 100644 --- a/packages/server/graphql/types/RemoveIntegrationProviderPayload.ts +++ b/packages/server/graphql/types/RemoveIntegrationProviderPayload.ts @@ -1,21 +1,11 @@ import {GraphQLNonNull, GraphQLObjectType} from 'graphql' -import toTeamMemberId from '../../../client/utils/relay/toTeamMemberId' import {GQLContext} from '../graphql' -import TeamMember from './TeamMember' import User from './User' import makeMutationPayload from './makeMutationPayload' export const RemoveIntegrationProviderSuccess = new GraphQLObjectType({ name: 'RemoveIntegrationProviderSuccess', fields: () => ({ - teamMember: { - type: new GraphQLNonNull(TeamMember), - description: 'The team member with the updated auth', - resolve: ({teamId, userId}, _args, {dataLoader}) => { - const teamMemberId = toTeamMemberId(teamId, userId) - return dataLoader.get('teamMembers').load(teamMemberId) - } - }, user: { type: new GraphQLNonNull(User), description: 'The user who updated TeamMemberIntegrationAuth object', diff --git a/packages/server/postgres/queries/upsertIntegrationProvider.ts b/packages/server/postgres/queries/upsertIntegrationProvider.ts index a01fb334f02..a3221ff4766 100644 --- a/packages/server/postgres/queries/upsertIntegrationProvider.ts +++ b/packages/server/postgres/queries/upsertIntegrationProvider.ts @@ -9,7 +9,7 @@ import { interface IUpsertIntegrationProviderInput { service: IntegrationProviderServiceEnum authStrategy: IntegrationProviderAuthStrategyEnum - scope?: IntegrationProviderScopeEnum | null + scope?: IntegrationProviderScopeEnum | null | undefined clientId?: string tenantId?: string clientSecret?: string diff --git a/tailwind.config.js b/tailwind.config.js index 73d7ba98197..62d741c7cd4 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -21,6 +21,9 @@ module.exports = { 'icon-md-40': '40px', 'icon-md-48': '48px' }, + padding: { + 'row-gutter': '16px', + }, boxShadow: { card: 'rgba(0,0,0,.2) 0px 2px 1px -1px, rgba(0,0,0,.14) 0px 1px 1px 0px, rgba(0,0,0,.12) 0px 1px 3px 0px', 'card-1': From e3b528ff2eda697797c0731df9041df34e77afd5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 14:56:10 +0200 Subject: [PATCH 451/529] chore(deps): bump fast-xml-parser from 4.3.2 to 4.4.1 (#10047) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Georg Bremer --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index 96645f4748c..3f2d7d6cd7d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13372,9 +13372,9 @@ fast-xml-parser@4.2.5: strnum "^1.0.5" fast-xml-parser@^4.2.5, fast-xml-parser@^4.2.7: - version "4.3.2" - resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.3.2.tgz#761e641260706d6e13251c4ef8e3f5694d4b0d79" - integrity sha512-rmrXUXwbJedoXkStenj1kkljNF7ugn5ZjR9FJcwmCfcCbtOMDghPajbc+Tck6vE6F5XsDmx+Pr2le9fw8+pXBg== + version "4.4.1" + resolved "https://registry.yarnpkg.com/fast-xml-parser/-/fast-xml-parser-4.4.1.tgz#86dbf3f18edf8739326447bcaac31b4ae7f6514f" + integrity sha512-xkjOecfnKGkSsOwtZ5Pz7Us/T6mrbPQrq0nh+aCO5V9nk5NLWmasAHumTKjiPJPWANe+kAZ84Jc8ooJkzZ88Sw== dependencies: strnum "^1.0.5" From 06f0b0b5491db2d4049d06758ea7afb35298e4a2 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 10 Sep 2024 06:26:18 -0700 Subject: [PATCH 452/529] chore(Snyk): Upgrade openapi-fetch from 0.9.8 to 0.10.0 (#9955) Co-authored-by: snyk-bot Co-authored-by: GitHub Action Co-authored-by: Georg Bremer --- .../ai_models/TextEmbeddingsInference.ts | 4 +-- packages/embedder/package.json | 2 +- packages/server/package.json | 1 - yarn.lock | 30 ++++++------------- 4 files changed, 12 insertions(+), 25 deletions(-) diff --git a/packages/embedder/ai_models/TextEmbeddingsInference.ts b/packages/embedder/ai_models/TextEmbeddingsInference.ts index 0ed12614cd4..c8f22d22796 100644 --- a/packages/embedder/ai_models/TextEmbeddingsInference.ts +++ b/packages/embedder/ai_models/TextEmbeddingsInference.ts @@ -20,8 +20,8 @@ const modelIdDefinitions: Record = { } const openAPIWithTimeout = - (client: ClientMethod, toError: (error: unknown) => any, timeout: number) => - async (...args: Parameters>) => { + (client: ClientMethod, toError: (error: unknown) => any, timeout: number) => + async (...args: Parameters>) => { const controller = new AbortController() const {signal} = controller const timeoutId = setTimeout(() => { diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 34fe59960ba..3e4e64695d9 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -30,7 +30,7 @@ "jest": "^29.5.0", "jest-extended": "^3.2.4", "jest-junit": "^16.0.0", - "openapi-fetch": "^0.9.3", + "openapi-fetch": "^0.10.0", "sucrase": "^3.32.0", "ts-jest": "^29.1.0", "ts-node-dev": "^1.0.0-pre.44", diff --git a/packages/server/package.json b/packages/server/package.json index b1a7940956a..0c6fec88e3b 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -122,7 +122,6 @@ "nodemailer": "^6.9.9", "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", - "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", "parabol-client": "7.46.3", "pg": "^8.5.1", diff --git a/yarn.lock b/yarn.lock index 3f2d7d6cd7d..e8afdb3aac7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18178,34 +18178,22 @@ openai@^4.53.0: node-fetch "^2.6.7" web-streams-polyfill "^3.2.1" -openapi-fetch@^0.9.3: - version "0.9.3" - resolved "https://registry.yarnpkg.com/openapi-fetch/-/openapi-fetch-0.9.3.tgz#37c1dbde7faec885eaa40f351cab1c231b794761" - integrity sha512-tC1NDn71vJHeCzu+lYdrnIpgRt4GxR0B4eSwXNb15ypWpZcpaEOwHFkoz8FcfG5Fvqkz2P0Fl9zQF1JJwBjuvA== - dependencies: - openapi-typescript-helpers "^0.0.7" - -openapi-fetch@^0.9.7: - version "0.9.7" - resolved "https://registry.yarnpkg.com/openapi-fetch/-/openapi-fetch-0.9.7.tgz#e224d1176e11ec68671caf0df6b736d649be97ed" - integrity sha512-NMp/GEmWSGO0b2d731IdGXcMP2PF85Rk1q+oy2Mx/DYMdP3pgTZTRamKxgZpkHhM4iOVsyD1iP5HKL9Fr6CH2Q== +openapi-fetch@^0.10.0: + version "0.10.2" + resolved "https://registry.yarnpkg.com/openapi-fetch/-/openapi-fetch-0.10.2.tgz#2c16bda62cf182ba780a9d94818294731de0b2d6" + integrity sha512-GCzgKIZchnxZRnztiOlRTKk9tQT0NHvs5MNXYFtOwG7xaj1iCJOGDsTLbn/2QUP37PjGTT890qERFjvmjxzQMg== dependencies: - openapi-typescript-helpers "^0.0.8" + openapi-typescript-helpers "^0.0.9" openapi-types@^12.1.0: version "12.1.1" resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.1.tgz#0aface4e05ba60efbf51153ed6af23988796617d" integrity sha512-m/DJaEqOUDSU8KoI74E6A3TokccuDOJ81ewZ6kLFwUT1KEIE0GDWvErtnJJDU4sySx8JKF5kk2GzHUuK6f+VHA== -openapi-typescript-helpers@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.7.tgz#1d0ead67c35864d189c2cb2d0556854ccbb16c38" - integrity sha512-7nwlAtdA1fULipibFRBWE/rnF114q6ejRYzNvhdA/x+qTWAZhXGLc/368dlwMlyJDvCQMCnADjpzb5BS5ZmNSA== - -openapi-typescript-helpers@^0.0.8: - version "0.0.8" - resolved "https://registry.yarnpkg.com/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.8.tgz#460f395362cc16e4a5de56264b7b1c5a03746e35" - integrity sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g== +openapi-typescript-helpers@^0.0.9: + version "0.0.9" + resolved "https://registry.yarnpkg.com/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.9.tgz#bb20cc0b79bf56d4a31a96477d7b2c8d33b9c836" + integrity sha512-BO2TvIDAO/FPVKz1Nj2gy+pUOHfaoENdK5UP3H0Jbh0VXBf3dwYMs58ZwOjiezrbHA2LamdquoyQgahTPvIxGA== opener@^1.5.2: version "1.5.2" From e7d25ea4048a0f2c4716968be072ca139ee09485 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 17:02:21 +0200 Subject: [PATCH 453/529] chore(deps-dev): bump webpack from 5.89.0 to 5.94.0 (#10168) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: Georg Bremer --- yarn.lock | 393 +++++++++++++++++++++++------------------------------- 1 file changed, 170 insertions(+), 223 deletions(-) diff --git a/yarn.lock b/yarn.lock index e8afdb3aac7..c4c980acf32 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5118,7 +5118,7 @@ "@jridgewell/resolve-uri" "3.1.0" "@jridgewell/sourcemap-codec" "1.4.14" -"@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": +"@jridgewell/trace-mapping@^0.3.20", "@jridgewell/trace-mapping@^0.3.24", "@jridgewell/trace-mapping@^0.3.25": version "0.3.25" resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.25.tgz#15f190e98895f3fc23276ee14bc76b675c2e50f0" integrity sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ== @@ -8681,26 +8681,10 @@ "@types/react" "*" immutable "~3.7.4" -"@types/eslint-scope@^3.7.3": - version "3.7.4" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16" - integrity sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.2.1" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.2.1.tgz#13f3d69bac93c2ae008019c28783868d0a1d6605" - integrity sha512-UP9rzNn/XyGwb5RQ2fok+DzcIRIYwc16qTXse5+Smsy8MOIccCChT15KAwnsgQx4PzJkaMq4myFyZ4CL5TjhIQ== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*", "@types/estree@^1.0.0": - version "1.0.1" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194" - integrity "sha1-qiJ1CWLzvw5511PTzAZ/AQyV8ZQ= sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==" +"@types/estree@*", "@types/estree@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" + integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw== "@types/estree@0.0.39": version "0.0.39" @@ -8878,7 +8862,7 @@ "@types/parse5" "*" "@types/tough-cookie" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.11", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.9": +"@types/json-schema@^7.0.11", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== @@ -9467,125 +9451,125 @@ "@typescript-eslint/types" "8.3.0" eslint-visitor-keys "^3.4.3" -"@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.11.5.tgz#6e818036b94548c1fb53b754b5cae3c9b208281c" - integrity "sha1-boGANrlFSMH7U7dUtcrjybIIKBw= sha512-LHY/GSAZZRpsNQH+/oHqhRQ5FT7eoULcBqgfyTB5nQHogFnK3/7QoN7dLnwSE/JkUAF0SrRuclT7ODqMFtWxxQ==" +"@webassemblyjs/ast@1.12.1", "@webassemblyjs/ast@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ast/-/ast-1.12.1.tgz#bb16a0e8b1914f979f45864c23819cc3e3f0d4bb" + integrity sha512-EKfMUOPRRUTy5UII4qJDGPpqfwjOmZ5jeGFwid9mnoqIFK+e0vqoi1qH56JpmZSzEL53jKnNzScdmftJyG5xWg== dependencies: - "@webassemblyjs/helper-numbers" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" + "@webassemblyjs/helper-numbers" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" -"@webassemblyjs/floating-point-hex-parser@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.5.tgz#e85dfdb01cad16b812ff166b96806c050555f1b4" - integrity "sha1-6F39sBytFrgS/xZrloBsBQVV8bQ= sha512-1j1zTIC5EZOtCplMBG/IEwLtUojtwFVwdyVMbL/hwWqbzlQoJsWCOavrdnLkemwNoC/EOwtUFch3fuo+cbcXYQ==" +"@webassemblyjs/floating-point-hex-parser@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.6.tgz#dacbcb95aff135c8260f77fa3b4c5fea600a6431" + integrity sha512-ejAj9hfRJ2XMsNHk/v6Fu2dGS+i4UaXBXGemOfQ/JfQ6mdQg/WXtwleQRLLS4OvfDhv8rYnVwH27YJLMyYsxhw== -"@webassemblyjs/helper-api-error@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.5.tgz#1e82fa7958c681ddcf4eabef756ce09d49d442d1" - integrity "sha1-HoL6eVjGgd3PTqvvdWzgnUnUQtE= sha512-L65bDPmfpY0+yFrsgz8b6LhXmbbs38OnwDCf6NpnMUYqa+ENfE5Dq9E42ny0qz/PdR0LJyq/T5YijPnU8AXEpA==" +"@webassemblyjs/helper-api-error@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.6.tgz#6132f68c4acd59dcd141c44b18cbebbd9f2fa768" + integrity sha512-o0YkoP4pVu4rN8aTJgAyj9hC2Sv5UlkzCHhxqWj8butaLvnpdc2jOwh4ewE6CX0txSfLn/UYaV/pheS2Txg//Q== -"@webassemblyjs/helper-buffer@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.5.tgz#91381652ea95bb38bbfd270702351c0c89d69fba" - integrity "sha1-kTgWUuqVuzi7/ScHAjUcDInWn7o= sha512-fDKo1gstwFFSfacIeH5KfwzjykIE6ldh1iH9Y/8YkAZrhmu4TctqYjSh7t0K2VyDSXOZJ1MLhht/k9IvYGcIxg==" +"@webassemblyjs/helper-buffer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-buffer/-/helper-buffer-1.12.1.tgz#6df20d272ea5439bf20ab3492b7fb70e9bfcb3f6" + integrity sha512-nzJwQw99DNDKr9BVCOZcLuJJUlqkJh+kVzVl6Fmq/tI5ZtEyWT1KZMyOXltXLZJmDtvLCDgwsyrkohEtopTXCw== -"@webassemblyjs/helper-numbers@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.5.tgz#23380c910d56764957292839006fecbe05e135a9" - integrity "sha1-IzgMkQ1WdklXKSg5AG/svgXhNak= sha512-DhykHXM0ZABqfIGYNv93A5KKDw/+ywBFnuWybZZWcuzWHfbp21wUfRkbtz7dMGwGgT4iXjWuhRMA2Mzod6W4WA==" +"@webassemblyjs/helper-numbers@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.6.tgz#cbce5e7e0c1bd32cf4905ae444ef64cea919f1b5" + integrity sha512-vUIhZ8LZoIWHBohiEObxVm6hwP034jwmc9kuq5GdHZH0wiLVLIPcMCdpJzG4C11cHoQ25TFIQj9kaVADVX7N3g== dependencies: - "@webassemblyjs/floating-point-hex-parser" "1.11.5" - "@webassemblyjs/helper-api-error" "1.11.5" + "@webassemblyjs/floating-point-hex-parser" "1.11.6" + "@webassemblyjs/helper-api-error" "1.11.6" "@xtuc/long" "4.2.2" -"@webassemblyjs/helper-wasm-bytecode@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.5.tgz#e258a25251bc69a52ef817da3001863cc1c24b9f" - integrity "sha1-4liiUlG8aaUu+BfaMAGGPMHCS58= sha512-oC4Qa0bNcqnjAowFn7MPCETQgDYytpsfvz4ujZz63Zu/a/v71HeCAAmZsgZ3YVKec3zSPYytG3/PrRCqbtcAvA==" +"@webassemblyjs/helper-wasm-bytecode@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.6.tgz#bb2ebdb3b83aa26d9baad4c46d4315283acd51e9" + integrity sha512-sFFHKwcmBprO9e7Icf0+gddyWYDViL8bpPjJJl0WHxCdETktXdmtWLGVzoHbqUcY4Be1LkNfwTmXOJUFZYSJdA== -"@webassemblyjs/helper-wasm-section@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.5.tgz#966e855a6fae04d5570ad4ec87fbcf29b42ba78e" - integrity "sha1-lm6FWm+uBNVXCtTsh/vPKbQrp44= sha512-uEoThA1LN2NA+K3B9wDo3yKlBfVtC6rh0i4/6hvbz071E8gTNZD/pT0MsBf7MeD6KbApMSkaAK0XeKyOZC7CIA==" +"@webassemblyjs/helper-wasm-section@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.12.1.tgz#3da623233ae1a60409b509a52ade9bc22a37f7bf" + integrity sha512-Jif4vfB6FJlUlSbgEMHUyk1j234GTNG9dBJ4XJdOySoj518Xj0oGsNi59cUQF4RRMS9ouBUxDDdyBVfPTypa5g== dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-buffer" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" - "@webassemblyjs/wasm-gen" "1.11.5" + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/wasm-gen" "1.12.1" -"@webassemblyjs/ieee754@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.5.tgz#b2db1b33ce9c91e34236194c2b5cba9b25ca9d60" - integrity "sha1-stsbM86ckeNCNhlMK1y6myXKnWA= sha512-37aGq6qVL8A8oPbPrSGMBcp38YZFXcHfiROflJn9jxSdSMMM5dS5P/9e2/TpaJuhE+wFrbukN2WI6Hw9MH5acg==" +"@webassemblyjs/ieee754@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/ieee754/-/ieee754-1.11.6.tgz#bb665c91d0b14fffceb0e38298c329af043c6e3a" + integrity sha512-LM4p2csPNvbij6U1f19v6WR56QZ8JcHg3QIJTlSwzFcmx6WSORicYj6I63f9yU1kEUtrpG+kjkiIAkevHpDXrg== dependencies: "@xtuc/ieee754" "^1.2.0" -"@webassemblyjs/leb128@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.5.tgz#482e44d26b6b949edf042a8525a66c649e38935a" - integrity "sha1-SC5E0mtrlJ7fBCqFJaZsZJ44k1o= sha512-ajqrRSXaTJoPW+xmkfYN6l8VIeNnR4vBOTQO9HzR7IygoCcKWkICbKFbVTNMjMgMREqXEr0+2M6zukzM47ZUfQ==" +"@webassemblyjs/leb128@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/leb128/-/leb128-1.11.6.tgz#70e60e5e82f9ac81118bc25381a0b283893240d7" + integrity sha512-m7a0FhE67DQXgouf1tbN5XQcdWoNgaAuoULHIfGFIEVKA6tu/edls6XnIlkmS6FrXAquJRPni3ZZKjw6FSPjPQ== dependencies: "@xtuc/long" "4.2.2" -"@webassemblyjs/utf8@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.5.tgz#83bef94856e399f3740e8df9f63bc47a987eae1a" - integrity "sha1-g775SFbjmfN0Do359jvEeph+rho= sha512-WiOhulHKTZU5UPlRl53gHR8OxdGsSOxqfpqWeA2FmcwBMaoEdz6b2x2si3IwC9/fSPLfe8pBMRTHVMk5nlwnFQ==" - -"@webassemblyjs/wasm-edit@^1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.5.tgz#93ee10a08037657e21c70de31c47fdad6b522b2d" - integrity "sha1-k+4QoIA3ZX4hxw3jHEf9rWtSKy0= sha512-C0p9D2fAu3Twwqvygvf42iGCQ4av8MFBLiTb+08SZ4cEdwzWx9QeAHDo1E2k+9s/0w1DM40oflJOpkZ8jW4HCQ==" - dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-buffer" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" - "@webassemblyjs/helper-wasm-section" "1.11.5" - "@webassemblyjs/wasm-gen" "1.11.5" - "@webassemblyjs/wasm-opt" "1.11.5" - "@webassemblyjs/wasm-parser" "1.11.5" - "@webassemblyjs/wast-printer" "1.11.5" - -"@webassemblyjs/wasm-gen@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.5.tgz#ceb1c82b40bf0cf67a492c53381916756ef7f0b1" - integrity "sha1-zrHIK0C/DPZ6SSxTOBkWdW738LE= sha512-14vteRlRjxLK9eSyYFvw1K8Vv+iPdZU0Aebk3j6oB8TQiQYuO6hj9s4d7qf6f2HJr2khzvNldAFG13CgdkAIfA==" - dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" - "@webassemblyjs/ieee754" "1.11.5" - "@webassemblyjs/leb128" "1.11.5" - "@webassemblyjs/utf8" "1.11.5" - -"@webassemblyjs/wasm-opt@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.5.tgz#b52bac29681fa62487e16d3bb7f0633d5e62ca0a" - integrity "sha1-tSusKWgfpiSH4W07t/BjPV5iygo= sha512-tcKwlIXstBQgbKy1MlbDMlXaxpucn42eb17H29rawYLxm5+MsEmgPzeCP8B1Cl69hCice8LeKgZpRUAPtqYPgw==" - dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-buffer" "1.11.5" - "@webassemblyjs/wasm-gen" "1.11.5" - "@webassemblyjs/wasm-parser" "1.11.5" - -"@webassemblyjs/wasm-parser@1.11.5", "@webassemblyjs/wasm-parser@^1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.5.tgz#7ba0697ca74c860ea13e3ba226b29617046982e2" - integrity "sha1-e6BpfKdMhg6hPjuiJrKWFwRpguI= sha512-SVXUIwsLQlc8srSD7jejsfTU83g7pIGr2YYNb9oHdtldSxaOhvA5xwvIiWIfcX8PlSakgqMXsLpLfbbJ4cBYew==" - dependencies: - "@webassemblyjs/ast" "1.11.5" - "@webassemblyjs/helper-api-error" "1.11.5" - "@webassemblyjs/helper-wasm-bytecode" "1.11.5" - "@webassemblyjs/ieee754" "1.11.5" - "@webassemblyjs/leb128" "1.11.5" - "@webassemblyjs/utf8" "1.11.5" - -"@webassemblyjs/wast-printer@1.11.5": - version "1.11.5" - resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.11.5.tgz#7a5e9689043f3eca82d544d7be7a8e6373a6fa98" - integrity "sha1-el6WiQQ/PsqC1UTXvnqOY3Om+pg= sha512-f7Pq3wvg3GSPUPzR0F6bmI89Hdb+u9WXrSKc4v+N0aV0q6r42WoF92Jp2jEorBEBRoRNXgjp53nBniDXcqZYPA==" - dependencies: - "@webassemblyjs/ast" "1.11.5" +"@webassemblyjs/utf8@1.11.6": + version "1.11.6" + resolved "https://registry.yarnpkg.com/@webassemblyjs/utf8/-/utf8-1.11.6.tgz#90f8bc34c561595fe156603be7253cdbcd0fab5a" + integrity sha512-vtXf2wTQ3+up9Zsg8sa2yWiQpzSsMyXj0qViVP6xKGCUT8p8YJ6HqI7l5eCnWx1T/FYdsv07HQs2wTFbbof/RA== + +"@webassemblyjs/wasm-edit@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-edit/-/wasm-edit-1.12.1.tgz#9f9f3ff52a14c980939be0ef9d5df9ebc678ae3b" + integrity sha512-1DuwbVvADvS5mGnXbE+c9NfA8QRcZ6iKquqjjmR10k6o+zzsRVesil54DKexiowcFCPdr/Q0qaMgB01+SQ1u6g== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/helper-wasm-section" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-opt" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + "@webassemblyjs/wast-printer" "1.12.1" + +"@webassemblyjs/wasm-gen@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-gen/-/wasm-gen-1.12.1.tgz#a6520601da1b5700448273666a71ad0a45d78547" + integrity sha512-TDq4Ojh9fcohAw6OIMXqiIcTq5KUXTGRkVxbSo1hQnSy6lAM5GSdfwWeSxpAo0YzgsgF182E/U0mDNhuA0tW7w== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wasm-opt@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-opt/-/wasm-opt-1.12.1.tgz#9e6e81475dfcfb62dab574ac2dda38226c232bc5" + integrity sha512-Jg99j/2gG2iaz3hijw857AVYekZe2SAskcqlWIZXjji5WStnOpVoat3gQfT/Q5tb2djnCjBtMocY/Su1GfxPBg== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-buffer" "1.12.1" + "@webassemblyjs/wasm-gen" "1.12.1" + "@webassemblyjs/wasm-parser" "1.12.1" + +"@webassemblyjs/wasm-parser@1.12.1", "@webassemblyjs/wasm-parser@^1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wasm-parser/-/wasm-parser-1.12.1.tgz#c47acb90e6f083391e3fa61d113650eea1e95937" + integrity sha512-xikIi7c2FHXysxXe3COrVUPSheuBtpcfhbpFj4gmu7KRLYOzANztwUU0IbsqvMqzuNK2+glRGWCEqZo1WCLyAQ== + dependencies: + "@webassemblyjs/ast" "1.12.1" + "@webassemblyjs/helper-api-error" "1.11.6" + "@webassemblyjs/helper-wasm-bytecode" "1.11.6" + "@webassemblyjs/ieee754" "1.11.6" + "@webassemblyjs/leb128" "1.11.6" + "@webassemblyjs/utf8" "1.11.6" + +"@webassemblyjs/wast-printer@1.12.1": + version "1.12.1" + resolved "https://registry.yarnpkg.com/@webassemblyjs/wast-printer/-/wast-printer-1.12.1.tgz#bcecf661d7d1abdaf989d8341a4833e33e2b31ac" + integrity sha512-+X4WAlOisVWQMikjbcvY2e0rwPsKQ9F688lksZhBcPycBBuii3O7m8FACbDMWDojpAqvjIncrG8J0XHKyQfVeA== + dependencies: + "@webassemblyjs/ast" "1.12.1" "@xtuc/long" "4.2.2" "@webpack-cli/configtest@^1.1.0": @@ -9742,6 +9726,11 @@ acorn-import-assertions@^1.9.0: resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -10672,34 +10661,14 @@ braces@^3.0.3, braces@~3.0.2: dependencies: fill-range "^7.1.1" -browserslist@^4.14.5, browserslist@^4.21.1, browserslist@^4.21.4, browserslist@^4.21.9: - version "4.21.10" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" - integrity sha512-bipEBdZfVH5/pwrvqc+Ub0kUPVfGUhlKxbvfD+z1BDnPEO/X98ruXGA1WP5ASpAFKan7Qr6j736IacbZQuAlKQ== - dependencies: - caniuse-lite "^1.0.30001517" - electron-to-chromium "^1.4.477" - node-releases "^2.0.13" - update-browserslist-db "^1.0.11" - -browserslist@^4.22.2: - version "4.22.3" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.22.3.tgz#299d11b7e947a6b843981392721169e27d60c5a6" - integrity sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A== - dependencies: - caniuse-lite "^1.0.30001580" - electron-to-chromium "^1.4.648" - node-releases "^2.0.14" - update-browserslist-db "^1.0.13" - -browserslist@^4.23.0, browserslist@^4.23.1: - version "4.23.2" - resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.2.tgz#244fe803641f1c19c28c48c4b6ec9736eb3d32ed" - integrity sha512-qkqSyistMYdxAcw+CzbZwlBy8AGmS/eEWs+sEV5TnLRGDOL+C5M2EnH6tlZyg0YoAxGJAFKh61En9BR941GnHA== - dependencies: - caniuse-lite "^1.0.30001640" - electron-to-chromium "^1.4.820" - node-releases "^2.0.14" +browserslist@^4.21.1, browserslist@^4.21.10, browserslist@^4.21.4, browserslist@^4.21.9, browserslist@^4.22.2, browserslist@^4.23.0, browserslist@^4.23.1: + version "4.23.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" + integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== + dependencies: + caniuse-lite "^1.0.30001646" + electron-to-chromium "^1.5.4" + node-releases "^2.0.18" update-browserslist-db "^1.1.0" bs-logger@0.x: @@ -10930,7 +10899,7 @@ camelcase@^6.2.0: resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a" integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== -caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001580, caniuse-lite@^1.0.30001640, caniuse-lite@~1.0.0: +caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001646, caniuse-lite@~1.0.0: version "1.0.30001651" resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz#52de59529e8b02b1aedcaaf5c05d9e23c0c28138" integrity sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg== @@ -12643,20 +12612,10 @@ ejs@^3.1.6, ejs@^3.1.7: dependencies: jake "^10.8.5" -electron-to-chromium@^1.4.477: - version "1.4.490" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.490.tgz#d99286f6e915667fa18ea4554def1aa60eb4d5f1" - integrity sha512-6s7NVJz+sATdYnIwhdshx/N/9O6rvMxmhVoDSDFdj6iA45gHR8EQje70+RYsF4GeB+k0IeNSBnP7yG9ZXJFr7A== - -electron-to-chromium@^1.4.648: - version "1.4.660" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.660.tgz#80be71d08c1224980e645904ab9155f3fa54a1ea" - integrity sha512-1BqvQG0BBQrAA7FVL2EMrb5A1sVyXF3auwJneXjGWa1TpN+g0C4KbUsYWePz6OZ0mXZfXGy+RmQDELJWwE8v/Q== - -electron-to-chromium@^1.4.820: - version "1.5.2" - resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.2.tgz#6126ad229ce45e781ec54ca40db0504787f23d19" - integrity sha512-kc4r3U3V3WLaaZqThjYz/Y6z8tJe+7K0bbjUVo3i+LWIypVdMx5nXCkwRe6SWbY6ILqLdc1rKcKmr3HoH7wjSQ== +electron-to-chromium@^1.5.4: + version "1.5.13" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz#1abf0410c5344b2b829b7247e031f02810d442e6" + integrity sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q== email-addresses@^3.0.1: version "3.1.0" @@ -12710,10 +12669,10 @@ end-of-stream@^1.1.0, end-of-stream@^1.4.1: dependencies: once "^1.4.0" -enhanced-resolve@^5.0.0, enhanced-resolve@^5.15.0: - version "5.15.0" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.15.0.tgz#1af946c7d93603eb88e9896cee4904dc012e9c35" - integrity sha512-LXYT42KJ7lpIKECr2mAXIaMldcNCh/7E0KBKOu4KSfkHmP+mZmSs+8V5gBAqisWBy0OO4W5Oyys0GO1Y8KtdKg== +enhanced-resolve@^5.0.0, enhanced-resolve@^5.17.1: + version "5.17.1" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" + integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -14186,7 +14145,7 @@ googleapis@^118.0.0: google-auth-library "^8.0.2" googleapis-common "^6.0.0" -graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: +graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ== @@ -17656,15 +17615,10 @@ node-readfiles@^0.2.0: dependencies: es6-promise "^3.2.1" -node-releases@^2.0.13: - version "2.0.13" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d" - integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ== - -node-releases@^2.0.14: - version "2.0.14" - resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" - integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== node-rsa@^1.1.1: version "1.1.1" @@ -21812,16 +21766,16 @@ terminal-link@2.1.1: ansi-escapes "^4.2.1" supports-hyperlinks "^2.0.0" -terser-webpack-plugin@^5.3.7, terser-webpack-plugin@^5.3.9: - version "5.3.9" - resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.9.tgz#832536999c51b46d468067f9e37662a3b96adfe1" - integrity sha512-ZuXsqE07EcggTWQjXUj+Aot/OMcD0bMKGgF63f7UxYcu5/AJF53aIpK1YoP5xR9l6s/Hy2b+t1AM0bLNPRuhwA== +terser-webpack-plugin@^5.3.10, terser-webpack-plugin@^5.3.9: + version "5.3.10" + resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-5.3.10.tgz#904f4c9193c6fd2a03f693a2150c62a92f40d199" + integrity sha512-BKFPWlPDndPs+NGGCr1U59t0XScL5317Y0UReNrHaw9/FwhPENlq6bfgs+4yPfyP51vqC1bQ4rp1EfXW5ZSH9w== dependencies: - "@jridgewell/trace-mapping" "^0.3.17" + "@jridgewell/trace-mapping" "^0.3.20" jest-worker "^27.4.5" schema-utils "^3.1.1" serialize-javascript "^6.0.1" - terser "^5.16.8" + terser "^5.26.0" terser@^4.6.3: version "4.8.1" @@ -21832,7 +21786,7 @@ terser@^4.6.3: source-map "~0.6.1" source-map-support "~0.5.12" -terser@^5.0.0, terser@^5.10.0, terser@^5.16.8: +terser@^5.0.0, terser@^5.10.0: version "5.19.2" resolved "https://registry.yarnpkg.com/terser/-/terser-5.19.2.tgz#bdb8017a9a4a8de4663a7983f45c506534f9234e" integrity sha512-qC5+dmecKJA4cpYxRa5aVkKehYsQKc+AHeKl0Oe62aYjBL8ZA33tTljktDHJSaxxMnbI5ZYw+o/S2DxxLu8OfA== @@ -21842,6 +21796,16 @@ terser@^5.0.0, terser@^5.10.0, terser@^5.16.8: commander "^2.20.0" source-map-support "~0.5.20" +terser@^5.26.0: + version "5.31.6" + resolved "https://registry.yarnpkg.com/terser/-/terser-5.31.6.tgz#c63858a0f0703988d0266a82fcbf2d7ba76422b1" + integrity sha512-PQ4DAriWzKj+qgehQ7LK5bQqCFNMmlhjR2PFFLuqGCpuCAauxemVBWwWOxo3UIwWQx8+Pr61Df++r76wDmkQBg== + dependencies: + "@jridgewell/source-map" "^0.3.3" + acorn "^8.8.2" + commander "^2.20.0" + source-map-support "~0.5.20" + test-exclude@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e" @@ -22529,22 +22493,6 @@ upath@^2.0.1: resolved "https://registry.yarnpkg.com/upath/-/upath-2.0.1.tgz#50c73dea68d6f6b990f51d279ce6081665d61a8b" integrity sha512-1uEe95xksV1O0CYKXo8vQvN1JEbtJp7lb7C5U9HMsIp6IVwntkH/oNUzyVNQSd4S1sYk2FpSSW44FqMc8qee5w== -update-browserslist-db@^1.0.11: - version "1.0.11" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940" - integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - -update-browserslist-db@^1.0.13: - version "1.0.13" - resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz#3c5e4f5c083661bd38ef64b6328c26ed6c8248c4" - integrity sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg== - dependencies: - escalade "^3.1.1" - picocolors "^1.0.0" - update-browserslist-db@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" @@ -22832,10 +22780,10 @@ warning@^4.0.1: dependencies: loose-envify "^1.0.0" -watchpack@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d" - integrity sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg== +watchpack@^2.4.1: + version "2.4.2" + resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.2.tgz#2feeaed67412e7c33184e5a79ca738fbd38564da" + integrity sha512-TnbFSbcOCcDgjZ4piURLCbJ3nJhznVh9kw6F6iokjiFPl8ONxe9A6nMDVXDiNbrSfLILs6vB07F7wLBrwPYzJw== dependencies: glob-to-regexp "^0.4.1" graceful-fs "^4.1.2" @@ -23008,33 +22956,32 @@ webpack-sources@^3.2.3: integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== webpack@^5.89.0: - version "5.89.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.89.0.tgz#56b8bf9a34356e93a6625770006490bf3a7f32dc" - integrity sha512-qyfIC10pOr70V+jkmud8tMfajraGCZMBWJtrmuBymQKCrLTRejBI8STDp1MCyZu/QTdZSeacCQYpYNQVOzX5kw== - dependencies: - "@types/eslint-scope" "^3.7.3" - "@types/estree" "^1.0.0" - "@webassemblyjs/ast" "^1.11.5" - "@webassemblyjs/wasm-edit" "^1.11.5" - "@webassemblyjs/wasm-parser" "^1.11.5" + version "5.94.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" + integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== + dependencies: + "@types/estree" "^1.0.5" + "@webassemblyjs/ast" "^1.12.1" + "@webassemblyjs/wasm-edit" "^1.12.1" + "@webassemblyjs/wasm-parser" "^1.12.1" acorn "^8.7.1" - acorn-import-assertions "^1.9.0" - browserslist "^4.14.5" + acorn-import-attributes "^1.9.5" + browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.15.0" + enhanced-resolve "^5.17.1" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" glob-to-regexp "^0.4.1" - graceful-fs "^4.2.9" + graceful-fs "^4.2.11" json-parse-even-better-errors "^2.3.1" loader-runner "^4.2.0" mime-types "^2.1.27" neo-async "^2.6.2" schema-utils "^3.2.0" tapable "^2.1.1" - terser-webpack-plugin "^5.3.7" - watchpack "^2.4.0" + terser-webpack-plugin "^5.3.10" + watchpack "^2.4.1" webpack-sources "^3.2.3" websocket-driver@>=0.5.1, websocket-driver@^0.7.4: From 45501a3950f1d511bfc428c80bbb8e537cae8837 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 10 Sep 2024 17:47:13 +0200 Subject: [PATCH 454/529] fix: Anonymous comments (#10206) --- packages/server/graphql/public/mutations/addComment.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/graphql/public/mutations/addComment.ts b/packages/server/graphql/public/mutations/addComment.ts index 6ccf9128c89..e20405c6184 100644 --- a/packages/server/graphql/public/mutations/addComment.ts +++ b/packages/server/graphql/public/mutations/addComment.ts @@ -108,6 +108,7 @@ const addComment: MutationResolvers['addComment'] = async ( .values({ id: commentId, content, + isAnonymous: isAnonymous ?? undefined, plaintextContent: extractTextFromDraftString(content), createdBy: viewerId, threadSortOrder, From 7e77d8b8f7f84f1af14687bc7665911b9289439a Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 10 Sep 2024 17:56:37 +0200 Subject: [PATCH 455/529] chore(release): release v7.47.0 (#10202) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 20 ++++++++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 32 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e381287fd82..b18a21909f4 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.46.3" + ".": "7.47.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index dc98cd33f0a..e802901b38c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,26 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.47.0](https://github.com/ParabolInc/parabol/compare/v7.46.3...v7.47.0) (2024-09-10) + + +### Added + +* Enable connecting to different GitLab integration providers ([#10025](https://github.com/ParabolInc/parabol/issues/10025)) ([8806839](https://github.com/ParabolInc/parabol/commit/880683996e4afb117ed21918a2c91574b649d4d9)) + + +### Fixed + +* Anonymous comments ([#10206](https://github.com/ParabolInc/parabol/issues/10206)) ([45501a3](https://github.com/ParabolInc/parabol/commit/45501a3950f1d511bfc428c80bbb8e537cae8837)) +* **orgAdmin:** user should be able to remove themselves from the org ([#10201](https://github.com/ParabolInc/parabol/issues/10201)) ([4368c0b](https://github.com/ParabolInc/parabol/commit/4368c0bc6af2310f62133ae07c331912bb048628)) + + +### Changed + +* **deps-dev:** bump webpack from 5.89.0 to 5.94.0 ([#10168](https://github.com/ParabolInc/parabol/issues/10168)) ([e7d25ea](https://github.com/ParabolInc/parabol/commit/e7d25ea4048a0f2c4716968be072ca139ee09485)) +* **deps:** bump fast-xml-parser from 4.3.2 to 4.4.1 ([#10047](https://github.com/ParabolInc/parabol/issues/10047)) ([e3b528f](https://github.com/ParabolInc/parabol/commit/e3b528ff2eda697797c0731df9041df34e77afd5)) +* **Snyk:** Upgrade openapi-fetch from 0.9.8 to 0.10.0 ([#9955](https://github.com/ParabolInc/parabol/issues/9955)) ([06f0b0b](https://github.com/ParabolInc/parabol/commit/06f0b0b5491db2d4049d06758ea7afb35298e4a2)) + ## [7.46.3](https://github.com/ParabolInc/parabol/compare/v7.46.2...v7.46.3) (2024-09-09) diff --git a/package.json b/package.json index 66631ee75c0..dcd9afa4562 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.46.3", + "version": "7.47.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 1469979b198..0dfab9d0cde 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.46.3", + "version": "7.47.0", "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.46.3" + "parabol-server": "7.47.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index d8ecd4195f5..82dbeb92452 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.46.3", + "version": "7.47.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 3e4e64695d9..7eb1911f09c 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.46.3", + "version": "7.47.0", "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 ed6e3406ee5..34cf9beafaa 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.46.3", + "version": "7.47.0", "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.46.3", - "parabol-server": "7.46.3", + "parabol-client": "7.47.0", + "parabol-server": "7.47.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index a5edf8dc531..f6a05744a67 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.46.3", + "version": "7.47.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 0c6fec88e3b..ea0ee5e3468 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.46.3", + "version": "7.47.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.46.3", + "parabol-client": "7.47.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From e48732b73e1c52de05ded693bc5e8b69d628dcba Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 11 Sep 2024 10:52:52 -0700 Subject: [PATCH 456/529] chore(rethinkdb): ReflectPrompt: Phase 1 (#10193) Signed-off-by: Matt Krick --- codegen.json | 6 ++ .../database/types/RetrospectivePrompt.ts | 6 +- .../dataloader/foreignKeyLoaderMakers.ts | 12 +++ .../dataloader/primaryKeyLoaderMakers.ts | 5 ++ .../mutations/addReflectTemplatePrompt.ts | 90 ------------------- .../mutations/helpers/makeRetroTemplates.ts | 36 +++++--- .../mutations/moveReflectTemplatePrompt.ts | 64 ------------- .../reflectTemplatePromptUpdateDescription.ts | 72 --------------- .../reflectTemplatePromptUpdateGroupColor.ts | 72 --------------- .../mutations/removeReflectTemplate.ts | 15 +++- .../mutations/removeReflectTemplatePrompt.ts | 82 ----------------- .../mutations/renameReflectTemplatePrompt.ts | 85 ------------------ .../graphql/mutations/updateTemplateScope.ts | 20 +++-- .../public/mutations/addReflectTemplate.ts | 37 +++++--- .../mutations/addReflectTemplatePrompt.ts | 75 ++++++++++++++++ .../mutations/moveReflectTemplatePrompt.ts | 62 +++++++++++++ .../reflectTemplatePromptUpdateDescription.ts | 59 ++++++++++++ .../reflectTemplatePromptUpdateGroupColor.ts | 55 ++++++++++++ .../mutations/removeReflectTemplatePrompt.ts | 58 ++++++++++++ .../mutations/renameReflectTemplatePrompt.ts | 63 +++++++++++++ .../graphql/public/typeDefs/Mutation.graphql | 2 +- .../types/AddReflectTemplatePromptPayload.ts | 15 ++++ .../types/MoveReflectTemplatePromptPayload.ts | 15 ++++ .../graphql/public/types/ReflectPhase.ts | 3 +- ...tTemplatePromptUpdateDescriptionPayload.ts | 16 ++++ ...ctTemplatePromptUpdateGroupColorPayload.ts | 16 ++++ .../RemoveReflectTemplatePromptPayload.ts | 22 +++++ .../RenameReflectTemplatePromptPayload.ts | 15 ++++ packages/server/graphql/rootMutation.ts | 12 --- .../types/AddReflectTemplatePromptPayload.ts | 22 ----- .../types/MoveReflectTemplatePromptPayload.ts | 22 ----- ...tTemplatePromptUpdateDescriptionPayload.ts | 22 ----- ...ctTemplatePromptUpdateGroupColorPayload.ts | 22 ----- .../RemoveReflectTemplatePromptPayload.ts | 30 ------- .../RenameReflectTemplatePromptPayload.ts | 22 ----- .../1725655687704_ReflectPrompt-phase1.ts | 56 ++++++++++++ .../postgres/queries/insertMeetingTemplate.ts | 39 -------- .../postgres/queries/removeMeetingTemplate.ts | 8 -- packages/server/postgres/select.ts | 2 + packages/server/postgres/types/index.d.ts | 2 + 40 files changed, 633 insertions(+), 704 deletions(-) delete mode 100644 packages/server/graphql/mutations/addReflectTemplatePrompt.ts delete mode 100644 packages/server/graphql/mutations/moveReflectTemplatePrompt.ts delete mode 100644 packages/server/graphql/mutations/reflectTemplatePromptUpdateDescription.ts delete mode 100644 packages/server/graphql/mutations/reflectTemplatePromptUpdateGroupColor.ts delete mode 100644 packages/server/graphql/mutations/removeReflectTemplatePrompt.ts delete mode 100644 packages/server/graphql/mutations/renameReflectTemplatePrompt.ts create mode 100644 packages/server/graphql/public/mutations/addReflectTemplatePrompt.ts create mode 100644 packages/server/graphql/public/mutations/moveReflectTemplatePrompt.ts create mode 100644 packages/server/graphql/public/mutations/reflectTemplatePromptUpdateDescription.ts create mode 100644 packages/server/graphql/public/mutations/reflectTemplatePromptUpdateGroupColor.ts create mode 100644 packages/server/graphql/public/mutations/removeReflectTemplatePrompt.ts create mode 100644 packages/server/graphql/public/mutations/renameReflectTemplatePrompt.ts create mode 100644 packages/server/graphql/public/types/AddReflectTemplatePromptPayload.ts create mode 100644 packages/server/graphql/public/types/MoveReflectTemplatePromptPayload.ts create mode 100644 packages/server/graphql/public/types/ReflectTemplatePromptUpdateDescriptionPayload.ts create mode 100644 packages/server/graphql/public/types/ReflectTemplatePromptUpdateGroupColorPayload.ts create mode 100644 packages/server/graphql/public/types/RemoveReflectTemplatePromptPayload.ts create mode 100644 packages/server/graphql/public/types/RenameReflectTemplatePromptPayload.ts delete mode 100644 packages/server/graphql/types/AddReflectTemplatePromptPayload.ts delete mode 100644 packages/server/graphql/types/MoveReflectTemplatePromptPayload.ts delete mode 100644 packages/server/graphql/types/ReflectTemplatePromptUpdateDescriptionPayload.ts delete mode 100644 packages/server/graphql/types/ReflectTemplatePromptUpdateGroupColorPayload.ts delete mode 100644 packages/server/graphql/types/RemoveReflectTemplatePromptPayload.ts delete mode 100644 packages/server/graphql/types/RenameReflectTemplatePromptPayload.ts create mode 100644 packages/server/postgres/migrations/1725655687704_ReflectPrompt-phase1.ts delete mode 100644 packages/server/postgres/queries/insertMeetingTemplate.ts delete mode 100644 packages/server/postgres/queries/removeMeetingTemplate.ts diff --git a/codegen.json b/codegen.json index 999ac8008ba..5774ea539d6 100644 --- a/codegen.json +++ b/codegen.json @@ -46,6 +46,12 @@ "config": { "contextType": "../graphql#GQLContext", "mappers": { + "ReflectTemplatePromptUpdateDescriptionPayload": "./types/ReflectTemplatePromptUpdateDescriptionPayload#ReflectTemplatePromptUpdateDescriptionPayloadSource", + "ReflectTemplatePromptUpdateGroupColorPayload": "./types/ReflectTemplatePromptUpdateGroupColorPayload#ReflectTemplatePromptUpdateGroupColorPayloadSource", + "RemoveReflectTemplatePromptPayload": "./types/RemoveReflectTemplatePromptPayload#RemoveReflectTemplatePromptPayloadSource", + "RenameReflectTemplatePromptPayload": "./types/RenameReflectTemplatePromptPayload#RenameReflectTemplatePromptPayloadSource", + "MoveReflectTemplatePromptPayload": "./types/MoveReflectTemplatePromptPayload#MoveReflectTemplatePromptPayloadSource", + "AddReflectTemplatePromptPayload": "./types/AddReflectTemplatePromptPayload#AddReflectTemplatePromptPayloadSource", "SetSlackNotificationPayload": "./types/SetSlackNotificationPayload#SetSlackNotificationPayloadSource", "SetDefaultSlackChannelSuccess": "./types/SetDefaultSlackChannelSuccess#SetDefaultSlackChannelSuccessSource", "AddCommentSuccess": "./types/AddCommentSuccess#AddCommentSuccessSource", diff --git a/packages/server/database/types/RetrospectivePrompt.ts b/packages/server/database/types/RetrospectivePrompt.ts index a278dee85ad..23f8b6b5373 100644 --- a/packages/server/database/types/RetrospectivePrompt.ts +++ b/packages/server/database/types/RetrospectivePrompt.ts @@ -8,7 +8,7 @@ interface Input { description: string groupColor: string removedAt: Date | null - parentPromptId?: string + parentPromptId?: string | null } export default class RetrospectivePrompt { @@ -22,7 +22,7 @@ export default class RetrospectivePrompt { question: string removedAt: Date | null updatedAt = new Date() - parentPromptId?: string + parentPromptId?: string | null constructor(input: Input) { const { @@ -43,6 +43,6 @@ export default class RetrospectivePrompt { this.description = description || '' this.groupColor = groupColor this.removedAt = removedAt - this.parentPromptId = parentPromptId + this.parentPromptId = parentPromptId || null } } diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 0f08e21d5a6..2c2381b23a9 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -4,6 +4,7 @@ import { selectAgendaItems, selectComments, selectOrganizations, + selectReflectPrompts, selectRetroReflections, selectSlackAuths, selectSlackNotifications, @@ -215,3 +216,14 @@ export const commentsByDiscussionId = foreignKeyLoaderMaker( return selectComments().where('discussionId', 'in', discussionIds).execute() } ) + +export const _pgreflectPromptsByTemplateId = foreignKeyLoaderMaker( + '_pgreflectPrompts', + 'templateId', + async (templateIds) => { + return selectReflectPrompts() + .where('templateId', 'in', templateIds) + .orderBy('sortOrder') + .execute() + } +) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index adeee70820a..e80d3785659 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -10,6 +10,7 @@ import { selectComments, selectMeetingSettings, selectOrganizations, + selectReflectPrompts, selectRetroReflections, selectSlackAuths, selectSlackNotifications, @@ -110,3 +111,7 @@ export const slackNotifications = primaryKeyLoaderMaker((ids: readonly string[]) export const comments = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectComments().where('id', 'in', ids).execute() }) + +export const _pgreflectPrompts = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectReflectPrompts().where('id', 'in', ids).execute() +}) diff --git a/packages/server/graphql/mutations/addReflectTemplatePrompt.ts b/packages/server/graphql/mutations/addReflectTemplatePrompt.ts deleted file mode 100644 index e0867254b79..00000000000 --- a/packages/server/graphql/mutations/addReflectTemplatePrompt.ts +++ /dev/null @@ -1,90 +0,0 @@ -import {GraphQLID, GraphQLNonNull} from 'graphql' -import {SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' -import dndNoise from 'parabol-client/utils/dndNoise' -import palettePickerOptions from '../../../client/styles/palettePickerOptions' -import {PALETTE} from '../../../client/styles/paletteV3' -import getRethink from '../../database/rethinkDriver' -import RetrospectivePrompt from '../../database/types/RetrospectivePrompt' -import getKysely from '../../postgres/getKysely' -import {getUserId, isTeamMember} from '../../utils/authorization' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import AddReflectTemplatePromptPayload from '../types/AddReflectTemplatePromptPayload' - -const addReflectTemplatePrompt = { - description: 'Add a new template full of prompts', - type: AddReflectTemplatePromptPayload, - args: { - templateId: { - type: new GraphQLNonNull(GraphQLID) - } - }, - async resolve( - _source: unknown, - {templateId}: {templateId: string}, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) { - const r = await getRethink() - const pg = getKysely() - const operationId = dataLoader.share() - const subOptions = {operationId, mutatorId} - const template = await dataLoader.get('meetingTemplates').load(templateId) - const viewerId = getUserId(authToken) - - // AUTH - if (!template || !template.isActive) { - return standardError(new Error('Template not found'), {userId: viewerId}) - } - if (!isTeamMember(authToken, template.teamId)) { - return standardError(new Error('Team not found'), {userId: viewerId}) - } - - // VALIDATION - const {teamId} = template - const activePrompts = await r - .table('ReflectPrompt') - .getAll(teamId, {index: 'teamId'}) - .filter({ - templateId, - removedAt: null - }) - .run() - if (activePrompts.length >= Threshold.MAX_REFLECTION_PROMPTS) { - return standardError(new Error('Too many prompts'), {userId: viewerId}) - } - - // RESOLUTION - const sortOrder = - Math.max(0, ...activePrompts.map((prompt) => prompt.sortOrder)) + 1 + dndNoise() - const pickedColors = activePrompts.map((prompt) => prompt.groupColor) - const availableNewColor = palettePickerOptions.find( - (color) => !pickedColors.includes(color.hex) - ) - const reflectPrompt = new RetrospectivePrompt({ - templateId: template.id, - teamId: template.teamId, - sortOrder, - question: `New prompt #${activePrompts.length + 1}`, - description: '', - groupColor: availableNewColor?.hex ?? PALETTE.JADE_400, - removedAt: null - }) - - await Promise.all([ - await r.table('ReflectPrompt').insert(reflectPrompt).run(), - pg - .updateTable('MeetingTemplate') - .set({updatedAt: new Date()}) - .where('id', '=', templateId) - .execute() - ]) - - const promptId = reflectPrompt.id - const data = {promptId} - publish(SubscriptionChannel.TEAM, teamId, 'AddReflectTemplatePromptPayload', data, subOptions) - return data - } -} - -export default addReflectTemplatePrompt diff --git a/packages/server/graphql/mutations/helpers/makeRetroTemplates.ts b/packages/server/graphql/mutations/helpers/makeRetroTemplates.ts index bd57b97b0c8..2c52d8eadff 100644 --- a/packages/server/graphql/mutations/helpers/makeRetroTemplates.ts +++ b/packages/server/graphql/mutations/helpers/makeRetroTemplates.ts @@ -1,5 +1,7 @@ +import {positionAfter} from '../../../../client/shared/sortOrder' import ReflectTemplate from '../../../database/types/ReflectTemplate' -import RetrospectivePrompt from '../../../database/types/RetrospectivePrompt' +import generateUID from '../../../generateUID' +import {ReflectPrompt} from '../../../postgres/types' import getTemplateIllustrationUrl from './getTemplateIllustrationUrl' interface TemplatePrompt { @@ -14,7 +16,7 @@ interface TemplateObject { } const makeRetroTemplates = (teamId: string, orgId: string, templateObj: TemplateObject) => { - const reflectPrompts: RetrospectivePrompt[] = [] + const reflectPrompts: ReflectPrompt[] = [] const templates: ReflectTemplate[] = [] Object.entries(templateObj).forEach(([templateName, promptBase]) => { const template = new ReflectTemplate({ @@ -25,18 +27,24 @@ const makeRetroTemplates = (teamId: string, orgId: string, templateObj: Template mainCategory: 'retrospective' }) - const prompts = promptBase.map( - (prompt, idx) => - new RetrospectivePrompt({ - teamId, - templateId: template.id, - sortOrder: idx, - question: prompt.question, - description: prompt.description, - groupColor: prompt.groupColor, - removedAt: null - }) - ) + let curSortOrder = positionAfter('') + const prompts = promptBase.map((prompt) => { + curSortOrder = positionAfter(curSortOrder) + return { + id: generateUID(), + teamId, + templateId: template.id, + sortOrder: curSortOrder, + question: prompt.question, + description: prompt.description, + groupColor: prompt.groupColor, + removedAt: null, + parentPromptId: null, + // can remove these after phase 3 + createdAt: new Date(), + updatedAt: new Date() + } + }) templates.push(template) reflectPrompts.push(...prompts) }) diff --git a/packages/server/graphql/mutations/moveReflectTemplatePrompt.ts b/packages/server/graphql/mutations/moveReflectTemplatePrompt.ts deleted file mode 100644 index 2a02c13f9d7..00000000000 --- a/packages/server/graphql/mutations/moveReflectTemplatePrompt.ts +++ /dev/null @@ -1,64 +0,0 @@ -import {GraphQLFloat, GraphQLID, GraphQLNonNull} from 'graphql' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import getKysely from '../../postgres/getKysely' -import {getUserId, isTeamMember} from '../../utils/authorization' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import MoveReflectTemplatePromptPayload from '../types/MoveReflectTemplatePromptPayload' - -const moveReflectTemplate = { - description: 'Move a reflect template', - type: MoveReflectTemplatePromptPayload, - args: { - promptId: { - type: new GraphQLNonNull(GraphQLID) - }, - sortOrder: { - type: new GraphQLNonNull(GraphQLFloat) - } - }, - async resolve( - _source: unknown, - {promptId, sortOrder}: {promptId: string; sortOrder: number}, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) { - const r = await getRethink() - const pg = getKysely() - const now = new Date() - const operationId = dataLoader.share() - const subOptions = {operationId, mutatorId} - const prompt = await r.table('ReflectPrompt').get(promptId).run() - const viewerId = getUserId(authToken) - - // AUTH - if (!prompt || prompt.removedAt) { - return standardError(new Error('Prompt not found'), {userId: viewerId}) - } - if (!isTeamMember(authToken, prompt.teamId)) { - return standardError(new Error('Team not found'), {userId: viewerId}) - } - - // RESOLUTION - const {teamId, templateId} = prompt - - await Promise.all([ - r - .table('ReflectPrompt') - .get(promptId) - .update({ - sortOrder, - updatedAt: now - }) - .run(), - pg.updateTable('MeetingTemplate').set({updatedAt: now}).where('id', '=', templateId).execute() - ]) - - const data = {promptId} - publish(SubscriptionChannel.TEAM, teamId, 'MoveReflectTemplatePromptPayload', data, subOptions) - return data - } -} - -export default moveReflectTemplate diff --git a/packages/server/graphql/mutations/reflectTemplatePromptUpdateDescription.ts b/packages/server/graphql/mutations/reflectTemplatePromptUpdateDescription.ts deleted file mode 100644 index 73254ea5522..00000000000 --- a/packages/server/graphql/mutations/reflectTemplatePromptUpdateDescription.ts +++ /dev/null @@ -1,72 +0,0 @@ -import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import getKysely from '../../postgres/getKysely' -import {getUserId, isTeamMember} from '../../utils/authorization' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import ReflectTemplatePromptUpdateDescriptionPayload from '../types/ReflectTemplatePromptUpdateDescriptionPayload' - -const reflectTemplatePromptUpdateDescription = { - description: 'Update the description of a reflection prompt', - type: ReflectTemplatePromptUpdateDescriptionPayload, - args: { - promptId: { - type: new GraphQLNonNull(GraphQLID) - }, - description: { - type: new GraphQLNonNull(GraphQLString) - } - }, - async resolve( - _source: unknown, - {promptId, description}: {promptId: string; description: string}, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) { - const r = await getRethink() - const pg = getKysely() - const now = new Date() - const operationId = dataLoader.share() - const subOptions = {operationId, mutatorId} - const prompt = await r.table('ReflectPrompt').get(promptId).run() - const viewerId = getUserId(authToken) - - // AUTH - if (!prompt || prompt.removedAt) { - return standardError(new Error('Prompt not found'), {userId: viewerId}) - } - if (!isTeamMember(authToken, prompt.teamId)) { - return standardError(new Error('Team not found'), {userId: viewerId}) - } - - // VALIDATION - const {teamId, templateId} = prompt - const normalizedDescription = description.trim().slice(0, 256) || '' - - // RESOLUTION - await Promise.all([ - r - .table('ReflectPrompt') - .get(promptId) - .update({ - description: normalizedDescription, - updatedAt: now - }) - .run(), - pg.updateTable('MeetingTemplate').set({updatedAt: now}).where('id', '=', templateId).execute() - ]) - - const data = {promptId} - publish( - SubscriptionChannel.TEAM, - teamId, - 'ReflectTemplatePromptUpdateDescriptionPayload', - data, - subOptions - ) - return data - } -} - -export default reflectTemplatePromptUpdateDescription diff --git a/packages/server/graphql/mutations/reflectTemplatePromptUpdateGroupColor.ts b/packages/server/graphql/mutations/reflectTemplatePromptUpdateGroupColor.ts deleted file mode 100644 index c7c397d1a99..00000000000 --- a/packages/server/graphql/mutations/reflectTemplatePromptUpdateGroupColor.ts +++ /dev/null @@ -1,72 +0,0 @@ -import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import getKysely from '../../postgres/getKysely' -import {getUserId, isTeamMember} from '../../utils/authorization' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import ReflectTemplatePromptUpdateGroupColorPayload from '../types/ReflectTemplatePromptUpdateGroupColorPayload' - -const reflectTemplatePromptUpdateGroupColor = { - groupColor: 'Update the groupColor of a reflection prompt', - type: ReflectTemplatePromptUpdateGroupColorPayload, - args: { - promptId: { - type: new GraphQLNonNull(GraphQLID) - }, - groupColor: { - type: new GraphQLNonNull(GraphQLString) - } - }, - async resolve( - _source: unknown, - {promptId, groupColor}: {promptId: string; groupColor: string}, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) { - const r = await getRethink() - const pg = getKysely() - const now = new Date() - const operationId = dataLoader.share() - const subOptions = {operationId, mutatorId} - const viewerId = getUserId(authToken) - - const prompt = await r.table('ReflectPrompt').get(promptId).run() - - // AUTH - if (!prompt || prompt.removedAt) { - return standardError(new Error('Prompt not found'), {userId: viewerId}) - } - if (!isTeamMember(authToken, prompt.teamId)) { - return standardError(new Error('Team not found'), {userId: viewerId}) - } - - // VALIDATION - const {teamId, templateId} = prompt - - // RESOLUTION - await Promise.all([ - r - .table('ReflectPrompt') - .get(promptId) - .update({ - groupColor, - updatedAt: now - }) - .run(), - pg.updateTable('MeetingTemplate').set({updatedAt: now}).where('id', '=', templateId).execute() - ]) - - const data = {promptId} - publish( - SubscriptionChannel.TEAM, - teamId, - 'ReflectTemplatePromptUpdateGroupColorPayload', - data, - subOptions - ) - return data - } -} - -export default reflectTemplatePromptUpdateGroupColor diff --git a/packages/server/graphql/mutations/removeReflectTemplate.ts b/packages/server/graphql/mutations/removeReflectTemplate.ts index 3357551a51a..14224dac4da 100644 --- a/packages/server/graphql/mutations/removeReflectTemplate.ts +++ b/packages/server/graphql/mutations/removeReflectTemplate.ts @@ -2,7 +2,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' -import removeMeetingTemplate from '../../postgres/queries/removeMeetingTemplate' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -23,6 +22,7 @@ const removeReflectTemplate = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -47,7 +47,6 @@ const removeReflectTemplate = { // RESOLUTION const {id: settingsId} = settings await Promise.all([ - removeMeetingTemplate(templateId), r .table('ReflectPrompt') .getAll(teamId, {index: 'teamId'}) @@ -58,9 +57,17 @@ const removeReflectTemplate = { removedAt: now, updatedAt: now }) - .run() + .run(), + pg + .with('RemoveTemplate', (qb) => + qb.updateTable('MeetingTemplate').set({isActive: false}).where('id', '=', templateId) + ) + .updateTable('ReflectPrompt') + .set({removedAt: now}) + .where('templateId', '=', templateId) + .execute() ]) - + dataLoader.clearAll('reflectPrompts') if (settings.selectedTemplateId === templateId) { const nextTemplate = templates.find((template) => template.id !== templateId) const nextTemplateId = nextTemplate?.id ?? 'workingStuckTemplate' diff --git a/packages/server/graphql/mutations/removeReflectTemplatePrompt.ts b/packages/server/graphql/mutations/removeReflectTemplatePrompt.ts deleted file mode 100644 index b0f2f1eebaf..00000000000 --- a/packages/server/graphql/mutations/removeReflectTemplatePrompt.ts +++ /dev/null @@ -1,82 +0,0 @@ -import {GraphQLID, GraphQLNonNull} from 'graphql' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import getKysely from '../../postgres/getKysely' -import {getUserId, isTeamMember} from '../../utils/authorization' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import RemoveReflectTemplatePromptPayload from '../types/RemoveReflectTemplatePromptPayload' - -const removeReflectTemplatePrompt = { - description: 'Remove a prompt from a template', - type: RemoveReflectTemplatePromptPayload, - args: { - promptId: { - type: new GraphQLNonNull(GraphQLID) - } - }, - async resolve( - _source: unknown, - {promptId}: {promptId: string}, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) { - const r = await getRethink() - const pg = getKysely() - const now = new Date() - const operationId = dataLoader.share() - const subOptions = {operationId, mutatorId} - const prompt = await r.table('ReflectPrompt').get(promptId).run() - const viewerId = getUserId(authToken) - - // AUTH - if (!prompt || prompt.removedAt) { - return standardError(new Error('Prompt not found'), {userId: viewerId}) - } - if (!isTeamMember(authToken, prompt.teamId)) { - return standardError(new Error('Team not found'), {userId: viewerId}) - } - - // VALIDATION - const {teamId, templateId} = prompt - const promptCount = await r - .table('ReflectPrompt') - .getAll(teamId, {index: 'teamId'}) - .filter({ - removedAt: null, - templateId: templateId - }) - .count() - .default(0) - .run() - - if (promptCount <= 1) { - return standardError(new Error('No prompts remain'), {userId: viewerId}) - } - - // RESOLUTION - await Promise.all([ - r - .table('ReflectPrompt') - .get(promptId) - .update({ - removedAt: now, - updatedAt: now - }) - .run(), - pg.updateTable('MeetingTemplate').set({updatedAt: now}).where('id', '=', templateId).execute() - ]) - - const data = {promptId, templateId} - publish( - SubscriptionChannel.TEAM, - teamId, - 'RemoveReflectTemplatePromptPayload', - data, - subOptions - ) - return data - } -} - -export default removeReflectTemplatePrompt diff --git a/packages/server/graphql/mutations/renameReflectTemplatePrompt.ts b/packages/server/graphql/mutations/renameReflectTemplatePrompt.ts deleted file mode 100644 index b1a74470ebc..00000000000 --- a/packages/server/graphql/mutations/renameReflectTemplatePrompt.ts +++ /dev/null @@ -1,85 +0,0 @@ -import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import getKysely from '../../postgres/getKysely' -import {getUserId, isTeamMember} from '../../utils/authorization' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import RenameReflectTemplatePromptPayload from '../types/RenameReflectTemplatePromptPayload' - -const renameReflectTemplatePrompt = { - description: 'Rename a reflect template prompt', - type: RenameReflectTemplatePromptPayload, - args: { - promptId: { - type: new GraphQLNonNull(GraphQLID) - }, - question: { - type: new GraphQLNonNull(GraphQLString) - } - }, - async resolve( - _source: unknown, - {promptId, question}: {promptId: string; question: string}, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) { - const r = await getRethink() - const pg = getKysely() - const now = new Date() - const operationId = dataLoader.share() - const subOptions = {operationId, mutatorId} - const prompt = await r.table('ReflectPrompt').get(promptId).run() - const viewerId = getUserId(authToken) - - // AUTH - if (!prompt || prompt.removedAt) { - return standardError(new Error('Prompt not found'), {userId: viewerId}) - } - if (!isTeamMember(authToken, prompt.teamId)) { - return standardError(new Error('Team not found'), {userId: viewerId}) - } - - // VALIDATION - const {teamId, templateId} = prompt - const trimmedQuestion = question.trim().slice(0, 100) - const normalizedQuestion = trimmedQuestion || 'Unnamed Prompt' - - const allPrompts = await r - .table('ReflectPrompt') - .getAll(teamId, {index: 'teamId'}) - .filter({ - removedAt: null, - templateId - }) - .run() - if (allPrompts.find((prompt) => prompt.question === normalizedQuestion)) { - return standardError(new Error('Duplicate question template'), {userId: viewerId}) - } - - // RESOLUTION - await Promise.all([ - r - .table('ReflectPrompt') - .get(promptId) - .update({ - question: normalizedQuestion, - updatedAt: now - }) - .run(), - pg.updateTable('MeetingTemplate').set({updatedAt: now}).where('id', '=', templateId).execute() - ]) - - const data = {promptId} - publish( - SubscriptionChannel.TEAM, - teamId, - 'RenameReflectTemplatePromptPayload', - data, - subOptions - ) - return data - } -} - -export default renameReflectTemplatePrompt diff --git a/packages/server/graphql/mutations/updateTemplateScope.ts b/packages/server/graphql/mutations/updateTemplateScope.ts index b11f778856d..1b7c7aeeca1 100644 --- a/packages/server/graphql/mutations/updateTemplateScope.ts +++ b/packages/server/graphql/mutations/updateTemplateScope.ts @@ -5,7 +5,6 @@ import {RDatum} from '../../database/stricterR' import {SharingScopeEnum as ESharingScope} from '../../database/types/MeetingTemplate' import PokerTemplate from '../../database/types/PokerTemplate' import ReflectTemplate from '../../database/types/ReflectTemplate' -import RetrospectivePrompt from '../../database/types/RetrospectivePrompt' import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' import {analytics} from '../../utils/analytics/analytics' @@ -89,12 +88,17 @@ const updateTemplateScope = { const activePrompts = prompts.filter(({removedAt}) => !removedAt) const promptIds = activePrompts.map(({id}) => id) const clonedPrompts = activePrompts.map((prompt) => { - return new RetrospectivePrompt({ - ...prompt, + return { + id: generateUID(), + teamId: prompt.teamId, templateId: clonedTemplateId!, parentPromptId: prompt.id, + sortOrder: prompt.sortOrder, + question: prompt.question, + description: prompt.description, + groupColor: prompt.groupColor, removedAt: null - }) + } }) await Promise.all([ pg @@ -103,7 +107,13 @@ const updateTemplateScope = { ) .with('MeetingTemplateDeactivate', (qc) => qc.updateTable('MeetingTemplate').set({isActive: false}).where('id', '=', templateId) - ), + ) + .with('RemovePrompts', (qc) => + qc.updateTable('ReflectPrompt').set({removedAt: now}).where('id', 'in', promptIds) + ) + .insertInto('ReflectPrompt') + .values(clonedPrompts.map((p) => ({...p, sortOrder: String(p.sortOrder)}))) + .execute(), r.table('ReflectPrompt').insert(clonedPrompts).run(), r.table('ReflectPrompt').getAll(r.args(promptIds)).update({removedAt: now}).run() ]) diff --git a/packages/server/graphql/public/mutations/addReflectTemplate.ts b/packages/server/graphql/public/mutations/addReflectTemplate.ts index 586a6e57f05..993abe9dc0f 100644 --- a/packages/server/graphql/public/mutations/addReflectTemplate.ts +++ b/packages/server/graphql/public/mutations/addReflectTemplate.ts @@ -2,9 +2,9 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {PALETTE} from '../../../../client/styles/paletteV3' import getRethink from '../../../database/rethinkDriver' import ReflectTemplate from '../../../database/types/ReflectTemplate' -import RetrospectivePrompt from '../../../database/types/RetrospectivePrompt' +import generateUID from '../../../generateUID' +import getKysely from '../../../postgres/getKysely' import decrementFreeTemplatesRemaining from '../../../postgres/queries/decrementFreeTemplatesRemaining' -import insertMeetingTemplate from '../../../postgres/queries/insertMeetingTemplate' import {analytics} from '../../../utils/analytics/analytics' import {getUserId, isTeamMember, isUserInOrg} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -18,6 +18,7 @@ const addPokerTemplate: MutationResolvers['addPokerTemplate'] = async ( {teamId, parentTemplateId}, {authToken, dataLoader, socketId: mutatorId} ) => { + const pg = getKysely() const r = await getRethink() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -76,20 +77,27 @@ const addPokerTemplate: MutationResolvers['addPokerTemplate'] = async ( mainCategory: parentTemplate.mainCategory }) const prompts = await dataLoader.get('reflectPromptsByTemplateId').load(parentTemplate.id) - const activePrompts = prompts.filter(({removedAt}: RetrospectivePrompt) => !removedAt) - const newTemplatePrompts = activePrompts.map((prompt: RetrospectivePrompt) => { - return new RetrospectivePrompt({ - ...prompt, + const activePrompts = prompts.filter(({removedAt}) => !removedAt) + const newTemplatePrompts = activePrompts.map((prompt) => { + return { + id: generateUID(), teamId, templateId: newTemplate.id, parentPromptId: prompt.id, + sortOrder: prompt.sortOrder, + question: prompt.question, + description: prompt.description, + groupColor: prompt.groupColor, removedAt: null - }) + } }) - await Promise.all([ r.table('ReflectPrompt').insert(newTemplatePrompts).run(), - insertMeetingTemplate(newTemplate), + pg + .with('MeetingTemplateInsert', (qc) => qc.insertInto('MeetingTemplate').values(newTemplate)) + .insertInto('ReflectPrompt') + .values(newTemplatePrompts.map((p) => ({...p, sortOrder: String(p.sortOrder)}))) + .execute(), decrementFreeTemplatesRemaining(viewerId, 'retro') ]) viewer.freeCustomRetroTemplatesRemaining = viewer.freeCustomRetroTemplatesRemaining - 1 @@ -113,8 +121,15 @@ const addPokerTemplate: MutationResolvers['addPokerTemplate'] = async ( const newTemplate = templates[0]! const {id: templateId} = newTemplate await Promise.all([ - r.table('ReflectPrompt').insert(newTemplatePrompts).run(), - insertMeetingTemplate(newTemplate), + r + .table('ReflectPrompt') + .insert(newTemplatePrompts.map((p, idx) => ({...p, sortOrder: idx}))) + .run(), + pg + .with('MeetingTemplateInsert', (qc) => qc.insertInto('MeetingTemplate').values(newTemplate)) + .insertInto('ReflectPrompt') + .values(newTemplatePrompts) + .execute(), decrementFreeTemplatesRemaining(viewerId, 'retro') ]) viewer.freeCustomRetroTemplatesRemaining = viewer.freeCustomRetroTemplatesRemaining - 1 diff --git a/packages/server/graphql/public/mutations/addReflectTemplatePrompt.ts b/packages/server/graphql/public/mutations/addReflectTemplatePrompt.ts new file mode 100644 index 00000000000..43de3440ea8 --- /dev/null +++ b/packages/server/graphql/public/mutations/addReflectTemplatePrompt.ts @@ -0,0 +1,75 @@ +import {SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' +import dndNoise from 'parabol-client/utils/dndNoise' +import {positionAfter} from '../../../../client/shared/sortOrder' +import palettePickerOptions from '../../../../client/styles/palettePickerOptions' +import {PALETTE} from '../../../../client/styles/paletteV3' +import getRethink from '../../../database/rethinkDriver' +import generateUID from '../../../generateUID' +import getKysely from '../../../postgres/getKysely' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const addReflectTemplatePrompt: MutationResolvers['addReflectTemplatePrompt'] = async ( + _source, + {templateId}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const r = await getRethink() + const pg = getKysely() + const operationId = dataLoader.share() + const subOptions = {operationId, mutatorId} + const template = await dataLoader.get('meetingTemplates').load(templateId) + const viewerId = getUserId(authToken) + + // AUTH + if (!template || !template.isActive) { + return standardError(new Error('Template not found'), {userId: viewerId}) + } + if (!isTeamMember(authToken, template.teamId)) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + + // VALIDATION + const {teamId} = template + const prompts = await dataLoader.get('reflectPromptsByTemplateId').load(templateId) + const activePrompts = prompts.filter(({removedAt}) => !removedAt) + + if (activePrompts.length >= Threshold.MAX_REFLECTION_PROMPTS) { + return standardError(new Error('Too many prompts'), {userId: viewerId}) + } + + // RESOLUTION + const lastPrompt = activePrompts.at(-1)! + const sortOrder = lastPrompt.sortOrder + 1 + dndNoise() + // can remove String coercion after ReflectPrompt is in PG + const pgSortOrder = positionAfter(String(lastPrompt.sortOrder)) + const pickedColors = activePrompts.map((prompt) => prompt.groupColor) + const availableNewColor = palettePickerOptions.find((color) => !pickedColors.includes(color.hex)) + const reflectPrompt = { + id: generateUID(), + templateId: template.id, + teamId: template.teamId, + sortOrder, + question: `New prompt #${activePrompts.length + 1}`, + description: '', + groupColor: availableNewColor?.hex ?? PALETTE.JADE_400, + removedAt: null + } + + await Promise.all([ + r.table('ReflectPrompt').insert(reflectPrompt).run(), + pg + .insertInto('ReflectPrompt') + .values({...reflectPrompt, sortOrder: pgSortOrder}) + .execute() + ]) + dataLoader.clearAll('reflectPrompts') + const promptId = reflectPrompt.id + const data = {promptId} + publish(SubscriptionChannel.TEAM, teamId, 'AddReflectTemplatePromptPayload', data, subOptions) + return data +} + +export default addReflectTemplatePrompt diff --git a/packages/server/graphql/public/mutations/moveReflectTemplatePrompt.ts b/packages/server/graphql/public/mutations/moveReflectTemplatePrompt.ts new file mode 100644 index 00000000000..f3a43d60055 --- /dev/null +++ b/packages/server/graphql/public/mutations/moveReflectTemplatePrompt.ts @@ -0,0 +1,62 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import {getSortOrder} from '../../../../client/shared/sortOrder' +import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const moveReflectTemplatePrompt: MutationResolvers['moveReflectTemplatePrompt'] = async ( + _source, + {promptId, sortOrder}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const r = await getRethink() + const pg = getKysely() + const now = new Date() + const operationId = dataLoader.share() + const subOptions = {operationId, mutatorId} + const prompt = await dataLoader.get('reflectPrompts').load(promptId) + const viewerId = getUserId(authToken) + + // AUTH + if (!prompt || prompt.removedAt) { + return standardError(new Error('Prompt not found'), {userId: viewerId}) + } + if (!isTeamMember(authToken, prompt.teamId)) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + + // RESOLUTION + const {teamId} = prompt + + const oldPrompts = await dataLoader.get('reflectPromptsByTemplateId').load(prompt.templateId) + const fromIdx = oldPrompts.findIndex((p) => p.id === promptId) + + await Promise.all([ + r + .table('ReflectPrompt') + .get(promptId) + .update({ + sortOrder, + updatedAt: now + }) + .run() + ]) + dataLoader.clearAll('reflectPrompts') + const newPrompts = await dataLoader.get('reflectPromptsByTemplateId').load(prompt.templateId) + const pgPrompts = await dataLoader.get('_pgreflectPromptsByTemplateId').load(prompt.templateId) + const toIdx = newPrompts.findIndex((p) => p.id === promptId) + const pgSortOrder = getSortOrder(pgPrompts, fromIdx, toIdx) + await pg + .updateTable('ReflectPrompt') + .set({sortOrder: pgSortOrder}) + .where('id', '=', promptId) + .execute() + const data = {promptId} + publish(SubscriptionChannel.TEAM, teamId, 'MoveReflectTemplatePromptPayload', data, subOptions) + return data +} + +export default moveReflectTemplatePrompt diff --git a/packages/server/graphql/public/mutations/reflectTemplatePromptUpdateDescription.ts b/packages/server/graphql/public/mutations/reflectTemplatePromptUpdateDescription.ts new file mode 100644 index 00000000000..5fc36d0fb48 --- /dev/null +++ b/packages/server/graphql/public/mutations/reflectTemplatePromptUpdateDescription.ts @@ -0,0 +1,59 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const reflectTemplatePromptUpdateDescription: MutationResolvers['reflectTemplatePromptUpdateDescription'] = + async (_source, {promptId, description}, {authToken, dataLoader, socketId: mutatorId}) => { + const r = await getRethink() + const pg = getKysely() + const now = new Date() + const operationId = dataLoader.share() + const subOptions = {operationId, mutatorId} + const prompt = await dataLoader.get('reflectPrompts').load(promptId) + const viewerId = getUserId(authToken) + + // AUTH + if (!prompt || prompt.removedAt) { + return standardError(new Error('Prompt not found'), {userId: viewerId}) + } + if (!isTeamMember(authToken, prompt.teamId)) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + + // VALIDATION + const {teamId} = prompt + const normalizedDescription = description.trim().slice(0, 256) || '' + + // RESOLUTION + await Promise.all([ + r + .table('ReflectPrompt') + .get(promptId) + .update({ + description: normalizedDescription, + updatedAt: now + }) + .run(), + pg + .updateTable('ReflectPrompt') + .set({description: normalizedDescription}) + .where('id', '=', promptId) + .execute() + ]) + dataLoader.clearAll('reflectPrompts') + const data = {promptId} + publish( + SubscriptionChannel.TEAM, + teamId, + 'ReflectTemplatePromptUpdateDescriptionPayload', + data, + subOptions + ) + return data + } + +export default reflectTemplatePromptUpdateDescription diff --git a/packages/server/graphql/public/mutations/reflectTemplatePromptUpdateGroupColor.ts b/packages/server/graphql/public/mutations/reflectTemplatePromptUpdateGroupColor.ts new file mode 100644 index 00000000000..f6de5ffc8f2 --- /dev/null +++ b/packages/server/graphql/public/mutations/reflectTemplatePromptUpdateGroupColor.ts @@ -0,0 +1,55 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const reflectTemplatePromptUpdateGroupColor: MutationResolvers['reflectTemplatePromptUpdateGroupColor'] = + async (_source, {promptId, groupColor}, {authToken, dataLoader, socketId: mutatorId}) => { + const r = await getRethink() + const pg = getKysely() + const now = new Date() + const operationId = dataLoader.share() + const subOptions = {operationId, mutatorId} + const viewerId = getUserId(authToken) + + const prompt = await dataLoader.get('reflectPrompts').load(promptId) + + // AUTH + if (!prompt || prompt.removedAt) { + return standardError(new Error('Prompt not found'), {userId: viewerId}) + } + if (!isTeamMember(authToken, prompt.teamId)) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + + // VALIDATION + const {teamId} = prompt + + // RESOLUTION + await Promise.all([ + r + .table('ReflectPrompt') + .get(promptId) + .update({ + groupColor, + updatedAt: now + }) + .run(), + pg.updateTable('ReflectPrompt').set({groupColor}).where('id', '=', promptId).execute() + ]) + dataLoader.clearAll('reflectPrompts') + const data = {promptId} + publish( + SubscriptionChannel.TEAM, + teamId, + 'ReflectTemplatePromptUpdateGroupColorPayload', + data, + subOptions + ) + return data + } + +export default reflectTemplatePromptUpdateGroupColor diff --git a/packages/server/graphql/public/mutations/removeReflectTemplatePrompt.ts b/packages/server/graphql/public/mutations/removeReflectTemplatePrompt.ts new file mode 100644 index 00000000000..c74b0b48b0a --- /dev/null +++ b/packages/server/graphql/public/mutations/removeReflectTemplatePrompt.ts @@ -0,0 +1,58 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const removeReflectTemplatePrompt: MutationResolvers['removeReflectTemplatePrompt'] = async ( + _source, + {promptId}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const r = await getRethink() + const pg = getKysely() + const now = new Date() + const operationId = dataLoader.share() + const subOptions = {operationId, mutatorId} + const prompt = await dataLoader.get('reflectPrompts').load(promptId) + const viewerId = getUserId(authToken) + + // AUTH + if (!prompt || prompt.removedAt) { + return standardError(new Error('Prompt not found'), {userId: viewerId}) + } + if (!isTeamMember(authToken, prompt.teamId)) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + + // VALIDATION + const {teamId, templateId} = prompt + const prompts = await dataLoader.get('reflectPromptsByTemplateId').load(templateId) + const activePrompts = prompts.filter((p) => !p.removedAt) + const promptCount = activePrompts.length + + if (promptCount <= 1) { + return standardError(new Error('No prompts remain'), {userId: viewerId}) + } + + // RESOLUTION + await Promise.all([ + r + .table('ReflectPrompt') + .get(promptId) + .update({ + removedAt: now, + updatedAt: now + }) + .run(), + pg.updateTable('ReflectPrompt').set({removedAt: now}).where('id', '=', promptId).execute() + ]) + dataLoader.clearAll('reflectPrompts') + const data = {promptId, templateId} + publish(SubscriptionChannel.TEAM, teamId, 'RemoveReflectTemplatePromptPayload', data, subOptions) + return data +} + +export default removeReflectTemplatePrompt diff --git a/packages/server/graphql/public/mutations/renameReflectTemplatePrompt.ts b/packages/server/graphql/public/mutations/renameReflectTemplatePrompt.ts new file mode 100644 index 00000000000..426901b79e6 --- /dev/null +++ b/packages/server/graphql/public/mutations/renameReflectTemplatePrompt.ts @@ -0,0 +1,63 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const renameReflectTemplatePrompt: MutationResolvers['renameReflectTemplatePrompt'] = async ( + _source, + {promptId, question}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const r = await getRethink() + const pg = getKysely() + const now = new Date() + const operationId = dataLoader.share() + const subOptions = {operationId, mutatorId} + const prompt = await dataLoader.get('reflectPrompts').load(promptId) + const viewerId = getUserId(authToken) + + // AUTH + if (!prompt || prompt.removedAt) { + return standardError(new Error('Prompt not found'), {userId: viewerId}) + } + if (!isTeamMember(authToken, prompt.teamId)) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + + // VALIDATION + const {teamId, templateId} = prompt + const trimmedQuestion = question.trim().slice(0, 100) + const normalizedQuestion = trimmedQuestion || 'Unnamed Prompt' + + const prompts = await dataLoader.get('reflectPromptsByTemplateId').load(templateId) + const allPrompts = prompts.filter(({removedAt}) => !removedAt) + if (allPrompts.find((prompt) => prompt.question === normalizedQuestion)) { + return standardError(new Error('Duplicate question template'), {userId: viewerId}) + } + + // RESOLUTION + await Promise.all([ + r + .table('ReflectPrompt') + .get(promptId) + .update({ + question: normalizedQuestion, + updatedAt: now + }) + .run(), + pg + .updateTable('ReflectPrompt') + .set({question: normalizedQuestion}) + .where('id', '=', promptId) + .execute() + ]) + dataLoader.clearAll('reflectPrompts') + const data = {promptId} + publish(SubscriptionChannel.TEAM, teamId, 'RenameReflectTemplatePromptPayload', data, subOptions) + return data +} + +export default renameReflectTemplatePrompt diff --git a/packages/server/graphql/public/typeDefs/Mutation.graphql b/packages/server/graphql/public/typeDefs/Mutation.graphql index 0a58cdfde64..1ff66c7adb6 100644 --- a/packages/server/graphql/public/typeDefs/Mutation.graphql +++ b/packages/server/graphql/public/typeDefs/Mutation.graphql @@ -41,7 +41,7 @@ type Mutation { """ Add a new template full of prompts """ - addReflectTemplatePrompt(templateId: ID!): AddReflectTemplatePromptPayload + addReflectTemplatePrompt(templateId: ID!): AddReflectTemplatePromptPayload! addSlackAuth(code: ID!, teamId: ID!): AddSlackAuthPayload! addGitHubAuth(code: ID!, teamId: ID!): AddGitHubAuthPayload! diff --git a/packages/server/graphql/public/types/AddReflectTemplatePromptPayload.ts b/packages/server/graphql/public/types/AddReflectTemplatePromptPayload.ts new file mode 100644 index 00000000000..59482f4f743 --- /dev/null +++ b/packages/server/graphql/public/types/AddReflectTemplatePromptPayload.ts @@ -0,0 +1,15 @@ +import {AddReflectTemplatePromptPayloadResolvers} from '../resolverTypes' + +export type AddReflectTemplatePromptPayloadSource = + | { + promptId: string + } + | {error: {message: string}} + +const AddReflectTemplatePromptPayload: AddReflectTemplatePromptPayloadResolvers = { + prompt: (source, _args, {dataLoader}) => { + return 'promptId' in source ? dataLoader.get('reflectPrompts').load(source.promptId) : null + } +} + +export default AddReflectTemplatePromptPayload diff --git a/packages/server/graphql/public/types/MoveReflectTemplatePromptPayload.ts b/packages/server/graphql/public/types/MoveReflectTemplatePromptPayload.ts new file mode 100644 index 00000000000..87096f79f55 --- /dev/null +++ b/packages/server/graphql/public/types/MoveReflectTemplatePromptPayload.ts @@ -0,0 +1,15 @@ +import {MoveReflectTemplatePromptPayloadResolvers} from '../resolverTypes' + +export type MoveReflectTemplatePromptPayloadSource = + | { + promptId: string + } + | {error: {message: string}} + +const MoveReflectTemplatePromptPayload: MoveReflectTemplatePromptPayloadResolvers = { + prompt: (source, _args, {dataLoader}) => { + return 'promptId' in source ? dataLoader.get('reflectPrompts').load(source.promptId) : null + } +} + +export default MoveReflectTemplatePromptPayload diff --git a/packages/server/graphql/public/types/ReflectPhase.ts b/packages/server/graphql/public/types/ReflectPhase.ts index 9943e8fac56..fb5bad8671e 100644 --- a/packages/server/graphql/public/types/ReflectPhase.ts +++ b/packages/server/graphql/public/types/ReflectPhase.ts @@ -1,5 +1,4 @@ import MeetingRetrospective from '../../../database/types/MeetingRetrospective' -import RetrospectivePrompt from '../../../database/types/RetrospectivePrompt' import {ReflectPhaseResolvers} from '../resolverTypes' const ReflectPhase: ReflectPhaseResolvers = { @@ -15,7 +14,7 @@ const ReflectPhase: ReflectPhaseResolvers = { // only show prompts that were created before the meeting and // either have not been removed or they were removed after the meeting was created return prompts.filter( - (prompt: RetrospectivePrompt) => + (prompt) => prompt.createdAt < meeting.createdAt && (!prompt.removedAt || meeting.createdAt < prompt.removedAt) ) diff --git a/packages/server/graphql/public/types/ReflectTemplatePromptUpdateDescriptionPayload.ts b/packages/server/graphql/public/types/ReflectTemplatePromptUpdateDescriptionPayload.ts new file mode 100644 index 00000000000..ab49737cd6e --- /dev/null +++ b/packages/server/graphql/public/types/ReflectTemplatePromptUpdateDescriptionPayload.ts @@ -0,0 +1,16 @@ +import {ReflectTemplatePromptUpdateDescriptionPayloadResolvers} from '../resolverTypes' + +export type ReflectTemplatePromptUpdateDescriptionPayloadSource = + | { + promptId: string + } + | {error: {message: string}} + +const ReflectTemplatePromptUpdateDescriptionPayload: ReflectTemplatePromptUpdateDescriptionPayloadResolvers = + { + prompt: (source, _args, {dataLoader}) => { + return 'promptId' in source ? dataLoader.get('reflectPrompts').load(source.promptId) : null + } + } + +export default ReflectTemplatePromptUpdateDescriptionPayload diff --git a/packages/server/graphql/public/types/ReflectTemplatePromptUpdateGroupColorPayload.ts b/packages/server/graphql/public/types/ReflectTemplatePromptUpdateGroupColorPayload.ts new file mode 100644 index 00000000000..c964750561f --- /dev/null +++ b/packages/server/graphql/public/types/ReflectTemplatePromptUpdateGroupColorPayload.ts @@ -0,0 +1,16 @@ +import {ReflectTemplatePromptUpdateGroupColorPayloadResolvers} from '../resolverTypes' + +export type ReflectTemplatePromptUpdateGroupColorPayloadSource = + | { + promptId: string + } + | {error: {message: string}} + +const ReflectTemplatePromptUpdateGroupColorPayload: ReflectTemplatePromptUpdateGroupColorPayloadResolvers = + { + prompt: (source, _args, {dataLoader}) => { + return 'promptId' in source ? dataLoader.get('reflectPrompts').load(source.promptId) : null + } + } + +export default ReflectTemplatePromptUpdateGroupColorPayload diff --git a/packages/server/graphql/public/types/RemoveReflectTemplatePromptPayload.ts b/packages/server/graphql/public/types/RemoveReflectTemplatePromptPayload.ts new file mode 100644 index 00000000000..8e81e655db0 --- /dev/null +++ b/packages/server/graphql/public/types/RemoveReflectTemplatePromptPayload.ts @@ -0,0 +1,22 @@ +import {RemoveReflectTemplatePromptPayloadResolvers} from '../resolverTypes' + +export type RemoveReflectTemplatePromptPayloadSource = + | { + promptId: string + templateId: string + } + | {error: {message: string}} + +const RemoveReflectTemplatePromptPayload: RemoveReflectTemplatePromptPayloadResolvers = { + reflectTemplate: (source, _args, {dataLoader}) => { + return 'templateId' in source + ? dataLoader.get('meetingTemplates').loadNonNull(source.templateId) + : null + }, + + prompt: (source, _args, {dataLoader}) => { + return 'promptId' in source ? dataLoader.get('reflectPrompts').load(source.promptId) : null + } +} + +export default RemoveReflectTemplatePromptPayload diff --git a/packages/server/graphql/public/types/RenameReflectTemplatePromptPayload.ts b/packages/server/graphql/public/types/RenameReflectTemplatePromptPayload.ts new file mode 100644 index 00000000000..980ad0ac390 --- /dev/null +++ b/packages/server/graphql/public/types/RenameReflectTemplatePromptPayload.ts @@ -0,0 +1,15 @@ +import {RenameReflectTemplatePromptPayloadResolvers} from '../resolverTypes' + +export type RenameReflectTemplatePromptPayloadSource = + | { + promptId: string + } + | {error: {message: string}} + +const RenameReflectTemplatePromptPayload: RenameReflectTemplatePromptPayloadResolvers = { + prompt: (source, _args, {dataLoader}) => { + return 'promptId' in source ? dataLoader.get('reflectPrompts').load(source.promptId) : null + } +} + +export default RenameReflectTemplatePromptPayload diff --git a/packages/server/graphql/rootMutation.ts b/packages/server/graphql/rootMutation.ts index ab88c2e03ba..918463c1dae 100644 --- a/packages/server/graphql/rootMutation.ts +++ b/packages/server/graphql/rootMutation.ts @@ -7,7 +7,6 @@ import addOrg from './mutations/addOrg' import addPokerTemplateDimension from './mutations/addPokerTemplateDimension' import addPokerTemplateScale from './mutations/addPokerTemplateScale' import addPokerTemplateScaleValue from './mutations/addPokerTemplateScaleValue' -import addReflectTemplatePrompt from './mutations/addReflectTemplatePrompt' import addTeam from './mutations/addTeam' import archiveOrganization from './mutations/archiveOrganization' import archiveTeam from './mutations/archiveTeam' @@ -41,7 +40,6 @@ import inviteToTeam from './mutations/inviteToTeam' import joinMeeting from './mutations/joinMeeting' import movePokerTemplateDimension from './mutations/movePokerTemplateDimension' import movePokerTemplateScaleValue from './mutations/movePokerTemplateScaleValue' -import moveReflectTemplatePrompt from './mutations/moveReflectTemplatePrompt' import moveTeamToOrg from './mutations/moveTeamToOrg' import navigateMeeting from './mutations/navigateMeeting' import newMeetingCheckIn from './mutations/newMeetingCheckIn' @@ -57,8 +55,6 @@ import pokerTemplateDimensionUpdateDescription from './mutations/pokerTemplateDi import promoteNewMeetingFacilitator from './mutations/promoteNewMeetingFacilitator' import promoteToTeamLead from './mutations/promoteToTeamLead' import pushInvitation from './mutations/pushInvitation' -import reflectTemplatePromptUpdateDescription from './mutations/reflectTemplatePromptUpdateDescription' -import reflectTemplatePromptUpdateGroupColor from './mutations/reflectTemplatePromptUpdateGroupColor' import removeAtlassianAuth from './mutations/removeAtlassianAuth' import removeGitHubAuth from './mutations/removeGitHubAuth' import removeIntegrationProvider from './mutations/removeIntegrationProvider' @@ -67,7 +63,6 @@ import removePokerTemplateDimension from './mutations/removePokerTemplateDimensi import removePokerTemplateScale from './mutations/removePokerTemplateScale' import removePokerTemplateScaleValue from './mutations/removePokerTemplateScaleValue' import removeReflectTemplate from './mutations/removeReflectTemplate' -import removeReflectTemplatePrompt from './mutations/removeReflectTemplatePrompt' import removeReflection from './mutations/removeReflection' import removeSlackAuth from './mutations/removeSlackAuth' import removeTeamMember from './mutations/removeTeamMember' @@ -75,7 +70,6 @@ import renameMeeting from './mutations/renameMeeting' import renameMeetingTemplate from './mutations/renameMeetingTemplate' import renamePokerTemplateDimension from './mutations/renamePokerTemplateDimension' import renamePokerTemplateScale from './mutations/renamePokerTemplateScale' -import renameReflectTemplatePrompt from './mutations/renameReflectTemplatePrompt' import resetPassword from './mutations/resetPassword' import resetRetroMeetingToGroupStage from './mutations/resetRetroMeetingToGroupStage' import selectTemplate from './mutations/selectTemplate' @@ -114,7 +108,6 @@ export default new GraphQLObjectType({ addPokerTemplateDimension, addPokerTemplateScale, addPokerTemplateScaleValue, - addReflectTemplatePrompt, addGitHubAuth, addOrg, addTeam, @@ -148,7 +141,6 @@ export default new GraphQLObjectType({ invalidateSessions, inviteToTeam, movePokerTemplateDimension, - moveReflectTemplatePrompt, moveTeamToOrg, navigateMeeting, newMeetingCheckIn, @@ -157,18 +149,14 @@ export default new GraphQLObjectType({ pushInvitation, promoteNewMeetingFacilitator, promoteToTeamLead, - reflectTemplatePromptUpdateDescription, pokerTemplateDimensionUpdateDescription, - reflectTemplatePromptUpdateGroupColor, removeAtlassianAuth, removeGitHubAuth, removeOrgUser, removeReflectTemplate, - removeReflectTemplatePrompt, removePokerTemplateDimension, renameMeeting, renameMeetingTemplate, - renameReflectTemplatePrompt, renamePokerTemplateDimension, renamePokerTemplateScale, removePokerTemplateScale, diff --git a/packages/server/graphql/types/AddReflectTemplatePromptPayload.ts b/packages/server/graphql/types/AddReflectTemplatePromptPayload.ts deleted file mode 100644 index 9061c731a8b..00000000000 --- a/packages/server/graphql/types/AddReflectTemplatePromptPayload.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import ReflectPrompt from './ReflectPrompt' -import StandardMutationError from './StandardMutationError' - -const AddReflectTemplatePromptPayload = new GraphQLObjectType({ - name: 'AddReflectTemplatePromptPayload', - fields: () => ({ - error: { - type: StandardMutationError - }, - prompt: { - type: ReflectPrompt, - resolve: ({promptId}, _args: unknown, {dataLoader}) => { - if (!promptId) return null - return dataLoader.get('reflectPrompts').load(promptId) - } - } - }) -}) - -export default AddReflectTemplatePromptPayload diff --git a/packages/server/graphql/types/MoveReflectTemplatePromptPayload.ts b/packages/server/graphql/types/MoveReflectTemplatePromptPayload.ts deleted file mode 100644 index c6d4ea310e7..00000000000 --- a/packages/server/graphql/types/MoveReflectTemplatePromptPayload.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import ReflectPrompt from './ReflectPrompt' -import StandardMutationError from './StandardMutationError' - -const MoveReflectTemplatePromptPayload = new GraphQLObjectType({ - name: 'MoveReflectTemplatePromptPayload', - fields: () => ({ - error: { - type: StandardMutationError - }, - prompt: { - type: ReflectPrompt, - resolve: ({promptId}, _args: unknown, {dataLoader}) => { - if (!promptId) return null - return dataLoader.get('reflectPrompts').load(promptId) - } - } - }) -}) - -export default MoveReflectTemplatePromptPayload diff --git a/packages/server/graphql/types/ReflectTemplatePromptUpdateDescriptionPayload.ts b/packages/server/graphql/types/ReflectTemplatePromptUpdateDescriptionPayload.ts deleted file mode 100644 index 4e27cbaa23c..00000000000 --- a/packages/server/graphql/types/ReflectTemplatePromptUpdateDescriptionPayload.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import ReflectPrompt from './ReflectPrompt' -import StandardMutationError from './StandardMutationError' - -const ReflectTemplatePromptUpdateDescriptionPayload = new GraphQLObjectType({ - name: 'ReflectTemplatePromptUpdateDescriptionPayload', - fields: () => ({ - error: { - type: StandardMutationError - }, - prompt: { - type: ReflectPrompt, - resolve: ({promptId}, _args: unknown, {dataLoader}) => { - if (!promptId) return null - return dataLoader.get('reflectPrompts').load(promptId) - } - } - }) -}) - -export default ReflectTemplatePromptUpdateDescriptionPayload diff --git a/packages/server/graphql/types/ReflectTemplatePromptUpdateGroupColorPayload.ts b/packages/server/graphql/types/ReflectTemplatePromptUpdateGroupColorPayload.ts deleted file mode 100644 index 6efd27f0c8c..00000000000 --- a/packages/server/graphql/types/ReflectTemplatePromptUpdateGroupColorPayload.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import ReflectPrompt from './ReflectPrompt' -import StandardMutationError from './StandardMutationError' - -const ReflectTemplatePromptUpdateGroupColorPayload = new GraphQLObjectType({ - name: 'ReflectTemplatePromptUpdateGroupColorPayload', - fields: () => ({ - error: { - type: StandardMutationError - }, - prompt: { - type: ReflectPrompt, - resolve: ({promptId}, _args: unknown, {dataLoader}) => { - if (!promptId) return null - return dataLoader.get('reflectPrompts').load(promptId) - } - } - }) -}) - -export default ReflectTemplatePromptUpdateGroupColorPayload diff --git a/packages/server/graphql/types/RemoveReflectTemplatePromptPayload.ts b/packages/server/graphql/types/RemoveReflectTemplatePromptPayload.ts deleted file mode 100644 index 8c66ecd729e..00000000000 --- a/packages/server/graphql/types/RemoveReflectTemplatePromptPayload.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import ReflectPrompt from './ReflectPrompt' -import ReflectTemplate from './ReflectTemplate' -import StandardMutationError from './StandardMutationError' - -const RemoveReflectTemplatePromptPayload = new GraphQLObjectType({ - name: 'RemoveReflectTemplatePromptPayload', - fields: () => ({ - error: { - type: StandardMutationError - }, - reflectTemplate: { - type: ReflectTemplate, - resolve: ({templateId}, _args: unknown, {dataLoader}) => { - if (!templateId) return null - return dataLoader.get('meetingTemplates').load(templateId) - } - }, - prompt: { - type: ReflectPrompt, - resolve: ({promptId}, _args: unknown, {dataLoader}) => { - if (!promptId) return null - return dataLoader.get('reflectPrompts').load(promptId) - } - } - }) -}) - -export default RemoveReflectTemplatePromptPayload diff --git a/packages/server/graphql/types/RenameReflectTemplatePromptPayload.ts b/packages/server/graphql/types/RenameReflectTemplatePromptPayload.ts deleted file mode 100644 index 0fe41ac3764..00000000000 --- a/packages/server/graphql/types/RenameReflectTemplatePromptPayload.ts +++ /dev/null @@ -1,22 +0,0 @@ -import {GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import ReflectPrompt from './ReflectPrompt' -import StandardMutationError from './StandardMutationError' - -const RenameReflectTemplatePromptPayload = new GraphQLObjectType({ - name: 'RenameReflectTemplatePromptPayload', - fields: () => ({ - error: { - type: StandardMutationError - }, - prompt: { - type: ReflectPrompt, - resolve: ({promptId}, _args: unknown, {dataLoader}) => { - if (!promptId) return null - return dataLoader.get('reflectPrompts').load(promptId) - } - } - }) -}) - -export default RenameReflectTemplatePromptPayload diff --git a/packages/server/postgres/migrations/1725655687704_ReflectPrompt-phase1.ts b/packages/server/postgres/migrations/1725655687704_ReflectPrompt-phase1.ts new file mode 100644 index 00000000000..425985da005 --- /dev/null +++ b/packages/server/postgres/migrations/1725655687704_ReflectPrompt-phase1.ts @@ -0,0 +1,56 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS "ReflectPrompt" ( + "id" VARCHAR(100) PRIMARY KEY, + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "removedAt" TIMESTAMP WITH TIME ZONE, + "description" VARCHAR(256) NOT NULL, + "groupColor" VARCHAR(9) NOT NULL, + "sortOrder" VARCHAR(64) NOT NULL COLLATE "C", + "question" VARCHAR(100) NOT NULL, + "teamId" VARCHAR(100) NOT NULL, + "templateId" VARCHAR(100) NOT NULL, + "parentPromptId" VARCHAR(100), + CONSTRAINT "fk_teamId" + FOREIGN KEY("teamId") + REFERENCES "Team"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_templateId" + FOREIGN KEY("templateId") + REFERENCES "MeetingTemplate"("id") + ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS "idx_ReflectPrompt_teamId" ON "ReflectPrompt"("teamId"); + CREATE INDEX IF NOT EXISTS "idx_ReflectPrompt_templateId" ON "ReflectPrompt"("templateId"); + CREATE INDEX IF NOT EXISTS "idx_ReflectPrompt_parentPromptId" ON "ReflectPrompt"("templateId"); + CREATE OR REPLACE TRIGGER "update_MeetingTemplate_updatedAt_from_ReflectPrompt" + AFTER INSERT OR UPDATE OR DELETE ON "ReflectPrompt" + FOR EACH ROW + EXECUTE FUNCTION "set_MeetingTemplate_updatedAt"(); + END $$; +`) + // TODO add constraint parentPromptId constraint + // CONSTRAINT "fk_parentPromptId" + // FOREIGN KEY("parentPromptId") + // REFERENCES "MeetingTemplate"("id") + // ON DELETE CASCADE + + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE IF EXISTS "ReflectPrompt"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/queries/insertMeetingTemplate.ts b/packages/server/postgres/queries/insertMeetingTemplate.ts deleted file mode 100644 index 78e06f2b53b..00000000000 --- a/packages/server/postgres/queries/insertMeetingTemplate.ts +++ /dev/null @@ -1,39 +0,0 @@ -import MeetingTemplate from '../../database/types/MeetingTemplate' -import getPg from '../getPg' - -const insertMeetingTemplate = async (meetingTemplate: MeetingTemplate) => { - const pg = getPg() - const { - id, - name, - teamId, - orgId, - parentTemplateId, - type, - scope, - lastUsedAt, - isStarter, - isFree, - mainCategory, - illustrationUrl - } = meetingTemplate - await pg.query( - `INSERT INTO "MeetingTemplate" (id, name, "teamId", "orgId", "parentTemplateId", type, scope, "lastUsedAt", "isStarter", "isFree", "mainCategory", "illustrationUrl") VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)`, - [ - id, - name, - teamId, - orgId, - parentTemplateId, - type, - scope, - lastUsedAt, - isStarter, - isFree, - mainCategory, - illustrationUrl - ] - ) -} - -export default insertMeetingTemplate diff --git a/packages/server/postgres/queries/removeMeetingTemplate.ts b/packages/server/postgres/queries/removeMeetingTemplate.ts deleted file mode 100644 index 8793f726ff6..00000000000 --- a/packages/server/postgres/queries/removeMeetingTemplate.ts +++ /dev/null @@ -1,8 +0,0 @@ -import getPg from '../getPg' - -const removeMeetingTemplate = async (templateId: string) => { - const pg = getPg() - await pg.query(`UPDATE "MeetingTemplate" SET "isActive" = FALSE WHERE id = $1;`, [templateId]) -} - -export default removeMeetingTemplate diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index f97275eb80b..affc2e06e00 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -228,3 +228,5 @@ export const selectComments = () => 'threadSortOrder' ]) .select(({fn}) => [fn('to_json', ['reactjis']).as('reactjis')]) + +export const selectReflectPrompts = () => getKysely().selectFrom('ReflectPrompt').selectAll() diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index 48fb2419996..affb35215d4 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -9,6 +9,7 @@ import { selectComments, selectMeetingSettings, selectOrganizations, + selectReflectPrompts, selectRetroReflections, selectSlackAuths, selectSlackNotifications, @@ -56,3 +57,4 @@ export type SlackAuth = ExtractTypeFromQueryBuilderSelect export type Comment = ExtractTypeFromQueryBuilderSelect +export type ReflectPrompt = ExtractTypeFromQueryBuilderSelect From 2952c3d88b60aa3ebc6001ecd10eca10357b7570 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 11 Sep 2024 10:57:26 -0700 Subject: [PATCH 457/529] fix: move to rrule-rust (#10181) Signed-off-by: Matt Krick --- .github/workflows/test.yml | 2 +- codegen.json | 4 +- package.json | 3 +- packages/client/components/AnalyticsPage.tsx | 22 +- .../Recurrence/RecurrenceSettings.tsx | 25 +- .../Recurrence/RecurrenceTimePicker.tsx | 9 +- .../UpdateRecurrenceSettingsModal.tsx | 2 +- packages/client/jest.config.js | 7 +- .../components/ProviderList/ProviderList.tsx | 20 +- .../client/shared/__tests__/rruleUtil.test.ts | 32 ++ packages/client/shared/rruleUtil.ts | 46 ++- .../__tests__/processRecurrence.test.ts | 100 ++---- packages/server/database/types/Meeting.ts | 2 +- .../database/types/MeetingRetrospective.ts | 2 +- .../mutations/helpers/createGcalEvent.ts | 17 +- .../helpers/safeCreateRetrospective.ts | 2 +- .../private/mutations/processRecurrence.ts | 48 +-- .../mutations/updateRecurrenceSettings.ts | 42 +-- packages/server/graphql/public/types/RRule.ts | 66 ++-- .../public/types/__tests__/RRule.test.ts | 104 ++---- packages/server/jest.config.js | 11 +- packages/server/package.json | 2 +- .../1676020597201_scheduledJobOrgIdIndex.ts | 4 +- packages/server/utils/getNextRRuleDate.ts | 12 + yarn.lock | 332 ++++++++++++------ 25 files changed, 469 insertions(+), 447 deletions(-) create mode 100644 packages/client/shared/__tests__/rruleUtil.test.ts create mode 100644 packages/server/utils/getNextRRuleDate.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 895c724e44c..ba531a63838 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -131,7 +131,7 @@ jobs: - name: Store Artifacts from Failed Tests if: failure() - uses: actions/upload-artifact@v2 + uses: actions/upload-artifact@v4 with: name: test-results path: packages/integration-tests/test-results/ diff --git a/codegen.json b/codegen.json index 5774ea539d6..b64f821cd74 100644 --- a/codegen.json +++ b/codegen.json @@ -4,7 +4,7 @@ "defaultScalarType": "string", "enumsAsTypes": true, "optionalResolveType": true, - "scalars": {"DateTime": "Date", "File": "TFile", "RRule": "RRule"} + "scalars": {"DateTime": "Date", "File": "TFile", "RRule": "RRuleSet"} }, "generates": { "packages/server/graphql/private/resolverTypes.ts": { @@ -146,7 +146,7 @@ "PokerMeeting": "../../database/types/MeetingPoker#default as MeetingPoker", "PokerMeetingMember": "../../database/types/MeetingPokerMeetingMember#default as PokerMeetingMemberDB", "PokerTemplate": "../../database/types/PokerTemplate#default as PokerTemplateDB", - "RRule": "rrule#RRule", + "RRule": "rrule-rust#RRuleSet", "Reactable": "../../database/types/Reactable#Reactable", "Reactji": "../types/Reactji#ReactjiSource", "ReflectPrompt": "../../database/types/RetrospectivePrompt#default", diff --git a/package.json b/package.json index dcd9afa4562..66451df6b23 100644 --- a/package.json +++ b/package.json @@ -87,7 +87,8 @@ "@graphql-codegen/typescript-resolvers": "^4.0.1", "@graphql-tools/merge": "^9.0.0", "@sucrase/webpack-loader": "^2.0.0", - "@swc/core": "^1.3.96", + "@swc/core": "^1.7.22", + "@swc/jest": "^0.2.36", "@tailwindcss/container-queries": "^0.1.0", "@tailwindcss/forms": "^0.5.3", "@types/dotenv": "^6.1.1", diff --git a/packages/client/components/AnalyticsPage.tsx b/packages/client/components/AnalyticsPage.tsx index 0b1cbcc4910..e27a528c60a 100644 --- a/packages/client/components/AnalyticsPage.tsx +++ b/packages/client/components/AnalyticsPage.tsx @@ -83,16 +83,18 @@ if (datadogEnabled) { datadogRum.startSessionReplayRecording() } -amplitude.init(window.__ACTION__.AMPLITUDE_WRITE_KEY, { - defaultTracking: { - attribution: false, - pageViews: false, - sessions: false, - formInteractions: false, - fileDownloads: false - }, - logLevel: __PRODUCTION__ ? amplitude.Types.LogLevel.None : amplitude.Types.LogLevel.Debug -}) +if (window.__ACTION__.AMPLITUDE_WRITE_KEY) { + amplitude.init(window.__ACTION__.AMPLITUDE_WRITE_KEY, { + defaultTracking: { + attribution: false, + pageViews: false, + sessions: false, + formInteractions: false, + fileDownloads: false + }, + logLevel: __PRODUCTION__ ? amplitude.Types.LogLevel.None : amplitude.Types.LogLevel.Debug + }) +} const AnalyticsPage = () => { const atmosphere = useAtmosphere() diff --git a/packages/client/components/Recurrence/RecurrenceSettings.tsx b/packages/client/components/Recurrence/RecurrenceSettings.tsx index f5cca8d6f43..d47254ab708 100644 --- a/packages/client/components/Recurrence/RecurrenceSettings.tsx +++ b/packages/client/components/Recurrence/RecurrenceSettings.tsx @@ -1,18 +1,20 @@ import clsx from 'clsx' -import dayjs from 'dayjs' +import dayjs, {Dayjs} from 'dayjs' +import timezonePlugin from 'dayjs/plugin/timezone' import utcPlugin from 'dayjs/plugin/utc' import React, {PropsWithChildren, useEffect} from 'react' import {Frequency, RRule} from 'rrule' import {MenuPosition} from '../../hooks/useCoords' import useMenu from '../../hooks/useMenu' -import {getJSDateFromRRuleDate, getRRuleDateFromJSDate} from '../../shared/rruleUtil' +import {fromRRuleDateTime, toRRuleDateTime} from '../../shared/rruleUtil' import plural from '../../utils/plural' import DropdownMenuToggle from '../DropdownMenuToggle' import {toHumanReadable} from './HumanReadableRecurrenceRule' import {Day, RecurrenceDayCheckbox} from './RecurrenceDayCheckbox' import {RecurrenceTimePicker} from './RecurrenceTimePicker' -dayjs.extend(utcPlugin) +dayjs.extend(utcPlugin) +dayjs.extend(timezonePlugin) export const ALL_DAYS: Day[] = [ { name: 'Monday', @@ -174,16 +176,10 @@ export const RecurrenceSettings = (props: Props) => { ? rrule.options.byweekday.map((weekday) => ALL_DAYS.find((day) => day.intVal === weekday)!) : [] ) - const [recurrenceStartTime, setRecurrenceStartTime] = React.useState( + const [recurrenceStartTime, setRecurrenceStartTime] = React.useState( rrule - ? getJSDateFromRRuleDate(rrule.options.dtstart) - : dayjs() - .add(1, 'day') - .set('hour', 6) - .set('minute', 0) - .set('second', 0) - .set('millisecond', 0) - .toDate() // suggest 6:00 AM tomorrow + ? fromRRuleDateTime(rrule) + : dayjs().add(1, 'day').set('hour', 6).set('minute', 0).set('second', 0).set('millisecond', 0) // suggest 6:00 AM tomorrow ) const {timeZone} = Intl.DateTimeFormat().resolvedOptions() @@ -222,14 +218,13 @@ export const RecurrenceSettings = (props: Props) => { freq: Frequency.WEEKLY, interval: recurrenceInterval, byweekday: recurrenceDays.map((day) => day.rruleVal), - dtstart: getRRuleDateFromJSDate(recurrenceStartTime), + dtstart: toRRuleDateTime(recurrenceStartTime), tzid: timeZone }) : null onRruleUpdated(rrule) }, [recurrenceDays, recurrenceInterval, recurrenceStartTime]) - return (
@@ -292,7 +287,7 @@ export const RecurrenceSettings = (props: Props) => { void + onClick: (n: Dayjs) => void } const OPTIONS = [...Array(96).keys()].map((n) => n * ms('15m')) @@ -16,7 +16,6 @@ const DEFAULT_MEETING_START_TIME_IDX = OPTIONS.findIndex((n) => n === ms('6h')) export const RecurrenceTimePicker = (props: Props) => { const {menuProps, onClick} = props - const startOfToday = new Date().setHours(0, 0, 0, 0) return ( { defaultActiveIdx={DEFAULT_MEETING_START_TIME_IDX} > {OPTIONS.map((n, idx) => { - const proposedTime = dayjs(startOfToday + n).add(1, 'day') + const proposedTime = dayjs().add(1, 'day').startOf('day').add(n, 'ms') return ( onClick(proposedTime.toDate())} + onClick={() => onClick(proposedTime)} /> ) })} diff --git a/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx b/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx index 63c17f230f6..0453caafc43 100644 --- a/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx +++ b/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx @@ -217,7 +217,7 @@ export const UpdateRecurrenceSettingsModal = (props: Props) => { return ( /packages/'], moduleNameMapper: { diff --git a/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx b/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx index 2e5881b2706..bdadb899580 100644 --- a/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx +++ b/packages/client/modules/teamDashboard/components/ProviderList/ProviderList.tsx @@ -121,48 +121,50 @@ const ProviderList = (props: Props) => { { name: 'Atlassian', connected: !!integrations?.atlassian?.accessToken, - component: + component: ( + + ) }, { name: 'Jira Server', connected: !!integrations?.jiraServer?.auth?.isActive && integrations.jiraServer?.sharedProviders[0], - component: + component: }, { name: 'GitHub', connected: !!integrations?.github?.accessToken, - component: + component: }, { name: 'GitLab', connected: !!integrations?.gitlab.auth, - component: + component: }, { name: 'Mattermost', connected: !!integrations?.mattermost.auth, - component: + component: }, { name: 'Slack', connected: integrations?.slack?.isActive, - component: + component: }, { name: 'Azure DevOps', connected: !!integrations?.azureDevOps.auth?.accessToken, - component: + component: }, { name: 'MS Teams', connected: !!integrations?.msTeams.auth, - component: + component: }, { name: 'Gcal Integration', connected: !!integrations?.gcal?.auth, - component: + component: } ] diff --git a/packages/client/shared/__tests__/rruleUtil.test.ts b/packages/client/shared/__tests__/rruleUtil.test.ts new file mode 100644 index 00000000000..e63c52dbb42 --- /dev/null +++ b/packages/client/shared/__tests__/rruleUtil.test.ts @@ -0,0 +1,32 @@ +import dayjs from 'dayjs' +import {fromDateTime, toDateTime} from '../rruleUtil' + +test('toDateTime: Should handle TZID', () => { + // at noon UTC what time is it in Phoenix? + const now = dayjs('2022-01-01T12:00:00Z') + const tzid = 'America/Phoenix' + const str = toDateTime(now, tzid) + expect(str).toBe('20220101T050000') +}) + +test('toDateTime: Should handle UTC TZID', () => { + // at noon UTC what time is it in UTC? + const now = dayjs('2022-01-01T12:00:00Z') + const tzid = 'UTC' + const str = toDateTime(now, tzid) + expect(str).toBe('20220101T120000') +}) + +test('fromDateTime: Should handle TZID', () => { + const dateTimeStr = '20220101T050000' + const tzid = 'America/Phoenix' + const day = fromDateTime(dateTimeStr, tzid) + expect(day.toISOString()).toBe('2022-01-01T12:00:00.000Z') +}) + +test('fromDateTime: Should handle UTC TZID', () => { + const dateTimeStr = '20220101T050000' + const tzid = 'UTC' + const day = fromDateTime(dateTimeStr, tzid) + expect(day.toISOString()).toBe('2022-01-01T05:00:00.000Z') +}) diff --git a/packages/client/shared/rruleUtil.ts b/packages/client/shared/rruleUtil.ts index b124578eb68..fc1b753d2de 100644 --- a/packages/client/shared/rruleUtil.ts +++ b/packages/client/shared/rruleUtil.ts @@ -1,21 +1,33 @@ -import {datetime} from 'rrule' +import dayjs, {Dayjs} from 'dayjs' +import customParsePlugin from 'dayjs/plugin/customParseFormat' +import timezonePlugin from 'dayjs/plugin/timezone' +import utcPlugin from 'dayjs/plugin/utc' +import {RRule} from 'rrule' -export const getRRuleDateFromJSDate = (date: Date) => { - return datetime( - date.getFullYear(), - date.getMonth() + 1, - date.getDate(), - date.getHours(), - date.getMinutes() - ) +dayjs.extend(customParsePlugin) +dayjs.extend(utcPlugin) +dayjs.extend(timezonePlugin) + +// the RRule package requires dstart to be a date object set to a negative UTC offset. It's ugly! +export const toRRuleDateTime = (date: Dayjs) => { + return date.tz('UTC', true).toDate() +} + +export const fromRRuleDateTime = (rrule: RRule) => { + const {options} = rrule + const {dtstart, tzid} = options + const tzidTimeStr = `${dtstart.getUTCFullYear()}-${dtstart.getUTCMonth() + 1}-${dtstart.getUTCDate()} ${dtstart.getUTCHours()}:${dtstart.getUTCMinutes()}` + return tzid ? dayjs.tz(tzidTimeStr, tzid) : dayjs(tzidTimeStr) +} + +// These are used by rrule-rust on the server, which has a special DateTime object +export const toDateTime = (date: Dayjs, tzid: string) => { + return tzid + ? date.tz(tzid).format('YYYYMMDD[T]HHmmss') + : date.utc().format('YYYYMMDD[T]HHmmss[Z]') } -export const getJSDateFromRRuleDate = (rruleDate: Date) => { - return new Date( - rruleDate.getUTCFullYear(), - rruleDate.getUTCMonth(), - rruleDate.getUTCDate(), - rruleDate.getUTCHours(), - rruleDate.getUTCMinutes() - ) +export const fromDateTime = (rfc5545String: string, tzid: string) => { + const rawDate = dayjs.utc(rfc5545String, 'YYYYMMDD[T]HHmmss') + return tzid ? dayjs.tz(rawDate.format('YYYY-MM-DD HH:mm'), tzid) : rawDate } diff --git a/packages/server/__tests__/processRecurrence.test.ts b/packages/server/__tests__/processRecurrence.test.ts index f3c94204c03..948fc11b8fa 100644 --- a/packages/server/__tests__/processRecurrence.test.ts +++ b/packages/server/__tests__/processRecurrence.test.ts @@ -1,6 +1,7 @@ +import dayjs from 'dayjs' import ms from 'ms' import TeamMemberId from 'parabol-client/shared/gqlIds/TeamMemberId' -import {RRule} from 'rrule' +import {toDateTime} from '../../client/shared/rruleUtil' import getRethink from '../database/rethinkDriver' import DiscussPhase from '../database/types/DiscussPhase' import MeetingRetrospective from '../database/types/MeetingRetrospective' @@ -58,17 +59,6 @@ const assertIdempotency = async () => { }) } -// :TODO: (jmtaber129): Handle cleanup better - -beforeEach(async () => { - // Process recurrence right before each test to prevent pending effects (i.e. meetings that will - // start or end on the next processRecurrence run) from interfering with test results. - await sendIntranet({ - query: PROCESS_RECURRENCE, - isPrivate: true - }) -}) - test('Should not end meetings that are not scheduled to end', async () => { const r = await getRethink() const {userId} = await signUp() @@ -192,24 +182,15 @@ test('Should end the current team prompt meeting and start a new meeting', async const {userId} = await signUp() const {id: teamId} = (await getUserTeams(userId))[0] const teamMemberId = TeamMemberId.join(teamId, userId) - - const now = new Date() - - // Create a meeting series that's been going on for a few days, and happens daily at 9a UTC. - const startDate = new Date( - Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - 2, 9) - ) - const recurrenceRule = new RRule({ - freq: RRule.WEEKLY, - dtstart: startDate, - interval: 1, - byweekday: [RRule.MO, RRule.TU, RRule.WE, RRule.TH, RRule.FR, RRule.SA, RRule.SU] - }) + const startDate = dayjs().utc().subtract(2, 'day').set('hour', 9) + const dateTime = toDateTime(startDate, 'UTC') + const recurrenceRule = `DTSTART:${dateTime} +RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` const meetingSeriesId = await insertMeetingSeriesQuery({ meetingType: 'teamPrompt', title: 'Daily Test Standup', - recurrenceRule: recurrenceRule.toString(), + recurrenceRule, duration: 24 * 60, // 24 hours teamId, facilitatorId: userId @@ -270,23 +251,16 @@ test('Should end the current retro meeting and start a new meeting', async () => const {userId} = await signUp() const {id: teamId} = (await getUserTeams(userId))[0] - const now = new Date() - // Create a meeting series that's been going on for a few days, and happens daily at 9a UTC. - const startDate = new Date( - Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - 2, 9) - ) - const recurrenceRule = new RRule({ - freq: RRule.WEEKLY, - dtstart: startDate, - interval: 1, - byweekday: [RRule.MO, RRule.TU, RRule.WE, RRule.TH, RRule.FR, RRule.SA, RRule.SU] - }) + const startDate = dayjs().utc().subtract(2, 'day').set('hour', 9) + const dateTime = toDateTime(startDate, 'UTC') + const recurrenceRule = `DTSTART:${dateTime} +RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` const meetingSeriesId = await insertMeetingSeriesQuery({ meetingType: 'retrospective', title: 'Daily Retro', //they're really committed to improving - recurrenceRule: recurrenceRule.toString(), + recurrenceRule, duration: 24 * 60, // 24 hours teamId, facilitatorId: userId @@ -351,23 +325,15 @@ test('Should only start a new meeting if it would still be active', async () => const {id: teamId} = (await getUserTeams(userId))[0] const teamMemberId = TeamMemberId.join(teamId, userId) - const now = new Date() - - // Create a meeting series that's been going on for a few days, and happens daily at 9a UTC. - const startDate = new Date( - Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - 5, 9) - ) - const recurrenceRule = new RRule({ - freq: RRule.WEEKLY, - dtstart: startDate, - interval: 1, - byweekday: [RRule.MO, RRule.TU, RRule.WE, RRule.TH, RRule.FR, RRule.SA, RRule.SU] - }) + const startDate = dayjs().utc().subtract(2, 'day').set('hour', 9) + const dateTime = toDateTime(startDate, 'UTC') + const recurrenceRule = `DTSTART:${dateTime} +RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` const newMeetingSeriesId = await insertMeetingSeriesQuery({ meetingType: 'teamPrompt', title: 'Async Standup', - recurrenceRule: recurrenceRule.toString(), + recurrenceRule, duration: 24 * 60, // 24 hours teamId, facilitatorId: userId @@ -418,18 +384,10 @@ test('Should not start a new meeting if the rrule has not started', async () => const {id: teamId} = (await getUserTeams(userId))[0] const teamMemberId = TeamMemberId.join(teamId, userId) - const now = new Date() - - // Create a meeting series that starts tomorrow, and happens daily at 9a UTC. - const startDate = new Date( - Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() + 1, 9) - ) - const recurrenceRule = new RRule({ - freq: RRule.WEEKLY, - dtstart: startDate, - interval: 1, - byweekday: [RRule.MO, RRule.TU, RRule.WE, RRule.TH, RRule.FR, RRule.SA, RRule.SU] - }) + const startDate = dayjs().utc().add(1, 'day').set('hour', 9) + const dateTime = toDateTime(startDate, 'UTC') + const recurrenceRule = `DTSTART:${dateTime} +RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` const newMeetingSeriesId = await insertMeetingSeriesQuery({ meetingType: 'teamPrompt', @@ -485,18 +443,10 @@ test('Should not hang if the rrule interval is invalid', async () => { const {id: teamId} = (await getUserTeams(userId))[0] const teamMemberId = TeamMemberId.join(teamId, userId) - const now = new Date() - - // Create a meeting series that's been going on for a few days, and happens daily at 9a UTC. - const startDate = new Date( - Date.UTC(now.getUTCFullYear(), now.getUTCMonth(), now.getUTCDate() - 2, 9) - ) - const recurrenceRule = new RRule({ - freq: RRule.WEEKLY, - tzid: 'America/Los_Angeles', - interval: NaN, - dtstart: startDate - }) + const startDate = dayjs().utc().subtract(2, 'day').set('hour', 9) + const dateTime = toDateTime(startDate, 'UTC') + const recurrenceRule = `DTSTART:${dateTime} +RRULE:FREQ=WEEKLY;INTERVAL=NaN;BYDAY=MO,TU,WE,TH,FR,SA,SU` const newMeetingSeriesId = await insertMeetingSeriesQuery({ meetingType: 'teamPrompt', diff --git a/packages/server/database/types/Meeting.ts b/packages/server/database/types/Meeting.ts index 70baaefa945..c9ed6cbf778 100644 --- a/packages/server/database/types/Meeting.ts +++ b/packages/server/database/types/Meeting.ts @@ -13,7 +13,7 @@ interface Input { facilitatorUserId: string showConversionModal?: boolean meetingSeriesId?: number - scheduledEndTime?: Date + scheduledEndTime?: Date | null summary?: string sentimentScore?: number } diff --git a/packages/server/database/types/MeetingRetrospective.ts b/packages/server/database/types/MeetingRetrospective.ts index 389aa2a2ad2..d727149bb78 100644 --- a/packages/server/database/types/MeetingRetrospective.ts +++ b/packages/server/database/types/MeetingRetrospective.ts @@ -29,7 +29,7 @@ interface Input { recallBotId?: string videoMeetingURL?: string meetingSeriesId?: number - scheduledEndTime?: Date + scheduledEndTime?: Date | null } export function isMeetingRetrospective(meeting: Meeting): meeting is MeetingRetrospective { diff --git a/packages/server/graphql/mutations/helpers/createGcalEvent.ts b/packages/server/graphql/mutations/helpers/createGcalEvent.ts index 8aaa7efeddd..80cad133ab3 100644 --- a/packages/server/graphql/mutations/helpers/createGcalEvent.ts +++ b/packages/server/graphql/mutations/helpers/createGcalEvent.ts @@ -1,7 +1,6 @@ import {google} from 'googleapis' -import {pick} from 'lodash' import makeAppURL from 'parabol-client/utils/makeAppURL' -import {RRule} from 'rrule' +import {RRuleSet} from 'rrule-rust' import appOrigin from '../../../appOrigin' import standardError from '../../../utils/standardError' import {DataLoaderWorker} from '../../graphql' @@ -10,17 +9,17 @@ import {CreateGcalEventInput, StandardMutationError} from '../../public/resolver const emailRemindMinsBeforeMeeting = 24 * 60 const popupRemindMinsBeforeMeeting = 10 -const convertRruleToGcal = (rrule: RRule | null | undefined) => { +const convertRruleToGcal = (rrule: RRuleSet | null | undefined) => { if (!rrule) { return [] } - + const {rrules} = rrule + const [firstRRule] = rrules + if (!firstRRule) return [] + return [firstRRule.toString()] // Google does not allow for all fields in rrule. For example DTSTART and DTEND are not allowed. // It also has trouble with BYHOUR, BYMINUTE, and BYSECOND. It's best to stick to fields known to work. // Also strip TZID as google wants the UNTIL field in Z, but rrule only uses that if no TZID is present. - const options = pick(rrule.options, 'freq', 'interval', 'byweekday', 'until', 'count') - const gcalRule = new RRule(options) - return [gcalRule.toString()] } type Input = { @@ -29,7 +28,7 @@ type Input = { meetingId: string viewerId: string teamId: string - rrule?: RRule | null + rrule?: RRuleSet | null dataLoader: DataLoaderWorker } @@ -117,7 +116,7 @@ const createGcalEvent = async ( export type UpdateGcalSeriesInput = { gcalSeriesId: string name?: string - rrule: RRule | null + rrule: RRuleSet | null userId: string teamId: string dataLoader: DataLoaderWorker diff --git a/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts b/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts index a756463e106..fa01659f21f 100644 --- a/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts @@ -14,7 +14,7 @@ const safeCreateRetrospective = async ( templateId: string videoMeetingURL?: string meetingSeriesId?: number - scheduledEndTime?: Date + scheduledEndTime?: Date | null name: string }, dataLoader: DataLoaderWorker diff --git a/packages/server/graphql/private/mutations/processRecurrence.ts b/packages/server/graphql/private/mutations/processRecurrence.ts index a6311f28343..32670a43866 100644 --- a/packages/server/graphql/private/mutations/processRecurrence.ts +++ b/packages/server/graphql/private/mutations/processRecurrence.ts @@ -1,8 +1,9 @@ +import dayjs from 'dayjs' import tracer from 'dd-trace' import ms from 'ms' -import {getJSDateFromRRuleDate, getRRuleDateFromJSDate} from 'parabol-client/shared/rruleUtil' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import {RRule} from 'rrule' +import {DateTime, RRuleSet} from 'rrule-rust' +import {fromDateTime, toDateTime} from '../../../../client/shared/rruleUtil' import getRethink from '../../../database/rethinkDriver' import MeetingRetrospective, { isMeetingRetrospective @@ -11,8 +12,8 @@ import MeetingTeamPrompt, {isMeetingTeamPrompt} from '../../../database/types/Me import {getActiveMeetingSeries} from '../../../postgres/queries/getActiveMeetingSeries' import {MeetingSeries} from '../../../postgres/types/MeetingSeries' import {analytics} from '../../../utils/analytics/analytics' +import {getNextRRuleDate} from '../../../utils/getNextRRuleDate' import publish, {SubOptions} from '../../../utils/publish' -import sendToSentry from '../../../utils/sendToSentry' import standardError from '../../../utils/standardError' import {DataLoaderWorker} from '../../graphql' import {createMeetingSeriesTitle} from '../../mutations/helpers/createMeetingSeriesTitle' @@ -45,17 +46,10 @@ const startRecurringMeeting = async ( dataLoader.get('meetingSettingsByType').load({teamId, meetingType}) ]) - const rrule = RRule.fromString(meetingSeries.recurrenceRule) - const nextMeetingStartDate = rrule.after(getRRuleDateFromJSDate(startTime)) - const scheduledEndTime = nextMeetingStartDate - ? getJSDateFromRRuleDate(nextMeetingStartDate) - : undefined + const rrule = RRuleSet.parse(meetingSeries.recurrenceRule) + const scheduledEndTime = getNextRRuleDate(rrule) - const meetingName = createMeetingSeriesTitle( - meetingSeries.title, - startTime, - rrule.options.tzid ?? 'UTC' - ) + const meetingName = createMeetingSeriesTitle(meetingSeries.title, startTime, rrule.tzid) const meeting = await (async () => { if (meetingSeries.meetingType === 'teamPrompt') { const teamPromptMeeting = lastMeeting as MeetingTeamPrompt | null @@ -172,32 +166,13 @@ const processRecurrence: MutationResolvers['processRecurrence'] = async ( dataLoader.get('lastMeetingByMeetingSeriesId').load(meetingSeries.id) ]) - // remove this check after 2024-05-05 - if ( - lastMeeting?.meetingSeriesId !== meetingSeries.id || - lastMeeting.teamId !== meetingSeries.teamId - ) { - const error = new Error( - 'lastMeetingByMeetingSeriesId returned a meeting that does not match the series' - ) - sendToSentry(error) - throw error - } - if (seriesOrg.lockedAt) { return } // For meetings that should still be active, start the meeting and set its end time. // Any subscriptions are handled by the shared meeting start code - const rrule = tracer.trace('RRule.fromString', () => - RRule.fromString(meetingSeries.recurrenceRule) - ) - // technically, RRULE should never return NaN here but there's a bug in the library - // https://github.com/jakubroztocil/rrule/issues/321 - if (isNaN(rrule.options.interval)) { - return - } + const rrule = RRuleSet.parse(meetingSeries.recurrenceRule) // Only get meetings that should currently be active, i.e. meetings that should have started // within the last 24 hours, started after the last meeting in the series, and started before @@ -207,15 +182,16 @@ const processRecurrence: MutationResolvers['processRecurrence'] = async ( Math.max(lastMeeting.createdAt.getTime() + ms('10m'), now.getTime() - ms('24h')) ) : new Date(0) - const newMeetingsStartTimes = tracer.trace('RRule.between', () => - rrule.between(getRRuleDateFromJSDate(fromDate), getRRuleDateFromJSDate(now)) + const newMeetingsStartTimes = rrule.between( + DateTime.fromString(toDateTime(dayjs(fromDate), rrule.tzid)), + DateTime.fromString(toDateTime(dayjs(), rrule.tzid)) ) for (const startTime of newMeetingsStartTimes) { const err = await tracer.trace('startRecurringMeeting', async (span) => { span?.addTags({meetingSeriesId: meetingSeries.id}) return startRecurringMeeting( meetingSeries, - getJSDateFromRRuleDate(startTime), + fromDateTime(startTime.toString(), rrule.tzid).toDate(), dataLoader, subOptions ) diff --git a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts index 96f4f1b590d..a41aea04e18 100644 --- a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts +++ b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts @@ -1,6 +1,7 @@ -import {getJSDateFromRRuleDate, getRRuleDateFromJSDate} from 'parabol-client/shared/rruleUtil' +import dayjs from 'dayjs' +import {toDateTime} from 'parabol-client/shared/rruleUtil' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import {RRule} from 'rrule' +import {DateTime, RRuleSet} from 'rrule-rust' import getRethink from '../../../database/rethinkDriver' import {insertMeetingSeries as insertMeetingSeriesQuery} from '../../../postgres/queries/insertMeetingSeries' import restartMeetingSeries from '../../../postgres/queries/restartMeetingSeries' @@ -9,6 +10,7 @@ import {MeetingTypeEnum} from '../../../postgres/types/Meeting' import {MeetingSeries} from '../../../postgres/types/MeetingSeries' import {analytics} from '../../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../../utils/authorization' +import {getNextRRuleDate} from '../../../utils/getNextRRuleDate' import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' import {updateGcalSeries} from '../../mutations/helpers/createGcalEvent' @@ -22,7 +24,7 @@ export const startNewMeetingSeries = async ( name: string facilitatorUserId: string }, - recurrenceRule: RRule, + recurrenceRule: RRuleSet, meetingSeriesName?: string | null ) => { const { @@ -32,7 +34,6 @@ export const startNewMeetingSeries = async ( name: meetingName, facilitatorUserId: facilitatorId } = meeting - const now = new Date() const r = await getRethink() const newMeetingSeriesParams = { @@ -45,11 +46,7 @@ export const startNewMeetingSeries = async ( facilitatorId } as const const newMeetingSeriesId = await insertMeetingSeriesQuery(newMeetingSeriesParams) - const rruleNow = getRRuleDateFromJSDate(now) - const nextMeetingStartRRuleDate = recurrenceRule.after(rruleNow) - const nextMeetingStartDate = nextMeetingStartRRuleDate - ? getJSDateFromRRuleDate(nextMeetingStartRRuleDate) - : null + const nextMeetingStartDate = getNextRRuleDate(recurrenceRule) await r .table('NewMeeting') @@ -66,16 +63,10 @@ export const startNewMeetingSeries = async ( } } -const updateMeetingSeries = async (meetingSeries: MeetingSeries, newRecurrenceRule: RRule) => { +const updateMeetingSeries = async (meetingSeries: MeetingSeries, newRecurrenceRule: RRuleSet) => { const r = await getRethink() const {id: meetingSeriesId} = meetingSeries - const now = new Date() - const rruleNow = getRRuleDateFromJSDate(now) - const nextMeetingStartDateRRule = newRecurrenceRule.after(rruleNow) - const nextMeetingStartDate = nextMeetingStartDateRRule - ? getJSDateFromRRuleDate(nextMeetingStartDateRRule) - : null await restartMeetingSeries(meetingSeriesId, {recurrenceRule: newRecurrenceRule.toString()}) // lets close all active meetings at the time when @@ -90,7 +81,7 @@ const updateMeetingSeries = async (meetingSeries: MeetingSeries, newRecurrenceRu .table('NewMeeting') .get(meeting.id) .update({ - scheduledEndTime: nextMeetingStartDate + scheduledEndTime: getNextRRuleDate(newRecurrenceRule) }) .run() ) @@ -112,14 +103,13 @@ const stopMeetingSeries = async (meetingSeries: MeetingSeries) => { .run() } -const updateGCalRecurrenceRule = (oldRule: RRule, newRule: RRule | null | undefined) => { - if (!newRule) { - return new RRule({ - ...oldRule.options, - until: new Date() - }) - } - return newRule +const updateGCalRecurrenceRule = (oldRule: RRuleSet, newRule: RRuleSet | null | undefined) => { + // null newRule means end the series + if (newRule) return newRule + const {tzid} = oldRule + const now = DateTime.fromString(toDateTime(dayjs(), tzid)) + oldRule.rrules.forEach((rrule) => rrule.setUntil(now)) + return oldRule } const updateRecurrenceSettings: MutationResolvers['updateRecurrenceSettings'] = async ( @@ -160,7 +150,7 @@ const updateRecurrenceSettings: MutationResolvers['updateRecurrenceSettings'] = analytics.recurrenceStarted(viewer, meetingSeries) } if (gcalSeriesId) { - const newRrule = updateGCalRecurrenceRule(RRule.fromString(recurrenceRule), rrule) + const newRrule = updateGCalRecurrenceRule(RRuleSet.parse(recurrenceRule), rrule) await updateGcalSeries({ gcalSeriesId, name: name ?? undefined, diff --git a/packages/server/graphql/public/types/RRule.ts b/packages/server/graphql/public/types/RRule.ts index fefea6c4200..0047ed1921a 100644 --- a/packages/server/graphql/public/types/RRule.ts +++ b/packages/server/graphql/public/types/RRule.ts @@ -1,52 +1,38 @@ import {Kind} from 'graphql' -import isValidDate from 'parabol-client/utils/isValidDate' -import {Frequency, RRule} from 'rrule' +import {Frequency, RRuleSet} from 'rrule-rust' import {RRuleScalarConfig} from '../resolverTypes' -const isRRuleValid = (rrule: RRule) => { - const {options} = rrule - const {interval, freq, count, tzid, dtstart} = options +const isRRuleValid = (rrule: RRuleSet) => { + const {tzid, rrules} = rrule + const [firstRule] = rrules + if (!firstRule || rrules.length > 1) { + // this is just for us, it's not part of the spec + throw new Error('Exactly 1 RRule must exist in an RRule Set') + } + const {interval, frequency, count} = firstRule if (!Number.isSafeInteger(interval)) { - return { - error: 'RRule interval must be an integer' - } + throw new Error('RRULE interval must be an integer') } - const isWithinRange = rrule.options.interval >= 1 && rrule.options.interval <= 52 + const isWithinRange = interval && interval >= 1 && interval <= 52 if (!isWithinRange) { - return { - error: 'RRule interval must be between 1 and 52' - } + throw new Error('RRULE interval must be between 1 and 52') } - if (freq !== Frequency.WEEKLY) { - return { - error: 'RRule frequency must be WEEKLY' - } + if (frequency !== Frequency.Weekly) { + throw new Error('RRULE frequency must be WEEKLY') } // using count option is not allowed - if (count !== null) { - return { - error: 'RRule count option is not supported' - } + if (count !== null && count !== undefined) { + throw new Error('RRULE count option is not supported') } try { Intl.DateTimeFormat(undefined, {timeZone: tzid!}) } catch (e) { - return { - error: 'RRule time zone is invalid' - } - } - - if (!isValidDate(dtstart)) { - return { - error: 'RRule dtstart is invalid' - } + throw new Error('RRULE time zone is invalid') } - - return {error: null} } const RRuleScalarType: RRuleScalarConfig = { @@ -56,27 +42,19 @@ const RRuleScalarType: RRuleScalarConfig = { if (typeof value !== 'string') { throw new Error(`RRule is not a string, it is a: ${typeof value}`) } - const rrule = RRule.fromString(value) - const {error} = isRRuleValid(rrule) - if (error) { - throw new Error(error) - } - + const rrule = RRuleSet.parse(value) + isRRuleValid(rrule) return rrule }, serialize(value: unknown) { - return (value as RRule).toString() + return (value as RRuleSet).toString() }, parseLiteral(ast) { if (ast.kind !== Kind.STRING) { throw new Error(`RRule is not a string, it is a: ${ast.kind}`) } - const rrule = RRule.fromString(ast.value) - const {error} = isRRuleValid(rrule) - if (error) { - throw new Error(error) - } - + const rrule = RRuleSet.parse(ast.value) + isRRuleValid(rrule) return rrule } } diff --git a/packages/server/graphql/public/types/__tests__/RRule.test.ts b/packages/server/graphql/public/types/__tests__/RRule.test.ts index 97264111eb3..a5010398156 100644 --- a/packages/server/graphql/public/types/__tests__/RRule.test.ts +++ b/packages/server/graphql/public/types/__tests__/RRule.test.ts @@ -1,106 +1,54 @@ -import {RRule} from 'rrule' +import {RRuleSet} from 'rrule-rust' import RRuleScalarType from '../RRule' test('Should not allow for NaN interval values', () => { - const rrule = new RRule({ - freq: RRule.WEEKLY, - interval: NaN, - dtstart: new Date(), - tzid: 'America/Los_Angeles' - }) + const rrule = `DTSTART;TZID=America/Phoenix:20230121T090000 +RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR` expect(() => { - RRuleScalarType.parseValue?.(rrule.toString()) - }).toThrow(new Error('RRule interval must be an integer')) + RRuleScalarType.parseValue?.(rrule) + }).toThrow(new Error('RRULE interval must be an integer')) }) test('Should not allow for interval values bigger than 52', () => { - const rrule = new RRule({ - freq: RRule.WEEKLY, - interval: 53, - dtstart: new Date(), - tzid: 'America/Los_Angeles' - }) + const rrule = `DTSTART;TZID=America/Phoenix:20230121T090000 +RRULE:FREQ=WEEKLY;INTERVAL=53;BYDAY=MO,TU,WE,TH,FR` expect(() => { RRuleScalarType.parseValue?.(rrule.toString()) - }).toThrow(new Error('RRule interval must be between 1 and 52')) + }).toThrow(new Error('RRULE interval must be between 1 and 52')) }) test('Should not allow for interval values smaller than 1', () => { - const rrule = new RRule({ - freq: RRule.WEEKLY, - interval: 0, - dtstart: new Date(), - tzid: 'America/Los_Angeles' - }) + const rrule = `DTSTART;TZID=America/Phoenix:20230121T090000 +RRULE:FREQ=WEEKLY;INTERVAL=0;BYDAY=MO,TU,WE,TH,FR` expect(() => { RRuleScalarType.parseValue?.(rrule.toString()) - }).toThrow(new Error('RRule interval must be between 1 and 52')) -}) - -test('Should not allow for negative interval values', () => { - const rrule = new RRule({ - freq: RRule.WEEKLY, - interval: -1, - dtstart: new Date(), - tzid: 'America/Los_Angeles' - }) - - expect(() => { - RRuleScalarType.parseValue?.(rrule.toString()) - }).toThrow(new Error('RRule interval must be between 1 and 52')) + }).toThrow(new Error('RRULE interval must be between 1 and 52')) }) test('Should allow only WEEKLY frequency', () => { - const rrule = new RRule({ - freq: RRule.DAILY, - interval: 1, - dtstart: new Date(), - tzid: 'America/Los_Angeles' - }) + const rrule = `DTSTART;TZID=America/Phoenix:20230121T090000 +RRULE:FREQ=DAILY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR` expect(() => { RRuleScalarType.parseValue?.(rrule.toString()) - }).toThrow(new Error('RRule frequency must be WEEKLY')) + }).toThrow(new Error('RRULE frequency must be WEEKLY')) }) -test('Should allow only interval values between 1 and 52', () => { - const rrule = new RRule({ - freq: RRule.WEEKLY, - interval: 53, - dtstart: new Date(), - tzid: 'America/Los_Angeles' - }) - - expect(() => { - RRuleScalarType.parseValue?.(rrule.toString()) - }).toThrow(new Error('RRule interval must be between 1 and 52')) +test('rrule-rust: TZID defaults to UTC', () => { + const str = `DTSTART:20221119T090000Z +RRULE:FREQ=WEEKLY;BYDAY=MO,TU,WE,TH,FR` + const rrule = RRuleSet.parse(str) + const {tzid} = rrule + expect(tzid).toBe('UTC') }) -test('Should not allow for missing tzid', () => { - const rrule = new RRule({ - freq: RRule.WEEKLY, - interval: 1, - dtstart: new Date() - }) - - expect(() => { - RRuleScalarType.parseValue?.(rrule.toString()) - }).toThrow(new Error('RRule time zone is invalid')) -}) - -test('Should not allow for using count option', () => { - const rrule = new RRule({ - freq: RRule.WEEKLY, - interval: 1, - count: 0, - dtstart: new Date(), - tzid: 'America/Los_Angeles' - }) - - expect(() => { - RRuleScalarType.parseValue?.(rrule.toString()) - }).toThrow(new Error('RRule count option is not supported')) +test('rrule-rust: TZID extracted', () => { + const str = `DTSTART;TZID=America/Los_Angeles:20230301T170000 +RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=TU,WE,MO,TH` + const rrule = RRuleSet.parse(str) + const {tzid} = rrule + expect(tzid).toBe('America/Los_Angeles') }) diff --git a/packages/server/jest.config.js b/packages/server/jest.config.js index 8ecc0d2013e..685b60f917d 100644 --- a/packages/server/jest.config.js +++ b/packages/server/jest.config.js @@ -4,10 +4,15 @@ module.exports = { testEnvironment: 'node', transform: { '\\.(gql|graphql)$': './__tests__/jest-transform-graphql-shim.js', - '^.+\\.tsx?$': [ - 'ts-jest', + '^.+\\.(t|j)sx?$': [ + '@swc/jest', { - diagnostics: false + jsc: { + transform: { + // abstract classes will lose their default values when compiled with SWC + useDefineForClassFields: false + } + } } ] }, diff --git a/packages/server/package.json b/packages/server/package.json index ea0ee5e3468..7f0a4f34b6a 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -129,7 +129,7 @@ "react-dom": "^17.0.2", "relay-runtime": "^14.1.0", "rethinkdb-ts": "2.6.0", - "rrule": "^2.7.2", + "rrule-rust": "^2.0.2", "samlify": "^2.8.2", "sanitize-html": "^2.13.0", "sharp": "^0.32.6", diff --git a/packages/server/postgres/migrations/1676020597201_scheduledJobOrgIdIndex.ts b/packages/server/postgres/migrations/1676020597201_scheduledJobOrgIdIndex.ts index f0a570b25d8..86ad23d6dfc 100644 --- a/packages/server/postgres/migrations/1676020597201_scheduledJobOrgIdIndex.ts +++ b/packages/server/postgres/migrations/1676020597201_scheduledJobOrgIdIndex.ts @@ -3,7 +3,9 @@ import connectRethinkDB from '../../database/connectRethinkDB' export const up = async function () { await connectRethinkDB() - await r.table('ScheduledJob').indexCreate('orgId').run() + try { + await r.table('ScheduledJob').indexCreate('orgId').run() + } catch {} await r.getPoolMaster()?.drain() } diff --git a/packages/server/utils/getNextRRuleDate.ts b/packages/server/utils/getNextRRuleDate.ts new file mode 100644 index 00000000000..1b3c75d91ef --- /dev/null +++ b/packages/server/utils/getNextRRuleDate.ts @@ -0,0 +1,12 @@ +import dayjs from 'dayjs' +import {fromDateTime, toDateTime} from 'parabol-client/shared/rruleUtil' +import {DateTime, RRuleSet} from 'rrule-rust' + +export const getNextRRuleDate = (rrule: RRuleSet) => { + const {tzid} = rrule + const now = DateTime.fromString(toDateTime(dayjs(), tzid)) + const nextYear = DateTime.fromString(toDateTime(dayjs().add(1, 'year'), tzid)) + const nextDateTime = rrule.between(now, nextYear)[0] + if (!nextDateTime) return null + return fromDateTime(nextDateTime.toString(), tzid).toDate() +} diff --git a/yarn.lock b/yarn.lock index c4c980acf32..ff0969d4238 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4900,6 +4900,13 @@ slash "^3.0.0" strip-ansi "^6.0.0" +"@jest/create-cache-key-function@^29.7.0": + version "29.7.0" + resolved "https://registry.yarnpkg.com/@jest/create-cache-key-function/-/create-cache-key-function-29.7.0.tgz#793be38148fab78e65f40ae30c36785f4ad859f0" + integrity sha512-4QqS3LY5PBmTRHj9sAg1HLoPzqAI0uOX6wI/TRqHIcOxlFidy6YEmCQJk6FSZjNLGCeubDMfmkWL+qaLKhSGQA== + dependencies: + "@jest/types" "^29.6.3" + "@jest/environment@^29.5.0": version "29.5.0" resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.5.0.tgz#9152d56317c1fdb1af389c46640ba74ef0bb4c65" @@ -4984,6 +4991,13 @@ dependencies: "@sinclair/typebox" "^0.25.16" +"@jest/schemas@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/schemas/-/schemas-29.6.3.tgz#430b5ce8a4e0044a7e3819663305a7b3091c8e03" + integrity sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA== + dependencies: + "@sinclair/typebox" "^0.27.8" + "@jest/source-map@^29.4.3": version "29.4.3" resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-29.4.3.tgz#ff8d05cbfff875d4a791ab679b4333df47951d20" @@ -5046,6 +5060,18 @@ "@types/yargs" "^17.0.8" chalk "^4.0.0" +"@jest/types@^29.6.3": + version "29.6.3" + resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.6.3.tgz#1131f8cf634e7e84c5e77bab12f052af585fba59" + integrity sha512-u3UPsIilWKOM3F9CXtrG8LEJmNxwoCQC/XVj4IKYXvvpx7QIi/Kg1LI5uDmDpKlac62NUtX7eLjRh+jVZcLOzw== + dependencies: + "@jest/schemas" "^29.6.3" + "@types/istanbul-lib-coverage" "^2.0.0" + "@types/istanbul-reports" "^3.0.0" + "@types/node" "*" + "@types/yargs" "^17.0.8" + chalk "^4.0.0" + "@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2": version "0.3.2" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz#c1aedc61e853f2bb9f5dfe6d4442d3b565b253b9" @@ -7519,6 +7545,86 @@ estree-walker "^1.0.1" picomatch "^2.2.2" +"@rrule-rust/lib-android-arm-eabi@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-android-arm-eabi/-/lib-android-arm-eabi-2.0.2.tgz#929802cf995c873829df42792bc419dde938f6d3" + integrity sha512-M8Ed5/tAdUyEtb9yt/K+OqGYS3l7+gS3rTlHP5xYItumGyz+uioMb4Vi2kJIn0Z/F8gwTHNur+8xlU09VhCNNg== + +"@rrule-rust/lib-android-arm64@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-android-arm64/-/lib-android-arm64-2.0.2.tgz#ea96930c0a79014919de2b394201a2d7c5db94b6" + integrity sha512-cAHseeDaWaoPSCK1bBhtbw3KzzfwFpgKuiqdU4b+JntsX8cNOaD+aalFwZlqMWQZCU8PSptRd/8Y2y8co5jKig== + +"@rrule-rust/lib-darwin-arm64@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-darwin-arm64/-/lib-darwin-arm64-2.0.2.tgz#21b12d105eb37ffdfac3e26874be2252fc269d1e" + integrity sha512-FXz1xBKtlATtpH2ZQWwo5MPhFY7Clveddkd4P7N75FmjcOsW+gergvddO5ZeANkoMP9P3hjq14W8UYHg7Yxjlw== + +"@rrule-rust/lib-darwin-universal@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-darwin-universal/-/lib-darwin-universal-2.0.2.tgz#eb1b6ab619d9da69b42e02a866b892c5926ea1cf" + integrity sha512-c7bASQv/5hxOsNU0fKnN0UnUv8srksA5cy1w6M1d/7/cOZnGpChjE4752liNQNRj8EINq1O6Pw3Flm/Q2+jKyQ== + +"@rrule-rust/lib-darwin-x64@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-darwin-x64/-/lib-darwin-x64-2.0.2.tgz#5069f1d23a09b5b066bcbefa3d889ea1b774d3cd" + integrity sha512-4+whisZNluw1XUUgFwjhw6s6Tq2ZqU1726BhylRE6s/4fzf5BHj80WF0DqDMZua5Y0Zl6cljTXoTKlzqQ7cEeQ== + +"@rrule-rust/lib-freebsd-x64@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-freebsd-x64/-/lib-freebsd-x64-2.0.2.tgz#ce60beb00f82dc11506e6761dee4b698436ed813" + integrity sha512-6a5TA1nazLUdKYTf8tGGoCOzD3AYOfiutAvhZm2ouRswNdiEzEXqRgVfI/q1zYX5BX+Bry/Oly6A1183u6aAHA== + +"@rrule-rust/lib-linux-arm-gnueabihf@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-linux-arm-gnueabihf/-/lib-linux-arm-gnueabihf-2.0.2.tgz#c40d6d52ec5feaaddb8b615b87fad81c95df4bc3" + integrity sha512-+ZmI712IUfU+AK9QM5OoAopY7sEHqrh+GN8L5zAXDOHZwOIMzDrmF4z+s5em+8j1W6N/YcW93ZAOfxaeaoVSHA== + +"@rrule-rust/lib-linux-arm-musleabihf@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-linux-arm-musleabihf/-/lib-linux-arm-musleabihf-2.0.2.tgz#e21ccdc0ce24386dae6cf792736cb9c88e30e1ca" + integrity sha512-TATE2VhNPFaZsNxgKk32xG/fUSkFByyXyzrWNatr8SE6gal4krieoJmVQZ4ny9b3Vaf5aZHC4wkXo6l6eZeBBw== + +"@rrule-rust/lib-linux-arm64-gnu@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-linux-arm64-gnu/-/lib-linux-arm64-gnu-2.0.2.tgz#9ea622a14312aeaea77253942514bf1f107f3c36" + integrity sha512-5EVWZJgDRopmL/aQVS8KObkEQWUaRJ3qqRrsJiTs0ERvQTS4wdPketxD/XwFisBlUFBEn0xloxcmP+WnxbGjrg== + +"@rrule-rust/lib-linux-arm64-musl@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-linux-arm64-musl/-/lib-linux-arm64-musl-2.0.2.tgz#7f210bd91962269d349c9daac4309653b049a4a4" + integrity sha512-fnpGBh9KkNDWRT7BXvLj1BhN6lgtuE2W//aXrx8OKQ7ZQmZTdwNLa5U5emxY348/zXPXyZaX91p2hwh4deMztQ== + +"@rrule-rust/lib-linux-riscv64-gnu@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-linux-riscv64-gnu/-/lib-linux-riscv64-gnu-2.0.2.tgz#e5c15ee5136d5c3b28191e8ef0b3853bc2253f33" + integrity sha512-ADy0sBANIkHLY0Nq0OuJShe9QceLrWlA5K6iqNkpBKYM2poU7HWpKXJ4Wpu1b1YYSjYDx1c0bLSMIXxCiGfYMw== + +"@rrule-rust/lib-linux-x64-gnu@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-linux-x64-gnu/-/lib-linux-x64-gnu-2.0.2.tgz#100008b762b0dd1fa567aa684d449fd96c865fef" + integrity sha512-jzKfXiuJA83YQA2xbkupwuKhIvwKaYWJdpEvhp8NeIW/jup/dJPOShfr5tDkSVtggSytRZnoU2j2P1IfzRS/wg== + +"@rrule-rust/lib-linux-x64-musl@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-linux-x64-musl/-/lib-linux-x64-musl-2.0.2.tgz#7037325947a2f685a2c2dbd7b5a1433e43eed76d" + integrity sha512-H9Eus0c1Q4AlunoKAf7lRe13acyCqBNdXldXVNpsdfLCl+lMGegH4j4PA5sz5RJOLovUIRTUUBuz1CccQbEwzQ== + +"@rrule-rust/lib-win32-arm64-msvc@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-win32-arm64-msvc/-/lib-win32-arm64-msvc-2.0.2.tgz#1a2f85ef6778ee8f3a0e1c44cc8afddc9d03ee0c" + integrity sha512-NHZXaE/ua2SfBcWpI19+PCxT+a+TBO9lvYkZuE1/aw+T3XNzPIvLidsvCdTZLVzB4laUScFzPpBbA7RBhQrmMA== + +"@rrule-rust/lib-win32-ia32-msvc@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-win32-ia32-msvc/-/lib-win32-ia32-msvc-2.0.2.tgz#59fd73af7881eecec41e6934461fcef009266a00" + integrity sha512-7HceD3cQxXh1PwuhkqJYPn3dkB/NYWQ4LUvtC1VA+4+PHaqItVeKAKxLIftHaj8IXTdYnlqKgUQJwW/PJQndjg== + +"@rrule-rust/lib-win32-x64-msvc@2.0.2": + version "2.0.2" + resolved "https://registry.yarnpkg.com/@rrule-rust/lib-win32-x64-msvc/-/lib-win32-x64-msvc-2.0.2.tgz#ee875f0ae0fde4d0be496f6c3cc1a00a5fa20640" + integrity sha512-It/yL37XbdOFspf2lw5zIYcYaxnu5jy6kkFKKk6TtFYLN44fk79IZ6mzUFt3tnqkDME/BspD9OGxp8R5Gj1wHw== + "@scarf/scarf@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@scarf/scarf/-/scarf-1.1.1.tgz#d8b9f20037b3a37dbf8dcdc4b3b72f9285bfce35" @@ -7646,6 +7752,11 @@ resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.24.tgz#8c7688559979f7079aacaf31aa881c3aa410b718" integrity sha512-XJfwUVUKDHF5ugKwIcxEgc9k8b7HbznCp6eUfWgu710hMPNIO4aw4/zB5RogDQz8nd6gyCDpU9O/m6qYEWY6yQ== +"@sinclair/typebox@^0.27.8": + version "0.27.8" + resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.27.8.tgz#6667fac16c436b5434a387a34dedb013198f6e6e" + integrity sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA== + "@sinonjs/commons@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-2.0.0.tgz#fd4ca5b063554307e8327b4564bd56d3b73924a3" @@ -8197,84 +8308,95 @@ magic-string "^0.25.0" string.prototype.matchall "^4.0.6" -"@swc/core-darwin-arm64@1.3.96": - version "1.3.96" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.3.96.tgz#7c1c4245ce3f160a5b36a48ed071e3061a839e1d" - integrity sha512-8hzgXYVd85hfPh6mJ9yrG26rhgzCmcLO0h1TIl8U31hwmTbfZLzRitFQ/kqMJNbIBCwmNH1RU2QcJnL3d7f69A== - -"@swc/core-darwin-x64@1.3.96": - version "1.3.96" - resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.3.96.tgz#4720ff897ca3f22fe77d0be688968161480c80f0" - integrity sha512-mFp9GFfuPg+43vlAdQZl0WZpZSE8sEzqL7sr/7Reul5McUHP0BaLsEzwjvD035ESfkY8GBZdLpMinblIbFNljQ== - -"@swc/core-linux-arm-gnueabihf@1.3.96": - version "1.3.96" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.3.96.tgz#2c238ae00b13918ac058b132a31dc57dbcf94e39" - integrity sha512-8UEKkYJP4c8YzYIY/LlbSo8z5Obj4hqcv/fUTHiEePiGsOddgGf7AWjh56u7IoN/0uEmEro59nc1ChFXqXSGyg== - -"@swc/core-linux-arm64-gnu@1.3.96": - version "1.3.96" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.3.96.tgz#be2e84506b9761b561fb9a341e587f8594a8e55d" - integrity sha512-c/IiJ0s1y3Ymm2BTpyC/xr6gOvoqAVETrivVXHq68xgNms95luSpbYQ28rqaZC8bQC8M5zdXpSc0T8DJu8RJGw== - -"@swc/core-linux-arm64-musl@1.3.96": - version "1.3.96" - resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.3.96.tgz#22c9ce17bd923ae358760e668ca33c90210c2ae5" - integrity sha512-i5/UTUwmJLri7zhtF6SAo/4QDQJDH2fhYJaBIUhrICmIkRO/ltURmpejqxsM/ye9Jqv5zG7VszMC0v/GYn/7BQ== - -"@swc/core-linux-x64-gnu@1.3.96": - version "1.3.96" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.3.96.tgz#c17c072e338341c0ac3507a31ab2a36d16d79c98" - integrity sha512-USdaZu8lTIkm4Yf9cogct/j5eqtdZqTgcTib4I+NloUW0E/hySou3eSyp3V2UAA1qyuC72ld1otXuyKBna0YKQ== - -"@swc/core-linux-x64-musl@1.3.96": - version "1.3.96" - resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.3.96.tgz#eb74594a48b4e9cabdce7f5525b3b946f8d6dd16" - integrity sha512-QYErutd+G2SNaCinUVobfL7jWWjGTI0QEoQ6hqTp7PxCJS/dmKmj3C5ZkvxRYcq7XcZt7ovrYCTwPTHzt6lZBg== - -"@swc/core-win32-arm64-msvc@1.3.96": - version "1.3.96" - resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.3.96.tgz#6f7c0d20d80534b0676dc6761904288c16e93857" - integrity sha512-hjGvvAduA3Un2cZ9iNP4xvTXOO4jL3G9iakhFsgVhpkU73SGmK7+LN8ZVBEu4oq2SUcHO6caWvnZ881cxGuSpg== - -"@swc/core-win32-ia32-msvc@1.3.96": - version "1.3.96" - resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.3.96.tgz#47bb24ef2e4c81407a6786649246983cc69e7854" - integrity sha512-Far2hVFiwr+7VPCM2GxSmbh3ikTpM3pDombE+d69hkedvYHYZxtTF+2LTKl/sXtpbUnsoq7yV/32c9R/xaaWfw== - -"@swc/core-win32-x64-msvc@1.3.96": - version "1.3.96" - resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.96.tgz#c796e3df7afe2875d227c74add16a7d09c77d8bd" - integrity sha512-4VbSAniIu0ikLf5mBX81FsljnfqjoVGleEkCQv4+zRlyZtO3FHoDPkeLVoy6WRlj7tyrRcfUJ4mDdPkbfTO14g== - -"@swc/core@^1.3.96": - version "1.3.96" - resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.96.tgz#f04d58b227ceed2fee6617ce2cdddf21d0803f96" - integrity sha512-zwE3TLgoZwJfQygdv2SdCK9mRLYluwDOM53I+dT6Z5ZvrgVENmY3txvWDvduzkV+/8IuvrRbVezMpxcojadRdQ== - dependencies: - "@swc/counter" "^0.1.1" - "@swc/types" "^0.1.5" +"@swc/core-darwin-arm64@1.7.22": + version "1.7.22" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-arm64/-/core-darwin-arm64-1.7.22.tgz#2ce216c4bb61627ff9689213e690ffc64ed02114" + integrity sha512-B2Bh2W+C7ALdGwDxRWAJ+UtNExfozvwyayGiNkbR3wmDKXXeQfhGM5MK+QYUWKu7UQ6ATq69OyZrxofDobKUug== + +"@swc/core-darwin-x64@1.7.22": + version "1.7.22" + resolved "https://registry.yarnpkg.com/@swc/core-darwin-x64/-/core-darwin-x64-1.7.22.tgz#78d028d72756193a1bedf3cc0766aede1b290878" + integrity sha512-s34UQntnQ6tL9hS9aX3xG7OfGhpmy05FEEndbHaooGO8O+L5k8uWxhE5KhYCOC0N803sGdZg6YZmKtYrWN/YxA== + +"@swc/core-linux-arm-gnueabihf@1.7.22": + version "1.7.22" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm-gnueabihf/-/core-linux-arm-gnueabihf-1.7.22.tgz#f95e45179e362dd5cfc3c9b8083af33e430195d9" + integrity sha512-SE69+oos1jLOXx5YdMH//Qc5zQc2xYukajB+0BWmkcFd/S/cCanGWYtdSzYausm8af2Fw1hPJMNIfndJLnBDFw== + +"@swc/core-linux-arm64-gnu@1.7.22": + version "1.7.22" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-gnu/-/core-linux-arm64-gnu-1.7.22.tgz#8058a7c18385cadf56ca7d2db4d6fa41e52f4bd3" + integrity sha512-59FzDW/ojgiTj4dlnv3Z3ESuVlzhSAq9X12CNYh4/WTCNA8BoJqOnWMRQKspWtoNlnVviFLMvpek0pGXHndEBA== + +"@swc/core-linux-arm64-musl@1.7.22": + version "1.7.22" + resolved "https://registry.yarnpkg.com/@swc/core-linux-arm64-musl/-/core-linux-arm64-musl-1.7.22.tgz#8d29b9574ad3ff615fcba85b6e661d510f48186e" + integrity sha512-cMQMI8YRO/XR3OrYuiUlWksNsJOZSkA6gSLNyH6eHTw+FOAzv05oJ4SFYe6s1WesrOqRwhpez6y5H6OIP/EKzg== + +"@swc/core-linux-x64-gnu@1.7.22": + version "1.7.22" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-gnu/-/core-linux-x64-gnu-1.7.22.tgz#ad863612d32c00ec28b3dc3fc1a8418ca9c94f2b" + integrity sha512-639kA7MXrWqWYfwuSJ+XTg21VYb/5o99R1zJrndoEjEX6m7Wza/sXssQKU5jbbkPoSEKVKNP3n/gazLWiUKgiQ== + +"@swc/core-linux-x64-musl@1.7.22": + version "1.7.22" + resolved "https://registry.yarnpkg.com/@swc/core-linux-x64-musl/-/core-linux-x64-musl-1.7.22.tgz#421740384a738631b1bbc6202eeb5489ed5e356b" + integrity sha512-f3zfGgY8EJQUOk3ve25ZTkNkhB/kHo9QlN2r+0exaE1g9W7X8IS6J8pWzF3hJrV2P9dBi6ofMOt+opVA89JKHA== + +"@swc/core-win32-arm64-msvc@1.7.22": + version "1.7.22" + resolved "https://registry.yarnpkg.com/@swc/core-win32-arm64-msvc/-/core-win32-arm64-msvc-1.7.22.tgz#ba62979f394faabe8a4c964e5590169269d8170c" + integrity sha512-p/Fav5U+LtTJD/tbbS0dKK8SVVAhXo5Jdm1TDeBPJ4BEIVguYBZEXgD3CW9wY4K34g1hscpiz2Q2rktfhFj1+A== + +"@swc/core-win32-ia32-msvc@1.7.22": + version "1.7.22" + resolved "https://registry.yarnpkg.com/@swc/core-win32-ia32-msvc/-/core-win32-ia32-msvc-1.7.22.tgz#7fe30501e853298a552c2bd89306cc41d15f95c8" + integrity sha512-HbmfasaCNTqeCTvDjleYj+jJZQ6MlraiVOdhW55KtbA9mAVQdPBq6DDAvR7VOero3wUNYUM/e36otFKgEJI5Rg== + +"@swc/core-win32-x64-msvc@1.7.22": + version "1.7.22" + resolved "https://registry.yarnpkg.com/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.7.22.tgz#8be5c1f89c61c0d6412093398274b3cbc1b4b52f" + integrity sha512-lppIveE+hpe7WXny/9cUT+T6sBM/ND0E+dviKWJ5jFBISj2KWomlSJGUjYEsRGJVPnTEc8uOlKK7etmXBhQx9A== + +"@swc/core@^1.7.22": + version "1.7.22" + resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.7.22.tgz#fe7d515bba08fdd29937eae6dc40077459634087" + integrity sha512-Asn79WKqyjEuO2VEeSnVjn2YiRMToRhFJwOsQeqftBvwWMn1FGUuzVcXtkQFBk37si8Gh2Vkk/+p0u4K5NxDig== + dependencies: + "@swc/counter" "^0.1.3" + "@swc/types" "^0.1.12" optionalDependencies: - "@swc/core-darwin-arm64" "1.3.96" - "@swc/core-darwin-x64" "1.3.96" - "@swc/core-linux-arm-gnueabihf" "1.3.96" - "@swc/core-linux-arm64-gnu" "1.3.96" - "@swc/core-linux-arm64-musl" "1.3.96" - "@swc/core-linux-x64-gnu" "1.3.96" - "@swc/core-linux-x64-musl" "1.3.96" - "@swc/core-win32-arm64-msvc" "1.3.96" - "@swc/core-win32-ia32-msvc" "1.3.96" - "@swc/core-win32-x64-msvc" "1.3.96" - -"@swc/counter@^0.1.1": - version "0.1.2" - resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.2.tgz#bf06d0770e47c6f1102270b744e17b934586985e" - integrity sha512-9F4ys4C74eSTEUNndnER3VJ15oru2NumfQxS8geE+f3eB5xvfxpWyqE5XlVnxb/R14uoXi6SLbBwwiDSkv+XEw== + "@swc/core-darwin-arm64" "1.7.22" + "@swc/core-darwin-x64" "1.7.22" + "@swc/core-linux-arm-gnueabihf" "1.7.22" + "@swc/core-linux-arm64-gnu" "1.7.22" + "@swc/core-linux-arm64-musl" "1.7.22" + "@swc/core-linux-x64-gnu" "1.7.22" + "@swc/core-linux-x64-musl" "1.7.22" + "@swc/core-win32-arm64-msvc" "1.7.22" + "@swc/core-win32-ia32-msvc" "1.7.22" + "@swc/core-win32-x64-msvc" "1.7.22" + +"@swc/counter@^0.1.3": + version "0.1.3" + resolved "https://registry.yarnpkg.com/@swc/counter/-/counter-0.1.3.tgz#cc7463bd02949611c6329596fccd2b0ec782b0e9" + integrity sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ== -"@swc/types@^0.1.5": - version "0.1.5" - resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.5.tgz#043b731d4f56a79b4897a3de1af35e75d56bc63a" - integrity sha512-myfUej5naTBWnqOCc/MdVOLVjXUXtIA+NpDrDBKJtLLg2shUjBu3cZmB/85RyitKc55+lUUyl7oRfLOvkr2hsw== +"@swc/jest@^0.2.36": + version "0.2.36" + resolved "https://registry.yarnpkg.com/@swc/jest/-/jest-0.2.36.tgz#2797450a30d28b471997a17e901ccad946fe693e" + integrity sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw== + dependencies: + "@jest/create-cache-key-function" "^29.7.0" + "@swc/counter" "^0.1.3" + jsonc-parser "^3.2.0" + +"@swc/types@^0.1.12": + version "0.1.12" + resolved "https://registry.yarnpkg.com/@swc/types/-/types-0.1.12.tgz#7f632c06ab4092ce0ebd046ed77ff7557442282f" + integrity sha512-wBJA+SdtkbFhHjTMYH+dEH1y4VpfGdAc2Kw/LK09i9bXd/K6j6PkDcFCEzb6iVfZMkPRrl/q0e3toqTAJdkIVA== + dependencies: + "@swc/counter" "^0.1.3" "@tailwindcss/container-queries@^0.1.0": version "0.1.0" @@ -16120,6 +16242,11 @@ jsonc-parser@3.2.0: resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.2.0.tgz#31ff3f4c2b9793f89c67212627c51c6394f88e76" integrity sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w== +jsonc-parser@^3.2.0: + version "3.3.1" + resolved "https://registry.yarnpkg.com/jsonc-parser/-/jsonc-parser-3.3.1.tgz#f2a524b4f7fd11e3d791e559977ad60b98b798b4" + integrity sha512-HUgH65KyejrUFPvHFPbqOY0rsFip3Bo5wb4ngvdi1EpCYWUQDC5V+Y7mZws+DLkr4M//zQJoanu1SP+87Dv1oQ== + jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" @@ -20406,6 +20533,28 @@ rope-sequence@^1.3.0: resolved "https://registry.yarnpkg.com/rope-sequence/-/rope-sequence-1.3.3.tgz#3f67fc106288b84b71532b4a5fd9d4881e4457f0" integrity sha512-85aZYCxweiD5J8yTEbw+E6A27zSnLPNDL0WfPdw3YYodq7WjnTKo0q4dtyQ2gz23iPT8Q9CUyJtAaUNcTxRf5Q== +rrule-rust@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/rrule-rust/-/rrule-rust-2.0.2.tgz#53d0f94d3ccf280ee6ce308603665fc49e53500a" + integrity sha512-G7esOuMCM2VoJtXJhyo/ABx+GayRQBXiMx1wfOPor2Rt8bW0HT+raSFcT6hgAnPQHLFqgAjPuu2ldYp3CCOuDQ== + optionalDependencies: + "@rrule-rust/lib-android-arm-eabi" "2.0.2" + "@rrule-rust/lib-android-arm64" "2.0.2" + "@rrule-rust/lib-darwin-arm64" "2.0.2" + "@rrule-rust/lib-darwin-universal" "2.0.2" + "@rrule-rust/lib-darwin-x64" "2.0.2" + "@rrule-rust/lib-freebsd-x64" "2.0.2" + "@rrule-rust/lib-linux-arm-gnueabihf" "2.0.2" + "@rrule-rust/lib-linux-arm-musleabihf" "2.0.2" + "@rrule-rust/lib-linux-arm64-gnu" "2.0.2" + "@rrule-rust/lib-linux-arm64-musl" "2.0.2" + "@rrule-rust/lib-linux-riscv64-gnu" "2.0.2" + "@rrule-rust/lib-linux-x64-gnu" "2.0.2" + "@rrule-rust/lib-linux-x64-musl" "2.0.2" + "@rrule-rust/lib-win32-arm64-msvc" "2.0.2" + "@rrule-rust/lib-win32-ia32-msvc" "2.0.2" + "@rrule-rust/lib-win32-x64-msvc" "2.0.2" + rrule@^2.7.2: version "2.7.2" resolved "https://registry.yarnpkg.com/rrule/-/rrule-2.7.2.tgz#cb899cb21bbe6c71b587107956604173b69f9143" @@ -21288,7 +21437,7 @@ string-similarity@^3.0.0: resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-3.0.0.tgz#07b0bc69fae200ad88ceef4983878d03793847c7" integrity sha512-7kS7LyTp56OqOI2BDWQNVnLX/rCxIQn+/5M0op1WV6P8Xx6TZNdajpuqQdiJ7Xx+p1C5CsWMvdiBp9ApMhxzEQ== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -21306,15 +21455,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.0.tgz#5ab00980cfb29f43e736b113a120a73a0fb569d3" @@ -21386,7 +21526,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -21400,13 +21540,6 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -23249,7 +23382,7 @@ workbox-window@6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.5.4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -23267,15 +23400,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 45714ea7f172bb7d72803f945c134e9889e32718 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 12:22:06 -0700 Subject: [PATCH 458/529] chore(release): release v7.47.1 (#10211) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b18a21909f4..1dbde5c5129 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.47.0" + ".": "7.47.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index e802901b38c..aa474bed114 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.47.1](https://github.com/ParabolInc/parabol/compare/v7.47.0...v7.47.1) (2024-09-11) + + +### Fixed + +* move to rrule-rust ([#10181](https://github.com/ParabolInc/parabol/issues/10181)) ([2952c3d](https://github.com/ParabolInc/parabol/commit/2952c3d88b60aa3ebc6001ecd10eca10357b7570)) + + +### Changed + +* **rethinkdb:** ReflectPrompt: Phase 1 ([#10193](https://github.com/ParabolInc/parabol/issues/10193)) ([e48732b](https://github.com/ParabolInc/parabol/commit/e48732b73e1c52de05ded693bc5e8b69d628dcba)) + ## [7.47.0](https://github.com/ParabolInc/parabol/compare/v7.46.3...v7.47.0) (2024-09-10) diff --git a/package.json b/package.json index 66451df6b23..f8b8937af3b 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.47.0", + "version": "7.47.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 0dfab9d0cde..9455a79c6ed 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.47.0", + "version": "7.47.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.47.0" + "parabol-server": "7.47.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index 82dbeb92452..2b37531f23b 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.47.0", + "version": "7.47.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 7eb1911f09c..d64723e0d06 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.47.0", + "version": "7.47.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 34cf9beafaa..b550c6762e7 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.47.0", + "version": "7.47.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.47.0", - "parabol-server": "7.47.0", + "parabol-client": "7.47.1", + "parabol-server": "7.47.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index f6a05744a67..e02226d48a6 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.47.0", + "version": "7.47.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 7f0a4f34b6a..e9a11af39dc 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.47.0", + "version": "7.47.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.47.0", + "parabol-client": "7.47.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 3fddb9750746c5b27ab6d9c59d9e785e5d7d6cd0 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 11 Sep 2024 14:46:59 -0700 Subject: [PATCH 459/529] chore(rethinkdb): ReflectPhase: Phase 2 (#10208) Signed-off-by: Matt Krick --- .../1725913333530_ReflectPrompt-phase2.ts | 169 ++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 packages/server/postgres/migrations/1725913333530_ReflectPrompt-phase2.ts diff --git a/packages/server/postgres/migrations/1725913333530_ReflectPrompt-phase2.ts b/packages/server/postgres/migrations/1725913333530_ReflectPrompt-phase2.ts new file mode 100644 index 00000000000..5654c71dc22 --- /dev/null +++ b/packages/server/postgres/migrations/1725913333530_ReflectPrompt-phase2.ts @@ -0,0 +1,169 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +const START_CHAR_CODE = 32 +const END_CHAR_CODE = 126 + +export function positionAfter(pos: string) { + for (let i = pos.length - 1; i >= 0; i--) { + const curCharCode = pos.charCodeAt(i) + if (curCharCode < END_CHAR_CODE) { + return pos.substr(0, i) + String.fromCharCode(curCharCode + 1) + } + } + return pos + String.fromCharCode(START_CHAR_CODE + 1) +} + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + try { + console.log('Adding index') + await r + .table('ReflectPrompt') + .indexCreate('updatedAtId', (row: any) => [row('updatedAt'), row('id')]) + .run() + await r.table('ReflectPrompt').indexWait().run() + } catch { + // index already exists + } + + console.log('Adding index complete') + + const MAX_PG_PARAMS = 65545 + const PG_COLS = [ + 'id', + 'createdAt', + 'updatedAt', + 'removedAt', + 'description', + 'groupColor', + 'sortOrder', + 'question', + 'teamId', + 'templateId', + 'parentPromptId' + ] as const + type ReflectPrompt = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + + let curUpdatedAt = r.minval + let curId = r.minval + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, String(curUpdatedAt), String(curId)) + const rawRowsToInsert = (await r + .table('ReflectPrompt') + .between([curUpdatedAt, curId], [r.maxval, r.maxval], { + index: 'updatedAtId', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'updatedAtId'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as ReflectPrompt[] + + const rowsToInsert = rawRowsToInsert.map((row) => { + const {description, groupColor, sortOrder, question, ...rest} = row as any + return { + ...rest, + description: description?.slice(0, 256) ?? '', + groupColor: groupColor?.slice(0, 9) ?? '#66BC8C', + sortOrder: String(sortOrder), + question: question?.slice(0, 100) ?? '' + } + }) + + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curUpdatedAt = lastRow.updatedAt + curId = lastRow.id + await Promise.all( + rowsToInsert.map(async (row) => { + try { + await pg + .insertInto('ReflectPrompt') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_templateId' || e.constraint === 'fk_teamId') { + console.log('Missing templateId or teamId', row.id) + return + } + console.log(e, row) + } + }) + ) + } + + // remap the sortOrder in PG because rethinkdb is too slow to group + console.log('Correcting sortOrder') + const pgRows = await sql<{items: {sortOrder: string; id: string}[]}>` + select jsonb_agg(jsonb_build_object('sortOrder', "sortOrder", 'id', "id", 'templateId', "templateId") ORDER BY "sortOrder") items from "ReflectPrompt" +group by "templateId";`.execute(pg) + + const groups = pgRows.rows.map((row) => { + const {items} = row + let curSortOrder = '' + for (let i = 0; i < items.length; i++) { + const item = items[i] + curSortOrder = positionAfter(curSortOrder) + item.sortOrder = curSortOrder + } + return row + }) + for (let i = 0; i < groups.length; i++) { + const group = groups[i] + await Promise.all( + group.items.map((item) => { + return pg + .updateTable('ReflectPrompt') + .set({sortOrder: item.sortOrder}) + .where('id', '=', item.id) + .execute() + }) + ) + } + + // if the threadParentId references an id that does not exist, set it to null + console.log('adding parentPromptId constraint') + await pg + .updateTable('ReflectPrompt') + .set({parentPromptId: null}) + .where(({eb, selectFrom}) => + eb( + 'id', + 'in', + selectFrom('ReflectPrompt as child') + .select('child.id') + .leftJoin('ReflectPrompt as parent', 'child.parentPromptId', 'parent.id') + .where('parent.id', 'is', null) + .where('child.parentPromptId', 'is not', null) + ) + ) + .execute() + await pg.schema + .alterTable('ReflectPrompt') + .addForeignKeyConstraint('fk_parentPromptId', ['parentPromptId'], 'ReflectPrompt', ['id']) + .onDelete('set null') + .execute() +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql`TRUNCATE TABLE "ReflectPrompt" CASCADE`.execute(pg) +} From 1131785e26e870909f5cb2db2c99322b007e4546 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 11 Sep 2024 14:47:10 -0700 Subject: [PATCH 460/529] chore(rethinkdb): ReflectPhase: Phase 3 (#10209) Signed-off-by: Matt Krick --- codegen.json | 2 +- .../components/GroupingKanbanColumn.tsx | 48 +++---------- .../meeting/components/AddTemplatePrompt.tsx | 6 +- .../meeting/components/TemplatePromptList.tsx | 16 +---- .../AddReflectTemplatePromptMutation.ts | 2 +- .../MoveReflectTemplatePromptMutation.ts | 2 +- packages/server/database/rethinkDriver.ts | 5 -- .../database/types/RetrospectivePrompt.ts | 48 ------------- .../dataloader/foreignKeyLoaderMakers.ts | 4 +- .../dataloader/primaryKeyLoaderMakers.ts | 2 +- .../rethinkForeignKeyLoaderMakers.ts | 13 ---- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../mutations/removeReflectTemplate.ts | 33 +++------ .../graphql/mutations/updateTemplateScope.ts | 30 ++++---- .../mutations/generateMeetingSummary.ts | 2 +- .../public/mutations/addReflectTemplate.ts | 7 -- .../mutations/addReflectTemplatePrompt.ts | 18 ++--- .../public/mutations/helpers/getTopics.ts | 2 +- .../mutations/moveReflectTemplatePrompt.ts | 28 +------- .../reflectTemplatePromptUpdateDescription.ts | 23 ++---- .../reflectTemplatePromptUpdateGroupColor.ts | 15 +--- .../mutations/removeReflectTemplatePrompt.ts | 20 ++---- .../mutations/renameReflectTemplatePrompt.ts | 23 ++---- .../graphql/public/typeDefs/Mutation.graphql | 2 +- .../public/typeDefs/ReflectPrompt.graphql | 2 +- .../graphql/public/typeDefs/Team.graphql | 1 - .../types/AddReflectTemplatePromptPayload.ts | 4 +- .../types/MoveReflectTemplatePromptPayload.ts | 4 +- .../graphql/public/types/ReflectPhase.ts | 2 +- .../graphql/public/types/ReflectPrompt.ts | 13 ++++ ...tTemplatePromptUpdateDescriptionPayload.ts | 4 +- ...ctTemplatePromptUpdateGroupColorPayload.ts | 4 +- .../RemoveReflectTemplatePromptPayload.ts | 4 +- .../RenameReflectTemplatePromptPayload.ts | 4 +- .../graphql/public/types/RetroReflection.ts | 2 +- .../public/types/RetroReflectionGroup.ts | 2 +- .../server/graphql/types/ReflectPrompt.ts | 70 ------------------- packages/server/graphql/types/Team.ts | 9 --- ...1685721573097_addPrePostMortemTemplates.ts | 3 +- ...96443115482_addCustomerFeedbackTemplate.ts | 3 +- .../1696616922662_addPeerReviewTemplates.ts | 3 +- 41 files changed, 104 insertions(+), 382 deletions(-) delete mode 100644 packages/server/database/types/RetrospectivePrompt.ts create mode 100644 packages/server/graphql/public/types/ReflectPrompt.ts delete mode 100644 packages/server/graphql/types/ReflectPrompt.ts diff --git a/codegen.json b/codegen.json index b64f821cd74..39b03e6f474 100644 --- a/codegen.json +++ b/codegen.json @@ -149,7 +149,7 @@ "RRule": "rrule-rust#RRuleSet", "Reactable": "../../database/types/Reactable#Reactable", "Reactji": "../types/Reactji#ReactjiSource", - "ReflectPrompt": "../../database/types/RetrospectivePrompt#default", + "ReflectPrompt": "../../postgres/types/index#ReflectPrompt", "ReflectTemplate": "../../database/types/ReflectTemplate#default", "RemoveApprovedOrganizationDomainsSuccess": "./types/RemoveApprovedOrganizationDomainsSuccess#RemoveApprovedOrganizationDomainsSuccessSource", "RemoveIntegrationSearchQuerySuccess": "./types/RemoveIntegrationSearchQuerySuccess#RemoveIntegrationSearchQuerySuccessSource", diff --git a/packages/client/components/GroupingKanbanColumn.tsx b/packages/client/components/GroupingKanbanColumn.tsx index 575496872bd..cc44b48af09 100644 --- a/packages/client/components/GroupingKanbanColumn.tsx +++ b/packages/client/components/GroupingKanbanColumn.tsx @@ -8,46 +8,15 @@ import {GroupingKanbanColumn_reflectionGroups$key} from '~/__generated__/Groupin import {useCoverable} from '~/hooks/useControlBarCovers' import useDeepEqual from '~/hooks/useDeepEqual' import useSubColumns from '~/hooks/useSubColumns' -import makeMinWidthMediaQuery from '~/utils/makeMinWidthMediaQuery' import useAtmosphere from '../hooks/useAtmosphere' import useMutationProps from '../hooks/useMutationProps' import CreateReflectionMutation from '../mutations/CreateReflectionMutation' -import {PALETTE} from '../styles/paletteV3' -import { - BezierCurve, - Breakpoint, - DragAttribute, - ElementWidth, - MeetingControlBarEnum -} from '../types/constEnums' +import {BezierCurve, DragAttribute, ElementWidth, MeetingControlBarEnum} from '../types/constEnums' import getNextSortOrder from '../utils/getNextSortOrder' import {SwipeColumn} from './GroupingKanban' import GroupingKanbanColumnHeader from './GroupingKanbanColumnHeader' import ReflectionGroup from './ReflectionGroup/ReflectionGroup' -const Column = styled('div')<{ - isLengthExpanded: boolean - isFirstColumn: boolean - isLastColumn: boolean -}>(({isLengthExpanded, isFirstColumn, isLastColumn}) => ({ - alignContent: 'flex-start', - background: PALETTE.SLATE_300, - borderRadius: 8, - display: 'flex', - flex: 1, - flexDirection: 'column', - height: '100%', - minWidth: ElementWidth.REFLECTION_COLUMN, - padding: 0, - position: 'relative', - transition: `all 100ms ${BezierCurve.DECELERATE}`, - [makeMinWidthMediaQuery(Breakpoint.SINGLE_REFLECTION_COLUMN)]: { - height: isLengthExpanded ? '100%' : `calc(100% - ${MeetingControlBarEnum.HEIGHT}px)`, - margin: `0 ${isLastColumn ? 16 : 8}px 0px ${isFirstColumn ? 16 : 8}px`, - maxWidth: 'min-content' - } -})) - const ColumnScrollContainer = styled('div')({ display: 'flex', // must hide X on firefox v84 @@ -160,8 +129,6 @@ const GroupingKanbanColumn = (props: Props) => { const isLengthExpanded = useCoverable(promptId, columnRef, MeetingControlBarEnum.HEIGHT, phaseRef, columnsRef) || !!endedAt - const isFirstColumn = prompt.sortOrder === 0 - const isLastColumn = Math.round(prompt.sortOrder) === reflectPromptsCount - 1 const groups = useDeepEqual(reflectionGroups) // group may be undefined because relay could GC before useMemo in the Kanban recomputes >:-( const filteredReflectionGroups = useMemo( @@ -188,12 +155,13 @@ const GroupingKanbanColumn = (props: Props) => { submitMutation() CreateReflectionMutation(atmosphere, {input}, {onError, onCompleted}) } - return ( - @@ -240,7 +208,7 @@ const GroupingKanbanColumn = (props: Props) => { ) })} - +
) } diff --git a/packages/client/modules/meeting/components/AddTemplatePrompt.tsx b/packages/client/modules/meeting/components/AddTemplatePrompt.tsx index 1e97063dbe1..886dae9fdd2 100644 --- a/packages/client/modules/meeting/components/AddTemplatePrompt.tsx +++ b/packages/client/modules/meeting/components/AddTemplatePrompt.tsx @@ -8,7 +8,7 @@ import {Threshold} from '~/types/constEnums' import {AddTemplatePrompt_prompts$key} from '../../../__generated__/AddTemplatePrompt_prompts.graphql' import LinkButton from '../../../components/LinkButton' import AddReflectTemplatePromptMutation from '../../../mutations/AddReflectTemplatePromptMutation' -import dndNoise from '../../../utils/dndNoise' +import {positionAfter} from '../../../shared/sortOrder' import withMutationProps, {WithMutationProps} from '../../../utils/relay/withMutationProps' const AddPromptLink = styled(LinkButton)({ @@ -51,8 +51,8 @@ const AddTemplatePrompt = (props: Props) => { const {templateId, onError, onCompleted, submitMutation, submitting} = props if (submitting) return submitMutation() - const sortOrders = prompts.map(({sortOrder}) => sortOrder) - const sortOrder = Math.max(0, ...sortOrders) + 1 + dndNoise() + const lastPrompt = prompts.at(-1)! + const sortOrder = positionAfter(lastPrompt.sortOrder) const promptCount = prompts.length AddReflectTemplatePromptMutation( atmosphere, diff --git a/packages/client/modules/meeting/components/TemplatePromptList.tsx b/packages/client/modules/meeting/components/TemplatePromptList.tsx index 8c7c8317e05..dd9e9fa14e3 100644 --- a/packages/client/modules/meeting/components/TemplatePromptList.tsx +++ b/packages/client/modules/meeting/components/TemplatePromptList.tsx @@ -6,8 +6,8 @@ import {useFragment} from 'react-relay' import {TemplatePromptList_prompts$key} from '../../../__generated__/TemplatePromptList_prompts.graphql' import useAtmosphere from '../../../hooks/useAtmosphere' import MoveReflectTemplatePromptMutation from '../../../mutations/MoveReflectTemplatePromptMutation' +import {getSortOrder} from '../../../shared/sortOrder' import {TEMPLATE_PROMPT} from '../../../utils/constants' -import dndNoise from '../../../utils/dndNoise' import TemplatePromptItem from './TemplatePromptItem' interface Props { @@ -53,19 +53,7 @@ const TemplatePromptList = (props: Props) => { ) { return } - - let sortOrder - if (destination.index === 0) { - sortOrder = destinationPrompt.sortOrder - 1 + dndNoise() - } else if (destination.index === prompts.length - 1) { - sortOrder = destinationPrompt.sortOrder + 1 + dndNoise() - } else { - const offset = source.index > destination.index ? -1 : 1 - sortOrder = - ((prompts[destination.index + offset]?.sortOrder ?? 0) + destinationPrompt.sortOrder) / 2 + - dndNoise() - } - + const sortOrder = getSortOrder(prompts, source.index, destination.index) const {id: promptId} = sourcePrompt const variables = {promptId, sortOrder} MoveReflectTemplatePromptMutation(atmosphere, variables, {templateId}) diff --git a/packages/client/mutations/AddReflectTemplatePromptMutation.ts b/packages/client/mutations/AddReflectTemplatePromptMutation.ts index 9daa5ec6a5f..5b92751cfdd 100644 --- a/packages/client/mutations/AddReflectTemplatePromptMutation.ts +++ b/packages/client/mutations/AddReflectTemplatePromptMutation.ts @@ -8,7 +8,7 @@ import handleAddReflectTemplatePrompt from './handlers/handleAddReflectTemplateP interface Handlers extends BaseLocalHandlers { promptCount: number - sortOrder: number + sortOrder: string } graphql` diff --git a/packages/client/mutations/MoveReflectTemplatePromptMutation.ts b/packages/client/mutations/MoveReflectTemplatePromptMutation.ts index 531d10357dc..3b87703c536 100644 --- a/packages/client/mutations/MoveReflectTemplatePromptMutation.ts +++ b/packages/client/mutations/MoveReflectTemplatePromptMutation.ts @@ -18,7 +18,7 @@ graphql` ` const mutation = graphql` - mutation MoveReflectTemplatePromptMutation($promptId: ID!, $sortOrder: Float!) { + mutation MoveReflectTemplatePromptMutation($promptId: ID!, $sortOrder: String!) { moveReflectTemplatePrompt(promptId: $promptId, sortOrder: $sortOrder) { error { message diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index 055b5800142..d2ba61b2728 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -16,14 +16,9 @@ import NotificationTeamArchived from './types/NotificationTeamArchived' import NotificationTeamInvitation from './types/NotificationTeamInvitation' import PasswordResetRequest from './types/PasswordResetRequest' import PushInvitation from './types/PushInvitation' -import RetrospectivePrompt from './types/RetrospectivePrompt' import Task from './types/Task' export type RethinkSchema = { - ReflectPrompt: { - type: RetrospectivePrompt - index: 'teamId' | 'templateId' - } MassInvitation: { type: MassInvitation index: 'teamMemberId' diff --git a/packages/server/database/types/RetrospectivePrompt.ts b/packages/server/database/types/RetrospectivePrompt.ts deleted file mode 100644 index 23f8b6b5373..00000000000 --- a/packages/server/database/types/RetrospectivePrompt.ts +++ /dev/null @@ -1,48 +0,0 @@ -import generateUID from '../../generateUID' - -interface Input { - teamId: string - templateId: string - sortOrder: number - question: string - description: string - groupColor: string - removedAt: Date | null - parentPromptId?: string | null -} - -export default class RetrospectivePrompt { - id: string - createdAt = new Date() - description: string - groupColor: string - sortOrder: number - teamId: string - templateId: string - question: string - removedAt: Date | null - updatedAt = new Date() - parentPromptId?: string | null - - constructor(input: Input) { - const { - teamId, - templateId, - sortOrder, - question, - description, - groupColor, - removedAt, - parentPromptId - } = input - this.id = generateUID() - this.sortOrder = sortOrder - this.teamId = teamId - this.templateId = templateId - this.question = question - this.description = description || '' - this.groupColor = groupColor - this.removedAt = removedAt - this.parentPromptId = parentPromptId || null - } -} diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 2c2381b23a9..77b010bc58e 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -217,8 +217,8 @@ export const commentsByDiscussionId = foreignKeyLoaderMaker( } ) -export const _pgreflectPromptsByTemplateId = foreignKeyLoaderMaker( - '_pgreflectPrompts', +export const reflectPromptsByTemplateId = foreignKeyLoaderMaker( + 'reflectPrompts', 'templateId', async (templateIds) => { return selectReflectPrompts() diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index e80d3785659..24a45d0b660 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -112,6 +112,6 @@ export const comments = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectComments().where('id', 'in', ids).execute() }) -export const _pgreflectPrompts = primaryKeyLoaderMaker((ids: readonly string[]) => { +export const reflectPrompts = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectReflectPrompts().where('id', 'in', ids).execute() }) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index b0bf83cf10a..15317dc9d3c 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -29,19 +29,6 @@ export const completedMeetingsByTeamId = new RethinkForeignKeyLoaderMaker( } ) -export const reflectPromptsByTemplateId = new RethinkForeignKeyLoaderMaker( - 'reflectPrompts', - 'templateId', - async (templateIds) => { - const r = await getRethink() - return r - .table('ReflectPrompt') - .getAll(r.args(templateIds), {index: 'templateId'}) - .orderBy('sortOrder') - .run() - } -) - export const massInvitationsByTeamMemberId = new RethinkForeignKeyLoaderMaker( 'massInvitations', 'teamMemberId', diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index 742b1b9ea39..2a7baaebc59 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -3,7 +3,6 @@ import RethinkPrimaryKeyLoaderMaker from './RethinkPrimaryKeyLoaderMaker' /** * all rethink dataloader types which also must exist in {@link rethinkDriver/RethinkSchema} */ -export const reflectPrompts = new RethinkPrimaryKeyLoaderMaker('ReflectPrompt') export const massInvitations = new RethinkPrimaryKeyLoaderMaker('MassInvitation') export const meetingMembers = new RethinkPrimaryKeyLoaderMaker('MeetingMember') export const newMeetings = new RethinkPrimaryKeyLoaderMaker('NewMeeting') diff --git a/packages/server/graphql/mutations/removeReflectTemplate.ts b/packages/server/graphql/mutations/removeReflectTemplate.ts index 14224dac4da..0518be77d31 100644 --- a/packages/server/graphql/mutations/removeReflectTemplate.ts +++ b/packages/server/graphql/mutations/removeReflectTemplate.ts @@ -1,6 +1,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' +import {sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -21,9 +21,7 @@ const removeReflectTemplate = { {templateId}: {templateId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { - const r = await getRethink() const pg = getKysely() - const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const template = await dataLoader.get('meetingTemplates').load(templateId) @@ -46,27 +44,14 @@ const removeReflectTemplate = { // RESOLUTION const {id: settingsId} = settings - await Promise.all([ - r - .table('ReflectPrompt') - .getAll(teamId, {index: 'teamId'}) - .filter({ - templateId - }) - .update({ - removedAt: now, - updatedAt: now - }) - .run(), - pg - .with('RemoveTemplate', (qb) => - qb.updateTable('MeetingTemplate').set({isActive: false}).where('id', '=', templateId) - ) - .updateTable('ReflectPrompt') - .set({removedAt: now}) - .where('templateId', '=', templateId) - .execute() - ]) + await pg + .with('RemoveTemplate', (qb) => + qb.updateTable('MeetingTemplate').set({isActive: false}).where('id', '=', templateId) + ) + .updateTable('ReflectPrompt') + .set({removedAt: sql`CURRENT_TIMESTAMP`}) + .where('templateId', '=', templateId) + .execute() dataLoader.clearAll('reflectPrompts') if (settings.selectedTemplateId === templateId) { const nextTemplate = templates.find((template) => template.id !== templateId) diff --git a/packages/server/graphql/mutations/updateTemplateScope.ts b/packages/server/graphql/mutations/updateTemplateScope.ts index 1b7c7aeeca1..41a0c48b590 100644 --- a/packages/server/graphql/mutations/updateTemplateScope.ts +++ b/packages/server/graphql/mutations/updateTemplateScope.ts @@ -100,23 +100,19 @@ const updateTemplateScope = { removedAt: null } }) - await Promise.all([ - pg - .with('MeetingTemplateInsert', (qc) => - qc.insertInto('MeetingTemplate').values(clonedTemplate) - ) - .with('MeetingTemplateDeactivate', (qc) => - qc.updateTable('MeetingTemplate').set({isActive: false}).where('id', '=', templateId) - ) - .with('RemovePrompts', (qc) => - qc.updateTable('ReflectPrompt').set({removedAt: now}).where('id', 'in', promptIds) - ) - .insertInto('ReflectPrompt') - .values(clonedPrompts.map((p) => ({...p, sortOrder: String(p.sortOrder)}))) - .execute(), - r.table('ReflectPrompt').insert(clonedPrompts).run(), - r.table('ReflectPrompt').getAll(r.args(promptIds)).update({removedAt: now}).run() - ]) + await pg + .with('MeetingTemplateInsert', (qc) => + qc.insertInto('MeetingTemplate').values(clonedTemplate) + ) + .with('MeetingTemplateDeactivate', (qc) => + qc.updateTable('MeetingTemplate').set({isActive: false}).where('id', '=', templateId) + ) + .with('RemovePrompts', (qc) => + qc.updateTable('ReflectPrompt').set({removedAt: now}).where('id', 'in', promptIds) + ) + .insertInto('ReflectPrompt') + .values(clonedPrompts.map((p) => ({...p, sortOrder: String(p.sortOrder)}))) + .execute() dataLoader.clearAll(['reflectPrompts', 'meetingTemplates']) } diff --git a/packages/server/graphql/private/mutations/generateMeetingSummary.ts b/packages/server/graphql/private/mutations/generateMeetingSummary.ts index 7be80c0d07b..8dbb9b5a071 100644 --- a/packages/server/graphql/private/mutations/generateMeetingSummary.ts +++ b/packages/server/graphql/private/mutations/generateMeetingSummary.ts @@ -119,7 +119,7 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn rawReflections.map(async (reflection) => { const {promptId, creatorId, plaintextContent} = reflection const [prompt, creator] = await Promise.all([ - dataLoader.get('reflectPrompts').load(promptId), + dataLoader.get('reflectPrompts').loadNonNull(promptId), creatorId ? dataLoader.get('users').loadNonNull(creatorId) : null ]) const {question} = prompt diff --git a/packages/server/graphql/public/mutations/addReflectTemplate.ts b/packages/server/graphql/public/mutations/addReflectTemplate.ts index 993abe9dc0f..c6ea27edc4b 100644 --- a/packages/server/graphql/public/mutations/addReflectTemplate.ts +++ b/packages/server/graphql/public/mutations/addReflectTemplate.ts @@ -1,6 +1,5 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {PALETTE} from '../../../../client/styles/paletteV3' -import getRethink from '../../../database/rethinkDriver' import ReflectTemplate from '../../../database/types/ReflectTemplate' import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' @@ -19,7 +18,6 @@ const addPokerTemplate: MutationResolvers['addPokerTemplate'] = async ( {authToken, dataLoader, socketId: mutatorId} ) => { const pg = getKysely() - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const viewerId = getUserId(authToken) @@ -92,7 +90,6 @@ const addPokerTemplate: MutationResolvers['addPokerTemplate'] = async ( } }) await Promise.all([ - r.table('ReflectPrompt').insert(newTemplatePrompts).run(), pg .with('MeetingTemplateInsert', (qc) => qc.insertInto('MeetingTemplate').values(newTemplate)) .insertInto('ReflectPrompt') @@ -121,10 +118,6 @@ const addPokerTemplate: MutationResolvers['addPokerTemplate'] = async ( const newTemplate = templates[0]! const {id: templateId} = newTemplate await Promise.all([ - r - .table('ReflectPrompt') - .insert(newTemplatePrompts.map((p, idx) => ({...p, sortOrder: idx}))) - .run(), pg .with('MeetingTemplateInsert', (qc) => qc.insertInto('MeetingTemplate').values(newTemplate)) .insertInto('ReflectPrompt') diff --git a/packages/server/graphql/public/mutations/addReflectTemplatePrompt.ts b/packages/server/graphql/public/mutations/addReflectTemplatePrompt.ts index 43de3440ea8..95557a587c0 100644 --- a/packages/server/graphql/public/mutations/addReflectTemplatePrompt.ts +++ b/packages/server/graphql/public/mutations/addReflectTemplatePrompt.ts @@ -1,9 +1,7 @@ import {SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' -import dndNoise from 'parabol-client/utils/dndNoise' import {positionAfter} from '../../../../client/shared/sortOrder' import palettePickerOptions from '../../../../client/styles/palettePickerOptions' import {PALETTE} from '../../../../client/styles/paletteV3' -import getRethink from '../../../database/rethinkDriver' import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' import {getUserId, isTeamMember} from '../../../utils/authorization' @@ -16,7 +14,6 @@ const addReflectTemplatePrompt: MutationResolvers['addReflectTemplatePrompt'] = {templateId}, {authToken, dataLoader, socketId: mutatorId} ) => { - const r = await getRethink() const pg = getKysely() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -41,10 +38,8 @@ const addReflectTemplatePrompt: MutationResolvers['addReflectTemplatePrompt'] = } // RESOLUTION - const lastPrompt = activePrompts.at(-1)! - const sortOrder = lastPrompt.sortOrder + 1 + dndNoise() - // can remove String coercion after ReflectPrompt is in PG - const pgSortOrder = positionAfter(String(lastPrompt.sortOrder)) + const lastPrompt = activePrompts.at(-1) + const sortOrder = positionAfter(lastPrompt?.sortOrder ?? '') const pickedColors = activePrompts.map((prompt) => prompt.groupColor) const availableNewColor = palettePickerOptions.find((color) => !pickedColors.includes(color.hex)) const reflectPrompt = { @@ -58,13 +53,8 @@ const addReflectTemplatePrompt: MutationResolvers['addReflectTemplatePrompt'] = removedAt: null } - await Promise.all([ - r.table('ReflectPrompt').insert(reflectPrompt).run(), - pg - .insertInto('ReflectPrompt') - .values({...reflectPrompt, sortOrder: pgSortOrder}) - .execute() - ]) + await pg.insertInto('ReflectPrompt').values(reflectPrompt).execute() + dataLoader.clearAll('reflectPrompts') const promptId = reflectPrompt.id const data = {promptId} diff --git a/packages/server/graphql/public/mutations/helpers/getTopics.ts b/packages/server/graphql/public/mutations/helpers/getTopics.ts index 94d23e228d9..4cb74174c5b 100644 --- a/packages/server/graphql/public/mutations/helpers/getTopics.ts +++ b/packages/server/graphql/public/mutations/helpers/getTopics.ts @@ -151,7 +151,7 @@ export const getTopics = async ( rawReflections.map(async (reflection) => { const {promptId, creatorId, plaintextContent} = reflection const [prompt, creator] = await Promise.all([ - dataLoader.get('reflectPrompts').load(promptId), + dataLoader.get('reflectPrompts').loadNonNull(promptId), creatorId ? dataLoader.get('users').loadNonNull(creatorId) : null ]) const {question} = prompt diff --git a/packages/server/graphql/public/mutations/moveReflectTemplatePrompt.ts b/packages/server/graphql/public/mutations/moveReflectTemplatePrompt.ts index f3a43d60055..93ff6a7024b 100644 --- a/packages/server/graphql/public/mutations/moveReflectTemplatePrompt.ts +++ b/packages/server/graphql/public/mutations/moveReflectTemplatePrompt.ts @@ -1,6 +1,4 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import {getSortOrder} from '../../../../client/shared/sortOrder' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {getUserId, isTeamMember} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -12,9 +10,7 @@ const moveReflectTemplatePrompt: MutationResolvers['moveReflectTemplatePrompt'] {promptId, sortOrder}, {authToken, dataLoader, socketId: mutatorId} ) => { - const r = await getRethink() const pg = getKysely() - const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const prompt = await dataLoader.get('reflectPrompts').load(promptId) @@ -30,30 +26,8 @@ const moveReflectTemplatePrompt: MutationResolvers['moveReflectTemplatePrompt'] // RESOLUTION const {teamId} = prompt - - const oldPrompts = await dataLoader.get('reflectPromptsByTemplateId').load(prompt.templateId) - const fromIdx = oldPrompts.findIndex((p) => p.id === promptId) - - await Promise.all([ - r - .table('ReflectPrompt') - .get(promptId) - .update({ - sortOrder, - updatedAt: now - }) - .run() - ]) + await pg.updateTable('ReflectPrompt').set({sortOrder}).where('id', '=', promptId).execute() dataLoader.clearAll('reflectPrompts') - const newPrompts = await dataLoader.get('reflectPromptsByTemplateId').load(prompt.templateId) - const pgPrompts = await dataLoader.get('_pgreflectPromptsByTemplateId').load(prompt.templateId) - const toIdx = newPrompts.findIndex((p) => p.id === promptId) - const pgSortOrder = getSortOrder(pgPrompts, fromIdx, toIdx) - await pg - .updateTable('ReflectPrompt') - .set({sortOrder: pgSortOrder}) - .where('id', '=', promptId) - .execute() const data = {promptId} publish(SubscriptionChannel.TEAM, teamId, 'MoveReflectTemplatePromptPayload', data, subOptions) return data diff --git a/packages/server/graphql/public/mutations/reflectTemplatePromptUpdateDescription.ts b/packages/server/graphql/public/mutations/reflectTemplatePromptUpdateDescription.ts index 5fc36d0fb48..1960399d7bc 100644 --- a/packages/server/graphql/public/mutations/reflectTemplatePromptUpdateDescription.ts +++ b/packages/server/graphql/public/mutations/reflectTemplatePromptUpdateDescription.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {getUserId, isTeamMember} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -8,9 +7,7 @@ import {MutationResolvers} from '../resolverTypes' const reflectTemplatePromptUpdateDescription: MutationResolvers['reflectTemplatePromptUpdateDescription'] = async (_source, {promptId, description}, {authToken, dataLoader, socketId: mutatorId}) => { - const r = await getRethink() const pg = getKysely() - const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const prompt = await dataLoader.get('reflectPrompts').load(promptId) @@ -29,21 +26,11 @@ const reflectTemplatePromptUpdateDescription: MutationResolvers['reflectTemplate const normalizedDescription = description.trim().slice(0, 256) || '' // RESOLUTION - await Promise.all([ - r - .table('ReflectPrompt') - .get(promptId) - .update({ - description: normalizedDescription, - updatedAt: now - }) - .run(), - pg - .updateTable('ReflectPrompt') - .set({description: normalizedDescription}) - .where('id', '=', promptId) - .execute() - ]) + await pg + .updateTable('ReflectPrompt') + .set({description: normalizedDescription}) + .where('id', '=', promptId) + .execute() dataLoader.clearAll('reflectPrompts') const data = {promptId} publish( diff --git a/packages/server/graphql/public/mutations/reflectTemplatePromptUpdateGroupColor.ts b/packages/server/graphql/public/mutations/reflectTemplatePromptUpdateGroupColor.ts index f6de5ffc8f2..55fb82ba522 100644 --- a/packages/server/graphql/public/mutations/reflectTemplatePromptUpdateGroupColor.ts +++ b/packages/server/graphql/public/mutations/reflectTemplatePromptUpdateGroupColor.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {getUserId, isTeamMember} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -8,9 +7,7 @@ import {MutationResolvers} from '../resolverTypes' const reflectTemplatePromptUpdateGroupColor: MutationResolvers['reflectTemplatePromptUpdateGroupColor'] = async (_source, {promptId, groupColor}, {authToken, dataLoader, socketId: mutatorId}) => { - const r = await getRethink() const pg = getKysely() - const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const viewerId = getUserId(authToken) @@ -29,17 +26,7 @@ const reflectTemplatePromptUpdateGroupColor: MutationResolvers['reflectTemplateP const {teamId} = prompt // RESOLUTION - await Promise.all([ - r - .table('ReflectPrompt') - .get(promptId) - .update({ - groupColor, - updatedAt: now - }) - .run(), - pg.updateTable('ReflectPrompt').set({groupColor}).where('id', '=', promptId).execute() - ]) + await pg.updateTable('ReflectPrompt').set({groupColor}).where('id', '=', promptId).execute() dataLoader.clearAll('reflectPrompts') const data = {promptId} publish( diff --git a/packages/server/graphql/public/mutations/removeReflectTemplatePrompt.ts b/packages/server/graphql/public/mutations/removeReflectTemplatePrompt.ts index c74b0b48b0a..71632eceb8f 100644 --- a/packages/server/graphql/public/mutations/removeReflectTemplatePrompt.ts +++ b/packages/server/graphql/public/mutations/removeReflectTemplatePrompt.ts @@ -1,5 +1,5 @@ +import {sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {getUserId, isTeamMember} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -11,9 +11,7 @@ const removeReflectTemplatePrompt: MutationResolvers['removeReflectTemplatePromp {promptId}, {authToken, dataLoader, socketId: mutatorId} ) => { - const r = await getRethink() const pg = getKysely() - const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const prompt = await dataLoader.get('reflectPrompts').load(promptId) @@ -38,17 +36,11 @@ const removeReflectTemplatePrompt: MutationResolvers['removeReflectTemplatePromp } // RESOLUTION - await Promise.all([ - r - .table('ReflectPrompt') - .get(promptId) - .update({ - removedAt: now, - updatedAt: now - }) - .run(), - pg.updateTable('ReflectPrompt').set({removedAt: now}).where('id', '=', promptId).execute() - ]) + await pg + .updateTable('ReflectPrompt') + .set({removedAt: sql`CURRENT_TIMESTAMP`}) + .where('id', '=', promptId) + .execute() dataLoader.clearAll('reflectPrompts') const data = {promptId, templateId} publish(SubscriptionChannel.TEAM, teamId, 'RemoveReflectTemplatePromptPayload', data, subOptions) diff --git a/packages/server/graphql/public/mutations/renameReflectTemplatePrompt.ts b/packages/server/graphql/public/mutations/renameReflectTemplatePrompt.ts index 426901b79e6..fc0805aeda6 100644 --- a/packages/server/graphql/public/mutations/renameReflectTemplatePrompt.ts +++ b/packages/server/graphql/public/mutations/renameReflectTemplatePrompt.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {getUserId, isTeamMember} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -11,9 +10,7 @@ const renameReflectTemplatePrompt: MutationResolvers['renameReflectTemplatePromp {promptId, question}, {authToken, dataLoader, socketId: mutatorId} ) => { - const r = await getRethink() const pg = getKysely() - const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const prompt = await dataLoader.get('reflectPrompts').load(promptId) @@ -39,21 +36,11 @@ const renameReflectTemplatePrompt: MutationResolvers['renameReflectTemplatePromp } // RESOLUTION - await Promise.all([ - r - .table('ReflectPrompt') - .get(promptId) - .update({ - question: normalizedQuestion, - updatedAt: now - }) - .run(), - pg - .updateTable('ReflectPrompt') - .set({question: normalizedQuestion}) - .where('id', '=', promptId) - .execute() - ]) + await pg + .updateTable('ReflectPrompt') + .set({question: normalizedQuestion}) + .where('id', '=', promptId) + .execute() dataLoader.clearAll('reflectPrompts') const data = {promptId} publish(SubscriptionChannel.TEAM, teamId, 'RenameReflectTemplatePromptPayload', data, subOptions) diff --git a/packages/server/graphql/public/typeDefs/Mutation.graphql b/packages/server/graphql/public/typeDefs/Mutation.graphql index 1ff66c7adb6..7b59ea7a5a5 100644 --- a/packages/server/graphql/public/typeDefs/Mutation.graphql +++ b/packages/server/graphql/public/typeDefs/Mutation.graphql @@ -433,7 +433,7 @@ type Mutation { """ Move a reflect template """ - moveReflectTemplatePrompt(promptId: ID!, sortOrder: Float!): MoveReflectTemplatePromptPayload + moveReflectTemplatePrompt(promptId: ID!, sortOrder: String!): MoveReflectTemplatePromptPayload """ Move a team to a different org. Requires billing leader rights on both orgs! diff --git a/packages/server/graphql/public/typeDefs/ReflectPrompt.graphql b/packages/server/graphql/public/typeDefs/ReflectPrompt.graphql index a6f9c9fd268..c9ab3bd801c 100644 --- a/packages/server/graphql/public/typeDefs/ReflectPrompt.graphql +++ b/packages/server/graphql/public/typeDefs/ReflectPrompt.graphql @@ -22,7 +22,7 @@ type ReflectPrompt { """ the order of the items in the template """ - sortOrder: Float! + sortOrder: String! """ FK for template diff --git a/packages/server/graphql/public/typeDefs/Team.graphql b/packages/server/graphql/public/typeDefs/Team.graphql index 6d197a733d5..3e8ee51d148 100644 --- a/packages/server/graphql/public/typeDefs/Team.graphql +++ b/packages/server/graphql/public/typeDefs/Team.graphql @@ -66,7 +66,6 @@ type Team { The datetime the team was last updated """ updatedAt: DateTime - customPhaseItems: [ReflectPrompt] @deprecated(reason: "Field no longer needs to exist for now") """ The outstanding invitations to join the team diff --git a/packages/server/graphql/public/types/AddReflectTemplatePromptPayload.ts b/packages/server/graphql/public/types/AddReflectTemplatePromptPayload.ts index 59482f4f743..57687fda374 100644 --- a/packages/server/graphql/public/types/AddReflectTemplatePromptPayload.ts +++ b/packages/server/graphql/public/types/AddReflectTemplatePromptPayload.ts @@ -8,7 +8,9 @@ export type AddReflectTemplatePromptPayloadSource = const AddReflectTemplatePromptPayload: AddReflectTemplatePromptPayloadResolvers = { prompt: (source, _args, {dataLoader}) => { - return 'promptId' in source ? dataLoader.get('reflectPrompts').load(source.promptId) : null + return 'promptId' in source + ? dataLoader.get('reflectPrompts').loadNonNull(source.promptId) + : null } } diff --git a/packages/server/graphql/public/types/MoveReflectTemplatePromptPayload.ts b/packages/server/graphql/public/types/MoveReflectTemplatePromptPayload.ts index 87096f79f55..16cc05e208e 100644 --- a/packages/server/graphql/public/types/MoveReflectTemplatePromptPayload.ts +++ b/packages/server/graphql/public/types/MoveReflectTemplatePromptPayload.ts @@ -8,7 +8,9 @@ export type MoveReflectTemplatePromptPayloadSource = const MoveReflectTemplatePromptPayload: MoveReflectTemplatePromptPayloadResolvers = { prompt: (source, _args, {dataLoader}) => { - return 'promptId' in source ? dataLoader.get('reflectPrompts').load(source.promptId) : null + return 'promptId' in source + ? dataLoader.get('reflectPrompts').loadNonNull(source.promptId) + : null } } diff --git a/packages/server/graphql/public/types/ReflectPhase.ts b/packages/server/graphql/public/types/ReflectPhase.ts index fb5bad8671e..8714fad14a0 100644 --- a/packages/server/graphql/public/types/ReflectPhase.ts +++ b/packages/server/graphql/public/types/ReflectPhase.ts @@ -5,7 +5,7 @@ const ReflectPhase: ReflectPhaseResolvers = { __isTypeOf: ({phaseType}) => phaseType === 'reflect', focusedPrompt: ({focusedPromptId}, _args, {dataLoader}) => { if (!focusedPromptId) return null - return dataLoader.get('reflectPrompts').load(focusedPromptId) + return dataLoader.get('reflectPrompts').loadNonNull(focusedPromptId) }, reflectPrompts: async ({meetingId}, _args, {dataLoader}) => { diff --git a/packages/server/graphql/public/types/ReflectPrompt.ts b/packages/server/graphql/public/types/ReflectPrompt.ts new file mode 100644 index 00000000000..f03675ef27f --- /dev/null +++ b/packages/server/graphql/public/types/ReflectPrompt.ts @@ -0,0 +1,13 @@ +import {ReflectPromptResolvers} from '../resolverTypes' + +const ReflectPrompt: ReflectPromptResolvers = { + team: ({teamId}, _args, {dataLoader}) => { + return dataLoader.get('teams').loadNonNull(teamId) + }, + + template: ({templateId}, _args, {dataLoader}) => { + return dataLoader.get('meetingTemplates').loadNonNull(templateId) + } +} + +export default ReflectPrompt diff --git a/packages/server/graphql/public/types/ReflectTemplatePromptUpdateDescriptionPayload.ts b/packages/server/graphql/public/types/ReflectTemplatePromptUpdateDescriptionPayload.ts index ab49737cd6e..defaec49b8d 100644 --- a/packages/server/graphql/public/types/ReflectTemplatePromptUpdateDescriptionPayload.ts +++ b/packages/server/graphql/public/types/ReflectTemplatePromptUpdateDescriptionPayload.ts @@ -9,7 +9,9 @@ export type ReflectTemplatePromptUpdateDescriptionPayloadSource = const ReflectTemplatePromptUpdateDescriptionPayload: ReflectTemplatePromptUpdateDescriptionPayloadResolvers = { prompt: (source, _args, {dataLoader}) => { - return 'promptId' in source ? dataLoader.get('reflectPrompts').load(source.promptId) : null + return 'promptId' in source + ? dataLoader.get('reflectPrompts').loadNonNull(source.promptId) + : null } } diff --git a/packages/server/graphql/public/types/ReflectTemplatePromptUpdateGroupColorPayload.ts b/packages/server/graphql/public/types/ReflectTemplatePromptUpdateGroupColorPayload.ts index c964750561f..93b4d5e12a7 100644 --- a/packages/server/graphql/public/types/ReflectTemplatePromptUpdateGroupColorPayload.ts +++ b/packages/server/graphql/public/types/ReflectTemplatePromptUpdateGroupColorPayload.ts @@ -9,7 +9,9 @@ export type ReflectTemplatePromptUpdateGroupColorPayloadSource = const ReflectTemplatePromptUpdateGroupColorPayload: ReflectTemplatePromptUpdateGroupColorPayloadResolvers = { prompt: (source, _args, {dataLoader}) => { - return 'promptId' in source ? dataLoader.get('reflectPrompts').load(source.promptId) : null + return 'promptId' in source + ? dataLoader.get('reflectPrompts').loadNonNull(source.promptId) + : null } } diff --git a/packages/server/graphql/public/types/RemoveReflectTemplatePromptPayload.ts b/packages/server/graphql/public/types/RemoveReflectTemplatePromptPayload.ts index 8e81e655db0..012ef4d918e 100644 --- a/packages/server/graphql/public/types/RemoveReflectTemplatePromptPayload.ts +++ b/packages/server/graphql/public/types/RemoveReflectTemplatePromptPayload.ts @@ -15,7 +15,9 @@ const RemoveReflectTemplatePromptPayload: RemoveReflectTemplatePromptPayloadReso }, prompt: (source, _args, {dataLoader}) => { - return 'promptId' in source ? dataLoader.get('reflectPrompts').load(source.promptId) : null + return 'promptId' in source + ? dataLoader.get('reflectPrompts').loadNonNull(source.promptId) + : null } } diff --git a/packages/server/graphql/public/types/RenameReflectTemplatePromptPayload.ts b/packages/server/graphql/public/types/RenameReflectTemplatePromptPayload.ts index 980ad0ac390..30f33368213 100644 --- a/packages/server/graphql/public/types/RenameReflectTemplatePromptPayload.ts +++ b/packages/server/graphql/public/types/RenameReflectTemplatePromptPayload.ts @@ -8,7 +8,9 @@ export type RenameReflectTemplatePromptPayloadSource = const RenameReflectTemplatePromptPayload: RenameReflectTemplatePromptPayloadResolvers = { prompt: (source, _args, {dataLoader}) => { - return 'promptId' in source ? dataLoader.get('reflectPrompts').load(source.promptId) : null + return 'promptId' in source + ? dataLoader.get('reflectPrompts').loadNonNull(source.promptId) + : null } } diff --git a/packages/server/graphql/public/types/RetroReflection.ts b/packages/server/graphql/public/types/RetroReflection.ts index 07d6c6a0b04..80c967c6797 100644 --- a/packages/server/graphql/public/types/RetroReflection.ts +++ b/packages/server/graphql/public/types/RetroReflection.ts @@ -39,7 +39,7 @@ const RetroReflection: RetroReflectionResolvers = { }, prompt: ({promptId}, _args, {dataLoader}) => { - return dataLoader.get('reflectPrompts').load(promptId) + return dataLoader.get('reflectPrompts').loadNonNull(promptId) }, reactjis: ({reactjis, id}, _args, {authToken}) => { diff --git a/packages/server/graphql/public/types/RetroReflectionGroup.ts b/packages/server/graphql/public/types/RetroReflectionGroup.ts index d7a56fd4c16..2c0834b7635 100644 --- a/packages/server/graphql/public/types/RetroReflectionGroup.ts +++ b/packages/server/graphql/public/types/RetroReflectionGroup.ts @@ -12,7 +12,7 @@ const RetroReflectionGroup: RetroReflectionGroupResolvers = { return retroMeeting as MeetingRetrospective }, prompt: ({promptId}, _args, {dataLoader}) => { - return dataLoader.get('reflectPrompts').load(promptId) + return dataLoader.get('reflectPrompts').loadNonNull(promptId) }, reflections: async ({id: reflectionGroupId, meetingId}, _args, {dataLoader}) => { // use meetingId so we only hit the DB once instead of once per group diff --git a/packages/server/graphql/types/ReflectPrompt.ts b/packages/server/graphql/types/ReflectPrompt.ts deleted file mode 100644 index 9658eef5ebf..00000000000 --- a/packages/server/graphql/types/ReflectPrompt.ts +++ /dev/null @@ -1,70 +0,0 @@ -import {GraphQLFloat, GraphQLID, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql' -import {GQLContext} from '../graphql' -import {resolveTeam} from '../resolvers' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import ReflectTemplate from './ReflectTemplate' -import Team from './Team' - -const ReflectPrompt: GraphQLObjectType = new GraphQLObjectType({ - name: 'ReflectPrompt', - description: - 'A team-specific reflection prompt. Usually 3 or 4 exist per team, eg Good/Bad/Change, 4Ls, etc.', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'shortid' - }, - createdAt: { - type: new GraphQLNonNull(GraphQLISO8601Type) - }, - teamId: { - type: new GraphQLNonNull(GraphQLID), - description: 'foreign key. use the team field' - }, - team: { - type: Team, - description: 'The team that owns this reflectPrompt', - resolve: resolveTeam - }, - updatedAt: { - type: new GraphQLNonNull(GraphQLISO8601Type) - }, - sortOrder: { - type: new GraphQLNonNull(GraphQLFloat), - description: 'the order of the items in the template' - }, - templateId: { - type: new GraphQLNonNull(GraphQLID), - description: 'FK for template' - }, - template: { - type: new GraphQLNonNull(ReflectTemplate), - description: 'The template that this prompt belongs to', - resolve: ({templateId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('meetingTemplates').load(templateId) - } - }, - question: { - description: - 'The question to answer during the phase of the retrospective (eg What went well?)', - type: new GraphQLNonNull(GraphQLString) - }, - description: { - description: - 'The description to the question for further context. A long version of the question.', - type: new GraphQLNonNull(GraphQLString), - resolve: ({description}) => description || '' - }, - groupColor: { - description: 'The color used to visually group a phase item.', - type: new GraphQLNonNull(GraphQLString), - resolve: ({groupColor}) => groupColor || '#FFFFFF' - }, - removedAt: { - type: GraphQLISO8601Type, - description: 'The datetime that the prompt was removed. Null if it has not been removed.' - } - }) -}) - -export default ReflectPrompt diff --git a/packages/server/graphql/types/Team.ts b/packages/server/graphql/types/Team.ts index be6246317e1..385be345be2 100644 --- a/packages/server/graphql/types/Team.ts +++ b/packages/server/graphql/types/Team.ts @@ -24,7 +24,6 @@ import MassInvitation from './MassInvitation' import MeetingTypeEnum from './MeetingTypeEnum' import NewMeeting from './NewMeeting' import Organization from './Organization' -import ReflectPrompt from './ReflectPrompt' import {TaskConnection} from './Task' import TeamInvitation from './TeamInvitation' import TeamMeetingSettings from './TeamMeetingSettings' @@ -122,14 +121,6 @@ const Team: GraphQLObjectType = new GraphQLObjectType({ type: GraphQLISO8601Type, description: 'The datetime the team was last updated' }, - customPhaseItems: { - type: new GraphQLList(ReflectPrompt), - deprecationReason: 'Field no longer needs to exist for now', - resolve: () => { - // not useful for retros since there is no templateId filter - return [] - } - }, teamInvitations: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(TeamInvitation))), description: 'The outstanding invitations to join the team', diff --git a/packages/server/postgres/migrations/1685721573097_addPrePostMortemTemplates.ts b/packages/server/postgres/migrations/1685721573097_addPrePostMortemTemplates.ts index f034e4e35a6..86d6f9ce92e 100644 --- a/packages/server/postgres/migrations/1685721573097_addPrePostMortemTemplates.ts +++ b/packages/server/postgres/migrations/1685721573097_addPrePostMortemTemplates.ts @@ -2,7 +2,6 @@ import {PALETTE} from 'parabol-client/styles/paletteV3' import {Client} from 'pg' import {r} from 'rethinkdb-ts' import connectRethinkDB from '../../database/connectRethinkDB' -import RetrospectivePrompt from '../../database/types/RetrospectivePrompt' import getPgConfig from '../getPgConfig' import getPgp from '../getPgp' @@ -1029,7 +1028,7 @@ type PromptInfo = { sortOrder: number } -const makePrompt = (promptInfo: PromptInfo, idx: number): RetrospectivePrompt => { +const makePrompt = (promptInfo: PromptInfo, idx: number) => { const {question, description, templateId, sortOrder} = promptInfo const paletteIdx = idx > promptColors.length - 1 ? idx % promptColors.length : idx const groupColor = promptColors[paletteIdx] diff --git a/packages/server/postgres/migrations/1696443115482_addCustomerFeedbackTemplate.ts b/packages/server/postgres/migrations/1696443115482_addCustomerFeedbackTemplate.ts index 976ef5295d4..f96e7641e37 100644 --- a/packages/server/postgres/migrations/1696443115482_addCustomerFeedbackTemplate.ts +++ b/packages/server/postgres/migrations/1696443115482_addCustomerFeedbackTemplate.ts @@ -2,7 +2,6 @@ import {PALETTE} from 'parabol-client/styles/paletteV3' import {Client} from 'pg' import {r} from 'rethinkdb-ts' import connectRethinkDB from '../../database/connectRethinkDB' -import RetrospectivePrompt from '../../database/types/RetrospectivePrompt' import getPgConfig from '../getPgConfig' import getPgp from '../getPgp' @@ -95,7 +94,7 @@ type PromptInfo = { sortOrder: number } -const makePrompt = (promptInfo: PromptInfo, idx: number): RetrospectivePrompt => { +const makePrompt = (promptInfo: PromptInfo, idx: number) => { const {question, description, templateId, sortOrder} = promptInfo const paletteIdx = idx > promptColors.length - 1 ? idx % promptColors.length : idx const groupColor = promptColors[paletteIdx] diff --git a/packages/server/postgres/migrations/1696616922662_addPeerReviewTemplates.ts b/packages/server/postgres/migrations/1696616922662_addPeerReviewTemplates.ts index 5b363c57095..f32f9ce5262 100644 --- a/packages/server/postgres/migrations/1696616922662_addPeerReviewTemplates.ts +++ b/packages/server/postgres/migrations/1696616922662_addPeerReviewTemplates.ts @@ -2,7 +2,6 @@ import {PALETTE} from 'parabol-client/styles/paletteV3' import {Client} from 'pg' import {r} from 'rethinkdb-ts' import connectRethinkDB from '../../database/connectRethinkDB' -import RetrospectivePrompt from '../../database/types/RetrospectivePrompt' import getPgConfig from '../getPgConfig' import getPgp from '../getPgp' @@ -167,7 +166,7 @@ type PromptInfo = { sortOrder: number } -const makePrompt = (promptInfo: PromptInfo, idx: number): RetrospectivePrompt => { +const makePrompt = (promptInfo: PromptInfo, idx: number) => { const {question, description, templateId, sortOrder} = promptInfo const paletteIdx = idx > promptColors.length - 1 ? idx % promptColors.length : idx const groupColor = promptColors[paletteIdx] From 12315b05e741f4de7289d020d63579e109465d1f Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 11 Sep 2024 14:48:27 -0700 Subject: [PATCH 461/529] chore(rethinkdb): PasswordResetRequest: One-shot (#10210) Signed-off-by: Matt Krick --- packages/server/__tests__/globalSetup.ts | 7 +-- packages/server/database/rethinkDriver.ts | 5 -- .../database/types/PasswordResetRequest.ts | 28 --------- .../graphql/mutations/emailPasswordReset.ts | 41 ++++++------ .../helpers/processEmailPasswordReset.ts | 29 +++++---- .../server/graphql/mutations/resetPassword.ts | 21 ++++--- .../1725996598345_PasswordResetRequest.ts | 62 +++++++++++++++++++ 7 files changed, 115 insertions(+), 78 deletions(-) delete mode 100644 packages/server/database/types/PasswordResetRequest.ts create mode 100644 packages/server/postgres/migrations/1725996598345_PasswordResetRequest.ts diff --git a/packages/server/__tests__/globalSetup.ts b/packages/server/__tests__/globalSetup.ts index 6cecd76a0f3..555249a35c2 100644 --- a/packages/server/__tests__/globalSetup.ts +++ b/packages/server/__tests__/globalSetup.ts @@ -1,12 +1,11 @@ +import {sql} from 'kysely' import '../../../scripts/webpack/utils/dotenv' -import getRethink from '../database/rethinkDriver' +import getKysely from '../postgres/getKysely' async function setup() { - const r = await getRethink() // The IP address is always localhost // so the safety checks will eventually fail if run too much - - await Promise.all([r.table('PasswordResetRequest').delete().run()]) + await sql`TRUNCATE TABLE "PasswordResetRequest"`.execute(getKysely()) } export default setup diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index d2ba61b2728..0a9e17ac7ce 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -14,7 +14,6 @@ import NotificationResponseReplied from './types/NotificationResponseReplied' import NotificationTaskInvolves from './types/NotificationTaskInvolves' import NotificationTeamArchived from './types/NotificationTeamArchived' import NotificationTeamInvitation from './types/NotificationTeamInvitation' -import PasswordResetRequest from './types/PasswordResetRequest' import PushInvitation from './types/PushInvitation' import Task from './types/Task' @@ -54,10 +53,6 @@ export type RethinkSchema = { | NotificationMentioned index: 'userId' } - PasswordResetRequest: { - type: PasswordResetRequest - index: 'email' | 'ip' | 'token' - } PushInvitation: { type: PushInvitation index: 'userId' diff --git a/packages/server/database/types/PasswordResetRequest.ts b/packages/server/database/types/PasswordResetRequest.ts deleted file mode 100644 index baa8187c571..00000000000 --- a/packages/server/database/types/PasswordResetRequest.ts +++ /dev/null @@ -1,28 +0,0 @@ -import generateUID from '../../generateUID' - -interface Input { - id?: string - ip: string - isValid?: boolean - email: string - token: string - time?: Date -} - -export default class PasswordResetRequest { - id: string - ip: string - email: string - time: Date - token: string - isValid: boolean - constructor(input: Input) { - const {id, email, ip, isValid, time, token} = input - this.id = id ?? generateUID() - this.email = email - this.ip = ip - this.time = time ?? new Date() - this.token = token - this.isValid = isValid ?? true - } -} diff --git a/packages/server/graphql/mutations/emailPasswordReset.ts b/packages/server/graphql/mutations/emailPasswordReset.ts index 65ab85d1a7c..75a994d0a4d 100644 --- a/packages/server/graphql/mutations/emailPasswordReset.ts +++ b/packages/server/graphql/mutations/emailPasswordReset.ts @@ -3,9 +3,8 @@ import ms from 'ms' import {AuthenticationError, Threshold} from 'parabol-client/types/constEnums' import {AuthIdentityTypeEnum} from '../../../client/types/constEnums' import getSSODomainFromEmail from '../../../client/utils/getSSODomainFromEmail' -import getRethink from '../../database/rethinkDriver' -import {RDatum} from '../../database/stricterR' import AuthIdentityLocal from '../../database/types/AuthIdentityLocal' +import getKysely from '../../postgres/getKysely' import {getUserByEmail} from '../../postgres/queries/getUsersByEmails' import {GQLContext} from '../graphql' import rateLimit from '../rateLimit' @@ -31,27 +30,31 @@ const emailPasswordReset = { return {error: {message: 'Resetting password is disabled'}} } const email = denormEmail.toLowerCase().trim() - const r = await getRethink() // we only wanna send like 2 emails/min or 5 per day to the same person const yesterday = new Date(Date.now() - ms('1d')) const user = await getUserByEmail(email) - const {failOnAccount, failOnTime} = await r({ - failOnAccount: r - .table('PasswordResetRequest') - .getAll(ip, {index: 'ip'}) - .filter({email}) - .filter((row: RDatum) => row('time').ge(yesterday)) - .count() - .ge(Threshold.MAX_ACCOUNT_DAILY_PASSWORD_RESETS) as unknown as boolean, - failOnTime: r - .table('PasswordResetRequest') - .getAll(ip, {index: 'ip'}) - .filter((row: RDatum) => row('time').ge(yesterday)) - .count() - .ge(Threshold.MAX_DAILY_PASSWORD_RESETS) as unknown as boolean - }).run() - if (failOnAccount || failOnTime) { + const pg = getKysely() + const [failOnAccount, failOnTime] = await Promise.all([ + pg + .selectFrom('PasswordResetRequest') + .where('ip', '=', ip) + .where('email', '=', email) + .where('time', '>=', yesterday) + .select(({eb, fn}) => + eb(fn.count('id'), '>=', Threshold.MAX_ACCOUNT_DAILY_PASSWORD_RESETS).as('res') + ) + .executeTakeFirstOrThrow(), + pg + .selectFrom('PasswordResetRequest') + .where('ip', '=', ip) + .where('time', '>=', yesterday) + .select(({eb, fn}) => + eb(fn.count('id'), '>=', Threshold.MAX_DAILY_PASSWORD_RESETS).as('res') + ) + .executeTakeFirstOrThrow() + ]) + if (failOnAccount.res || failOnTime.res) { return {error: {message: AuthenticationError.EXCEEDED_RESET_THRESHOLD}} } const domain = getSSODomainFromEmail(email) diff --git a/packages/server/graphql/mutations/helpers/processEmailPasswordReset.ts b/packages/server/graphql/mutations/helpers/processEmailPasswordReset.ts index 9ee0d7aef8e..f9b94215737 100644 --- a/packages/server/graphql/mutations/helpers/processEmailPasswordReset.ts +++ b/packages/server/graphql/mutations/helpers/processEmailPasswordReset.ts @@ -1,12 +1,11 @@ import base64url from 'base64url' import crypto from 'crypto' import {AuthenticationError} from 'parabol-client/types/constEnums' -import {r} from 'rethinkdb-ts' import util from 'util' import AuthIdentity from '../../../database/types/AuthIdentity' -import PasswordResetRequest from '../../../database/types/PasswordResetRequest' import getMailManager from '../../../email/getMailManager' import resetPasswordEmailCreator from '../../../email/resetPasswordEmailCreator' +import getKysely from '../../../postgres/getKysely' import updateUser from '../../../postgres/queries/updateUser' const randomBytes = util.promisify(crypto.randomBytes) @@ -17,19 +16,25 @@ const processEmailPasswordReset = async ( identities: AuthIdentity[], userId: string ) => { + const pg = getKysely() const tokenBuffer = await randomBytes(48) const resetPasswordToken = base64url.encode(tokenBuffer) // invalidate all other tokens for this email - await r - .table('PasswordResetRequest') - .getAll(email, {index: 'email'}) - .filter({isValid: true}) - .update({isValid: false}) - .run() - await r - .table('PasswordResetRequest') - .insert(new PasswordResetRequest({ip, email, token: resetPasswordToken})) - .run() + await pg + .with('InvalidateOtherTokens', (qb) => + qb + .updateTable('PasswordResetRequest') + .set({isValid: false}) + .where('email', '=', email) + .where('isValid', '=', true) + ) + .insertInto('PasswordResetRequest') + .values({ + ip, + email, + token: resetPasswordToken + }) + .execute() await updateUser({identities}, userId) diff --git a/packages/server/graphql/mutations/resetPassword.ts b/packages/server/graphql/mutations/resetPassword.ts index d90523d3cfc..89f82b8ac64 100644 --- a/packages/server/graphql/mutations/resetPassword.ts +++ b/packages/server/graphql/mutations/resetPassword.ts @@ -2,10 +2,8 @@ import bcrypt from 'bcryptjs' import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {Security, Threshold} from 'parabol-client/types/constEnums' import {AuthIdentityTypeEnum} from '../../../client/types/constEnums' -import getRethink from '../../database/rethinkDriver' import AuthIdentityLocal from '../../database/types/AuthIdentityLocal' import AuthToken from '../../database/types/AuthToken' -import PasswordResetRequest from '../../database/types/PasswordResetRequest' import getKysely from '../../postgres/getKysely' import {getUserByEmail} from '../../postgres/queries/getUsersByEmails' import updateUser from '../../postgres/queries/updateUser' @@ -39,13 +37,11 @@ const resetPassword = { return {error: {message: 'Resetting password is disabled'}} } const pg = getKysely() - const r = await getRethink() - const resetRequest = (await r - .table('PasswordResetRequest') - .getAll(token, {index: 'token'}) - .nth(0) - .default(null) - .run()) as PasswordResetRequest + const resetRequest = await pg + .selectFrom('PasswordResetRequest') + .selectAll() + .where('token', '=', token) + .executeTakeFirst() if (!resetRequest) { return {error: {message: 'Invalid reset token'}} @@ -69,7 +65,12 @@ const resetPassword = { if (!localIdentity) { return standardError(new Error(`User ${email} does not have a local identity`), {userId}) } - await r.table('PasswordResetRequest').get(resetRequestId).update({isValid: false}).run() + await pg + .updateTable('PasswordResetRequest') + .set({isValid: false}) + .where('id', '=', resetRequestId) + .execute() + // MUTATIVE localIdentity.hashedPassword = await bcrypt.hash(newPassword, Security.SALT_ROUNDS) localIdentity.isEmailVerified = true diff --git a/packages/server/postgres/migrations/1725996598345_PasswordResetRequest.ts b/packages/server/postgres/migrations/1725996598345_PasswordResetRequest.ts new file mode 100644 index 00000000000..b949a622405 --- /dev/null +++ b/packages/server/postgres/migrations/1725996598345_PasswordResetRequest.ts @@ -0,0 +1,62 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {Client} from 'pg' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' +import getPgConfig from '../getPgConfig' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS "PasswordResetRequest" ( + "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + "ip" cidr NOT NULL, + "email" "citext" NOT NULL, + "time" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "token" VARCHAR(64) NOT NULL, + "isValid" BOOLEAN NOT NULL DEFAULT TRUE + ); + CREATE INDEX IF NOT EXISTS "idx_PasswordResetRequest_ip" ON "PasswordResetRequest"("ip"); + CREATE INDEX IF NOT EXISTS "idx_PasswordResetRequest_email" ON "PasswordResetRequest"("email"); + CREATE INDEX IF NOT EXISTS "idx_PasswordResetRequest_token" ON "PasswordResetRequest"("token"); + END $$; +`.execute(pg) + + const rRequests = await r.table('PasswordResetRequest').coerceTo('array').run() + + await Promise.all( + rRequests.map(async (row) => { + const {ip, email, time, token, isValid} = row + try { + return await pg + .insertInto('PasswordResetRequest') + .values({ + ip, + email, + time, + token, + isValid + }) + .execute() + } catch (e) { + console.log(e, row) + } + }) + ) +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE IF EXISTS "PasswordResetRequest"; + ` /* Do undo magic */) + await client.end() +} From 7f95a81b5facdd780f2c9e3a99470636c884d941 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 11 Sep 2024 14:49:53 -0700 Subject: [PATCH 462/529] chore(rethinkdb): PushInvitation: One-shot (#10213) Signed-off-by: Matt Krick --- packages/server/database/rethinkDriver.ts | 5 -- .../server/database/types/PushInvitation.ts | 26 ------- .../graphql/mutations/denyPushInvitation.ts | 33 ++++---- .../graphql/mutations/pushInvitation.ts | 26 ++++--- .../private/mutations/hardDeleteUser.ts | 1 - .../1726078121329_PushInvitation.ts | 75 +++++++++++++++++++ .../safeMutations/acceptTeamInvitation.ts | 6 +- 7 files changed, 113 insertions(+), 59 deletions(-) delete mode 100644 packages/server/database/types/PushInvitation.ts create mode 100644 packages/server/postgres/migrations/1726078121329_PushInvitation.ts diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index 0a9e17ac7ce..0776e9efdad 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -14,7 +14,6 @@ import NotificationResponseReplied from './types/NotificationResponseReplied' import NotificationTaskInvolves from './types/NotificationTaskInvolves' import NotificationTeamArchived from './types/NotificationTeamArchived' import NotificationTeamInvitation from './types/NotificationTeamInvitation' -import PushInvitation from './types/PushInvitation' import Task from './types/Task' export type RethinkSchema = { @@ -53,10 +52,6 @@ export type RethinkSchema = { | NotificationMentioned index: 'userId' } - PushInvitation: { - type: PushInvitation - index: 'userId' - } Task: { type: Task index: diff --git a/packages/server/database/types/PushInvitation.ts b/packages/server/database/types/PushInvitation.ts deleted file mode 100644 index de742d0aafe..00000000000 --- a/packages/server/database/types/PushInvitation.ts +++ /dev/null @@ -1,26 +0,0 @@ -import generateUID from '../../generateUID' - -interface Input { - id?: string - userId: string - teamId: string - denialCount?: number - lastDenialAt?: Date -} - -export default class PushInvitation { - id: string - userId: string - teamId: string - denialCount: number - lastDenialAt?: Date - - constructor(input: Input) { - const {id, userId, teamId, denialCount, lastDenialAt} = input - this.id = id || generateUID() - this.userId = userId - this.teamId = teamId - this.denialCount = denialCount || 0 - this.lastDenialAt = lastDenialAt || undefined - } -} diff --git a/packages/server/graphql/mutations/denyPushInvitation.ts b/packages/server/graphql/mutations/denyPushInvitation.ts index f0868266627..aa3e7e3dea0 100644 --- a/packages/server/graphql/mutations/denyPushInvitation.ts +++ b/packages/server/graphql/mutations/denyPushInvitation.ts @@ -1,7 +1,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' +import {sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import PushInvitation from '../../database/types/PushInvitation' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -29,9 +29,8 @@ export default { {userId, teamId}: {userId: string; teamId: string}, {authToken, socketId: mutatorId}: GQLContext ) => { - const r = await getRethink() + const pg = getKysely() const viewerId = getUserId(authToken) - const now = new Date() // AUTH if (!isTeamMember(authToken, teamId)) { @@ -39,23 +38,27 @@ export default { } // VALIDATION - const teamBlacklist = (await r - .table('PushInvitation') - .getAll(userId, {index: 'userId'}) - .filter({teamId}) - .nth(0) - .run()) as PushInvitation | null + const teamBlacklist = await pg + .selectFrom('PushInvitation') + .selectAll() + .where('userId', '=', userId) + .where('teamId', '=', teamId) + .limit(1) + .executeTakeFirst() if (!teamBlacklist) { return standardError(new Error('User did not request push invitation'), {userId: viewerId}) } // RESOLUTION - await r - .table('PushInvitation') - .get(teamBlacklist.id) - .update({denialCount: teamBlacklist.denialCount + 1, lastDenialAt: now}) - .run() + await pg + .updateTable('PushInvitation') + .set((eb) => ({ + denialCount: eb('denialCount', '+', 1), + lastDenialAt: sql`CURRENT_TIMESTAMP` + })) + .where('id', '=', teamBlacklist.id) + .execute() const data = {teamId, userId} publish(SubscriptionChannel.TEAM, teamId, 'DenyPushInvitationPayload', data, {mutatorId}) diff --git a/packages/server/graphql/mutations/pushInvitation.ts b/packages/server/graphql/mutations/pushInvitation.ts index 3638b0f9a8d..967b7dcb55f 100644 --- a/packages/server/graphql/mutations/pushInvitation.ts +++ b/packages/server/graphql/mutations/pushInvitation.ts @@ -1,8 +1,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import ms from 'ms' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import PushInvitation from '../../database/types/PushInvitation' +import getKysely from '../../postgres/getKysely' import {getUserId, isAuthenticated} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -36,7 +35,7 @@ export default { {meetingId, teamId}: {meetingId?: string | null; teamId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { - const r = await getRethink() + const pg = getKysely() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const viewerId = getUserId(authToken) @@ -56,10 +55,12 @@ export default { if (approvalError instanceof Error) { return {error: {message: approvalError.message}} } - const pushInvitations = (await r - .table('PushInvitation') - .getAll(viewerId, {index: 'userId'}) - .run()) as PushInvitation[] + const pushInvitations = await pg + .selectFrom('PushInvitation') + .selectAll() + .where('userId', '=', viewerId) + .execute() + const teamPushInvitation = pushInvitations.find((row) => row.teamId === teamId) if (teamPushInvitation) { const {denialCount, lastDenialAt} = teamPushInvitation @@ -82,10 +83,13 @@ export default { // RESOLUTION if (!teamPushInvitation) { // create a row so we know there was a request so denials are substantiated - await r - .table('PushInvitation') - .insert(new PushInvitation({userId: viewerId, teamId})) - .run() + await pg + .insertInto('PushInvitation') + .values({ + userId: viewerId, + teamId + }) + .execute() } const data = {userId: viewerId, teamId, meetingId} diff --git a/packages/server/graphql/private/mutations/hardDeleteUser.ts b/packages/server/graphql/private/mutations/hardDeleteUser.ts index 6c19edc395b..2c9bd2e58c0 100644 --- a/packages/server/graphql/private/mutations/hardDeleteUser.ts +++ b/packages/server/graphql/private/mutations/hardDeleteUser.ts @@ -93,7 +93,6 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( .getAll(r.args(teamIds), {index: 'teamId'}) .filter((row: RValue) => row('createdBy').eq(userIdToDelete)) .delete(), - pushInvitation: r.table('PushInvitation').getAll(userIdToDelete, {index: 'userId'}).delete(), invitedByTeamInvitation: r .table('TeamInvitation') .getAll(r.args(teamIds), {index: 'teamId'}) diff --git a/packages/server/postgres/migrations/1726078121329_PushInvitation.ts b/packages/server/postgres/migrations/1726078121329_PushInvitation.ts new file mode 100644 index 00000000000..aedaeda9c87 --- /dev/null +++ b/packages/server/postgres/migrations/1726078121329_PushInvitation.ts @@ -0,0 +1,75 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {Client} from 'pg' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' +import getPgConfig from '../getPgConfig' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS "PushInvitation" ( + "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + "userId" VARCHAR(100) NOT NULL, + "teamId" VARCHAR(100) NOT NULL, + "denialCount" SMALLINT NOT NULL DEFAULT 0, + "lastDenialAt" TIMESTAMP WITH TIME ZONE, + CONSTRAINT "fk_userId" + FOREIGN KEY("userId") + REFERENCES "User"("id") + ON DELETE SET NULL, + CONSTRAINT "fk_teamId" + FOREIGN KEY("teamId") + REFERENCES "Team"("id") + ON DELETE SET NULL + ); + CREATE INDEX IF NOT EXISTS "idx_PushInvitation_userId" ON "PushInvitation"("userId"); + CREATE INDEX IF NOT EXISTS "idx_PushInvitation_teamId" ON "PushInvitation"("teamId"); + END $$; +`.execute(pg) + + const rRequests = await r.table('PushInvitation').coerceTo('array').run() + + await Promise.all( + rRequests.map(async (row) => { + const {userId, teamId, denialCount, lastDenialAt} = row + try { + return await pg + .insertInto('PushInvitation') + .values({ + userId, + teamId, + denialCount, + lastDenialAt + }) + .execute() + } catch (e) { + if (e.constraint === 'fk_teamId') { + console.log(`Skipping ${row.id} because it has no valid teamId`) + return + } + if (e.constraint === 'fk_userId') { + console.log(`Skipping ${row.id} because it has no valid userId`) + return + } + console.log(e, row) + } + }) + ) +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE IF EXISTS "PushInvitation"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/safeMutations/acceptTeamInvitation.ts b/packages/server/safeMutations/acceptTeamInvitation.ts index ec91003e8c2..63171c108eb 100644 --- a/packages/server/safeMutations/acceptTeamInvitation.ts +++ b/packages/server/safeMutations/acceptTeamInvitation.ts @@ -130,7 +130,11 @@ const acceptTeamInvitation = async (team: Team, userId: string, dataLoader: Data } // if accepted to team, don't count it towards the global denial count - await r.table('PushInvitation').getAll(userId, {index: 'userId'}).filter({teamId}).delete().run() + await pg + .deleteFrom('PushInvitation') + .where('userId', '=', userId) + .where('teamId', '=', teamId) + .execute() return { teamLeadUserIdWithNewActions, invitationNotificationIds: invitationNotificationIds as string[] From 88f31be10f3f25eb884bfaec0f3d056899642d4b Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 11 Sep 2024 15:12:00 -0700 Subject: [PATCH 463/529] chore(release): release v7.47.2 (#10215) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 10 ++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 22 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1dbde5c5129..75cf0e48db2 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.47.1" + ".": "7.47.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index aa474bed114..4af6b6b0337 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,16 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.47.2](https://github.com/ParabolInc/parabol/compare/v7.47.1...v7.47.2) (2024-09-11) + + +### Changed + +* **rethinkdb:** PasswordResetRequest: One-shot ([#10210](https://github.com/ParabolInc/parabol/issues/10210)) ([12315b0](https://github.com/ParabolInc/parabol/commit/12315b05e741f4de7289d020d63579e109465d1f)) +* **rethinkdb:** PushInvitation: One-shot ([#10213](https://github.com/ParabolInc/parabol/issues/10213)) ([7f95a81](https://github.com/ParabolInc/parabol/commit/7f95a81b5facdd780f2c9e3a99470636c884d941)) +* **rethinkdb:** ReflectPhase: Phase 2 ([#10208](https://github.com/ParabolInc/parabol/issues/10208)) ([3fddb97](https://github.com/ParabolInc/parabol/commit/3fddb9750746c5b27ab6d9c59d9e785e5d7d6cd0)) +* **rethinkdb:** ReflectPhase: Phase 3 ([#10209](https://github.com/ParabolInc/parabol/issues/10209)) ([1131785](https://github.com/ParabolInc/parabol/commit/1131785e26e870909f5cb2db2c99322b007e4546)) + ## [7.47.1](https://github.com/ParabolInc/parabol/compare/v7.47.0...v7.47.1) (2024-09-11) diff --git a/package.json b/package.json index f8b8937af3b..77fbc6e4dc4 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.47.1", + "version": "7.47.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 9455a79c6ed..82eae1e64e9 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.47.1", + "version": "7.47.2", "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.47.1" + "parabol-server": "7.47.2" } } diff --git a/packages/client/package.json b/packages/client/package.json index 2b37531f23b..fa329d452fa 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.47.1", + "version": "7.47.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index d64723e0d06..722d8ce1b9e 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.47.1", + "version": "7.47.2", "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 b550c6762e7..19c30203fbd 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.47.1", + "version": "7.47.2", "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.47.1", - "parabol-server": "7.47.1", + "parabol-client": "7.47.2", + "parabol-server": "7.47.2", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index e02226d48a6..8d9b865b01e 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.47.1", + "version": "7.47.2", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index e9a11af39dc..1a833df83dc 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.47.1", + "version": "7.47.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.47.1", + "parabol-client": "7.47.2", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 45964d128679ea7c50634d9ad3ef2cd3c6dbde7d Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Thu, 12 Sep 2024 17:33:41 +0200 Subject: [PATCH 464/529] chore: Speed up processRecurrence test (#10204) --- .../__tests__/processRecurrence.test.ts | 39 ++++++++----------- 1 file changed, 16 insertions(+), 23 deletions(-) diff --git a/packages/server/__tests__/processRecurrence.test.ts b/packages/server/__tests__/processRecurrence.test.ts index 948fc11b8fa..bcbf2ab7fbd 100644 --- a/packages/server/__tests__/processRecurrence.test.ts +++ b/packages/server/__tests__/processRecurrence.test.ts @@ -59,11 +59,24 @@ const assertIdempotency = async () => { }) } +let userId: string +let teamId: string +let teamMemberId: string + +beforeAll(async () => { + userId = (await signUp()).userId + teamId = (await getUserTeams(userId))[0].id + teamMemberId = TeamMemberId.join(teamId, userId) + + // in case there are pending recurrence events + await sendIntranet({ + query: PROCESS_RECURRENCE, + isPrivate: true + }) +}) + test('Should not end meetings that are not scheduled to end', async () => { const r = await getRethink() - const {userId} = await signUp() - const {id: teamId} = (await getUserTeams(userId))[0] - const teamMemberId = TeamMemberId.join(teamId, userId) const meetingId = generateUID() const meeting = new MeetingTeamPrompt({ @@ -99,9 +112,6 @@ test('Should not end meetings that are not scheduled to end', async () => { test('Should not end meetings that are scheduled to end in the future', async () => { const r = await getRethink() - const {userId} = await signUp() - const {id: teamId} = (await getUserTeams(userId))[0] - const teamMemberId = TeamMemberId.join(teamId, userId) const meetingId = generateUID() const meeting = new MeetingTeamPrompt({ @@ -140,9 +150,6 @@ test('Should not end meetings that are scheduled to end in the future', async () test('Should end meetings that are scheduled to end in the past', async () => { const r = await getRethink() - const {userId} = await signUp() - const {id: teamId} = (await getUserTeams(userId))[0] - const teamMemberId = TeamMemberId.join(teamId, userId) const meetingId = generateUID() const meeting = new MeetingTeamPrompt({ @@ -179,9 +186,6 @@ test('Should end meetings that are scheduled to end in the past', async () => { test('Should end the current team prompt meeting and start a new meeting', async () => { const r = await getRethink() - const {userId} = await signUp() - const {id: teamId} = (await getUserTeams(userId))[0] - const teamMemberId = TeamMemberId.join(teamId, userId) const startDate = dayjs().utc().subtract(2, 'day').set('hour', 9) const dateTime = toDateTime(startDate, 'UTC') const recurrenceRule = `DTSTART:${dateTime} @@ -248,8 +252,6 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` test('Should end the current retro meeting and start a new meeting', async () => { const r = await getRethink() - const {userId} = await signUp() - const {id: teamId} = (await getUserTeams(userId))[0] // Create a meeting series that's been going on for a few days, and happens daily at 9a UTC. const startDate = dayjs().utc().subtract(2, 'day').set('hour', 9) @@ -321,9 +323,6 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` test('Should only start a new meeting if it would still be active', async () => { const r = await getRethink() - const {userId} = await signUp() - const {id: teamId} = (await getUserTeams(userId))[0] - const teamMemberId = TeamMemberId.join(teamId, userId) const startDate = dayjs().utc().subtract(2, 'day').set('hour', 9) const dateTime = toDateTime(startDate, 'UTC') @@ -380,9 +379,6 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` test('Should not start a new meeting if the rrule has not started', async () => { const r = await getRethink() - const {userId} = await signUp() - const {id: teamId} = (await getUserTeams(userId))[0] - const teamMemberId = TeamMemberId.join(teamId, userId) const startDate = dayjs().utc().add(1, 'day').set('hour', 9) const dateTime = toDateTime(startDate, 'UTC') @@ -439,9 +435,6 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` test('Should not hang if the rrule interval is invalid', async () => { const r = await getRethink() - const {userId} = await signUp() - const {id: teamId} = (await getUserTeams(userId))[0] - const teamMemberId = TeamMemberId.join(teamId, userId) const startDate = dayjs().utc().subtract(2, 'day').set('hour', 9) const dateTime = toDateTime(startDate, 'UTC') From 05ac90b3830b9ff0cbf65ebea21cf4907e6b2916 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 12 Sep 2024 15:21:33 -0700 Subject: [PATCH 465/529] fix: threadParent can exist outside comment table (#10228) Signed-off-by: Matt Krick --- package.json | 4 +-- .../client/hooks/useSortNewReflectionGroup.ts | 2 +- .../components/OrgMembers/OrgMembers.tsx | 3 +-- .../client/utils/handleSuccessfulLogin.ts | 8 +++--- packages/embedder/ai_models/ModelManager.ts | 4 +-- packages/embedder/package.json | 3 +-- packages/gql-executor/package.json | 3 +-- packages/integration-tests/package.json | 3 +-- .../fileStorage/LocalFileStoreManager.ts | 2 +- packages/server/package.json | 1 - packages/server/parseBody.ts | 6 ++++- packages/server/parseFormBody.ts | 6 ++++- .../1726174443131_dropThreadParentId.ts | 27 +++++++++++++++++++ yarn.lock | 8 +++--- 14 files changed, 54 insertions(+), 26 deletions(-) create mode 100644 packages/server/postgres/migrations/1726174443131_dropThreadParentId.ts diff --git a/package.json b/package.json index 77fbc6e4dc4..125512d57eb 100644 --- a/package.json +++ b/package.json @@ -52,7 +52,7 @@ "test:server": "yarn workspace parabol-server test" }, "resolutions": { - "typescript": "^5.3.3", + "typescript": "^5.6.2", "hoist-non-react-statics": "^3.3.0", "@types/react": "16.9.11", "@types/react-dom": "16.9.4", @@ -126,7 +126,7 @@ "tailwindcss": "^3.2.7", "terser-webpack-plugin": "^5.3.9", "ts-loader": "9.2.6", - "typescript": "^5.3.3", + "typescript": "^5.6.2", "typescript-eslint": "^8.3.0", "vscode-apollo-relay": "^1.5.0", "webpack": "^5.89.0", diff --git a/packages/client/hooks/useSortNewReflectionGroup.ts b/packages/client/hooks/useSortNewReflectionGroup.ts index 173b47984cf..6696b1aa14f 100644 --- a/packages/client/hooks/useSortNewReflectionGroup.ts +++ b/packages/client/hooks/useSortNewReflectionGroup.ts @@ -31,7 +31,7 @@ const useSortNewReflectionGroup = ( ) as string reflectionGroup.setValue(parseInt(smallestSubColumnIdx), 'subColumnIdx') } else { - subColumnIdxCounts[currentSubColumnIdx] += 1 + subColumnIdxCounts[currentSubColumnIdx]! += 1 } }) }) diff --git a/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx b/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx index 8fc04aab8c9..27ea54959d4 100644 --- a/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx +++ b/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx @@ -62,8 +62,7 @@ const OrgMembers = (props: Props) => { ) const {data} = paginationRes const {viewer} = data - const {organization} = viewer - if (!organization) return null + const organization = viewer.organization! const {organizationUsers, name: orgName, isBillingLeader} = organization const billingLeaderCount = organizationUsers.edges.reduce( (count, {node}) => diff --git a/packages/client/utils/handleSuccessfulLogin.ts b/packages/client/utils/handleSuccessfulLogin.ts index 1c17d50b719..518863e63e9 100644 --- a/packages/client/utils/handleSuccessfulLogin.ts +++ b/packages/client/utils/handleSuccessfulLogin.ts @@ -18,13 +18,13 @@ graphql` ` type Payload = Omit -type GA4SignUpEventEmissionRequiredArgs = { +export type GA4SignUpEventEmissionRequiredArgs = { isNewUser: boolean userId: string isPatient0: boolean } -const emitGA4SignUpEvent = (args: GA4SignUpEventEmissionRequiredArgs) => { +export const emitGA4SignUpEvent = (args: GA4SignUpEventEmissionRequiredArgs) => { const {isNewUser, userId, isPatient0} = args if (isNewUser) { ReactGA.event('sign_up', { @@ -37,7 +37,7 @@ const emitGA4SignUpEvent = (args: GA4SignUpEventEmissionRequiredArgs) => { } } -const handleSuccessfulLogin = (payload: Payload) => { +export const handleSuccessfulLogin = (payload: Payload) => { const email = payload?.user?.email const userId = payload?.user?.id if (!email || !userId) return @@ -46,5 +46,3 @@ const handleSuccessfulLogin = (payload: Payload) => { const isNewUser = payload?.isNewUser ?? false emitGA4SignUpEvent({isNewUser, userId, isPatient0: payload.user.isPatient0}) } - -export {GA4SignUpEventEmissionRequiredArgs, emitGA4SignUpEvent, handleSuccessfulLogin} diff --git a/packages/embedder/ai_models/ModelManager.ts b/packages/embedder/ai_models/ModelManager.ts index 09091499121..3eb888fb126 100644 --- a/packages/embedder/ai_models/ModelManager.ts +++ b/packages/embedder/ai_models/ModelManager.ts @@ -17,8 +17,8 @@ export class ModelManager { generationModels: Map getEmbedder(tableName?: EmbeddingsTableName): AbstractEmbeddingsModel { return tableName - ? this.embeddingModels.get(tableName) - : this.embeddingModels.values().next().value + ? this.embeddingModels.get(tableName)! + : this.embeddingModels.values().next().value! } private parseModelEnvVars(envVar: 'AI_EMBEDDING_MODELS' | 'AI_GENERATION_MODELS'): ModelConfig[] { diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 722d8ce1b9e..d160c9306f8 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -33,8 +33,7 @@ "openapi-fetch": "^0.10.0", "sucrase": "^3.32.0", "ts-jest": "^29.1.0", - "ts-node-dev": "^1.0.0-pre.44", - "typescript": "^5.3.3" + "ts-node-dev": "^1.0.0-pre.44" }, "dependencies": { "dd-trace": "^4.2.0", diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 19c30203fbd..eb493b8fdcc 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -22,8 +22,7 @@ "babel-plugin-inline-import": "^3.0.0", "chokidar": "^3.3.1", "sucrase": "^3.32.0", - "ts-node-dev": "^1.0.0-pre.44", - "typescript": "^5.3.3" + "ts-node-dev": "^1.0.0-pre.44" }, "dependencies": { "dd-trace": "^4.2.0", diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 8d9b865b01e..ce8697722d6 100644 --- a/packages/integration-tests/package.json +++ b/packages/integration-tests/package.json @@ -12,7 +12,6 @@ "devDependencies": { "@playwright/test": "^1.34.3", "lint-staged": "^12.3.3", - "ts-app-env": "^1.4.2", - "typescript": "^5.3.3" + "ts-app-env": "^1.4.2" } } diff --git a/packages/server/fileStorage/LocalFileStoreManager.ts b/packages/server/fileStorage/LocalFileStoreManager.ts index c51a5b3bd84..8f6efa0e0b5 100644 --- a/packages/server/fileStorage/LocalFileStoreManager.ts +++ b/packages/server/fileStorage/LocalFileStoreManager.ts @@ -20,7 +20,7 @@ export default class LocalFileStoreManager extends FileStoreManager { protected async putFile(file: ArrayBufferLike, fullPath: string) { const fsAbsLocation = path.join(process.cwd(), fullPath) await fs.promises.mkdir(path.dirname(fsAbsLocation), {recursive: true}) - await fs.promises.writeFile(fsAbsLocation, Buffer.from(file)) + await fs.promises.writeFile(fsAbsLocation, Buffer.from(file) as any) return this.getPublicFileLocation(fullPath) } diff --git a/packages/server/package.json b/packages/server/package.json index 1a833df83dc..288762747df 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -66,7 +66,6 @@ "sucrase": "^3.32.0", "ts-jest": "^29.1.0", "ts-node": "10.9.2", - "typescript": "^5.3.3", "url-loader": "4.1.1", "vscode-apollo-relay": "^1.5.0", "webpack-bundle-analyzer": "4.3.0", diff --git a/packages/server/parseBody.ts b/packages/server/parseBody.ts index 94149926846..ba19a932eb8 100644 --- a/packages/server/parseBody.ts +++ b/packages/server/parseBody.ts @@ -14,7 +14,11 @@ const parseBody = ({ let buffer: Buffer res.onData((ab, isLast) => { const curBuf = Buffer.from(ab) - buffer = buffer ? Buffer.concat([buffer, curBuf]) : isLast ? curBuf : Buffer.concat([curBuf]) + buffer = buffer + ? Buffer.concat([buffer as any, curBuf]) + : isLast + ? curBuf + : Buffer.concat([curBuf as any]) if (isLast) { try { resolve(parser(buffer)) diff --git a/packages/server/parseFormBody.ts b/packages/server/parseFormBody.ts index 7768ba82ed6..9b29bd0a208 100644 --- a/packages/server/parseFormBody.ts +++ b/packages/server/parseFormBody.ts @@ -29,7 +29,11 @@ const parseRes = (res: HttpResponse) => { let buffer: Buffer res.onData((ab, isLast) => { const curBuf = Buffer.from(ab) - buffer = buffer ? Buffer.concat([buffer, curBuf]) : isLast ? curBuf : Buffer.concat([curBuf]) + buffer = buffer + ? Buffer.concat([buffer as any, curBuf]) + : isLast + ? curBuf + : Buffer.concat([curBuf as any]) // give an extra MB for the rest of the payload if (buffer.length > Threshold.MAX_AVATAR_FILE_SIZE * 2) resolve(null) if (isLast) resolve(buffer) diff --git a/packages/server/postgres/migrations/1726174443131_dropThreadParentId.ts b/packages/server/postgres/migrations/1726174443131_dropThreadParentId.ts new file mode 100644 index 00000000000..be7c0fb86e6 --- /dev/null +++ b/packages/server/postgres/migrations/1726174443131_dropThreadParentId.ts @@ -0,0 +1,27 @@ +import {Kysely, PostgresDialect} from 'kysely' +import {Client} from 'pg' +import getPg from '../getPg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + // threadParentId can exist outside comment table (task, poll, etc) + await client.query( + `ALTER TABLE "Comment" DROP CONSTRAINT IF EXISTS fk_threadParentId;` /* Do good magic */ + ) + await client.end() +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await pg.schema + .alterTable('Comment') + .addForeignKeyConstraint('fk_threadParentId', ['threadParentId'], 'Comment', ['id']) + .onDelete('set null') + .execute() +} diff --git a/yarn.lock b/yarn.lock index ff0969d4238..75581e85db5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -22460,10 +22460,10 @@ typescript-eslint@^8.3.0: "@typescript-eslint/parser" "8.3.0" "@typescript-eslint/utils" "8.3.0" -"typescript@^3 || ^4", typescript@^5.3.3, typescript@^5.5.4: - version "5.4.2" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372" - integrity sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ== +"typescript@^3 || ^4", typescript@^5.3.3, typescript@^5.5.4, typescript@^5.6.2: + version "5.6.2" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.6.2.tgz#d1de67b6bef77c41823f822df8f0b3bcff60a5a0" + integrity sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw== uWebSockets.js@uNetworking/uWebSockets.js#v20.34.0: version "20.34.0" From b78166900121188e3d1b87362eef44944031ce5e Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 15:32:27 -0700 Subject: [PATCH 466/529] chore(release): release v7.47.3 (#10224) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 75cf0e48db2..eb706427947 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.47.2" + ".": "7.47.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4af6b6b0337..75ed5883eaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.47.3](https://github.com/ParabolInc/parabol/compare/v7.47.2...v7.47.3) (2024-09-12) + + +### Fixed + +* threadParent can exist outside comment table ([#10228](https://github.com/ParabolInc/parabol/issues/10228)) ([05ac90b](https://github.com/ParabolInc/parabol/commit/05ac90b3830b9ff0cbf65ebea21cf4907e6b2916)) + + +### Changed + +* Speed up processRecurrence test ([#10204](https://github.com/ParabolInc/parabol/issues/10204)) ([45964d1](https://github.com/ParabolInc/parabol/commit/45964d128679ea7c50634d9ad3ef2cd3c6dbde7d)) + ## [7.47.2](https://github.com/ParabolInc/parabol/compare/v7.47.1...v7.47.2) (2024-09-11) diff --git a/package.json b/package.json index 125512d57eb..124d17eca01 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.47.2", + "version": "7.47.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 82eae1e64e9..012b4566ed0 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.47.2", + "version": "7.47.3", "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.47.2" + "parabol-server": "7.47.3" } } diff --git a/packages/client/package.json b/packages/client/package.json index fa329d452fa..d62acd8f161 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.47.2", + "version": "7.47.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index d160c9306f8..536ad4eaa60 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.47.2", + "version": "7.47.3", "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 eb493b8fdcc..89beb5b068d 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.47.2", + "version": "7.47.3", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.47.2", - "parabol-server": "7.47.2", + "parabol-client": "7.47.3", + "parabol-server": "7.47.3", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index ce8697722d6..d7715424fa4 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.47.2", + "version": "7.47.3", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 288762747df..bc67e1696c1 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.47.2", + "version": "7.47.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.47.2", + "parabol-client": "7.47.3", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 7071759492febb7df44d3189a4e9fae103ca1cef Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 12 Sep 2024 15:55:51 -0700 Subject: [PATCH 467/529] fix: add quotes to constraint (#10230) Signed-off-by: Matt Krick --- .../postgres/migrations/1726174443131_dropThreadParentId.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/postgres/migrations/1726174443131_dropThreadParentId.ts b/packages/server/postgres/migrations/1726174443131_dropThreadParentId.ts index be7c0fb86e6..b7341660ebe 100644 --- a/packages/server/postgres/migrations/1726174443131_dropThreadParentId.ts +++ b/packages/server/postgres/migrations/1726174443131_dropThreadParentId.ts @@ -8,7 +8,7 @@ export async function up() { await client.connect() // threadParentId can exist outside comment table (task, poll, etc) await client.query( - `ALTER TABLE "Comment" DROP CONSTRAINT IF EXISTS fk_threadParentId;` /* Do good magic */ + `ALTER TABLE "Comment" DROP CONSTRAINT IF EXISTS "fk_threadParentId";` /* Do good magic */ ) await client.end() } From e6f6e28160a7bdef0920db076df5c1264422e149 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 12 Sep 2024 16:04:36 -0700 Subject: [PATCH 468/529] chore(release): release v7.47.4 (#10231) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index eb706427947..0917c80dd09 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.47.3" + ".": "7.47.4" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 75ed5883eaa..6130980db11 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.47.4](https://github.com/ParabolInc/parabol/compare/v7.47.3...v7.47.4) (2024-09-12) + + +### Fixed + +* add quotes to constraint ([#10230](https://github.com/ParabolInc/parabol/issues/10230)) ([7071759](https://github.com/ParabolInc/parabol/commit/7071759492febb7df44d3189a4e9fae103ca1cef)) + ## [7.47.3](https://github.com/ParabolInc/parabol/compare/v7.47.2...v7.47.3) (2024-09-12) diff --git a/package.json b/package.json index 124d17eca01..f93bfe24776 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.47.3", + "version": "7.47.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 012b4566ed0..76c1b4e4a9d 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.47.3", + "version": "7.47.4", "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.47.3" + "parabol-server": "7.47.4" } } diff --git a/packages/client/package.json b/packages/client/package.json index d62acd8f161..aaec79d1545 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.47.3", + "version": "7.47.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 536ad4eaa60..4ff3d5a8785 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.47.3", + "version": "7.47.4", "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 89beb5b068d..60a5896d1b0 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.47.3", + "version": "7.47.4", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.47.3", - "parabol-server": "7.47.3", + "parabol-client": "7.47.4", + "parabol-server": "7.47.4", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index d7715424fa4..defab97401b 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.47.3", + "version": "7.47.4", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index bc67e1696c1..c53b311e1ec 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.47.3", + "version": "7.47.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.47.3", + "parabol-client": "7.47.4", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From cafbf320f02f652ba9b011a4a06b0606a4faf81e Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Mon, 16 Sep 2024 08:47:10 +0200 Subject: [PATCH 469/529] fix: Remove duplicate org users (#10198) --- packages/server/__tests__/autoJoin.test.ts | 2 +- .../server/billing/helpers/adjustUserCount.ts | 21 +++++---- ...254_addOrganizationUserUniqueConstraint.ts | 45 +++++++++++++++++++ 3 files changed, 58 insertions(+), 10 deletions(-) create mode 100644 packages/server/postgres/migrations/1725438336254_addOrganizationUserUniqueConstraint.ts diff --git a/packages/server/__tests__/autoJoin.test.ts b/packages/server/__tests__/autoJoin.test.ts index e48415ee854..d8e63a9d36c 100644 --- a/packages/server/__tests__/autoJoin.test.ts +++ b/packages/server/__tests__/autoJoin.test.ts @@ -74,7 +74,7 @@ const signUpVerified = async (email: string) => { } } -test.skip('autoJoin on multiple teams does not create duplicate `OrganizationUser`s', async () => { +test('autoJoin on multiple teams does not create duplicate `OrganizationUser`s', async () => { const domain = `${faker.internet.domainWord()}.parabol.fun` const email = `${faker.internet.userName()}@${domain}`.toLowerCase() diff --git a/packages/server/billing/helpers/adjustUserCount.ts b/packages/server/billing/helpers/adjustUserCount.ts index 3ddd78a3e4d..8654f8a0d69 100644 --- a/packages/server/billing/helpers/adjustUserCount.ts +++ b/packages/server/billing/helpers/adjustUserCount.ts @@ -61,19 +61,13 @@ const changePause = (inactive: boolean) => async (_orgIds: string[], user: IUser const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWorker) => { const {id: userId} = user - const [rawOrganizations, organizationUsers] = await Promise.all([ - dataLoader.get('organizations').loadMany(orgIds), - dataLoader.get('organizationUsersByUserId').load(userId) - ]) + const rawOrganizations = await dataLoader.get('organizations').loadMany(orgIds) const organizations = rawOrganizations.filter(isValid) const docs = orgIds.map((orgId) => { - const oldOrganizationUser = organizationUsers.find( - (organizationUser) => organizationUser.orgId === orgId - ) const organization = organizations.find((organization) => organization.id === orgId)! // continue the grace period from before, if any OR set to the end of the invoice OR (if it is a free account) no grace period return { - id: oldOrganizationUser?.id || generateUID(), + id: generateUID(), orgId, userId, tier: organization.tier @@ -83,7 +77,16 @@ const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWork await getKysely() .insertInto('OrganizationUser') .values(docs) - .onConflict((oc) => oc.doNothing()) + .onConflict((oc) => + oc.constraint('unique_org_user').doUpdateSet({ + joinedAt: sql`CURRENT_TIMESTAMP`, + removedAt: null, + inactive: false, + role: null, + suggestedTier: null, + tier: (eb) => eb.ref('excluded.tier') + }) + ) .execute() await Promise.all( orgIds.map((orgId) => { diff --git a/packages/server/postgres/migrations/1725438336254_addOrganizationUserUniqueConstraint.ts b/packages/server/postgres/migrations/1725438336254_addOrganizationUserUniqueConstraint.ts new file mode 100644 index 00000000000..adb0eb29a54 --- /dev/null +++ b/packages/server/postgres/migrations/1725438336254_addOrganizationUserUniqueConstraint.ts @@ -0,0 +1,45 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import getPg from '../getPg' + +export async function up() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + // get rid of duplicates + // no kysely here, because I tested the SQL directly and don't want to touch it + await sql` + DELETE FROM "OrganizationUser" d USING "OrganizationUser" k + WHERE d."userId" = k."userId" + AND d."orgId" = k."orgId" + AND ( + -- keep non-removed over removed + (k."removedAt" IS NULL AND d."removedAt" IS NOT NULL) + -- or removed later + OR (k."removedAt" IS NOT NULL + AND d."removedAt" IS NOT NULL + AND (k."removedAt" > d."removedAt" + OR ((k."removedAt" = d."removedAt") AND k.id > d.id) + ) + ) + -- or newer non-removed + OR (k."removedAt" IS NULL AND d."removedAt" IS NULL AND k.id > d.id) + ); + `.execute(pg) + + await pg.schema + .alterTable('OrganizationUser') + .addUniqueConstraint('unique_org_user', ['orgId', 'userId']) + .execute() +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await pg.schema.alterTable('OrganizationUser').dropConstraint('unique_org_user').execute() +} From 893a64fe5d64e8f2107a73c207dffc722d65acf4 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 17 Sep 2024 15:51:54 +0100 Subject: [PATCH 470/529] chore(release): release v7.47.5 (#10233) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0917c80dd09..ff2ef7b4f06 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.47.4" + ".": "7.47.5" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 6130980db11..e8f33aeab75 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.47.5](https://github.com/ParabolInc/parabol/compare/v7.47.4...v7.47.5) (2024-09-16) + + +### Fixed + +* Remove duplicate org users ([#10198](https://github.com/ParabolInc/parabol/issues/10198)) ([cafbf32](https://github.com/ParabolInc/parabol/commit/cafbf320f02f652ba9b011a4a06b0606a4faf81e)) + ## [7.47.4](https://github.com/ParabolInc/parabol/compare/v7.47.3...v7.47.4) (2024-09-12) diff --git a/package.json b/package.json index f93bfe24776..90c97117e66 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.47.4", + "version": "7.47.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 76c1b4e4a9d..1124079a529 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.47.4", + "version": "7.47.5", "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.47.4" + "parabol-server": "7.47.5" } } diff --git a/packages/client/package.json b/packages/client/package.json index aaec79d1545..bec5a37c452 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.47.4", + "version": "7.47.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 4ff3d5a8785..1516a4b5a08 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.47.4", + "version": "7.47.5", "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 60a5896d1b0..6daabf55bc5 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.47.4", + "version": "7.47.5", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.47.4", - "parabol-server": "7.47.4", + "parabol-client": "7.47.5", + "parabol-server": "7.47.5", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index defab97401b..97e62da9c62 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.47.4", + "version": "7.47.5", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index c53b311e1ec..08b438aaf01 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.47.4", + "version": "7.47.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.47.4", + "parabol-client": "7.47.5", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 89661a7425898418461aefb65428179622c70b78 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Thu, 19 Sep 2024 10:07:37 -0700 Subject: [PATCH 471/529] fix(misc): show full length of agenda item text when hovering (#10251) --- packages/client/components/MeetingSubnavItem.tsx | 16 +++++++++++++++- .../components/AgendaItem/AgendaItem.tsx | 4 +++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/client/components/MeetingSubnavItem.tsx b/packages/client/components/MeetingSubnavItem.tsx index 4cfb260611f..fdacf66b198 100644 --- a/packages/client/components/MeetingSubnavItem.tsx +++ b/packages/client/components/MeetingSubnavItem.tsx @@ -3,6 +3,9 @@ import React, {ReactNode, useRef} from 'react' import useScrollIntoView from '../hooks/useScrollIntoVIew' import {PALETTE} from '../styles/paletteV3' import {NavSidebar} from '../types/constEnums' +import {Tooltip} from '../ui/Tooltip/Tooltip' +import {TooltipContent} from '../ui/Tooltip/TooltipContent' +import {TooltipTrigger} from '../ui/Tooltip/TooltipTrigger' const lineHeight = NavSidebar.SUB_LINE_HEIGHT @@ -88,6 +91,10 @@ const MeetingSubnavItem = (props: Props) => { } = props const ref = useRef(null) useScrollIntoView(ref, isActive) + const labelRef = useRef(null) + const isOverflowing = + labelRef.current && labelRef.current.scrollWidth > labelRef.current.clientWidth + return ( { isUnsyncedFacilitatorStage={isUnsyncedFacilitatorStage} onClick={!isDisabled ? onClick : undefined} > - {children} + + + + {children} + + + {isOverflowing && {children}} + {metaContent} ) diff --git a/packages/client/modules/teamDashboard/components/AgendaItem/AgendaItem.tsx b/packages/client/modules/teamDashboard/components/AgendaItem/AgendaItem.tsx index 579ad32697b..f2b20c6cb03 100644 --- a/packages/client/modules/teamDashboard/components/AgendaItem/AgendaItem.tsx +++ b/packages/client/modules/teamDashboard/components/AgendaItem/AgendaItem.tsx @@ -238,7 +238,9 @@ const AgendaItem = (props: Props) => { /> {tooltipPortal( - pinned ? `Unpin "${content}" from every check-in` : `Pin "${content}" to every check-in` + pinned + ? `Unpin this agenda topic from every check-in` + : `Pin this agenda topic to every check-in` )} ) From 5893e38a008597ec2e659b488ec0e3444338a2ff Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 23 Sep 2024 14:27:56 -0600 Subject: [PATCH 472/529] fix: bump relay so it shares react's scheduler (#10262) Signed-off-by: Matt Krick --- package.json | 4 +- packages/client/Atmosphere.ts | 4 +- .../components/ActionMeetingUpdatesPrompt.tsx | 6 +- .../ActivityDetailsSidebar.tsx | 2 +- .../ActivityLibrary/TeamPickerModal.tsx | 2 +- .../DashNavList/DashNavListTeams.tsx | 2 +- packages/client/components/DueDateToggle.tsx | 2 +- .../components/GroupingKanbanColumnHeader.tsx | 2 +- .../JiraScopingSearchFilterMenu.tsx | 4 +- .../ReflectionCard/ReflectionCard.tsx | 6 +- .../ReflectionGroup/ReflectionGroup.tsx | 2 +- .../ReflectionGroupTitleEditor.tsx | 2 +- .../ReflectionGroup/RemoteReflection.tsx | 2 +- .../client/components/TimelineFeedList.tsx | 4 +- .../hooks/useAnimatedSpotlightSource.ts | 2 +- .../client/hooks/useMakeStageSummaries.ts | 2 +- packages/client/hooks/useSlackChannels.ts | 2 +- packages/client/hooks/useUsageSnackNag.ts | 2 +- .../EmailNotifications/EmailTaskInvolves.tsx | 2 +- .../TaskSummarySection.tsx | 2 +- .../components/OrgUserRow/OrgMemberRow.tsx | 2 +- .../components/UserProfileRoot.tsx | 2 +- .../mutations/LoginWithGoogleMutation.ts | 2 +- .../mutations/LoginWithMicrosoftMutation.ts | 2 +- .../mutations/LoginWithPasswordMutation.ts | 2 +- .../mutations/SignUpWithPasswordMutation.ts | 2 +- .../StartDraggingReflectionMutation.ts | 2 +- .../handlers/handleRemoveOrgMembers.ts | 2 +- .../mutations/handlers/handleUpsertTasks.ts | 10 ++-- packages/client/package.json | 4 +- .../schemaExtensions/clientSchema.graphql | 15 +++++ .../client/utils/AzureDevOpsClientManager.ts | 2 +- packages/client/utils/getNonNullEdges.ts | 4 +- relay.config.js | 2 +- yarn.lock | 57 +++++++++++++------ 35 files changed, 106 insertions(+), 60 deletions(-) diff --git a/package.json b/package.json index 90c97117e66..bb1decbc61e 100644 --- a/package.json +++ b/package.json @@ -120,8 +120,8 @@ "prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-tailwindcss": "^0.5.13", "raw-loader": "^4.0.2", - "relay-compiler": "^14.1.0", - "relay-config": "^12.0.0", + "relay-compiler": "^18.0.0", + "relay-config": "^12.0.1", "sucrase": "^3.32.0", "tailwindcss": "^3.2.7", "terser-webpack-plugin": "^5.3.9", diff --git a/packages/client/Atmosphere.ts b/packages/client/Atmosphere.ts index d32458a27ba..1e36534f1f4 100644 --- a/packages/client/Atmosphere.ts +++ b/packages/client/Atmosphere.ts @@ -384,8 +384,8 @@ export default class Atmosphere extends Environment { window.location.href = '/' } - setAuthToken = async (authToken: string | null) => { - this.authToken = authToken + setAuthToken = async (authToken: string | null | undefined) => { + this.authToken = authToken || null if (!authToken) { this.authObj = null window.localStorage.removeItem(LocalStorageKey.APP_TOKEN_KEY) diff --git a/packages/client/components/ActionMeetingUpdatesPrompt.tsx b/packages/client/components/ActionMeetingUpdatesPrompt.tsx index ea41e45abb8..34059e68295 100644 --- a/packages/client/components/ActionMeetingUpdatesPrompt.tsx +++ b/packages/client/components/ActionMeetingUpdatesPrompt.tsx @@ -28,7 +28,11 @@ const StyledHeader = styled(PhaseHeaderTitle)({ overflowWrap: 'break-word' }) -const getQuestion = (isConnected: boolean | null, taskCount: number, preferredName: string) => { +const getQuestion = ( + isConnected: boolean | null | undefined, + taskCount: number, + preferredName: string +) => { if (isConnected) { return taskCount > 0 ? 'what’s changed with your tasks?' : 'what are you working on?' } diff --git a/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx b/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx index b7b048d8cce..ff970cf3d63 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx @@ -33,7 +33,7 @@ interface Props { selectedTemplateRef: ActivityDetailsSidebar_template$key teamsRef: ActivityDetailsSidebar_teams$key type: MeetingTypeEnum - preferredTeamId: string | null + preferredTeamId: string | null | undefined } const ActivityDetailsSidebar = (props: Props) => { diff --git a/packages/client/components/ActivityLibrary/TeamPickerModal.tsx b/packages/client/components/ActivityLibrary/TeamPickerModal.tsx index 2d16d9571b7..ab5943f93bd 100644 --- a/packages/client/components/ActivityLibrary/TeamPickerModal.tsx +++ b/packages/client/components/ActivityLibrary/TeamPickerModal.tsx @@ -22,7 +22,7 @@ const ACTION_BUTTON_CLASSES = 'w-max cursor-pointer rounded-full px-4 py-2 text-center font-sans text-base font-medium' interface Props { - preferredTeamId: string | null + preferredTeamId: string | null | undefined teamsRef: TeamPickerModal_teams$key category: string parentTemplateId: string diff --git a/packages/client/components/DashNavList/DashNavListTeams.tsx b/packages/client/components/DashNavList/DashNavListTeams.tsx index 560567cdb79..4a76af84fa7 100644 --- a/packages/client/components/DashNavList/DashNavListTeams.tsx +++ b/packages/client/components/DashNavList/DashNavListTeams.tsx @@ -56,7 +56,7 @@ const DashNavListTeams = (props: Props) => { onClick && onClick() } - const getIcon = (lockedAt: string | null, isPaid: boolean | null) => + const getIcon = (lockedAt: string | null | undefined, isPaid: boolean | null | undefined) => lockedAt || !isPaid ? 'warning' : 'group' if (!viewerTeams.length) return null diff --git a/packages/client/components/DueDateToggle.tsx b/packages/client/components/DueDateToggle.tsx index 3feb5e60c3b..049c918713c 100644 --- a/packages/client/components/DueDateToggle.tsx +++ b/packages/client/components/DueDateToggle.tsx @@ -114,7 +114,7 @@ const formatDueDate = (dueDate: string) => { } const action = 'tap to change' -const getDateInfo = (dueDate: string | null) => { +const getDateInfo = (dueDate: string | null | undefined) => { if (!dueDate) return {title: 'Add a Due Date'} const date = new Date(dueDate) const timeDiff = date.getTime() - Date.now() diff --git a/packages/client/components/GroupingKanbanColumnHeader.tsx b/packages/client/components/GroupingKanbanColumnHeader.tsx index 077894c48f2..95083ec9cf2 100644 --- a/packages/client/components/GroupingKanbanColumnHeader.tsx +++ b/packages/client/components/GroupingKanbanColumnHeader.tsx @@ -69,7 +69,7 @@ interface Props { groupColor: string isWidthExpanded: boolean onClick: () => void - phaseType: string | null + phaseType: string | null | undefined question: string submitting: boolean toggleWidth: (e: MouseEvent) => void diff --git a/packages/client/components/JiraScopingSearchFilterMenu.tsx b/packages/client/components/JiraScopingSearchFilterMenu.tsx index 5210003cb20..4b91d879fbc 100644 --- a/packages/client/components/JiraScopingSearchFilterMenu.tsx +++ b/packages/client/components/JiraScopingSearchFilterMenu.tsx @@ -51,7 +51,7 @@ type JiraSearchQuery = { type Project = { id: string name: string - avatar: string | null + avatar: string | null | undefined } interface Props { @@ -157,7 +157,7 @@ const JiraScopingSearchFilterMenu = (props: Props) => { active={projectKeyFilters.includes(globalProjectKey)} disabled={isJQL} /> - + } diff --git a/packages/client/components/ReflectionCard/ReflectionCard.tsx b/packages/client/components/ReflectionCard/ReflectionCard.tsx index 402aa5722a1..1e606a2fdbd 100644 --- a/packages/client/components/ReflectionCard/ReflectionCard.tsx +++ b/packages/client/components/ReflectionCard/ReflectionCard.tsx @@ -72,7 +72,11 @@ interface Props { } const getReadOnly = ( - reflection: {id: string; isViewerCreator: boolean | null; isEditing: boolean | null}, + reflection: { + id: string + isViewerCreator: boolean | null | undefined + isEditing: boolean | null | undefined + }, phaseType: NewMeetingPhaseTypeEnum, stackCount: number | undefined, phases: any | null, diff --git a/packages/client/components/ReflectionGroup/ReflectionGroup.tsx b/packages/client/components/ReflectionGroup/ReflectionGroup.tsx index ffb5efa3419..f213a284f39 100644 --- a/packages/client/components/ReflectionGroup/ReflectionGroup.tsx +++ b/packages/client/components/ReflectionGroup/ReflectionGroup.tsx @@ -43,7 +43,7 @@ const Group = styled('div')<{staticReflectionCount: number; isSpotlightSource: b const ReflectionWrapper = styled('div')<{ staticIdx: number - isDropping: boolean | null + isDropping: boolean | null | undefined groupCount: number isHiddenSpotlightSource: boolean }>(({staticIdx, isDropping, groupCount, isHiddenSpotlightSource}) => { diff --git a/packages/client/components/ReflectionGroup/ReflectionGroupTitleEditor.tsx b/packages/client/components/ReflectionGroup/ReflectionGroupTitleEditor.tsx index c02a5677c69..e5c6aeb8353 100644 --- a/packages/client/components/ReflectionGroup/ReflectionGroupTitleEditor.tsx +++ b/packages/client/components/ReflectionGroup/ReflectionGroupTitleEditor.tsx @@ -77,7 +77,7 @@ const NameInput = styled('input')<{isExpanded: boolean; readOnly: boolean}>( const getValidationError = ( title: string | null, - reflectionGroups: readonly {id: string; title: string | null}[], + reflectionGroups: readonly {id: string; title: string | null | undefined}[], reflectionGroupId: string ) => { if (!title || title.length < 1) { diff --git a/packages/client/components/ReflectionGroup/RemoteReflection.tsx b/packages/client/components/ReflectionGroup/RemoteReflection.tsx index 02c05d48ade..7275d86114f 100644 --- a/packages/client/components/ReflectionGroup/RemoteReflection.tsx +++ b/packages/client/components/ReflectionGroup/RemoteReflection.tsx @@ -135,7 +135,7 @@ const getHeaderTransform = (ref: RefObject, topPadding = 18) => */ const getStyle = ( remoteDrag: RemoteReflection_reflection$data['remoteDrag'], - isDropping: boolean | null, + isDropping: boolean | null | undefined, isSpotlight: boolean, style: React.CSSProperties ) => { diff --git a/packages/client/components/TimelineFeedList.tsx b/packages/client/components/TimelineFeedList.tsx index b0c1159f355..8aa8f21e038 100644 --- a/packages/client/components/TimelineFeedList.tsx +++ b/packages/client/components/TimelineFeedList.tsx @@ -120,7 +120,9 @@ const TimelineFeedList = (props: Props) => { ))} {lockedHistory && ( <> - + {lockedHistory.map(({node: timelineEvent}) => ( ))} diff --git a/packages/client/hooks/useAnimatedSpotlightSource.ts b/packages/client/hooks/useAnimatedSpotlightSource.ts index 2eeb2d60e76..82fdf45333d 100644 --- a/packages/client/hooks/useAnimatedSpotlightSource.ts +++ b/packages/client/hooks/useAnimatedSpotlightSource.ts @@ -11,7 +11,7 @@ import cloneReflection from '~/utils/retroGroup/cloneReflection' const useAnimatedSpotlightSource = ( portalStatus: PortalStatus, - reflectionId: string | null, + reflectionId: string | null | undefined, dragIdRef: MutableRefObject ) => { const atmosphere = useAtmosphere() diff --git a/packages/client/hooks/useMakeStageSummaries.ts b/packages/client/hooks/useMakeStageSummaries.ts index d5e5fb557a8..2827ab0637c 100644 --- a/packages/client/hooks/useMakeStageSummaries.ts +++ b/packages/client/hooks/useMakeStageSummaries.ts @@ -129,7 +129,7 @@ const useMakeStageSummaries = (phaseRef: useMakeStageSummaries_phase$key, localS isNavigable: batch.some(({isNavigable}) => isNavigable), isActive: !!batch.find(({id}) => id === localStageId), stageIds: batch.map(({id}) => id) as [string, ...string[]], - finalScores: batch.map(({finalScore}) => finalScore), + finalScores: batch.map(({finalScore}) => finalScore || null), taskId }) i += batch.length - 1 diff --git a/packages/client/hooks/useSlackChannels.ts b/packages/client/hooks/useSlackChannels.ts index 37dfb4e1f15..3b0d359b060 100644 --- a/packages/client/hooks/useSlackChannels.ts +++ b/packages/client/hooks/useSlackChannels.ts @@ -8,7 +8,7 @@ interface MinimalChannel { } const useSlackChannels = ( - slackAuth: {botAccessToken: string | null; slackUserId: string} | null + slackAuth: {botAccessToken: string | null | undefined; slackUserId: string} | null | undefined ) => { const [channels, setChannels] = useState([]) useEffect(() => { diff --git a/packages/client/hooks/useUsageSnackNag.ts b/packages/client/hooks/useUsageSnackNag.ts index f8427654ab3..16b5a4ea810 100644 --- a/packages/client/hooks/useUsageSnackNag.ts +++ b/packages/client/hooks/useUsageSnackNag.ts @@ -17,7 +17,7 @@ const getIsNaggingPath = (history: RouterProps['history']) => { return !(pathname.includes('/usage') || pathname.includes('/meet/')) } -const shouldNag = (billingTier: TierEnum, suggestedTier: TierEnum | null) => { +const shouldNag = (billingTier: TierEnum, suggestedTier: TierEnum | null | undefined) => { if (!suggestedTier) return false const suggestPro = suggestedTier === 'team' && billingTier === 'starter' const suggestEnterprise = suggestedTier === 'enterprise' && billingTier !== 'enterprise' diff --git a/packages/client/modules/email/components/EmailNotifications/EmailTaskInvolves.tsx b/packages/client/modules/email/components/EmailNotifications/EmailTaskInvolves.tsx index 942f993fe21..69659c3fb31 100644 --- a/packages/client/modules/email/components/EmailNotifications/EmailTaskInvolves.tsx +++ b/packages/client/modules/email/components/EmailNotifications/EmailTaskInvolves.tsx @@ -71,7 +71,7 @@ const EmailTaskInvolves = (props: Props) => { linkUrl={linkUrl} >
- {discussionPromptQuestion && ( - <> -
{'🤖 Discussion Question'}
{discussionPromptQuestion}
{'🤖 Discussion Summary'}
{discussionSummary}
{'🤖 Discussion Summary'}
{discussionSummary}
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

T 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%0A_Ht%T<$;Fcwx;De|k_? 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 z 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≧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�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

DeDq~bq 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%0AtU=aI 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

?$2MI@+g4^}S4a`H|_o}ZtB5>&G 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%0D3@G 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

(Iahc1x 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 SZZcv=f8yJTF#9v>fZ7)H?y;mr`4V4#A%KfE- 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 A5C8xG literal 0 HcmV?d00001 From 5c40fcfb1d6df9ac32c2f2277735169f0e1ae95d Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 20 May 2024 10:43:56 -0700 Subject: [PATCH 224/529] feat: saml upload (#9750) Signed-off-by: Matt Krick --- .../OrgAuthenticationMetadata.tsx | 49 ++++++++++++++++++- .../mutations/useUploadIdPMetadataMutation.ts | 43 ++++++++++++++++ packages/client/ui/Button/Button.tsx | 15 +++--- .../server/fileStorage/FileStoreManager.ts | 5 ++ .../public/mutations/uploadIdPMetadata.ts | 24 +++++++++ packages/server/graphql/public/permissions.ts | 12 +++-- .../public/rules/getResolverDotPath.ts | 9 ++++ .../server/graphql/public/rules/isOrgTier.ts | 29 ++++------- .../public/rules/isViewerBillingLeader.ts | 43 ++++++---------- .../graphql/public/rules/isViewerOnOrg.ts | 17 +++++++ .../public/typeDefs/uploadIdPMetadata.graphql | 22 +++++++++ 11 files changed, 209 insertions(+), 59 deletions(-) create mode 100644 packages/client/mutations/useUploadIdPMetadataMutation.ts create mode 100644 packages/server/graphql/public/mutations/uploadIdPMetadata.ts create mode 100644 packages/server/graphql/public/rules/getResolverDotPath.ts create mode 100644 packages/server/graphql/public/rules/isViewerOnOrg.ts create mode 100644 packages/server/graphql/public/typeDefs/uploadIdPMetadata.graphql diff --git a/packages/client/modules/userDashboard/components/OrgAuthentication/OrgAuthenticationMetadata.tsx b/packages/client/modules/userDashboard/components/OrgAuthentication/OrgAuthenticationMetadata.tsx index d49313ec356..ba2d6ece67c 100644 --- a/packages/client/modules/userDashboard/components/OrgAuthentication/OrgAuthenticationMetadata.tsx +++ b/packages/client/modules/userDashboard/components/OrgAuthentication/OrgAuthenticationMetadata.tsx @@ -1,5 +1,6 @@ +import UploadFileIcon from '@mui/icons-material/UploadFile' import graphql from 'babel-plugin-relay/macro' -import React, {useState} from 'react' +import React, {useRef, useState} from 'react' import {commitLocalUpdate, useFragment} from 'react-relay' import orgAuthenticationMetadataQuery, { OrgAuthenticationMetadataQuery @@ -9,6 +10,8 @@ import BasicInput from '../../../../components/InputField/BasicInput' import SecondaryButton from '../../../../components/SecondaryButton' import useAtmosphere from '../../../../hooks/useAtmosphere' import useMutationProps from '../../../../hooks/useMutationProps' +import {useUploadIdPMetadata} from '../../../../mutations/useUploadIdPMetadataMutation' +import {Button} from '../../../../ui/Button/Button' import getOAuthPopupFeatures from '../../../../utils/getOAuthPopupFeatures' import getTokenFromSSO from '../../../../utils/getTokenFromSSO' @@ -40,6 +43,7 @@ const OrgAuthenticationMetadata = (props: Props) => { fragment OrgAuthenticationMetadata_saml on SAML { id metadataURL + orgId } `, samlRef @@ -49,7 +53,7 @@ const OrgAuthenticationMetadata = (props: Props) => { const isMetadataURLSaved = saml ? saml.metadataURL === metadataURL : false const {error, onCompleted, onError, submitMutation, submitting} = useMutationProps() const submitMetadataURL = async () => { - if (submitting) return + if (submitting || !metadataURL) return submitMutation() const domain = saml?.id if (!domain) { @@ -99,6 +103,36 @@ const OrgAuthenticationMetadata = (props: Props) => { key: 'submitMetadata' }) } + + const uploadInputRef = useRef(null) + const onUploadClick = () => { + uploadInputRef.current?.click() + } + const [commit] = useUploadIdPMetadata() + const uploadXML = (e: React.ChangeEvent) => { + const {files} = e.currentTarget + const file = files?.[0] + if (!file || !saml?.orgId) return + commit({ + variables: {orgId: saml.orgId}, + uploadables: {file: file}, + onCompleted: (res) => { + const {uploadIdPMetadata} = res + const {error, url} = uploadIdPMetadata + const message = error?.message + if (message) { + atmosphere.eventEmitter.emit('addSnackbar', { + key: 'errorUploadIdPtMetadata', + message, + autoDismiss: 5 + }) + return + } + setMetadataURL(url!) + } + }) + } + return ( <>

{error?.message}
diff --git a/packages/client/mutations/useUploadIdPMetadataMutation.ts b/packages/client/mutations/useUploadIdPMetadataMutation.ts new file mode 100644 index 00000000000..0c50bf2540d --- /dev/null +++ b/packages/client/mutations/useUploadIdPMetadataMutation.ts @@ -0,0 +1,43 @@ +import graphql from 'babel-plugin-relay/macro' +import {useMutation} from 'react-relay' +import {useUploadIdPMetadataMutation as TuseUploadIdPMetadataMutation} from '../__generated__/useUploadIdPMetadataMutation.graphql' + +const mutation = graphql` + mutation useUploadIdPMetadataMutation($file: File!, $orgId: ID!) { + uploadIdPMetadata(file: $file, orgId: $orgId) { + ... on ErrorPayload { + error { + message + } + } + ... on UploadIdPMetadataSuccess { + url + } + } + } +` +interface TTuseUploadIdPMetadataMutation extends Omit { + variables: Omit + uploadables: {file: File} +} + +export const useUploadIdPMetadata = () => { + const [commit, submitting] = useMutation(mutation) + type Execute = ( + config: Parameters[0] & {uploadables: {file: File}} + ) => ReturnType + + const execute: Execute = (config) => { + const {variables} = config + const {orgId} = variables + return commit({ + updater: (store) => { + const org = store.get(orgId) + org?.setValue(orgId, 'id') + }, + // allow components to override default handlers + ...config + }) + } + return [execute, submitting] as const +} diff --git a/packages/client/ui/Button/Button.tsx b/packages/client/ui/Button/Button.tsx index e214959b5c7..fcfb00ec8eb 100644 --- a/packages/client/ui/Button/Button.tsx +++ b/packages/client/ui/Button/Button.tsx @@ -1,6 +1,7 @@ import {Slot} from '@radix-ui/react-slot' import clsx from 'clsx' import React from 'react' +import {twMerge} from 'tailwind-merge' type Variant = 'primary' | 'secondary' | 'destructive' | 'ghost' | 'link' | 'outline' type Size = 'sm' | 'md' | 'lg' | 'default' @@ -45,12 +46,14 @@ const Button = React.forwardRef( const Comp = asChild ? Slot : 'button' return ( { + // VALIDATION + const {contentType, buffer: jsonBuffer} = file + const buffer = Buffer.from(jsonBuffer.data) + if (!contentType || !contentType.includes('xml')) { + return {error: {message: 'file must be XML'}} + } + if (buffer.byteLength > 1000000) { + return {error: {message: 'file must be less than 1MB'}} + } + if (buffer.byteLength <= 1) { + return {error: {message: 'file must be larger than 1 byte'}} + } + + // RESOLUTION + const manager = getFileStoreManager() + const url = await manager.putOrgIdPMetadata(buffer, orgId) + return {url} +} + +export default uploadIdPMetadata diff --git a/packages/server/graphql/public/permissions.ts b/packages/server/graphql/public/permissions.ts index dd0930cdb35..edf2d4ccce6 100644 --- a/packages/server/graphql/public/permissions.ts +++ b/packages/server/graphql/public/permissions.ts @@ -4,10 +4,11 @@ import {Resolvers} from './resolverTypes' import getTeamIdFromArgTemplateId from './rules/getTeamIdFromArgTemplateId' import isAuthenticated from './rules/isAuthenticated' import isEnvVarTrue from './rules/isEnvVarTrue' -import {isOrgTier, isOrgTierSource} from './rules/isOrgTier' +import {isOrgTier} from './rules/isOrgTier' import isSuperUser from './rules/isSuperUser' import isUserViewer from './rules/isUserViewer' -import {isViewerBillingLeader, isViewerBillingLeaderSource} from './rules/isViewerBillingLeader' +import {isViewerBillingLeader} from './rules/isViewerBillingLeader' +import {isViewerOnOrg} from './rules/isViewerOnOrg' import isViewerOnTeam from './rules/isViewerOnTeam' import rateLimit from './rules/rateLimit' @@ -50,9 +51,10 @@ const permissionMap: PermissionMap = { verifyEmail: rateLimit({perMinute: 50, perHour: 100}), addApprovedOrganizationDomains: or( isSuperUser, - and(isViewerBillingLeader, isOrgTier('enterprise')) + and(isViewerBillingLeader('args.orgId'), isOrgTier('args.orgId', 'enterprise')) ), - removeApprovedOrganizationDomains: or(isSuperUser, isViewerBillingLeader), + removeApprovedOrganizationDomains: or(isSuperUser, isViewerBillingLeader('args.orgId')), + uploadIdPMetadata: isViewerOnOrg('args.orgId'), updateTemplateCategory: isViewerOnTeam(getTeamIdFromArgTemplateId) }, Query: { @@ -61,7 +63,7 @@ const permissionMap: PermissionMap = { SAMLIdP: rateLimit({perMinute: 120, perHour: 3600}) }, Organization: { - saml: and(isViewerBillingLeaderSource, isOrgTierSource('enterprise')) + saml: and(isViewerBillingLeader('source.id'), isOrgTier('source.id', 'enterprise')) }, User: { domains: or(isSuperUser, isUserViewer) diff --git a/packages/server/graphql/public/rules/getResolverDotPath.ts b/packages/server/graphql/public/rules/getResolverDotPath.ts new file mode 100644 index 00000000000..cac815882a0 --- /dev/null +++ b/packages/server/graphql/public/rules/getResolverDotPath.ts @@ -0,0 +1,9 @@ +export const getResolverDotPath = ( + dotPath: ResolverDotPath, + source: Record, + args: Record +) => { + return dotPath.split('.').reduce((val: any, key) => val?.[key], {source, args}) +} + +export type ResolverDotPath = `source.${string}` | `args.${string}` diff --git a/packages/server/graphql/public/rules/isOrgTier.ts b/packages/server/graphql/public/rules/isOrgTier.ts index 8b1da02e726..06e10388dac 100644 --- a/packages/server/graphql/public/rules/isOrgTier.ts +++ b/packages/server/graphql/public/rules/isOrgTier.ts @@ -1,25 +1,16 @@ import {rule} from 'graphql-shield' import {GQLContext} from '../../graphql' import {TierEnum} from '../resolverTypes' +import {ResolverDotPath, getResolverDotPath} from './getResolverDotPath' -const resolve = async (requiredTier: TierEnum, orgId: string, {dataLoader}: GQLContext) => { - const organization = await dataLoader.get('organizations').load(orgId) - if (!organization) return new Error('Organization not found') - const {tier} = organization - if (tier !== requiredTier) return new Error(`Organization is not ${requiredTier}`) - return true -} - -export const isOrgTierSource = (requiredTier: TierEnum) => - rule(`isOrgTierSource-${requiredTier}`, {cache: 'strict'})( - async ({id: orgId}, _args, context: GQLContext) => { - return resolve(requiredTier, orgId, context) - } - ) - -export const isOrgTier = (requiredTier: TierEnum) => - rule(`isOrgTier-${requiredTier}`, {cache: 'strict'})( - async (_source, {orgId}, context: GQLContext) => { - return resolve(requiredTier, orgId, context) +export const isOrgTier = (orgIdDotPath: ResolverDotPath, requiredTier: TierEnum) => + rule(`isViewerOnOrg-${orgIdDotPath}-${requiredTier}`, {cache: 'strict'})( + async (source, args, {dataLoader}: GQLContext) => { + const orgId = getResolverDotPath(orgIdDotPath, source, args) + const organization = await dataLoader.get('organizations').load(orgId) + if (!organization) return new Error('Organization not found') + const {tier} = organization + if (tier !== requiredTier) return new Error(`Organization is not ${requiredTier}`) + return true } ) diff --git a/packages/server/graphql/public/rules/isViewerBillingLeader.ts b/packages/server/graphql/public/rules/isViewerBillingLeader.ts index 87462f99486..28708113671 100644 --- a/packages/server/graphql/public/rules/isViewerBillingLeader.ts +++ b/packages/server/graphql/public/rules/isViewerBillingLeader.ts @@ -1,31 +1,20 @@ import {rule} from 'graphql-shield' import {getUserId} from '../../../utils/authorization' import {GQLContext} from '../../graphql' +import {ResolverDotPath, getResolverDotPath} from './getResolverDotPath' -const resolve = async (orgId: string, {authToken, dataLoader}: GQLContext) => { - const viewerId = getUserId(authToken) - const organizationUser = await dataLoader - .get('organizationUsersByUserIdOrgId') - .load({orgId, userId: viewerId}) - if (!organizationUser) return new Error('Organization User not found') - const {role} = organizationUser - if (role !== 'BILLING_LEADER' && role !== 'ORG_ADMIN') - return new Error('User is not billing leader') - return true -} - -export const isViewerBillingLeader = rule({cache: 'strict'})(async ( - _source, - {orgId}, - context: GQLContext -) => { - return resolve(orgId, context) -}) - -export const isViewerBillingLeaderSource = rule({cache: 'strict'})(async ( - {id: orgId}, - _args, - context: GQLContext -) => { - return resolve(orgId, context) -}) +export const isViewerBillingLeader = (orgIdDotPath: ResolverDotPath) => + rule(`isViewerBillingLeader-${orgIdDotPath}`, {cache: 'strict'})( + async (source, args, {authToken, dataLoader}: GQLContext) => { + const orgId = getResolverDotPath(orgIdDotPath, source, args) + const viewerId = getUserId(authToken) + const organizationUser = await dataLoader + .get('organizationUsersByUserIdOrgId') + .load({orgId, userId: viewerId}) + if (!organizationUser) return new Error('Organization User not found') + const {role} = organizationUser + if (role !== 'BILLING_LEADER' && role !== 'ORG_ADMIN') + return new Error('User is not billing leader') + return true + } + ) diff --git a/packages/server/graphql/public/rules/isViewerOnOrg.ts b/packages/server/graphql/public/rules/isViewerOnOrg.ts new file mode 100644 index 00000000000..01b0cbab18f --- /dev/null +++ b/packages/server/graphql/public/rules/isViewerOnOrg.ts @@ -0,0 +1,17 @@ +import {rule} from 'graphql-shield' +import {getUserId} from '../../../utils/authorization' +import {GQLContext} from '../../graphql' +import {ResolverDotPath, getResolverDotPath} from './getResolverDotPath' + +export const isViewerOnOrg = (orgIdDotPath: ResolverDotPath) => + rule(`isViewerOnOrg-${orgIdDotPath}`, {cache: 'strict'})( + async (source, args, {authToken, dataLoader}: GQLContext) => { + const orgId = getResolverDotPath(orgIdDotPath, source, args) + const viewerId = getUserId(authToken) + const organizationUser = await dataLoader + .get('organizationUsersByUserIdOrgId') + .load({orgId, userId: viewerId}) + if (!organizationUser) return new Error('Viewer is not on Organization') + return true + } + ) diff --git a/packages/server/graphql/public/typeDefs/uploadIdPMetadata.graphql b/packages/server/graphql/public/typeDefs/uploadIdPMetadata.graphql new file mode 100644 index 00000000000..346824208d0 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/uploadIdPMetadata.graphql @@ -0,0 +1,22 @@ +extend type Mutation { + """ + Upload the IdP Metadata file for an org for those who cannot self-host the file + """ + uploadIdPMetadata( + """ + the XML Metadata file for the IdP + """ + file: File! + + """ + The orgId to upload the IdP Metadata for + """ + orgId: ID! + ): UploadIdPMetadataPayload! +} + +union UploadIdPMetadataPayload = ErrorPayload | UploadIdPMetadataSuccess + +type UploadIdPMetadataSuccess { + url: String! +} From 28c743274df3c8ed97e3e8dbe2677a58483a851e Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Mon, 20 May 2024 10:57:12 -0700 Subject: [PATCH 225/529] chore: update tutorial card thumbnail & video links (#9746) --- packages/client/components/MeetingsDash.tsx | 1 + .../client/components/TutorialMeetingCard.tsx | 10 ++++++++-- .../TeamDashActivityTab.tsx | 1 + .../illustrations/pokerTutorialThumb.jpg | Bin 59506 -> 847569 bytes .../illustrations/retroTutorialThumb.png | Bin 0 -> 749107 bytes .../illustrations/standupTutorialThumb.jpg | Bin 114736 -> 786917 bytes 6 files changed, 10 insertions(+), 2 deletions(-) create mode 100644 static/images/illustrations/retroTutorialThumb.png diff --git a/packages/client/components/MeetingsDash.tsx b/packages/client/components/MeetingsDash.tsx index 7faf932e1b6..bffee6c9a76 100644 --- a/packages/client/components/MeetingsDash.tsx +++ b/packages/client/components/MeetingsDash.tsx @@ -137,6 +137,7 @@ const MeetingsDash = (props: Props) => { {!teamFilterIds ? ( + diff --git a/packages/client/components/TutorialMeetingCard.tsx b/packages/client/components/TutorialMeetingCard.tsx index 543a9eb1f6a..da38f967417 100644 --- a/packages/client/components/TutorialMeetingCard.tsx +++ b/packages/client/components/TutorialMeetingCard.tsx @@ -2,6 +2,7 @@ import styled from '@emotion/styled' import React, {useCallback} from 'react' import SendClientSideEvent from '~/utils/SendClientSideEvent' import pokerTutorialThumb from '../../../static/images/illustrations/pokerTutorialThumb.jpg' +import retroTutorialThumb from '../../../static/images/illustrations/retroTutorialThumb.png' import standupTutorialThumb from '../../../static/images/illustrations/standupTutorialThumb.jpg' import useAtmosphere from '../hooks/useAtmosphere' import useBreakpoint from '../hooks/useBreakpoint' @@ -100,14 +101,19 @@ const TopLine = styled('div')({ }) interface Props { - type: 'poker' | 'standup' + type: 'retro' | 'poker' | 'standup' } const TUTORIAL_MAP = { + retro: { + label: 'Starting a Retrospective Meeting', + thumbnail: retroTutorialThumb, + url: 'https://www.youtube.com/embed/C96fNtypaww?modestbranding=1&rel=0' + }, poker: { label: 'Starting a Sprint Poker Meeting', thumbnail: pokerTutorialThumb, - url: 'https://www.youtube.com/embed/X_i60AMxPBU?modestbranding=1&rel=0' + url: 'https://www.youtube.com/embed/RJGnNXvvShY?modestbranding=1&rel=0' }, standup: { label: 'Starting a Standup Meeting', diff --git a/packages/client/modules/teamDashboard/components/TeamDashActivityTab/TeamDashActivityTab.tsx b/packages/client/modules/teamDashboard/components/TeamDashActivityTab/TeamDashActivityTab.tsx index 1698bc099a5..5a09a122b4c 100644 --- a/packages/client/modules/teamDashboard/components/TeamDashActivityTab/TeamDashActivityTab.tsx +++ b/packages/client/modules/teamDashboard/components/TeamDashActivityTab/TeamDashActivityTab.tsx @@ -65,6 +65,7 @@ const TeamDashActivityTab = (props: Props) => { ) : ( <> + diff --git a/static/images/illustrations/pokerTutorialThumb.jpg b/static/images/illustrations/pokerTutorialThumb.jpg index 9bdf023066518582e46851843be70cee7ce3c48d..e10236a1417db8c87f0d43c53c5ac08451a30ce9 100644 GIT binary patch literal 847569 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≪J}tBjR>x2hP6h~=r1;%#PYE;Aj8@S_9wivOuGR$ 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 z∾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

8bD;s81-!ZSXNbu}JDd+R(f3b9G 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%0Ag)A?S5y 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 P00000NkvXXu0mjfH!9Qq literal 59506 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 P|D!_x?=yY(mGu7sD1Hye diff --git a/static/images/illustrations/retroTutorialThumb.png b/static/images/illustrations/retroTutorialThumb.png new file mode 100644 index 0000000000000000000000000000000000000000..560c16a1e8d6525932a3709dba97c6c876fbb997 GIT binary patch literal 749107 zcmV(+K;6HIP)2Tk;I`p`2by_k81Nt9 zxizp2H{gLs^3($ZCIkbP(J~}K4_kuUu$%0r*lv-dh-Fq|hoW0iyt$6$*;#+H< zyh)11zE#R~@5!^z+G~B!_#%E0UqpQTulz6nw$Pu-^+T>^FP`bs_2YYeJpG71EAfU% z-evt}|9iVW(Yw#Tpm#s`0X={DmTu3_wB4@u6tln2a;!JX+&0=!cKgoC-Tb#dBT=

|)_MuYgZ-c@MT^0ZA}`2O`?&a&)3d)+*u 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 _ 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೬LO4C7sbC1tZ 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%0ABiqMZpLT#xBG3gNAAfBudt}9DJ6FMP&@-YkX*I= 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

(H!Fqwsivu99dN?H9t+kpuyyb5T>)jn z1Q$Reah~2fRWnON{|W%u2fi=tS{8mQ$U{i?(vZVWgb{WHHGvU*6?EigF!ux zi0dYyqA|Qt&B94cl2waHfkFocOFbAk2w!;wt}!@Hsx>%xB;n#bZn9@~67=NK06z6j zR%s3c7o*jk*PLq=A@Vxqedx| zXXb00&;Z;@)L$>x2fKobG7`9$3f=1NlWI}okd9dwAYelWC^Dt!cM~Gb-1kDvef{?o z8h|R?vk9|F`~4W|(R zEl_FiQ~w4T{D-;j+hegR*i&DPpgGH9n=&3UdXte|%rU{zm(5$+K-{EHZP}A$jVMj_ zPmRE-ZQaZu_sPkr&;u^()w}nkPoH@Fad`TzPs5$Z9+S24t>>PD=bw9CqiOP7CYiiO zXCdeT2NbzA&jqF4hy}~b;a7n+i4q`fC9V&gmy6lgBzhODC18Yb(n*iu-Pw{ zVeNK@{t_sM*UeP+0U7woha<){&KO7`90!(_{y7MZ!QV`jhe`5=?Jqba_*Y#^2(avj z0S@>FVAu`&-o2xpJ@E_M?L79eJvm&JaPN)l5AN0rH2PV~@AmNg#K2kaHwlGvAP((! z0Dvt$gl_FCm`h3HoTq~NJ|!4$ujj#3f^JT96qvTLpWM43E0(26?rzdZ+9PEU5GTMn zHPDNQy#q>HM1v5Uj?gs-&DId#m1ghe13S)Zr_p!k^`(IP#)9XlkKf7mR5n)r`g51? z^Z)sC@biEFTkvN;atHp@58Q?iz4sJ8^u7~#?6&S7uiTyBnddLzw`NS^m;dEm`23g8 z#e`S`4e)zqA3EubQ4@ULB=dFKA{@5h=H@@`u;43eF23|gY zF5Bn4r|}*&`P{-X#t01sws}mjWbOJ+v`&Yz?-@P52?Fv6Cp>;_k!)|$O}a@p=}}CZ zp_%|MxvWNDf~%NC^-(1ZSAcgxO?1dT1DRMLWr3Q%i?6TwPo11An8ytN$+7wd6rp8i`aOsbng$#v zH#=2R+fh%(jgc1cjWj3*U)u?2GZsP0P4iUyj|Fs&#HZksnHCSJ{paStg9X0e&(ZvW zxnL)zg5%gqcI4WGn1Gzmb!(fOGfGYd*y)tSX#Z$+Cj-XBJlRZ-Gw&h5IQ3PxF#sM{452Q|>PT3+2m2%$06*4kN-lOwLXkF>o~HzoKQ zv>npiTKvAT52r}Y zzC|6ovZYoztkI^JPJWRCcL?Y{1I(-yyx9p5ApK$>KS$%{cX^Gm=J4Cz{Cg@3I8;#D2f!&n zKbB=HTIn8a#~kXeRxU={e0|tbZ`gK%&#kSvlf3W;O#$8K_VX6D}T9$CQ{aV+a4 z<`A~iAZ;BW51G8Sq-0gJYgzMtGnhI4Acsw|R-i;lG^E3K77Aw}ZR3vap#jZA0Hb9&UPhE#h1$4W80ETu7Y-W@bgGOxz=JcJwQfXt z^4tjKb|M|ssRDZZPLY#(h6@vHa6raxK=xM=ROIJmCQ)sQQKu9<^J>d9U)?+HJ4Ab) zqfSG~<*Ql-_W7=S3#QS^v-cLYc94Qxqp$7LFcqWByPZx{=4t_LynF4P&60~Z>GBeW8F)KBQnOzV(!hVN+2&DmCE6!Iv|UaA zw1-s7YZUDvzQ!6t4hp$eDz5x~YJEINQWYq3RVY~_^I^yVKokV@h>cb#q(}>kZ;DgpVxv7z z8myMTDQOJadxl|Jq-YcF+%(g`APuTAN*}9lNQ-C;lL1THEQ>X@ShG~LK@`;8Nc81R zL`jylDO%zLshK>r_}if1V)zD>`k`o8Ayz_+by0M^;P>mIE#JG>ODDBbcii&+r0n;` z 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%0A)Z3OU>d@8*eJOlwIpK5Q6qkkuV~6 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%0A$J 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

lw zwS@Rx`Ded_?}7IP?G)rfqd?NVe2_T(!tQLZdjzyu=c}LT*0$94!yvkb0dUDA=-c1O z?L3v|FrS?I#1UOP)6W>rAKkh3U&#L6eb^jd^Zt2{kTN}qoVN8b#Uy{H@BfSo<3FFr z_ur+!x6PyTq?y8pGTz&7{qPQF8kDn#PFlOu&PUer{inF$J9tnn!CMhEwTLt)1iz8* z4?KPF6`e0}xx-Zg8PPJu`ymhca}$wN(V&3@bs9A=8seULlUD2|H^iucVVV+7{hcGO zNO_2>2#7a{f+X`$0loJLSmn0Dg|F)TVE&HxB6sNGAxs>OBUNmKMqWg>VTFhFYTnt) ztR9N1=M(3m-TL3^0*!@b_Z{wgxd(UfHrB6%6HTk)+n<#_|K4{)YMxw=!0M(|kl-IX3Ux%TDx$?^m~YvGXX-|g*{s3~RpoB^-gc#)zdD0Z0;bYbaAqBLF7aG?)I}rRsW|7t19c|qJmr4CT<_t` z;WhwP*DDPK?;e=PmzFJVA1i)zZ%NBMDlAdyb+2qQz$Koyr3z!Q7*Tz z;0JMy+&=oAS~aE(k*U|7Df(vjDqJr&x^Dluww=5purin7>Gt+|^N^X$`6?hR zHAGql5YDK$>COq1B(Or|7~g)zdNQnv9D)l6qpwc?+qyh%%G@uP$MlpiVS_^O1K|tn z=6K2^6B`2&>=i9IQH5~1{^LeqFwYS7fZP$Tn#zc1p%<3vvmQc_^Gw&l#yT<2cr1Rb z!+0^WX{ygjEDq~HBkaT z!`dG`&**N8a^NBolIvc;DS0M^&L=e(8~@J6IX9W=sd2T2d4T)K<>)sE$I0alblm87kE_$qqKu4J<*P(! zSKPF4)Cq!C;Q!cgEkgD6>reFcr(fvTfBQH3^7GI1`1F}Bn^8*S;CZO&Gy7> zMCCnKKvlyI-ar$Vt6-*&%(Kr?A2T|=(e>Nn9txSG*>-u z)}W^u=8UBhRUYT!a4Ec?+|L#zr7_;AHc6xLE;* zOXVe;!W)r8;y~e#eiHqwpl5SM?n~l;*qV@fOo=x@1(`F!O0 z5#--(`0k93=(n(Kz(&!I*dHnZ6^p)S)$qAf%zUO85t3}{j7H(N3}^V{fuIcFrvs5- zZmF4}z9hqk9?Hs5u)}lbxv^nt;izhapRE{&dpv6hv0NomyD85XFe(zBM$@6!9G(>K zew|8*=pM#v7U}_f817?~a^O^U+0umvpyhCn!Xs8lak^N0D*turo?d)G(=%OCw8K=i z6kgBz-x7uolQ|hh8admvI*V`&SWRVU^k7lIi%}0^%bTwI+wzKi@wZpof;>?1Hn!!5fGzo80Cmv`RAV{!^Ft*IqCQwE|gd!lIX5OsL&Z(6pTgmi*e~WqP z*1@~0o_s%7lBUo}!K$~BXN3t$cN`Qn2L;9yOp;c|dfWtc%c;2$^_TU<3f!QO1~(58 ziqI3AV&vLVFV1yxhIiin*06gG0iU+<&WDiS-L?BN1q2ztM(81*zTD0VZYSJe9d>Ue z!Qy{LZ?HPbmXkdDm_{i?>!yMZ)8G3W4Ek3<-iqUiC z1G6xEb8IxN{@wf55ZRh|rp{|0zw|~33N)f(3W?r z>Bwn@0bmdNCZ4Z9JRi`lprKth@miU)(~`5O=MxU@>^dH8;|086PD{MIMuSF2LTEt> z4y`t?UVp#-;~%B~)Leg$Pfr_vaJA4HMR^O2p*=0ug+L4aD=s$mz|s3y*T}h>c6k#x zm$Wz+x|hPCasH-G+lcF2Hhk?~kLI&xNb( zV(F9qoNZHs3px9)3nr*u8B3eEDu})sQ!RbR&6R%_tGF4Va7$8m=T~G+;X^-9K%64U zdv!?V+Rhvsl9#)ZV@+I1{Z#_=HeAh(t_cKN=k3Y&KJ?TX%B{)|ahCPiav$PRW#kWTR5pW54N{GA*M~(D6mMA1axlc>%l@+H{G&I-V z)ch&Oi914j^Zlr-8@9ROCT>6L&JN1*+;IcWq96EL`twN15UGFAtZZjy+63n7^}%G{ zhlfXcdw!y4RRTUg)9W{t72YBxhprXpIl;K6HstiaAK)|2g*3buLx8V*8*p2*5r+(_ zwZ23RHb4tZSnO5 z4_*bz|8>`&B?Eu=b$5L7H;g=aG|w@k`T30D9Zqr|LI)WploU68ohlI;V{6tKgr-Dz z3WO8Q{iBu!p51%ELO>P}t?*u&-OA$?fR_k4jLz8sV%e%B=fHc3 z+!Mykt1Gl7OMQFucGmrQ^zo>$^Xc(Qk(S2t-S9StY4KbMQ@RlB04@gR_Q)iBHtTMdg-QK*v9cJ}0mPIxZ7XfJ(vGV!X>m7-9u9dk&GM3sorVe^%FvxfB z2!0Cwl7P1$kde32teH|?pcrTJYSL1`c4jRW2I2I2!j#?7+>K94$ml_13E96O@g-e}wj7 zNyU%}^*m~ZMW;rH;+Y<@Wphcq@=okA+bUqOQ zKOk9AouCxzd%-T;xzchn{-M?>A`>F#FYtQUKO%%EgavIw`IHy|yd_LVq_NK+pgBJ*?c@yN`2!yoLK;zNZ}c?|E^7yR@n7 z&@Feo{*g<**Xu|sN*UPh5z!H~O1<9{5umnm)=z32$BysO-u`mxzuEJNE!6q>W%qa} z%tH9+vVuE4%;B-i<2zG%03syU;TZ6xoYF-FFM11S3YV{V;29xRq*sp=Eb0Y=y&17+ zO7CjmVMfZEXfqV5f!Yi}&8z#~f~Ex%h|I z`d)$lx(KvJsPbw`qsctK%6lGRnToT_I2B67+!OAHJ@$?mY29<~^+4yX+8JiLBWv%m zTsGSK0c>!wl+w;peD;9Fo zqM(|O-emY%QP2iXJln<{bV7)R;+J^a@Vu0)HO_XtFePqJ=^L}rWp36xKiTF1XhlGl zOcrAK|H!>T^}CGlYW)l>3qFfvikgmS1{NJ7+nt4ct{)fBK|f@52Bhes?~gB*vDU)5 z%Gp+SA0xi2WPTsL0$=62MpJrC;Z>Z)*=m@6mYA)`s?qP;q9MRS2jPP{kzm@BabIM* zzx)5!UH2A4zDp?oY`324W{yPDn!9hN91*)mRmd}=5-|Q$rD5pb%xdD^d=VCmoO{}q z5n7U07vhj-~!2oMqP8V6DwLXPRY8zAa09$gblUZ;nihUHZ=xba1K_d~R`xoyONAEnxXF{B7f%}sQe%x;LK9IL=c&9%w zFkUBkm+8Jd31!@Adn4Jd#vC^cY>@tHu5Encad~eB`)453996Z{a4hHV{`bCv4)v4_ z4bff!$PnIl{p2J2W$heQqN9L+Mgak#c7O}VGG*QuN`tq4plVd-2dq>`f@gurB6p z#YCm}bF1`xeW1rrn(OZi{q)PP^yTYMuK3g9KN~Gn&HvhZNjrb8Cr3!J#CrKiibn8o z9=(wegw3mE%9ITdOg%r&+McID(PYC9g;)3Rt>^;?xMj==YYM?T!i82e4-XmSN%S>z z@96LZj1X9Cg;!BrhP_3q6XFr#dOljL9no-1bNwc+T#evS2s}gPA@%k0A|NiLA;CC! z3V)`1=d8ncQ%Nb5;dPcrOqH(?v^j84di)g)K&T?}8~5TS6tK(T4(0O+evLx;XF0j% z$fH|I21z(1>H?7e#vVebmd5{d>#VVNMq|3>o-Z?C-D!ox z&J^^@t2XMUtV+Fo29BObUj^9OwHbol!$PM-Lm`cE?{@d{{ zcn`GW;kJdlus%cRa@|gEVU@isqA6&E;wp(@a(`}%d=1}V z&Jy^8JMcj`PB>sn-RurR1-uN`s~S44xf-u(*xTfOEA;jBWX>;-&!6b+<(Xcdy^63# z5v`HBtIie?-vN>dXN!b-u}tiU^WiN-m+{WAEx-b3ReCj=XFfNYTka@{^JCjQZP#QK ze>||j4Cayv<)TOr|7FSX=kNWlG9=v}LQ==k zcbG*EeA5N_`ckBC4|>cjKjCL8~yg%Z*)m{n|x9T(QNA%oiL?vodh4y<4?om z=w6hIe>wd6u}l8=g57O0k?NmmB!B!h*LUVA{Tb#=BI^uwc^uUd>mUOL29I$vtOnen zus|-mbRE^C;TyC18t-2%tn}ivwU65x-Udu)-2uHYK)Kda z0y*5C%W_x5#D9B!fFcw-gnzP~onYcwOuzgdsF<3z-`S`fTZI=xf~BL{4yN}9)y9HP zeGYz}RvMzZPZ>ujP054^fJX|MO@QKIP@}hv4CAVw`}=`=^E)2C7y1!_fcGtP;W)G5 zDPwini0{*b!`*G(grOUiR}07EY`IhP@7L|TF7j%{Zq~32!TSZ4XT40Qjl|6huhf-) zK&tSTP$X|wz)b<5V|?ALfHi78h|-g?*w`~EJdfwAPj^_0V5;a2j>O}8tM`GD?wSVw zvm=dv=#u`I9PifGyfe7<0|QQhTOPoV;q!q;7SQE)INzfpPljh4&g8gkxhLaTg4_=+ z+#8$!(Nz9W91QPEfOT{SeR|d&yzHkDqc+!$8&x zVpt0ky!R&a%($w7>v1!5J$*KBzbEzn`}%de|H%x28{soWX4Ao)oFW?VulXMe3~Re5 zr43ne;5sFKqcHO6`>6aYvOmv$0=?2BWmRJ+2H`;u8f(Qd;Gu`L51O1pMZ7DK_}DY{6+|5DOi(~Vh|WBlp$Cv?NdD$Er+v>^J+ZS zC6V18FzW>5IXLhae0VAd?jFImJWq7I3gM1>#{DxnUa{yGd~cyn|(*g=#~@q_0x2z)mVuAQ*M zvt6i`;rb;AQ!yivgMAIWS4y&J^%jt7K?>2?4|rvx@@{H8A@jss&ACKV6NK?ZWx{!_qKL*5 z$NFGi{k-6F_pY8GZ#v?xIbnSy3=pB1Wp-T6Q_OW)+;e;iLsQ}`Y>8xE^9ec&?ClIxTk7#JbHe{#-Ws_qxgD z51VpF6@kz6`uvniK>zoeiJte0;GA{pdFp%p%cbU^y#$WzgP1A|cu+ig6q=va1@pmD z1z~eG!}IT5LBqkPb(vV?WE_Fhhi_z0N-$xE$i(G+92}naVo_AUlY81r1MZ|;9sDEO za}KAAw0m-#%W#BuQEr3&V0b^C3w}an#!rt=^y_9QRwpX26+k0mLAZ?Yeu7PKj}3K1 z0_ME2#`os(Ki6M))iHJk@)G6Rr$IS29Q=0j%QHiQ5EN0C2XES>gn0^=7hSQ97UNrUaZLJjQaO z-5j}XjHn`sn}#}lda{Ba8Z|j>KlJ{ixv^g?>EG>b(G0v)DF&uoNn8`w2ZU?|K?x$f zO3lV3x&WEJ(YBAAZHH`ZMvlyYiAe;!BOd`G!q zjm^*fd)nQvuX6dhh`I_1mLPpQkE#UMir0EhiKCE>hQGhL#Z4I}efd@MJRyfu%uAK^ zm+j1Ic$%O8s^Z-hf8FB;M2+!-%hNPL848|&e;Y4JRQwdWkDB?;8ZZq?oc=wE#6-Iw z@N$Q_=#7u(s|faZXMc4yn$SI&ONO4>-B_RFu^!YzcSh9O>`Lz)K1~?rBK=^dQ+yvZ zktGcxC&7$Q~ZXg}wUrBpyzc|0+$oT?SrqZ7vkGAaqF z4Ky*?@!Fs3vFlK{Mxu5Ej%qq`b4=6lj?KXJ>GK!M?WYPq_5Rb(vl+;)gaq#r-fvio zT5RCXgu+1>klbJK!ViT9xx4afuUOQtbSqPY8UtPLy~zHF#W!V);8E$eY^=30vsx7xO4%s zzm^m#31$kvO>ov5CfsamaaAMaHU^`8p{TUApc1~_`)>}|^WtYPMBlRR9mjU#m%9}@e6u{mi(Ak@--;WZ$p@qST& zLdYkSkH9<(-|&dU`Fja)d%x0JX5bG0`z!*9PN>zfT`ppn1VQq#m^p$S0MP9`qqIs36xE znIbX0p*PsPg{OL+xhXMsrgi9RtGJWJ3m~GXqk75Z-ULpyO73uIkTRWhCkHE1a&aY~ zey{I1eq6oKhmM!M=;ONM|MOZY4NEYT8xGVySERgf@8?_KQd9CMO$mHyrVNWhQ+e{{ z5olTrWCnOfN`pKHl0S3J7#@JhP&h9pk#n%GC15|s96O)KF;8vN1aU`N22@cEJv`Yv zjB&>)yn{d4tzz;Vt$lGEP7xWyxKrci~|}QL_8mYfiH`BMd}aTC-wTf&hErBN$qK_ zku+pOWkl#v1@#-WL55GtC9RF`Jn#a3vJEFVu3QO~x41dLA+oS&5DkB~t=^3W3(v!7 zhYNVd{;bwgY-sJSxt=(i<&~^3{AD{AzyA7*&0HhM#vFkClnNLIrVpjOkmE%Dd%DK| zf9djVho<~u$}VTR((N{pi6k@S=fbVsjtug|2XTnQzgATusqV%Tic^@mEyNUm$$(Q> zp_+Bf#-e?*BO!pU=9^e8c{dTX@0Jrl!&f66hbKc{EbQ_5<<;}&5F24I!DAcqrws`z zgkH3$!XtY)6%-=jfz{nq0U)UkY)Xw<2QW%gJR9IHPP{#jM+5jZ5HuDHN<1niBF6z? zUUGr_Su8pfxgG;Hj2XnQUjo=YV3K;5M*33-J$3|p&5h`k!p#K0or!=6d* zD;sOihO&ejW3pcYIr+Qs0?EAPpWVYj4sE%uBm^N^^V|z>^c7Cjn++8No$VKw5bU!=%t-Q7zlGOdc=prunCYs)4Ty0&bsxYxrmG_f1@%Q zE)g0U`TO*on~V~9v+zE9S`QQW2p)%1oYggzW173DS2fJc?v{+3({N(+c;ajm!nGxe zCrVm9DEJ|Sz&npi=ynErrLJU)C_MrnWn-@J2Ui$EaHa_FRb{96d@+J4{zg8(66$LV zcpkgrup}Vt4d!ru#*9a%or6e74?3-_Sh2OuAnc0V z7V=Hwkfu%tn6pA^MxGE+g@+#@hZ{XRL|61@V*GtAK6W(>Akt`XLxDnfc?SMyH-Gu{ zqaDlnJuo=n`@Z+eM~0NhVo%SG4TCPe+gY^3LG(`PK_~+yp2_c%R`LKb##05(o#LHE zoFBD&hQHWDN!jz0n*tATJ4~8e@sFwT!jw#zDfm{$giafw{3@Q-NEft+%i~yaS zTWSCw7Y}*-9AS{_hq!oI$#tgSpI75MHFqJ#0wtdD<%((}>I$L2%OV53dhzkXiF8;O zhpr4iMIP2v%Esy$tICOUQ5}~Bm@xVXIwP00I3uCV4V&j6j2t{i?l|sFF*p}k_qlxk zplpF>aNFPJ`(Hl?cqts$akHdqv9-t}XUkJD8z^EXCvfBFyPtec??eflno(%1irXtK zkB=5Xr#yrz0^eRWJowe*W#|HcqB+KpM(%E?h4K$Fbn;v=Q)zANFDbqiP6J7UAaA!Y zhdenmp+b-5A+SWF+v945(%eNFuKMZICl?&I&u^h5M(c`bVWnH*{LPms3fpjo za=}wj!_h6j#R}#?QkX(DMLjRtk0!+Bcz=y*gZ8w|jXLhl96702C@E|2i`?pOvTAX**ghenj_ykm~vRTzM%VubFVXOxt(Jfnk=g~ zweNZCmH-qu2YJfg8Fyz>k<8p1XLY~YaO{w|d&Dk8Qb z7J;*>s$&X27T zLkCPKjC_EGI~L@nb5B5w$ndt5$HE$UiY*<2CPr6p0mbo4j0u7b@&yk33}|xS)jh97 z)P?H9{lKkAbK+gW@Z?_LmYSsrp77K&`WHBUo_gvQtUb_56PDXK^SsJHzm50cj`wq7 z-iYob8X_e}s;m$}TbxHyUu|?%Ds2a?d!V#?v+WiL=!C-Fw&l zZ{8on*?Xz%<2DG9yKM~pLz#%Z3HTn2UtI|pZ~NC=bUi^t+?WsoxS6#B@K&z1nSw`C zMc}j+ElXNIPB3^nSCCg^hr^cQC@-?OhOtDfV1>lHbIT*$8}2l>8!KuuynA*rX<*6} z2%)5peb%NHt3v+fke7QBJe(&B1>OE2=g7Fm2%R z=#}nmzrwY~9C+)H-h*5TxQ9W7v!}e4s1E!~8f|`2(+f_bL$k?{j@!C9h$>BnxyD zJ%&{$7|aF*9H#;ASu)s#BSaQW$EneAWJ2j;%u1OS&m-A4IdCl&mtF+UUEQbfH1J$H zR)ArBq{pYX?GXRADgJJm`1SQoi+p%KKvd$>ynolls+-st%&K5e#pF1rfZDFYAU+p? zrC+EQB5*-!O6wT6_YxKD6b)$w3S@!O_PCdQHX;G+;Nb&K7=d@3DDVyi3+*s>`}R!S zX$LBY%EOq}hO`0GR<%iqiYWlegtfOt!-TZz?Ci=rbM@DJL0Z0^Eo4@c(`&xEo94|E zFQ}pYX+B3&{%(Xzm0#;d;4fFVK0=_JLb2$cZ;R*R3*pbs{l)S*DrId1)Fjy)Z_&QEhqzTNzLO(L%jd&(3-0ch3R%wlY{;t3?B ztd5doXB8PdKp_0XTTd&N!W-9iB&uG5a1Fv>?vGF^oq{3A+)>gkw@r~IlzC8bdXgkC z9ho}rCnnCg;Jqc+SJ!8Wigq>klkCs;h$N3+`8w_%;lnFAi($-nuYwQh?KSUUO6HK? zdB6Hg45y67I;F>GU+{4~yRg!~pxlIU?!eYmj0~912D{Xl&5>2CVei*Z+IL3}hu!!R zR_?t|qmq!Di6oWR;gz~jDK3a=JsJthQ5^O>4tl^!3bw?=!}*nCBh?M<1uopj9wcMk z1+Dfo@5#O|I?!|OUj{E#N*fngt`AT2xG`(0{QLa#Pn+fT>!twQthY~}Jx?gbIvHn; z@X;xM>$PSKw9x3`7BCXH1~k_ZFp63MlH5z%6R%S$3JnK_5yx`{YuMSEE7Ps@zWysA z)ZUC{vxh1ZXvtKCGged$CaS^|exOM)Fr0;g$pM zGA+RpNaFmQ6V`)Wu&^qE)0$z&5_@KbDDY)sC&O_4+cto=>l;1Xu2#MH5()URN?j?* zjVCNYjB-7Za>c%x;B0^+Q2dks?(-ztEA9r|(Y%z>lY`*Z!2t8a1#HXIAU$YyCasv=eV8eh>LRW^k+tOd>he${%U=Y`0TO-jAT4CBWI(L{Wye11Y5{n_A`>* zo2j@foa>4K4}a%SMCexl-8Ihr6a}NA<{wD{paT_0_`aj9b-;lZsTIm7PgCI0Oci47 z{NpSU{Q{4|gcly^WD;6RqPxN=L%b-+6b=~bdAO{c_{4eG#bmBBgx7MKA%9(5wwyfI zv?-IOvDgTz8+VVRY^!Y*_&~^XzNireCPl{^ijMXaZ_U}KD~r77$vkgLz-9Kd1gbdN zlo~vZX%Db=PyjlQ5Ghe4x8C+{rJ_4|KToK8WCH(<=Vzqp%ngTO{(HR4cOc4Mjs%E} zzZ&ROgC)mXp$v+1lDX3M_2U#+mt~n&!{Kh>&qsOJ-;*u2B9+pFHynu z8u^;L9Fmh7*u{diqCXiAVxE30=uA9oa`il=b4}_asLoe!U+B1&asvNHtOEMT!#tpb z1YP7d51^KsX3#b8>BNI0(HUL^xb{I8#^UkG@z^A<2z>yr>s`#;d6w%28>#?&RL{T2 z4Q(Exdus7x#7C&xPy=2L#OHsmzw&BK8&N7)%}RCYiKHhJ$UEai4W^;!Tbn}3zw0b$ zTp&0a)?!HVW}nx_yk_4-<^iVp{MH+Vkmo?3%En+m7Z8y^%=-83%@Kexk{1WV5>&Ql zE!V+pxpgj@gY9KD&nyriP4oBdbvy9aH_tN^@%k1dl|WMh^UB?Gkw<{T+6K{0FKuyw zLzPhmP{#CwU`c~+8Sn=F@~V@rG_#yZ4TU*;_mE|81F91aV!}K!ZjA>ZpubL(eH(YM zxH{q>JAdFz%!3HCPVg- z`oRe}NrW%>giV>W{Ud&~wbL{EdV8^u+D{Lk=;4!Bh}L|N`l@=(n7^HRf3D8_%vQPg z!E;7!&uQMJPoFuOVEUOt@i67zHc?o$W@}sNLQXC#}vGW zIM2O)>WaC(-he%6d(y+him6UoT8Z*=%Rr5uJyc{<;pT|q1!i|V`yjeSY(ShI$=44#J899XAAZ1)(X`iL{ zn?HCMgXZ9y;^9;b2xVkMKl9XQav($Yfoyj< z`&sj|MBa+0G_T(m4fFkVQviN8MWBcKKA2^H>HSR}h6L6bbhC z*hM520Hus*{SQ-@;5~d_RAC_dA}gubtrr1D)z*FtZfvq+62(tIPat4WB-S_nLwPAG zs4z#Ibq&vv&G@<*Vy_oB;;HAx#c1hF$?!4JJx|y@M(Nu!!g*84HW+;m#!fF8J9e}x z=?!%L3M8E)?)%~X|K{s>;)uS3h-lP)evexIMc2u4`fc8C(Lf05`($h>fjOB~e-l&d zJS-Y6l}RZ}KzAU-dlU?e0h}^!FD6VyGLZQ&-&gItlb zKVJjhpyFfmpiG71TxN~&N2-ZT=df$1qbZwfdi+IGXfMZcicJWiytJnQy>;G=p9(xA zCTZsaGl}zLm2^W9sJz5=wbLSwsdNN`wt(B( zkqmqh+9(;5Dwm}DvPnxay0bmDF7_Oq{_>=)w$7sgtvE5lj>kMdcE{eDeAERWuAU5Ay{0G_W?Akw*WGFXXOaNt_j_Q9;bT?ss8`p<_{~~X zNWB%V>V|!_vgDQzMj2f3YIGnL3xK!fkXgY1U%ZDUa`)&phqsd*=crI;#8BKi@VFtZ zFPj2Kzx$@v3rj@quc)=|O{)W|6${=Gx+dE&H@e~Z9*mecQnwloF~_M1%L}bZ^vxo1 zxnvS=U1KGP23VVB-8^yOH97HsnL42aBd^abo3``y?Usv5UQ>m1&MkL;-$qey}Z z-^%Rh8N7YF1tGkdlGf%DMRr!Jg1Cf`Ck7;zELqRKC4KTrzN*!{=@zb7)5*ggxhZD2 z@_kw2{Sz-aFijBHIx`^6(-1)1LqQRaD<33|OvmR2LW@4A7(ICM>K>^@QGe^1)$oE_ z%j*HOGK8Puqr7-|_5yX~>2Bkos z_rX*U;K{1{e$fK@-v?kpdlPwh=xk7 zOktttk{$ltTcs}RFrz1$%u}`^aTiY6v zp_P1p`k8+IQq7=xwr_m;GB0XWn_Lo4o}O`|jD3 zt;Bn#)HK`_XGC+_zPX`ny($0s<~)h=8WVC-Mr-;< zI2H@cB_N43;e5p1XP7({dqy|MknrKf^CtiD>#u6M54rB}>5~w=$2Z@9>wEoxU~}eDt-33`);8d%L`aM$6 zdsi=1v?KF&bE?%G@uwX7@3T#6maI<^3=~u0-RO&FCTbHN8?F&FMx=e+pGm785jMx1 zT{M5RrH*+FKGBtcm(!g^&#=sIEGkW*qY#;=I}ZtD0KU;Gyh^&G+d;nqI63Z?6m}fLZJXd$WsHBK<=)G3dLCv*UM$i+&)tf zyFQDxA6WFaM&1cLvxrp=hVZEhzCK$3xDhpWf#J(fUo3?5^Y-5B>#KQy>4;`_M5hSw zRRxC0twa}{%p6v{)e$24E8$Su3i~=iF=NUjOP(3ZIgY$UsyNbIaX|1q6?x%Gwrc{= z?A4mQaLG2uZQLdhZU23H{UJpgdG!oS_LzRPu_-eWu8o zW*e7QebS__dObY3`>$T`%+hDwYRKjqPQgUXfq>thpXvACe;e_E{SL)4Tbe4*MP-E2 zLnJIc(WcbZ;>+g6xjlc=J=&CZk53O9v0j}xn-XyQ`ts-s$D5TT^srzH6Z6AkUVl;i zMHPA`3tt@t>1Qp-VHGJYLCb2^S^Lj&M|xjx>#HdrEu`0&vu()JV*m6z6R5W5KW|9V zBhKA}jRGz-EW`GVok3Li>hw*M#I8D@6e6~@RpsdITjba%QVl?06Pjnr+(&jK%L8g% zyAs8KhAN17BsS3{r7{T0Bn~B^6wWwdU&d?qWv5Y&D;CNO0S!OsJXPuUvMB)(Ua~AG z2*9WPB%R}auPL^OMg<^`cn>e`<@#cS3i0fX8;3a_hxeiDPLoM1Yvq$Oq|Pdxb}S$5 zea})1e0AJw-Yxy{rd;D;9(C?|cp)8nI=k0B1LWje$~^^Ri@heC+j#h1)HMx^VcqXx zD?dV$2Z-QOI(G&h&&4A(AL)qkUiD&UY%0$w~D0UaDPeQ#dQ#FIlNRZskn3{0YZpc zPGc_2FleLC_UxXrko@fRG!6rv+Alz+S@HH(2-t0oWAwl{5-0&6m=Ye9D3*1AcS^+y z*m9;itLPWM&A_;}MB?x;jEd5wh#=$n%=>Q9ba_3yA z=J(!{{U80iFjdDOUG78}p4{nUyx_rBO26^G^cI?um|OlkDELeM#cboX=zI>`YvNGg znpu^E)08+e>Bk;ja8%Id;N>F3a)PlKJXxebST7UuAw`7@q3HS$<5u7IE0 znrg0d^~IgY6^hD>tA2lam~0Mn6iG6V-870wq^lWIM7Ysd`T5yW(fB${=J23$nF!<= zcnP{JxGugfk_Mz)(F`-*?W8M4%m=~e2cAS49|ETkU#q$Dtq0C@LzO$6nDWzP0L4v{ z$rLLJV`<7ko1BQv1@r27T?gi}&wy{n6>G-8t|STzyf)rb5H$yFmkdz=8jbBd@aL!Yd7hhO%v@ z;^Cb~dE8(Ij!Kp%Ir9yx0#Ir+d&`;IQwo9BkU5oraNq6j^Ns%APn~q{jQl5F{~gBA zFZqE13H6kf6iPAm9D6hck$P`AlUK~5zgPR%-H%cW3Z9+EUBH?dI=jaS5*`tR3DW&s?*7v5!#+k`hi;UOm1&hEU>RwW@qj zrPIQyjc(wBOvs!_uh>!^>1cA+0eA3FT>Pox)*6&pD z_^R)&9E?MRiCR9xDMA6*6hmrhaWC^tk$9OSS+H1wRuzC=HHX@Uc(tEZ83ZNRN?e!8 z6pWVVaheA~Lv!U#64jhm_Y^d5HTHM3ikT;H7tgz7lk)J~i)-)fo+&hAr}SM}ywHU) zFMAa1m^v9Hj=U(Iz173{yoi~&7e)|z3wsg|IZy>5nWu;s0dR$&BaZnBuT3@qLv#Jn zn}?t4UU_0)pT7}vc5epz9+txhU9j8?iz}y0xv3125_e^6^c+pjEZj_aET%v=WXu#{ zZl0((PlT;AnI(8oHZ3^+1zXCLt+!XIO4}$FLj_~ZNrb?BdiXBUpka%?3+W4y97B7t zK^aQf-oscS(2eg(GDI{m8jZjNvsGbwy-%P2+qTXM69&uge4ns%(8 zu}wW>stZJv-@((Rma&wq-n9%Jos5pg33sm!@CrCZL}%k4ZjT$ddx(=KIc-MH{uC-t zrv2EvU7C;m#jRBEP0kqidwC|&Vk$ZNx#8h6*1s#797njIqDnpu@%{YuXZrac|Da!f z{fCAB>V2*JD{+2-WKr219thIr%{yryI5$@ZgOYI}oz9{l6NiCniu|2xhVJ%uP*EJ7 zDV`r{d*>EsLW-g9;kId*5DuIO8aah5@A!L-zJ~%bRLWurgP2|>Oh+zWrx%jB@!(ME zXQFw`HAQiT+R-}fX$Jsln_8Z7QkC6B1_hln-qD^FxtXXG7M^_Hly1^;zzWW9Y$IbG z@LMl|3J1iayFBy2dX!-jcv6Y&^O0lvDQ2HKML)u^*Eg zn|UShhlVB0Qobgy<_aDdbqol%ekRlB;QKT{%bF>XeA+elb&8Y>nOX_`%~n*ALg{Yr zz`!g;qV%K7XKM5=B|V1%(b64FHBO0qpB#!n@VZ{eZpwCD$u_t5$mZC+_-h^9$bh|K zn=c!`p1zym(QO{+O1Dhuhc#WpQ*zCGu%7m5oA(?zrX4D)qq;kOjAtOf#F^?*0oe7< zYcSD3gX=5cJ}wq7o$BqxcOCEg?xILU| z7~#R1+kI2?>Th*+p>LZU%FmlTE#VDVApT)HHPMbQ15XI@Ci3faDDX$*%_c$4Ps*1Bm@D`$ zz0|;}MdLL5caCCfSj!6~ixnf^xxs^>7?gw7co1wZ#ukfLL~&S+y!C(ICEugfA8#lA z8jwreoGr! zg|Z})LuijGs+=%+-k-NOPbkfyu!3M>Dmk+%At|a90%W(0sXv`6J+JCTxGB)SEzk6# zN{H7RJri39>UDePak|jw%L9E;Pq^)Gy|0K+QQGUqz)qI<)^pg*!MK8vK+ZPRY|W2X z6VgoKrg;-oV2R}4#_Lb6(6mqiRXD1`>Ba`L>XUiN%3S36MJ`^wP(40Y5Lfl$ncub2CdM8zdwIBK-pJeb-X$uP z-PDs~v*_QTR10KnO0}?%>0W8bf`sB00?ZPLh&7?33J%B{VXos6c}0}4yg8A!HYN^% zS2C|vdXh#c(F(@r!d^8}qPiKe2_o>w(6DMGg*SXQd4k`?s`JinvAmVnhRv7W%!Bl8 z8}s#Y$?CpgAhp6K>RxY4p`MPB0|Il;+U0JR~J+V%PCQKqagJF6pnnCl5%!Y z%1V$K2-93Q?li*#nW>3%=BpG8NrtpN$%m5(7Hao~@$*Q-nzCu=wy$s!bB^k6V>A8D z!&xFe{l4yu*muwU41!gcb_w~!eAgf^;aM&cANyL*`QU|Qmz=MZfBi~!)^&utR*ldmdA94(7Oc8Sp`s!^IbFQ2z0uL04z+ zT)UxYFZM38)+BA6Z4bz?d5elN6+rqKJw&8pS$<&J;7zK{Ji`A zaHDY4KzNc~k1)w}gi+iVYVRcB25#fRS>RQO(-tFgI=Z2UG(NJ21+PgLnzElIb8<_} zY0A8r;oWTBV49vlofEv6+!Ts52_rRz;-1}{>3I_O0N>72g#be-)_5@}yxicwS|0VQ z;*(3|CdKjtSICZ1I3MZrMSH-zs(^$7FpKQPJ8PGdR^(s}uE#Z41>xpp4&{YM_>iFZ z!^SgP&OeDLB?1v~xn;kAMBOg=;yqr3l)r;Gq!AlJ&y55hNM(T48JO5W)@@G%# z2QJejZyS7>A50krCMvy!_Rn(_w*U<_Awylw@rEK%bz5G;GMp!!s@RBmefc?9{Q zcWC?fus!%ZdtS0n>U}X^P1z^vDR-SM$ASK*&CjuZQ{?e5CRfLAd~q@zlQ$2B~5 zODUw~zVRq4B`L4d0NvZLJ&mc$F-WuC*4xAb58<6bVLZj*HDkU=J#C4;QF+b7mE~+D zPCgFFk#WYsF(pvSI|t~_7c#0fyOQ+&>r95J_FPoUi-|{?N=Fbt{hw}hBnT#P}(pd7e0VM36V9LHu437~R{sup? zr)h=Xc>LX#ht9e^j-rNt_Uw-^|IGAXay63opD}_?F>vLQ4`;gX36JEedsI<*dv>_q zx5wi*`Jgvd(2{W0E2H^BhNVU zwDT0Sy3u{$IH6u3=wI9ZfBR;k z!Ed))?BSj_txgWu7iT3rN2k;q^iXVY?c83X?_LKk`3F#Mf9VYM@302t4hS8e9%$A$ z9^3)yl43mkQsx+LuF|4vxb0r(F;O0HXk{jOo1o~J-s7f!2`Asi0mNo5I2uz9xNT)U^veYe{aT^|)my;}R`F+dugxY8?o znDD0!AfC2|RSBpBj;~%2TNWhcrC<&?EF^l~L+H3nt~6U+5v8HMS9wqhnM;iIpr~l1 z{O=sz73u0vV&Sf)$XY|urt4{5ZnL>xYq*nHf#A)+h#Uw6r2S0p!D-{2ITNTdy!C*H z+d0rNUvBE5_K>dRV*INmOT2#e6@zzF^#XRpcTzC!zAcU#ek|w*bWjXv7R~DMWPm|SdLWo+c zK7ICZYYS^u&*?W0z0y2&Z#J(vLdo?#4}~)?1?Eh&@6|Qu6fi|7V2r{;PzuhZ7~I0m z-GWFBHM&)kT4hdn+oo58U~h!HLNX0-fse9;f7;PpV!l1zzNKZHtgrt$#k=8;1 z&~Z4G;b1Rh_8eqs4TA#m9RcH%DxB~tcE0KR7ie#1e+Z0zqggd+a`?8-=w!zRyxXzR z8Q+n;fNtiv_}%7qEYlhHk+=7h`~9oVRY zPr-#Kjhqo)i{epp6hB#_ax%PNr3poDESPGPQdK#VSC5>BOgINNyA*(Y{iQq^a}OH* z;aHalMRwZm<8=65gFM7}o;`8AR}^%b|8`SeMfY8-%}e<31Wvm|I)d~NCg&Hg8o^_7l@Aqo-3;AzN&cTBVoFd%QPUm1UOyqiZY*+0$eQyP&h!X#0NTM!P zgg{#^adN}X6h@_-n?yd+cn7?yr&WjoOw(d`21WM_({J*fiXN(Kg;RuYC#T$$bckv_ zaYokY+p+{Tpctj`c7}IwzQ*Bc&VD$c30I(vJO>{=m4c7NJ^5UDxp;0xYJ4}8dbU!9H zN^4wtC+#QIngqzK_&Z+V+-DwnO&Tvba@qR_&hDN)M*YbF@8tD9ow(QVUV9_`H7D-= zM{9~jh8Z#Qm_;V=7q?;LiVq53d)Z`PRsN}?i^E}dZa*)npaHipXd-Ozfi4w z7Xa-RrfDm=h2MN=OWwc!K4seb<_% zCK+3QTc}%PaAr)f+6dFztA;<{jObn#ca=Bq1WhWd`?bwYiNg}%-qT#lswr}c=-B zIr{dx(1ow&RcO!LB7DJ=wwinL=HcM8f1c-?6wI?Ju)Y7x8J0{DA5hUKKS=c_bWYEC_uT2lCwDs2(+Fv906Cm>-i3C5^E==5 zzL<`D!@iA^_oW8g9vi(iDbK~&9#(|U){KbGLbS3eNTzm$8-Q9YjK{>#MqfGg+^tSe_9`#=G>iKPu9;4`O+;5wf z;i(uAl=3Qp1oLc)zb{{`z{lsGelaDWCS+D-n!{UPI=T=Efv3TE41PUX_jn~~6JQ+` zU6C|CxrR_4t&t}R^oj)qr9xpxkp=^uw+_t+7>C}%VI%jh7%mKaI2De;zQ8zTUhDIm z;lLE*aTFx!5$J}4|;kn9@q+cTjF1M?l-TCrnv|Yo&RFFrS0}C#ATj{q=K(yj4 znhQ|*GIq}&;{|o^q5QY}i+e92tRa;A#9-qjpL3^a} z1Y?-5w`E>{5fS8k7G*(@Wi3hrgM^*zmLfo9k|hdg5OB1*Ca5BDxe=J7G|X`+`O~9V zB=MTt3M9`nBDcf|g?aClk=YF?YVhuP!#tJ2#skZR`d-+5&94`8p~-piEMlm5VFqbgmbvaO|d4i1qeRU_~}6FQfsV9M_uMU``i8(~f6!WA>UUrI7ni*p1wp z4oUIK;w9-VRTA@(w1S(w2J{U)&TxLAP{J+p|T1RbE?IwniKSm}bFxhrxF(n#nn4Zm*4Nf2KIgP3#}{m)!q2y!l=H zR^Rjf#vLrWCsn0)JGp{K=SaV1`!-eH$K-d!zpuGB-bI{uQ9e^W0Ow|S?W4c+gA9T! zF_98#qVc4=u-wPr`9!(X78RCLLFA8I|J{^-f1BdZ8$Y=xBOyHi02?BmH|0deriV5k z%rS`HkhK8xee&ncuSd#iZ;pYNDNo-V9&nZu;_662m4aytk{!N`BUW_B$K;_tYg(K% z|B!l(>3c6PZ>AW#m?rLO$|)~`!7dnuBCsDVD5^5wR`wPB6Y}#+mZR@wb;Z@=bwiii zzfWr2m_0|_!c*zPjZi!CRE3s>7;m37Uz`d(<|(U+u2)~k#X@9UF|@qCnajKhGb@O& zk5rXq^yq?_S5~~aV4zCT?Y-Ol>bW57zI#1@uvi$C6$pt?Bn^8tg1N@#Mj!0@TW$9i zZu@J*v8u54`mD)a-I}L)6P1y# z36F*L{D8>6XUg9>tSe6RCmk!@qOgs7KTN?mSk6RKq_if z2xbw2ZmZ!YXSe`p=ZD<03YD4X@Y++)iPxnGog#^k`;kX7%U=_3Nnpe5DANZ!cm-L3) z2wI2Pto9QY=0sfkOXQ?88TgHk)&@R>Kn9CA=W{d+`|#J;%I^8TfcHr2y{1G0PqQcC zZktlMSgH|5hXdiE^Tu;LoZ}m0b!p5Tgi7AM!zbPFaT@&-%eaCNH2nO_Q8`{p?*cW7+s907(` zgk(Xogj?Dx2IQb%gF+;QMQKX8eg^VjOWh;M9)N@A$c1NKE!aOU9(zpz-}63+Gb|Re zW+W}VAt@Bh?tR_*$6W5feV!3bm0>)HqEs*V*a&hVbncQvKP@4OjVAHJCqu)-c)s5P%m7?*WCnR{`6-9vKc*8$~rypJbF#<4!W z)78Nd@gsQn@4EhiKL6ffH-g*x8;Y2(qY{YbMgMa6S+#{|HA(aL~{Pz1V1^px^jO=%6o zf22nU{8ahwU=Udi*L&2=n$8}3o`&N#jCAgOHkqMt;tEklMPkkQDJyWZ5DeCnMEagJ zawnP>rAic`8DpYw*0$&}eQuhaPFwpcQt+^Cl1TG<|QEzsZY?TJf)k z2YPyZr0ezfO?LFk!OBOthyJZnfYBYY6z+dVy^hckcO>IcRD2-B_2%idf0o~QX1G2; z77T5lf*|rg=5ug^(!&Ih7=kuoa(B zD)EMSPac9Bc^1@&q{Ix#IQ8$}v@Ci6mMOS2Z=Sdp<2t=&a@sX=8mjJVip0L=2@r%- zJu58X?el8}{3y|!B1HA}`(|Ca`8!@vBxA9%z=a)$)rC462NX!U_eu=H zDfp&aAOW(RhxQ&h({Nken_!)yV1*Lc#lwlAOGJ-NzcNqy-Sg#V3g|E$fMd%bCV-|`wp1x-4x&o8rkENpY$ z3{f_wX}wrJ$w@s~m&w|u&WJ6ZFT|9GH-{ZnPQJZ)1=|V54hTu-S9;jqd)Nq`!Itjr z+pU-qQ(||a&-b(}p2g#u-cuETT0lmH=YSP>$K?KUwNdEPVOC*B-`Dr_tRrFa6!YU0 z6ggoKaE@`N(ofLtsPyYN+n5!!H=rI)qKpb~u#kh`jE{1YnVcJuLBWKDO4VhqDZ(_$ zOQ($woXw7(lA-Aktfey(L_O4{Jz8VwpR1(FZbevMK$3R?ok$ zUpCzF$#AnPQf5!Jfu6XpXLxfl=V_sxFjC5@VC*=&@RmrC5Of)DSg7avYI${yrwl>M zD=FUWT{VJ;G?+t}=`x0fa|%puzNo?**h@*gf;9o{wxbQNK0Xq)Rp774+9qpRV$ z?g~XtTm>blKJ7W@I0fSLHXurT*Y~y5;vJ{3^8*1y@SyX9B1HRhhrjz*9G>sfoFTxL zV3DZs=~|v0UR5(%p!pK#*rE(luqm^ z4~$2P{HXAT^GdgpQ}kS>oU}Z7wlylbB@u|_9)%kZVqM^CLrwIM^!ah^TQc{#Lcq>x^hG(9H%vd~Molk?~;K@ZU zxgXP1dGVOnTqDjkZ{f+N9G!iPQ+V%15_J16Aa@DnrOZmJW;2ARhaI)^AE}tImXh>= zsD}y5j_;|+3g9Hl-DT$8Img+X4uFUzrd9w2+QcZMb891p zMaMY-Rlo{L$T*zGD0xyCjRB`0LJx-t%Hd&dBYPi}A34E$-TW9y=p$&Ts`HgN5D^s^ zG2SOUOxNSpy3OHKHG!_v$p2n~>kMYiP*F4~yJc}fgF7UD?@EOF|BdTUDgXY&tM{7A z@a%S9wez1oeWItQPxK%Ep~+f#Pb!BthTJx}udoJhr$KYnE$%79ut3jKDC}He7M!W? zwc+bIT(Q^UKhb7{E^1R&_+xQ~_ktv~#HLtzd2ylScJo84Ir|=;7MfSLBqbvZBoT-T z3OFcDyn-3u}^nN;X}uJQwxpD_s%U#& zG(q^AefAItG6CXE-du60`TM5TcHq_$!F&EcRBXOE@sx<}GKJynm6qeYOvX@bFX1U@908f~P{*xdycTx;CZ;HSd)2+Vxq>(hAzE3OHU;lBDjJ%X=cc^78I!R# z4yf4g#yS)X0P=(~kCpag*3NkjxPxFaQTITB6;$F~x44C+Epj6!oIzr%9`gr?OZRAz zB-CYXEk!0#Nr2QY&U0R8s6yNmJcR%at`rM+j6qPb{4&xDq1It3$2xH=q8IS7HaJ0Q z0BOyo5=?Ubow<;q@4%OQMBbhB_7CSzaz4jFM=M;F@_XLBbLi~ub)*9&?C>Dm<&lW} z#Ug^Y@UR1_vBh)45TwRFa0v|N0}W=6)%V$4f(IE!xmOBH+jrVernOVY__UiGLi}Ka zNwv04`IT5taHvRIV_$KF{c16f&tHF{U;pjj>8D?Q_Hf_NpF{Dd3ml5SWd6DR+u_eP zPif-@p43ad9mF>9aEB%J>0)>OWOLWl;)Z>LFWcxYEjsS?Q5?i#D#cjcN6Q zQi5D~32dRvT0M`G(b1^Lo0U|BDvuXvP8hn<7(G#Xo#?nc2d;ZW>=z1o1E+>ND@3>L zqevYU*(v=3_rlw5=IEDd%`w)!vOY}~gQB^-W~Pqwn8$+_+wbWo{0pb?ob9Ly%)D5p z>Cm^O5s@YGbIplUrNK>}A(95<6rp3lnUd>RaBdGZ+B8L^$s9R%*D$7vArLILM)z%O z4E^@|OV0hvmrpL_&xjO|2F6@)CQV6~U&kUfx-=*P+fvqt9W-;@KMlV-hyFX)k39P) zFTQu1fB3zlvTuLm1kdhqs=U+0_vlsQ+qpj;N{4|G#+f{}L8rsSJL!#eG8n*!hr>4QAYTU77^a5>M+-cMQA3DQS!bC`Ekb8WFKQ3tg{SWXnTk1bp@Tp3f10X? z8@)h&YBkhT6u(1s2lCgMBFoaiiR0&Mz%p^>hvx80b386oJYU=+cmjuvF}Y%D-qDBh zNCSQJ@hmDIEVJj8J8V`m7Zn1UI~HR#ex%{lph1O3B_o>DQ_*q{hKxz@VAixsn1gsh zspM6;l&9Q}lu*t$BRB-gCu#gc9iE~kG~nh!XQKhic_%q>qH6D9zpOg1xLiizNMU=n zmQs$fr`|u(e{iG#Az72V_c7FrxifH3dQPLP!3X&d}EPaXJsr|Jf_{U}UiK z8~^C*=D9kIwsUr(Dd2q)nSt3;Pk0LM0g*d-Mabku0LPN>wdS*nxov|0+n(I~e(pCDbu0lj zUT#_ZR?$v9*wz?~2^0jLt1CE)kfo9;lm!zv%YPlLB7lP4!lPs5@>*!4afZng{`)c5 z6n&8=!uBv8RT{4hQq3sl9bpn(QT4XX=e8*j8HFzrflZl6oN6`=$iQOq6oj^pewcSgq*&0ub`&1tuGQjAjyfc-pEDH6RePI&3~ zSz2RYBGLm|b`R^viHb?vlQ+0UNE;_mEA5_*?{ES*9u_GkOuFI}FT8Wfj>h6=LSR~1 z12?rqI`GWO)9T2>t;rOOXZvo@$8k;4(MtQ9IBAKm?K%#c&*nkH8ZybahEjd(ubh4V zo+&w_w1xu3k+d`KVN#slzQOV`w{~^|t$XnB$CB2A@XsaYZ~*^&hc0MAlq z&>0R&V^etbqIf|!MoA?K4DV8p09jSQ*WaFT-|>CJMf&;WRSkB}Zh(a+C_Fb|Ko&Q~ zA`MOEVT)bEY-V+~C?{v<6;h|6CC%J~%RTJWE`k?@1xUI0ls6 zvH02dbc!?i-WSJ*4v)#5$LP>)rx*CH6t)9O=KGDGp>El`6v{wRhGwNH&O1KJCx>xx zdH~AVL=VJFs8Dc&H3_)ToYA~m5kF%F=1>v_{`6vZ+RhZ?5((Sap-YL50S2e{J6 zjW@oiprg-B?p-~HvD$Ffx`u}$fx97iUTeGR?c1YAx*3wa_@x_#w zw@mGnUXsAc7JVh2jz}!ap|5!me#_BqipzDNv0hAsDZN6+6Y62NIAj8vCuuuLj;A39 z$SXvJTP*B4jM2!Wy-M**U7-nY?y2pS(PE*bNf3_`Zzav*OxxkcKKGW_|5hY77B0Nm z<^ivDEPv# zK42m1OvR_U6^YF8w#B<(Gk}jRClUu!Om_l&^f^BH=CF=!Bmdwcf9#U)Z1>;Q?SFQ^ z1%%_hab1$#sfeN(=OaAPlEG@R z2X!%hxYE<-CyO=zrU1WaKDjsZHiJOOZM>#ndAntPx9j%a_39~X6c%6 zFW2`z_BtPyBT4Tskteb3nkly7QJVv3`@pSdIY>wzao;3ALdHVV6{RAxwiSK}mYxq;FsfbwoW$@UXUVu$jR8G4+uif1Yg4>AmRh zt<2vY{MuVmuoJx7%X)&dn(ZlepUfhFE_sPsqYwZaUjh>MWM^jUn+t?vwf|Uro zY+l!ER1Th*I%UC_IRxPDjhhpXwsLQRPUm;}eh5{vL~#(VTyr<-xZeevrQ=j7fen<( zLZ_xyl0w|WZ4EDe>aa)c-B3~?{JC22e%;vV!nVG(N~10(=NWJ)sWc$?;+8LHGA&bL7Rr#X?p&isIONK>5Hg zOd$Tt1b&2_4jZ1--0QRFELUtIpnNVv9c;O)Vq<-Zt1I;OQflfHF(M09VX z&aT3Jf6FE3A4jw$4bK6O3DxL=N6lwLd5ev2_%rER?CjvJo8+J<$A!+u8{eK1n2)a4 z?zz8tng8=I`7cm9%Ex+C!lG`u>Vfe5tO+_*3;&9wMMQy^APg1J3FIfRrjQ|o`Qn7g ziP&Vhf;2auRyQ<_|E6sF^7XSR-v0Oh{@?U}HlWY3_<$18tPygwMDkcIc3?SSA~CL6 z3R=<)Cxn&r&hix*@znw(s?fY75oz=@OV4ZGLJg;V+Z2wsHz-J&C5(Asl6Af-H^dm> zT1%|I_3|XF=QlFL&>uB|CB~21X>R-SuP0_nyxF|F7-QS&5vvwoM^p z5k4}3>h2j1wbFvT(^V)SZDe>;_(V$OV6F`oI5Gy<*9w!N$pg7(s%mRA{Z>C8fzHVC z9;oO8-=?saFJL^4C9m7iGG)KxIOma$Xfg6f*^4!N+?q8SV1kSbI5-b|k65Eetk0Fd z?oCHWH!szeI~=~H2ZK*FQz`!xOa&*NOWSumN!JA*Pn_g@LVv&Z75 zq~}WB_Z+UQ22vA_TPoNm;!t&}0kboclY^9*h-YlqP(Xg4&$M{-oYzsHAKIw9nPV0mGj`sjW>Yccd~2Jm82U@Vg=s~SNQ$#0p4%QzYm{&f;aC!z}3yI zcm#G!B(6#keYbiV5#(Tee-WMsHO;%K>!V?qX5D3>lD*z2J*O^RzMkC_KQ&JexNzd< zr|0!FF;n5zPjBwq4ob;vwh#wO94sCoOXic zXS8B0=k$p1IUtU7)NybfZ-nyznOBLAeK(leO8oUyA9}777F9W^b6XHc+~;f1)&E9A z-7O@$jh>4BuR2CM8RRwOG#b8iEF5a@sNoTw)3{(M^4|-r44eew0XLCj_W8wmh`ANz z;@IT4uLmFFy$c5n@wtYZT-1+DtC6cqOZT=d_D8XE1REj`ivVZbsw6X zl)w8IjK8j4-}7V67ujR#0I26%qJA{@jTMTr_qK2vKyx4@~ zuxmjhJk*x^U(2>K4t4Kl347(vOMy>0rQK5An75jEd9U5PeLOXQ83H=(azi^jcy!Bh zUL=R76m=rSf6H=;oPZi)3OSDjEhjdLQOR??g5i&O$J`L*g`z}B8u)+mw)TIakb*Qo zEm{ynlb$=5A+A!9-lF#txS^>43=CKo%Q>du&+#?0592awu1889M8}Mg>kVgqK;q8r zIV_%%e6`F$NkeC*zmvR+Spbl;YRPh(eN3o@5dp&+HSqRAk%B_gEh#n#e2nfSV?3HN zP{UdZ-Q_plKot36cx6vO&9DAc95TRY3fTJS9ijux6Fv9!U%*#G8-E!leqE1+mGVD+ zOU-xAV}UXiP4`aVw`Kq8tf#P!NmbRr;5fx$2n)tXs{%~MK!z6}Du4n`)={)!k`e0N z+{wA>K;GyL0(ejG=NJ2bkBeSQ!S-*$|BA~Q;J0tyZpyDmIeb5u_;68))sbjHAI`AV zd65DTAL8rlTe!QulL83BtaFYb_IkLx6#?k(4JQJN76E{(7_xQBiOh*&0^O+fs8Hg{y-$*$ltYU}A(1XA;W6P# z#-(MYE-p%<;U`XL)cJzLYgy?cURS8ctyK<@3KKhMz$W72hcdKXJK9k=&g1(7@9Hx) z0v9__F-vKL4g~X>8^Kv4Crm=DsGQ?67$FK1<5v|PwGXfSq>8*+1uFj*n`bb|b++!& z-hHqQp;pxNc5oQWxCWI?s9}FJ{sI@TdDw-J4^xlrHo{x*sw>aN_+e*qO8Y*%A~5(| zJse5|RZu~hb8kFuFyGE6>30Rk*%;?a!gOI4K(K&n3QR%h(MN6bG{Kn9)st1R=Ci{g zdty1>EPuGJp}eZZZd}@4=4}tVA;9Mx8WK#r1IiaxauaNw#)- z0JXX4Iuhp&j$Z6P7KYqa@CabOJK0fLdQ%7QN9fPMN@w`WpO2LxUgTbUK+A98n0iGQ; zqtIS6j}t6Zz{aKM2Lpx^^_yZYly!#dDZ|d(u+_y=(L(b=Y3fjyNU|!y$NW+l=bCJ5 z4v*)oB!wKG>`8$iKX)!TQY6!-cu(5#2oTT9XnSga1Dr~uj>Ty-b}f{~>3^9w{c-=` zn0Nc}N@0&ZM5zTWuDCSFbAe%h28(>*cVi99Sk(C_^JFPnD8h9Wj^J5L133H_%ijF8 z*TohO6o+*1(oEZ5*XO*R&%aH(hj1jMQ0^TNO+Qmk;|X3Ipq_nz@SqMzclN;^qGy`W zA&t*b`KEZ7)VLpEzaYZ5^TDDRG{b=7WX&TE07K-7MZZYCx?+5>bP4N;M32X3!_xyS zO?fviiswA{V#N|YU6SC1?jg5s9MaHVJSE;dZ*tSAUulkTpR41fo@0c9)x;xRa$TRJ zFXyM2cjPf}KbpTg(=&M_LAKEg-s#zfg|@C{_-Qy963El@ETI(j&8vKq&>Y4)oF6`5 z%&0b&aO9OknOE?N>oaW}(k;XssDhk*#^G}S&V2c@!idYj)HRr>KV3eT?IsRF3O#F|tk4h60MhOSJ^ z?+hE97myXjx0^wfi-T~Xkncafiy}}8SFW1E$o<$cH#gzX_>7qc9WixX@)xnktF8k! z;V-;s*S^!_gGUqg#KYB%p9T=CD%|67cY&rQ_&?Edm@-z=4(J+7WFuBaV($5n#NdS~ z)kVv1%3BkRO2GevFM1_N%#Yz81tq!&eKdR;k#;gKoCog~-oO8_S(_gvbmQAMg2(<) zxhJSD##Lb>iAPTh+}*vAHa>2G&h6cudKs7%>Lewmuc4h#g#Yi4-{H%bZ&I9~7gt|p zj*=nhX8Bf25-SVT3bu+8VNGGhm~mz}0aJO%Rt+bfsbHf-nns)*m#{!^b$zlNf~pW) z*FJDesu1xCcl__P&8>^&3r<#{ira0L<%q|#V!C!u+veT~Rkx)}ZK13Mg}IP75}CS} zdEtN&0FM0rT<~_Jxl>r96R|zg+2erm*HrS+=phbRpir;Dvm_dK0Vx>*wLW5*c-C3d zCDY$pFj5|(kvY4_^8_id<@yCLg2UvTN7TQC;A4h##_z)tU=h~FwW*5T=p)8q%8aH( zY#c^q>-&775uyT389*{fHuf?EhDjyYUCz*tM?Hg@%d<)Ny3Y@#5RM0|kL!{%D9XR# zN~Mo8RgX_kVq{=~F2Xz~2)?sDK*+_zYdLR>P;cWh>0({NT8 z=0gs75R4LyAUaR?!bJz4_Rr96hGi-~A@6xyBf8WYgcsy~XY9Y_P>WziU=~|UR%N_ZNtZL* z4sZrQg$U30M~)|{(1vD(qNruas|kJn7k~moCalmfM^!eV2~@Tx=nNr7xCoZD3&oH# z<^iHvj6-mZC9n@t-_6s%9BLVN7!xoJZJ;o;nh_x?SGrKm@3HXgkqLHbC~AhTiZO0! z$rXiw*rs!iw#_ZqKM)V5qN!_6KIc9B!I5ra<`>3T z3}7(_7!)*N2PeE`&rM27ghF%3yq7F5GvbN$B(_a=03O zA)g1p#Bo93v#0Zu;4H2jyezbXrQG_0IegFQ_}OFFv977|K=auhc^88H`F2Bg&Eld) zGca%Np0;y)hU}Kw_q755=YL!c5zG25szmZMT#|RzbIfi$#>#mwV^jfMaYz2!i?Pd7 zFOl*b0;iIXcF`!o#~b)@b1mV)QYm=T5%52j#e>G7<;@2S9u-VUUHRnvv)_f`ydPfw z3hw#Ws6){6qx-N&NLcR2{);VB*PN6=0#_8LAIrWwedh3PIIs zb0SQfDH4WD(p@Glk|0;d6sO+_i~;+bH(WvZ2AN~hHNX($LG|wU<3LX?6i{|UKO;oto?xa3uVEwmwxyQ8Oq%) zZhG@M3M4~=^LDfh6VZtaM*=tGenPZxA8(h+7!3$)H3()xM=(ty<}wi6$YqL__`Qu| z10{UH>|idlS1k@VOibmK{#4*K##a^(%R6=K@f~yQn7dLHWN6PQ>8hXY3Co>?(N;aF zwjxDf+bIiT4UB0TUo}BX-+z31fZu=nya`$lB5>uig)ORtlE$A?`3Er?pdJj}YL;2f z<0+MpAN4Pv)2j{N$Y0KX#<{;1jI)@80|z<-W!-16)DcivFVP2o;d1!DL|>{xYp48! zc~=n1!HFODWSX8C3U9W0h%kX>t;!x}US;4AJOd>9(y`cCL4Z*9rdPt$GfiW1d|!}@ zdw~|R6JwM|O0zK!G$oq|Y#Cw`XOZ##)wQI(c>Cc!eApC!tN?tw8OLteFt!a+=_yiQU)1E3R8EKQ-R3rii2vlAH?Uniy zYiAy3e65x~#A%8|I&cnRE?m;o8wE?gVDWM=C1fj=DA96RfqO|9AiD5N4kV;r4(^qx zxwOo1)ib}Irc{8`4#X_;BvauB)cb?d@PinYi^1A-1uwtsc-ayG6q_8qjd%FDm-n<9r-wi$9p$6&QZ32BSUBf#Gc!XhPvC z2C_6Na`B}WXTmew!u``j*eTY&aeQN@3S*mify(UPt?%vMsgtE-&|R%6rA z0FLPo9Sni0Vk@4k=OO=S0W|W*2b$#H?KCKDK=aV2WHYV2ogI5UBjO;z3MPL0tX{s1 zI~GtyH+w|&#XW(wNCt;zK5a^IDTHXo*;O*U^*E)j6qG!j&DD}N;uv&HP8En{KI{S5 zS&`?SK=!B?#)Bf~cc;Cm1t~a(Jj-V6@Zpi9oJ)3PW7z3h@vc4Vo_u6F1*Z#)(>*PW zVL6p04KH=e%_`|3EX4rl+g>eP+EY5jF3EdL-_C)v`Tdi7qVar+XQR^7GdojCIw+AV zX)$kftl%+<0EI8@_{<@Ngvx;ziEqa^0E5>7fw4^0qUXZHdNabTw1-N=GM_8icir^W zdo?2GbiftEOhY&;IKu!dLmMrL*Gvuzf{!{PZbc%9y;@v7EKWadKNd*ZdJ;MQByz#+ z!!0~)N`jgcgKTQX0nevRpoI-k)=Ab~GnE;-a{UPE`8GXI^>+7ub~ykKmJd>o;|LQ0_eq-I zXc3*k{NMR{QTg|({iSCf{UZSK+G}2KOa?ztzgkk>+`ZWd3&PE&fq!#%D+iBr19I4& z6b4u+61K`a8?xp6fgj#~gr9%jgq%0;M03vvI5aieb4(am$?)adXZXiI|G6mu{~!4B z`HL`xs_28kt(yw>&&cQcYLOg(qEz5_)}rZ0_YkyX-lF{LaDBZP;qc0L>qcW#I{Nn@ zoh7HpA|5c^*OwVdwg&UrXZ+#e{G#+@Z(wxqB2*&-Ol`%VX?cOx&V1}=}OtY zn1>&GauJn~Awd|r-mO*7LS5}gT*Q%#i=7evmf`3nIDB7re zk_*L9)?@_gsTZPpkjfTNc#yQ=ZJi>OoQLjVdepx_SF;?@@)?8=;>w_X<8_#KJD;9Z zKyhX8fcy1|)QH08okPLtywS3#l2|=B7ociP@J22GrSmy;lEM36@+$eiY%J&_zKRNE zpYy{LZuM`M+M{^-GOoQyCGnv37YCk_!O`5c6KofhT|OQS=fcg4Z~Vch zUeUj!K&rf&DOCt|aDvszK!j}!M+3C*Ts8)|5kkDIxEoUMScx(^X~?@&r(H490Wfc+ ziY`>;_i9ELOydy$ueqzRd)-f$U^_cI5dynvagCFb8peadoIG`qAJ+(a7jqJ@c^s@W?ce1 zSp=2oH!%GBd@>e=H;!yDv0XMcYCMSaxTyP@bJTua%y>Q zu~IQ}yQ&uiGt}=kVdvc@;JkhNP7u8kA!fuiT$hE9Z{NPd@4x@HDgXWf|NQ(LeE$-s1;yLy$MP!lUq^Pn2Q@K$ULqVu%>KiW82ge=5b95PxmbC^`5>UBM)1zLQ zI2)hvXlZU9Prv>ue>wl(=~{E*k|~_M@4)31?-CsHPX2riyIP!6L$8k8Gfb;Y zMm$I?d<-RToudM06AoK2;G0hc;@h++2a4`=+@y2H!Vf7qRx)RdHNtJ#Y%39-S%Vc#|AVYqu z0F@X(L)hHo$CPp*C@Gy04#e)u+EwKyRM07vG6xvk(>n>N#gedF(U|4ZJTPLAdq#}3 z+G$#SSjOe+jK1$0p5BIW*26@x4}4pS&0xzDXsI613IVV}kQOxrc`At2(!fZc1r=ym zxEiJ&+LJ|kpcWe7O4S@vETNUC6*Qrqox+F26e-{V>SLk&!@}!~IDO!204|cFv1yz> zMxMVel4Tp~Yu4#v!x4YGr>n=bG}kA3e>v7K zULB**?1Us~0af@x8qzTD*-^=&e?XFo%jr`n=o&^*9JDZ2_NdZ|zbLS*_+pXJNPhq6 zyqzER670%PF<*6cvH~C2g8!2DsD<&*m1jJP?m1c~1}A8REBg9sp5^>}`u#Y4kPu^u z*id5t^)$4w(dBIU*~LI)MJA|x4q9JPNZGYYlK~QcN>0Ot!=BHI|H?f0xAnA*>xqJ| z&Nh3}AmbGyzq}MbNqXX8uZq^lDkE!j7w6#1!dV$fF{~c}90OZAd35XpNT=La=3C`0 zsL$bao)LD;!i?N3Vu7=JYhHf}nu~MMH{(6fJo}qD(I>rB&&$=kH+7k4>aWbIwc_kh z7@Xj_D%~vbcVZdh6cIwq6I?x9!_&q`u_xgA_@p#`lQC%f3oK(W9CnvM*WlfNC~$kd zVyp0I$MnEE4tXAj;f2rH&Y!zRynR`CfT0hp2rGUj&HE%N(ag}wrd6~kO*8xTbGLLG z7Gc6V4NlF(;qbm%EzU0vgtK<`(vm%Q1%r!!zKy^7`l~$u{xmH7(JQHZ>HYXp5^WWx zB+)Eu-fuN2=j|JKdh@6j5EB$uJ5-ue=Oj72ZnlFdN;*T#>ebib;qei^e)$6b{OvdR z=Rf`dzkmHKdH;=o{XGU6biKYbO2puno-1nOsV zl<%q-Y=bh&mMoZxF4Y+MP^^)Jo6H4X7k2UjI&VrwA?EI?UuztMBB)Kfh$C~FA5#zq*x`@?2iKdaX)A=uPP_?!2`j_Y zAqmRdrnf;s%p7NgEBzn@0!)}}?CEeq^H{PqtaGw)aB-VQR!~WS7!+^UCgIvRR6&YW zj)nD^B%yRC>w)0GxDIt+W^0X}wwm-9LO~}?A@1Ar=f)0>7Q;yVkatpHCQ=5HRM%9nD1saVr~oL&vrYNOg+4z0`ZxG!%D?66MwB09uto7dW9f}ao<}};1(PFyS$0Ui_UFfdnCO0~VT(fj~7JsaZZ?D?oOJCk>r7PYLx_Q?E%9@3;zY3Ui*nFa=wh z05`*PGyh_?LH11IdHqp~M)7-Uek9}D0=5&Jqf75+I3%_@S)c_IAP`GVt@Pj|%0&NK z2t<_{TIFP9V;! z6$BBDOZR+znnH-r+7&DP*t3p9^-K;rZ)fg$(Q4@u9t>(g5u9##F+HleYYSmD+JK&O zw(Lmc8nh5w2@ouN8+#ghRdSXS1>bd}k?xJyRcY1@XDKgRD)br-gQ7sv=NdpO&|Q>m zjzcu>Ve*VpuS&qPrI#Y{XwyZu z9^82Z(i_zG7ZYdgN=9U!$PyoZC^0x3Jv1DO5L|jTlTuCQ|!U+yTwDF?WX@BpMh zW&_wcPgD+qA@lg_h|&QOVQOT+-LF-b;k+S#_@b9+&A+zEG;Pkm{i5TwI~qUY-O^_YeD#IFVQRGwop=$A;rhXGyM4R1AhPfSrmW2{r;O&_2K8F$}l9UvPwx<{cBXeWS@ zCt|(d=>09cxn9hh1EWfkBK@|9Z=Zg}@}V@haRu>!LuPG2i!R??1R2b84*-YIe9%cM zb6ij;vvB5zupnyy!sBMCjtDEtPN8Rb&z?nlhA-aRjYPl$D*#zZ%nXItK}eF|HW&Pc zxm65TZ%>Mhqr){XNzqKX_mEWL1!q<~-v$~(1a;Pl@DZlI_t%af6u8Ke9-5STAd_AI zBHqSbFFF<*wVjtPo_;;N{(7l)Cuh?W-vbn7#yG)IDTtL6jC6XKgnZrjEpll$JyHt zpEjl6&+yBC{s+AK@DXnA-a?bG-)1zzPC1d~uJu!p8#aV!J1ml~vBH%rXNIciMwDte zm#?Z9&v5Sz&ow2AvhCj0MlZKv+8BE(ezI2BOc0W=(G<7V@lr2wd& z_wKQ{Mo!_P+?)gv?+=^%Scw(jMqZ);6O?f@8IY0E$28tE)MI7?rCI18?E>Muo=khjGOj6sQ zQB&R|fC(>=TCt7CEnj{t*7yb@V=6dUX1s;I=|h zclY*xhus~U9QwB}iE1YLjW9%WIIXXb(%5_D`(c8QggfI%%L}T5&&Hx~lN}d7AL7j? zd{WnE3~#X00O!7CRQPZU|!CwpgebcY%rHk@-ZPdxz3oo1fR2GZT< zZB;|D}_-+ zA=Smh5`vb4tTi{?e7_AE>iMn!Gai%bs8Aon_=7n9@2=jtoJQ==&SSMrr9 zIi8E@2CMeX*EeNUzteod?5%bdCEx=*ZT~n@@!|f*273>j+*}PyyObEaQ$AjSFCp_K4*T;A zlBE5Om-+seuGmG0owAR)&eIf!FAf{Zs_DLw$YXc6Z{W?Fw_>A`RS&vvt|K;0P_{XOB<9}bjeigUf)AeZ+kUqkvpFWC` z;J^R(|9ex+J!%5dRv7(Kdfk(MgDDv@^jDd>X0G8U$(5mH;-A8rU>eyF2TQLCJt|yq(U0$}!2JE&rV)S8(B8@+f(aE^y)Zxp8q!r10@cwHm3GPe zxAsLX;fzSvO{uu`(QdDy-E0;P^F)(`xRv{m^(o4ZgI%th5yDwJdUmfc^PF0v;$lL2 zltS?AqVHs7@K_P1r$aEI&_WQ4QBXy)(^wM}vdtZlqVhWp#M2>lCckQRLQ6yFEC`lG zgW+U6i%|+kSS>Tg#)gHp9!2t@`Z^o#t4=hsWahn7k$kK`owG3l14~iYtisUId??R< zD@@03Ln;?R8g3@}K+Qw;d|o45!kLA|^VnEO85K&E7D+!dB;g}slcd-c$Z)v;zL8uQ zT%sfb_94>q`&Q@aU_;^0xxdO9_Gg-ly)njYc$J=caq0@|;0lSnfXa8RjH=UYcu=I# z)T4dp9ni4e_z%DftGR2lSi$$iy87^|AQtf~w`Lz`hfM(%V{eX$yk2_Xn zB8`F92`!I|c{{kJo2FM{xwbbqLL$XJ-oJ;RfBn0J{eJxE7r5dgSf|Cpv(ocV6+?A= z(>U>9KaEkQqRi49ewv>s3ZuwU)?9!T3J~`IWX8`6fyf%0l8Qe!W4fuI?ToLXXj1{t z756R19JB*sD{aiSuZGZM&f)!+`TzL8tIcDvR3G6%243(Yb4+O{vnHt*r5(73iV4u> zfiF1^w|*A%kY6)E$j~YB4s$PykH6u_+X=`LK=arQ=BJ%V1WX6<23Ue1mxM1 zW{efVp#?nX@hpm3X472J2kNN=kRgb!?DpJ?EO5)dpV+fWJwBzsNOX?^&u*xd01W2k zHvSSfw}XP}0blKe%@$86PG9sixo z18Q~rN16xl2Cg9s;pvRjkiEpYxoTJ8UF^;cs=Tq8S%Dq({3eUEI)@>*nU|r2@R~8f zi+BkwGDSgn=2;<+Uh?QRs|t*kawd&yzK&)j3GZZ0HO2WqRbE;Q91z?qPZWVz3_;Bu zE1DBo+XAxlZTo2aMYnlMVw|mc#vOfu$P0_M*;?%JEcE=eUTt{GyxnY1S09%l2&Zgh zI5on^lQ=4`J?RF*Q_o;&4vqD><3&A(GoSloNY*bt7akL@n*qn)bDrH&Y!G_R{Y5?B zF{9JS_AdAWQ#`Zlh2yf61;=oEhfUA5i&7m}llpb;mvc|DobTh2Xopoi|2Qw;kMG~N ze_zG%=#dqHkE$44%@Brm7DHwSv|O`P*357QjMy;n%hB=$PNLm%KC{dh+QDecQ3sR| z^{V`fHKVfjwHKP&1UnXXkE1r+6kcBEOCBlTGKi$LUZyWP} zb0hfzuPn)|%)mXR1Sb_M`);=ZW@dK$SkQuvQ-!KUZg5c&$e{n+!e>Qky&r;!P27nMBQ+PSbVsYQ zsW5f^m&htXLV?dbGo4I0jxk#c4p)%(>v#tQUe1=ko~txX&eJe3 zkp1d=KIr0+g2_0scg^G3y#36hh^j!27V?EH2z{){(SDh-WrV8&R>Mm;LW8W8iF>Zp zooEqWA{qeC7qQ(x9xYy4{(-lp=Ubec7+H?;@zPe~{^uhsA@}I$?!@d+DghPc<4C{D zfJQQ2c8VsLr4hFBNjzAeveQKSpT$XNRZj^~$ZU)#wGleV{xcY7FnPNiz%WJ#yf}xj z+8h)~*sEOW_X_Uby@wB&$*gx&u~7a+P{}R%-ATyyIcC>`4XZZnVt3*(hlz(Xco!V5xV2 zg|7|23c*|Ydh9+p&J*~hY?`*RD|-q?z@LflRpO#Ir7+JM&4QL>g6~xOn5{t zULjgn2SfQQ0&=A)^ba3g;k;*v-R6 zb5AySYL7rMa)q5(E)P3HEI3Wi6DvV}+?zKNha|r3l4j!EELRn%u2T#KaE$*ey^lSd zpDDl3zrafb7P_v|$y{KR7yFWS9~|V!u$R>^lv)&b__k zBctmmN@jKgTQSGt%IqM0Iwm1Eb|$qr%Sl@@dY>>zw!V(x-SA5R5p^_+Rf87!0&EEx z7T%oU&1QsseE0|to7~RPg%97q!ROyU!?&-W#VJX>WXaM!*&GN4uV_U{2ecxW_3%ob zpXy3a>sc6J9N$g6m>niQqM?bZ{JZ2}=IbNzk&3cr8;9e)4)clh%8vmnd6O&I#{{sVma^a0*|c(0n| zt0syyQ3^@YZdCv}f%VVstp&#PclX9H*lCS4uii|Dkf3sNJ zY)GqdnAWYj;>x3yLBg^&RiRQxMbpuk&Rr;+K<~Gv0-3n%GqJUN8Yj{UWs4~-&z1v2 z*SuxVE?ktc7?n29%T)*@+d8}Dnpo=dGc6ln0P-AB5)2yNn~;DbMQfp2dgO0U{3Qw5 zkqYCH@Pse2*S%qh$TXd$;VKy9ooZ~ z`o+r$5$=~Oly(W<6B~<(%gJEDlfr7UXV1r(ia%VOX~EJbl{TsDcW!t;!kYyil918R zqR$ax@39b1Z-$;-pXaVWNdjKJUP;4d(mDJ#kyQT@1`=P>Ns=5_&BN64kR=fxs~E;ML{TKqm^+rfF*=`a|#s6c71HnD+11pO0VH49d7B zOmd0;IDF>d20r)P%WakA(TiX=tY?*f7dp8(t=i9oSPxqanv`?{cW>Bl`N&~+dgC>V zbkIP2J>nSZZ&bd)==eSjab6>;KlJgLSn{%2;zh-tz8n2uejm&j?WI{+zrnn>9?nHZ zcV;I9Q~t9TAUCy&N0oYSb1qHveJOZ>FR$31DOhY`1|#9MM_LiyWg#`BD39mLL-8U% zx$#_;XM(R0cU$BsBSZ%9JbewVa=aRN#7Ux2EHajMoY!nJJ`U6E5rtMg&VnZ}x-E1N zq}+v`Z}(VsVw^i6`^6rFVn{^I+ob2-cv_|%WnQWT?BZm?Q*bgXUhSv_xDc1}g8jig z4V$lV{nBuWReLj$(@RGh4}Pm>%GnzXZE^lKvUiv=sPSgv32JMa9*ZWY2i#@oneYk> zf7Ig)x}q8^^2ZH?1JaFQOmx=(-QF<8xrV)~p-DrA*`els`{(<@-hpzCVvN4Vk%VPD zsulka<4H_8j4i zOqo2N*F|z{`Ya4Vq>CzK;er`mRtcpHFqXjc1Ljwly;SiC(l1Q@PhYQYk6sY&{tD&a zUpTn}U3B5TeoBf~nRG`KzV~`|)Uxae|2by->(?*vkAM6IpTGPr`SuuKK7RNJKmGjE zru_Q|Z{NJx4(6t9zE%XOVY?^!$BH{zGeLMa=DcAFHKAB5FeXW8%{^TdYhJe9s!XOP zdxJ3xuaRWzUA_F+kg-TbL-SZNOEnWUR$Ovyyb1|gC5O*Tm;4rI3%?>;Im<)g>+D1c zAkIj_Jcc5!Nxlk{@MvDYX4dQLD_j4_S(Gs6OgmUxe%z=K!TM9h=uQaH#^u$+WGy&x z>Z*tcH(N2{DGyDBpt%FShnWU|ox3(x z<)b|x(kvvuSy(JSsnN%ocX>uCwR&Z2j1`HHR>K=^#lH-o6#HhA9B_b#srsW5Qq-0Z zFjg}zPzhJA?J!RE95qoq1FX2ox|(t>VaIbt@e)M^NIQTbFry~TpQTyKgBoz7c%M^= z7%q2D=W>oz`0OD7V=)-kR3=M!J~m6CkWg618gnonLK6K0hBDO)=~P$=d}#1nn%=`w z-7-(Z;<+FtWEaGHhYJ1o@NYEUCI97E0+#&-3-avoxEYGBHzDo%;YO0WYb^nTIy*O1 z>6dPR^e+Xcp4f1ixz5BJXnW)hFU6(|q`-S!&mE5BARyTDU=su%p&cS*5u)<);JsT` z`+#iuy6+z{IEvdZAbM+*2!^fAcVCiADwV`>?8Y2b8agOFX*Y~$=3s~e{w$q?MiUSm z{7~LN;}dNhwdp{k1Ero4NDIq+O8J8cm0yfJ_z2Mbu@{&g=kZraOBw*(jzmv z!Mo#g_cC5Z0jRPLhaXES7pdBUGOkJLnMV{u{4NhF7WVww`r=pYxESdbi;!bWq^1W&X4% zkY$kv%HQ<#!u!|2@NiV?@|Jp!3^g6?f9TMev3O;RXx_fo$cLul;0?R zp#slp|X*2k|-Tz=E;FG5EAj+sGfRR=arP3C0_?@QD`@vnlh(_2A-Io#ydFhzdwz&APe~g61Jty)d0>9ZF@(T0HAU?X)3y zt7<5Tal0Qjp8*6=OYRD{^q7}}IJYiI9Jn&h& zQ8X?VN9SQIE?keP`ILBY-U1hX`fZQI?UDk)=NJgxE;}c_Cy8Kav=S2pns$r53KF^B zOu&1iAxsKN!uBLe`!hB&IeE@?|8P(yCQ{p@(QpAb@4+8E zGQMe{Y9UDJa`X`XXzXlvx#dXDM0yyKDJ-HIrOicgdM}Cx9aD2}B~Sq)UCuFxC&u?n zI1Mjiy2PILF7AJ?%u+ooRY*|}kFH7eSqc33Bw@dv@HrIc*vvZHrV#+Z#qrbdxkFJX zH~>}vp5`7u<+oiwRN9DVFqxzA*`fKvYu5|C@a!dWP?@~vE_@v|3!WfeqrzO+f3XNT zG;DL}3nvE0n4pV{O<`E#rRdT6+6(dly943~M4D@3LJVMG+9_tJ0BJy$za20>YY^we zn}NWEWn~a^wY*LAA!LNV+yDI1cgDwp@N_9AyGdB`|yFVg>kw9J>Stvxl8PY7ah@MGVEaUbmleoHIm2o$X%Qa zDh55}&sjkj+~qpkZ<1iY9_wiEQ!!jpL&o6&8dAtisG^Xcj#5`b0~#b}7-Rsx&n z;9cQkg|Ql^_uw8eo`wPr(50+xIRd(b`QV8A^M;~-UCU)LFK@)it*3BqG|lFLp+LpD zT!x0dCsjy@7cD1tzkAwvt0&>9G$b!QX590yMriQAb+xMV+t>R|NwM6k(OFy9@m)^X}_V=k15STrQvNu(v>&IH5ItEWM z0302*D8Bd93|?)$n-?OFDT`5Li@r^cOI<#q*mE*kvYI>~423ABBqh@J-qq5gA&NYS zmX;!Za8Jh;az*7cV=RB>z)kPec>x({`li=@>l-uNVm?j=H?$nQVlXlEX}m@^C7RP_ zPlqupIDJotlkF)tJp32)w;XIAc$gT2@nqn%5ppixN|!Tsz3trz-jI%g^$g#>UBmsi z@9^|+FM0x`6%JD(A`YqTa9X~c8SUPC?xOOaVQ$pN-`$!`IN?83ryim~E=x9D|Fu5Iub9LG;1s43L+$hDoTzE*hbmlbHi7Mj7;Z5y98xIeS zmljx>*(2n?@*?>E%T*5>z%lcaKPgaX=&XkOeuFPxRp}>HavwEc#D@2&1cx&NHMb+B8SR||qoF>w}))wz4lx;huF zCd7ZaQYk)>a>^@n*m;`cc3CUbL4VaLK%McN_c?#hwt!k&rE9`EBA(COB^E` z4(XA~UF;(7Rr`$l4hF|3(m*V2-!|mL_ZT_d+}^^CG25-3iWerBFi{S1tt0CK^A2m7 zdab4KjS)77B@E2-|0UrQMGsekMjV@j@jPgrARG(27_xEUAarO@h(Z`&;<_b4)vvmCt=be+{zW1*5P8rP^*3B z!sv5aInB3Um1Wfr2zTq4JB#s4{(ki7(uL)}Fno3x6dK%<;*)U%-ITC=Ue-<7FBV}_ zoC_0ovZ|Lu>$GF+$Jr%r_C3^f0yCb|v-61e>lzPF(ew(Ik=gg8!k4c@Mym9QIShKP zjTgGG5glJRJ_j#6Kc2(#u;WZuG_|rLL3Z)DL76K>TUd5;_eN1(jbz1FzU5#rJE_p@ z@HjW_>zuiJ=4XQ281TzHQmR4*98B4jEoKpQ15Y<3!ybOOZ#U!CPd~%o{_{U2^!MF| zPm<3`=|)RDT>ci-YZSQAR24M#?Pt9xsN6uIo>48}TKOq!9dXZG4seb`KYT51tPI=2 zgUgL-zLsb+FdW+4gStt9jZn-OzKb*q^(==ciy8P_NoJMicuk}tyM?Q9Ph!ZU!cCHT zn>2@CQV#^riRa#row6v2P=Tf?2?Sp)8G7dHrUG{2B%$FpFZSMcrKe!a5F{xoDPJRo z_uin~_5Ej|oiyb~X5G(qN81Ze(C^mx@K*F4w0=U-SaP1aSD*;y1Dt7J9;WX*sqdMw z-Of4W<6vaZ<^TahK~D(+zTI0@6nf_U2WDu~@V)>tJyw{46Q7$mZ_IObyY>CsH@Lt5 zVN6$^1t^s{9m@;JAl*y$WOdJ!ZjmHx41RleD?H|6{Dj9ID5$iuCQ#C?#Db4F;v%9x zxF-G#hBef`Nr?YQ!_PgB$3sNe@e+UCb=;;MmqXfBWxK#m@q#)B2rq8fT3@1}4cO6n z2>u0;svhrP0OgnYA)Id{-$oQ88oWA3POUsMbxp=ou;Nc-p6wrh?$M8jRh9}rg5NY` z_T10n?MmXqXI0(%Vq@z%KFUZ2UqMm6f#E-sfx1Vd1a9v!2lXTdJIAX#PdL?xY<4=6Z7t2?!pyK`<#|ysQ z=A(s1R!Jcx71UWyVFewkzmrq{$mt7Yo{mQEMAk5R%*LFRC*CdF5A*5Uy!rX182g=; ztv$}7(gS|xY-^$E4d%;vBg6q{VL&c+i#&(%eBOh$IK-hA<;d-vJ01E6YHs@!G`V+6 zk&*D@N*l-Nfi2D8-;CCZ!$vb7KWv8w597o;FN30lW@197z$#`>$q(yifINc8&k*2% zLWdRZWJFteiCd05T)2wu4cy(mh3~gt;My^Ou%``cg)zJ=iNfJxN` z$#Xi6nI$C1b&t=@w$&oDYzmr+%m;))%h6<1U3*aFt;3DI>wN;Y3=(zw8Q9qC z@5hgO`1buP{PUl`iSmyNe=vxDc>i9*eLsGLw{PF7@=F+QKgb9!1iCk#Anmq9ZXO>& z1=xB}%8-Kb<%h&A{cIHxcZy8Z>Vk_3XL{%nZkpu`@SF`))&Ny8sL`;Vl$zTCQ5v?W zN~eioeRzDAi4l7Wo|lbyYy^rEY%Y@XBP(fvE5aP7S>GJiB-N5PLVJ6Aqw_0DGe`Ca z$$;l}VG{5ia%6;SSi+G2ByqulC@Mqaf0yM19&UCCXwI zCSZO_V8Lx~RA!fRvJd$U_w&s4gB`x=zA4_Ozh8(emfvK^hq;>ovLduISYE9%1!{+_ ziO=1wpYbn11IJs}=IGt}C3iQQ4r&)f$-^>MtM2QcyD7MFY3AwJVB}-yU`KX?fr_B3 zz&tCCj;~|nNz-3^@yW~g2Q=V~m3#HPdXA|-e^J>_y>@->-hj$mTz;msSJ#Cm8lAhb ze;*GXpy@q29NYM7Sla=2&l(ol-KT*F01O@&IF4Z+d#+&-xFOra(z)s5L}5UN&-j*y zSqVQWd7G{_W#2pa^vmDimtX%5KmGbEynX*kQVB>CO5%19R#`CjZVv@t9d}iZmGAnl zSF*_YxiO+(yI9;Los zgz6a}!dUSJoFi)72uD+1Pxr1zn=e{OHJBH^tagbn3k}8R@@D6$Q!AA zfGRz&Zm#5ARorUcF%N^DNT7ajr3I={5kvt%8y~s`UshR;cRVgumb5}iXQW|EM1?=t zA-C6DxTCP;Cxn0J<@}_hOGgxCMt?yk7doa_E)-TYJ}vOTOTgj!@8@#4UwZauZQS|8 zG*bE9q+5ymXn5OR-Ax@22JSe4l~9XSBp03$HO+#YJqJ4S!a^1%2_BZasF$5>DoW_B zDx)|R(HirC-KC+!7J6Ic_2dN-r^bZr{wnQr{_iU@4lUqDkZ#&JuQ(hA65~zz-NjNe zM1iMm3{p^FJCo<-%vwIBv#fwxL5(jbtLToI7AnSPyeGf^*dBqCfl3jet1fG@Scvr6 zdlIevZbn=7MaN(ao>Z4bmF>C|Ss_Sei5h<-O@_&~-RU+L0%5t2_p7%NP#hM3V3wT_ z2=>gJL3>d$9<}8&D>s9$nVSsUARlHpMM#fMZ_DIOOrwkQ7Yc+HMdoXRFm!x`1)X-7 z3Q$wXG%S_xZB7l-bDk`oDtJgdo)?%B*+`d2i(K5|#teI#EY7gUwFkH`$d65baXxcD zPlh`OZU^WB0;%i0S4QIgFD6Zy@n3}j8aq*lCEp4kleer&ZY)ufG3s29aVRljjP>cY zU>hRr)s-1lb5e%R0h2~T<9Re*a+Q>j9>N3ZVb`b)*G8 z1d3<78{gPhV*Epm{fTQDv2EIZY6+HpIdCxp&Q)>Q>+iSURPp!y`!^AqSW(4AKG^e* ztNgO2{Ynx$E0Uq8Wa~<=L;`BvYNg*oj1kjYr7(`Z?GhPl)rVN&#tfk3 z{;(08D-QeR5Z}nj;Qi<_Gz;BTBQXjStcshH5jiGiKj6pr$8BA*@Q7a`OtSTi3z2Mu zVqrz#W;x>ozAP>->`{hJAau;l@D3|kAMYRF{DzbK-iU(o5>r_(U0OT74NA@%I)r1i zHHI?($iX!;H5I|lTt*Pa4pBOE?#bVGqX8UjQ?wg_zz089i`emA4+_8R9u#)+U8=~d z_OWkpod3Sz=r^uF$X@kuiAS7CDm33aaBO3qc;HR4^k+N!M3X)E_om& z@75Y&bY!_XBmp!egIBw$DPF6`PMu0JUP(}(JQ<sjz}9)syw@Qw2Zd*XiN~IqupO*nX<%v-j$!r!X^e3@KK7f{sE|+A_HhjJ_4i;5 zs|&?`yqFwMDA5U^dh`!#S;phf23{-sFe?lR>V38STdI@8Rfr<1#p~4Rl(*8&i-?)i zWy9NdZ{ESro8s@+zyBwE`uP{B{>z>m67p-~=>uR>%+vRN$8ir6SibHU#c;>Mc#Z)WCH^9g*6!*!u%5_azmYMJ|b<@^^X8$~l>ZF2|LL7)K-=xU?R= z{5zgw9M?CN{sQuT&4q~z?JHcyg2QXi{ysmmqdN$%7r6q5U+!>c45Zp;!Tprrde{*j zbu@KJM{nvN>+YqW+=Z9u=$DbNQ4enWCuU&}bxjn91-_ZTJD*jV&nQ$va9P3CUGWEo z3p6DTM>VO#1wc(BfCUG#0*)Oe<|$M|NR1-$uw$l$SCF(wJ?}IZXES4vS6AhVxb9V^ zMvYYzkcwX|KN~wmJT0=<-3&L}yN2;!#d#OcKr2*~JhM z3QBa`tKQY-gr<0qaj)n8vlI_9Pf3TN<7uR69=heub($+mNN(@kG8Isv&GwkliOOl% z>KBGU6jzWq$9ZUi(fkkP_q-Qe8C(MXnGY~3t=?t0$63K|3w`U6SI?jAN_e&3R$s?y zT-uiJAr-kGQkclv7<{^JV4^(%tyt$|{bTJ~+tjMtC%8s+ELqGuklT9r@grb0BN=kk zJ7mYtA|zX7NY_aT3~M{JK`?Hd6Dpv{#p(0QRkO9DJzlJiExtAmk3NL~0tV$VT+o+H z7)-!YCvd86x>8EmBO2=L6Q;*SE678en;!H75T_h=?d1V*ZS~yIUw8dGlz*@275>`m zSo!zrx4V{d|N8AaeEITOlz+ef{#le%tV#d)QNw*X-yi4wyVPWX)II$>xd8E!is|gl1o!Ma$JgsNp#4nu>&aNE%vkhOBJGA)VfaGpx>(2civS z8YW0?MC|(NdQ*D7fj4j8ZwkQsZC$=?e;~tW-?u+?5e! zSN{im*xLNDt>>LsE!=vo+lL1`sLW2oMB1%jLi7pd)4qs+h0USbTzwYj-YOzVVIGJ4 zPXk9+K3q5xbn(~+0UWI@PLycG(FHF!o(ht&-E{dYC2+mbi;evK08AJC#MK-4x0*_O2TK>3~e@_nz!@i$Nhb&*Eg)G;0Ao}pn z1TW#1ozep+a>RHw;V72kdpHr*-5wP>Tl@J#WPvXKKy?r5!`LpqI$D>@Q#Q3!q!m`| z5cm+Dm8@)@1NOw}HKL1S92DY5YYTNN3Nu&tv7SEfIPf{f08{Cw3`?|A8l!lJ0{(ms z*>*Br-j>g&$Frazmjw^dj94Jgl%JJo$Cz2k`M1$IuI~HOFTcWn{I~xLKK=9)+`N6K z;aeO|h5Bq+ZUCYo&s=(i8NPC_s^O*FiBP=5gs=Prd)k6p_`~O8IM|i}JU3QVq7Fc?fQVy14?5 za=%3wHYIBCMgZZVDC1w(k}s>khQqphCR!dUG^?R|RLFW-)y?#3a-IOqOVK?Bbic&2 zn{X6*Y@9*AcnHn*d-YsTO)Kr%JI&Ipw3foJKtxazpO-@gj*o^& ziF!Z_4XV6V{rJZ`#V=vvm)uK4-}D#r6iAZoIJS z{%qk#^b>IC^oVBSP-%>P=g*>Nrx7#jS`?lS$c!qW`OY2htJX@-pVL3bD3nE&&}bU} zkCoc1$Q4@9Nb?46Mkjg-8AFPG06U+p8;-t`VM#bEEn%D>va~H$C}&;PVL=cA%#-%` zKJ-z6?{q@&{<=;M9wyHq1s(NwyQP_Nd5oPz&fD2^gLAjA|MQtW1T$wK6?zGfywzgL z(8lR$JYt2FfaX~#MjXrnA%d@)8RVTdi-C`_4@;zoo_oy{m|F($FQS)4;JO_@-qo9`F zYjKW(_jTB0$C%C7BtvV;eK1V0r|S9fF>yUn!7EOGF>z7pFQq%zap8oN$X?Bz@UtPG zdr|(uexICH?|!s(1H(U|x1e02L2QlncG~W6Z;Hbx&N+r~M@9l5^#F7McgTNA$q1l* zS0rG__!8OK-KM-rmw(E|`PYcizw5e8g7?48Yco#sxt8+X=bxH-r{~(sUKt^Kkq=h< zeg6Cz?l431CwTwigH-qBe09piwK(9?&cOH(6ucy359VDtvN){D*t>-R z^CfEs!rIgZhCc)?#}Cc(8R@vZa+M$qIS^wGGiO3t<>)E{$ltd%Pwli3sT=vnx$zLb z$~w6c5+bvQwjqglt*REFR?|hM)mK=Qc4!QFm$2&Zn*#9e?qTcWTJqHS+8pj1R)!?m zz=7$+i>DA0)e_9OCYgc4Fx6MxBiI+bAbco!IJ~tH{w5+wBhM8d2*y@|P#AKg@eI&f z=2|-p=B5&iaaC{{pW#=C(6wlbhBdOAkX|miE&;h@d5~yAW|i40YY-uT1i?r;jC#=* zVL#k!$Qr8(2l?H`?iHZHh&6GQx9yBSYzqGF%v?)2@^hN2Ip!@Jpaf}vFQWOG5q;-8 zAQ(i|Ula%!!an!#>!5Z7*Ge0FonImnmEs*e?Q1da8`iThf2iMSJ~I$|HHP0CVSQ=1 zcsxEIlkA#pG#TS{bcNc_4tWNrJku2oZ?jaK^54OTSL7sJ@-I~=gQQ6j1 zj?CkEjTuoXdu*$lY#+o}YjH6@LAX|AG&j^6&c1TL~+vo`2-Sqhy4` z5Eb|O;aF#Ljxcf!OVNgk%WSPRc~N)?5G1Zh=9P^8Hn;%Pg%;QRsCu9@hg;XYN<*o9 z8h8hawW!$MLySF77k40qKUmN<-HS;0Xp4>PAuzTXyB6<_Wgc9cA5Pt1B#+ zqJWiu5X?Tg@8J87@9^~aplccQ&*o|466FS;6C0wxz}8eN3R6_Dg&234 zYw_U03a_l(>sUyBeXi`4DVL{_$8(iXMK9QsSu{R@VU?@Tzj^mop5*=f%ATYGOV!hm zU_9i*hhZG?2aouj$}130SwKg8UxPFPqB#?G*Ht{SiEO(F?!7=J`E(pkrc&74YeW zp}DX#9w2{L{!41jKR-QgOxO?jofUsv-FH(EFtFa;z7dZ%LHdmoOTf3qMr9NO`w)$*>)=2;Te5>)+6@~o=l69&Djo3rRz@v9%&?JyCHb z1olovYzUCbvut1A#gR~!*YvSI-teydo&zOSC4=|1U(X=tRNl3IFq)i*996`dp2`F2 zKk!hkfNjnrVE7o^vmu3_!Z2EXK}|9bgMh_TUyXs$zzQ<2k(`saDan<$XIM}YAsoG2 zJPcO6O4P@(69A0LAQ(2NdY0n!8<@w+no2ijfFZnwdu1MlD{#IeH{KX3j zI4nqrhEde-wEOR+aUI9LE9|u7T0C2IXww0LM`KgxM2te#G|R!dY>J!Qj>S z3es6HyM4mL5u^gCcoz>{P#)(#Dx5Cw0LI3xG(#+k^6%#67T$mQ1i$?HH~8Csvhwd2 zxY>+dsr<`)p@DM)2jsY&b{vm|cHzq6DB_kg^8`8e0Khm;diGcIe6ny>^7G5ejU~^D z-v?U)td?PKSrs~*UAi|FbVS+Y_^A@)ptMJ`qa+P}$8S&xg$B!U#ljyb?&?&=Tl%3* z(8~fBWo?Ku#~@9kL-j_JFk$ydG!XD4^c8(gV8G;u#%TGY5xJ^#;`00B^Yx+&CkzV5diia?`m2mWY4qu+Sv* z2d(0!oilQN84{-h5Z>@zd24=I zJ)F07XI!%=uPQ~3BF}8oXfNIyM;i9mi?6ts)(mxn(Y68@9q^JzM*?-n-DjlSx+>BCu+b#YI% za{Oa$aO6c|8RVlW4VPvGLf8{3~HU`=MJ?zKZ(dz=hqlr3jf z9&}lE-mc!)P}*I*94QpVYc>prXffEU0e|U_!}geM zKQ5$~Te~KP8#ZP+JGM8Z$eXRN-?oGB`=$x+DKxiSe2Uo`ZMH6&%i1rEzq_rxKGH1&YD7sDyzqOpxi3rEzEq02CBRlY;@ z1;`Mv@mNI;5vqNbmVC2Z`8JHhg-vfqe^TY1d33n99}}$NGTsP68K@vYnTVG%hhI10 zR4hJa??PA>1NeX^^*l69~|Lectm%slV-hccA zr|WA`47$BTycMRl)9Y_HDyRm9pmmOC4bX9zn=#hI+CVA56Ie3N5K)KGz@Ue32l?b- zqZd}ma5%NhuLnju>)JYo!+|^YQ(Z>~14&f9Q&HHtk_w6!oa288XqIa&MO227*gbh4 zod)T*vok5+N*5t~`^*NfgfB zuY3Q=ID0}==2lW(q531Du>fvt%(b9^y8+fq2-+TBDs54R(iVOfR66thO+&5z^CCH# z8@Xv^&(lf5@&=v(Ec_3WuVcj>KdU@k=0xLa&DWda{qF7ErUbmzf^7!Zt_&wztCe}C zmfo4%(;4*at36eX+4md&>I>t!A8+Y;<7s(M7hPEn6I>{0g|AN42qOA=AJ7ITa=FOU z?<4*kM6rS7mA%2bhOmRv+W64K(D{BamG;8!`Mk4hlXBdzxhAP-{H>A^7?Cg?jD~i< z9OL-0e;-A8cf!l`?7P`lP)I!91}smxVomY9d&wF9v9yp|D&LoDjltWBf_zh`(V`w? zC-bc3eBzADIF3k+S6w~Ey)9@pdI}0Ya#_a5Ndw!QA7Unfr>fBzEVri==~zu}U}f0S zG9Rle&}>jUqqpF&OrIcab(QU@&;^h3VvldH_{(0rzThHD7;cZ$6kxd$xx*(fI;ou! z*7J;s-)!#5f7@pd3-0a>*hR_d3QF$6(W?~4^L1rqBv2Smnt84@xa5X0GHHp22#yiT z@h>4idpj-*l$QcFcO#)e?w#98AjhGta1t>6Otf7Q2tg_X4=&oR29}g9&XeSd3`;Y; zIk$^JFiG}E3`p{Blk^b$``z0&aC>_TKfZn6&eUV%d8}(k^lVpP1bLWagJXO_{+}0c z*qpv#LV>XkFSK%exopX~D@o1S&$Yx|oz(5sEWwQ&OsbAY4+A*!7P}8s`%h(ZI1@Vi zFsyGmxEIdDAGluEz-}oHH|rUt++T9NFjm?#2Tp{Pp~ig^&S;P`Jc_^{pEU2>Z@>Qr zUp{}4kk<32+#eyrNchujlgPnmb0VR;Nu_coyUi@|l%~&D`;I2pH?l z??^@D4QbpzJcw(4y5yH}VxD80M&u&Yu)8byfyb>?lWpa;=Qsqr|F$u)+rA)}+nI?VY?V22Le(8vdz&z9qFQf83%;zY5jGJ}Hp`qR;$^G)HCyS}o2*&R1@wH5 z#@MjHWR(c3aG}rOpm-gU>H3(K#2IK~41zsDUuIe<^Qph%{+;p4wS&@LJ|7$djNXFf zJj@8Z!a}(%#W>1+Rru-tcvANamGwMjrcmbJ8ij&SV0we?Gp{MB(7B@4Pwo+(Zr+3l zCLAxRfG)yJoYjM=9AC8iwa%$3NYpEq(hVKdW++`oH>)$K)!(E(IOs*a_8n7`}uww@O@YR5~2Ld9uAeNwWe?DmH2 zzvHQ*;oHT&Y5z(nbpn_1mkg6goQC_>4ZkQ)v1f00V-W*_G8*dfs)`6M_;Gvp27cZY ze?R^0@9=h0{w>!x8_qh(2z)GhF0*0UgS~f?t3;z%9JmXfzkFS=wCTnpSEN-z%hyWY zxxi!w^L}u-)!y-QEICjB{NcNS1NC#S-r*F`WrB1sA+Kl)RdPnfS{0}xH=y;>tV3a@ z_`otgpXbtAtO;}q153`?y6BeY&Z8B~yMwPs<{gaI@eAycA~}Rs_Y5z1nhr!P`Mt+7 zY2{p{`CctJ^E%dW;yTKtW=6@UP7c8^a=%KBCdj#Hd6n4dsf zL-x?=R%k^%m$o&1qM$pxRvaG49*Aht{!)t2F@1z3Hfz7273OhR^5F~{TA7}kpE|mX z7eg52rN8P5!5m@VXT5{-a0p($=vBA$GNez_bA^s4bsn3$Oj8QQs;_mwH}2<-=I5_3 z-g)+#aC~r1&ApthAR)w1QoWB5#vO)JWTR8$-qW^K$$rI58|cnBPRLxY7|Qw)zs2$P z+^kLW$p%wY`EP&Qcr)=Vwl(zJsva)6_BC*ahRRw6U+{3sToa#n^8GComU1FVJseM* zUIDLE+4*!fnTd;utQNhcG{eW@X%y1K+Z_((#4SmOOoW64blVLps3d#lJ197PTZ6Nv z#-Q*vN6Y!xbUlq{m*OZKUBlklEu9U95Lw)T&VXWu@0S4I=(47I0plMjaIn(xlv{Pr zXveok_9~nM z`q3%H?5inu4Soh(yc7M0(D(CNSeA00>weJGSJ?|cKON{5*n^(I zYtA9Q)B?PKYj;n}rH0P4IYy07SF+`EKCP=KUru7IKw!Ru&e;@i9y($eNv$H9*l~t> z{Z9o3d=(vhjy(LaYqyqkL>B1ag_pPcPhABm=UJdQ)cIVoXjF66eE7;ZdGjA1A0)>g z7x?)6`|py2j=law@yFGDH{~D4%(GQWm4B6(re)=&)vw_?{QP2Jt)7d|6UZtr=vIgn zYyyc2!Z8MO%{X>fxCNoI1mJv6+{jl=EX@k5$H((VOrAuMcYpswkhv)Q77qVf;O@<> zR;pr#k(x5HW)$FI^5JB8n=m=iFHyEm88)76=!*pCluokCdaVvM`n9In6 zjFbOyIIs&X;aC864yoXs3I3|!)Ju4rjtHmc64gZ8ZoM)@~vJZuW& zM-hmwZ<&clxN3`4U{pfu%Du&tvRi?d$j{U;m=we|BonVpNqoW_H_*sjR3cyJXtD-8 zM^gv9MMq)OBO@t$Yj`g`N)k6JL*N2pup33bz=0@Jq20YFENR$RRhU6kkZYF9VKpy5 zu|-+sNr8E(s%6P>o$$(eQ6_S!f}Vs$wNNVT7Ke5plUb+B7?6C@l#HG;j~bd^02=^C zWe;4W0p*e1FKC;tf2tyxasqDn!b(9f#sHHrG)^l1b1K>?^DH`sPgACMD+pv{}u?{7w zO$~~Z3qV4q02j+WXb9@frhNGL%dhbB-~J<%f0CETLaJOb>1QaF$%&y5MjS$T{1$? zDL$ky2|v4bEtItN&-@YR=jHKvqIi!0sFi?yz5KXWe%SLKsuFJ1N}DGfDjJX8b6`A% zr$@noW`G&mdYNNvNHOwx7?=MRq1Y|Q8FX5rB#J>!BruflAvudy#6(h**gtY+ZGz8g z-oc0SB6)1!@1bY zzx^6eF71B{k4RG3cATAT;wQnq+^z3q!#tJ?+ zJ6EXmh5xz1BhIbiSj~;6Mg`{Gx_K8XF41|ghpoyb>Ot%DQTai`&84??G{_RkkmQk->h070W= znZl=sm!j|uRvZL9l$k92c{Q)W#R^y)!4)2HhiE8UdH&_-nQ}|f%BTeNQxPd`DlWEX zsN%HoP#lhlL{_hr>{WqW(|Gy%M<3!vne&|YV^CJwddN&792o*f=x6n|=VzL|t2*pw zFoekAV7wt|Ruqo(Sjh6|g5(2aJqYVQ9)J9hD)hth)OCVf-*`a&6o(%KI1cy*7=9_! zKg8phKgKDo52DV89I+Mpr0WS~{YT2i{LFez$0&Dry>y*4?+A!io2%@x+q{n6b<7BR zs+lg_g@nb-D}!6n@1P|o90|(7m3=((&?_$bqYvUAe)C_u>hwy&=L%{mAf-~Jf}Vfl z{d~B;moV0U{NsPZ*Dqh?FuZ&JUX*?xKYfJTn>z`A5=Icz>kIK{E9j4{z7Y&&u!ZP| zHN0Cs3m?7$cp`#2pJqUtmMKRl?rxlxA*)nqN`|MW4&T4uOVzk_eS+^Bqbb71MtE;; zH^K^B*u<5N&5-MAicVv$1-Y;0u}Dyb;{*vB5Up3b(p*O_fcW7vYf`V6o!{lW6O>4m z3L)c4Q}%s4+Of!Sq88F>M|#_nn&(UCdkY?Etc<H?Kt4_o$4o90zdcw@GI=*Ei$5w6Wyd|ltROE@S2Mb zKGmNo^VT?BE)UORoB!)q3U7Gn<^44*eX@s%gIu*Ux@A)d-=)@qV+v+*h=FId~|DtxPKZQtWp%+O_3m-tQP5 zb`tL8Q?M4ghH}9PVCND{&VLqXBAWRv}&U7gOT{)j>8LY5T1hHY}lxyZyed$#86|Klg z80IkOMHS%WJX%8kD3pRY?0D^PFv1i#QVvQ5Ob*MszP;TH&dhgyhvnfOR*rWxkG+gw zz~FmNQke~n!p3loS(1XN3Lt6K?Ay}_>J~Baes@R`S$Fi z(+@hEWq5JSR9RUQv(Nt`0L&{q|b(XKsIq9>FrR*(ms3 zr;#0I)(8DecMNBRIJ1;vbs^6~awTeN)gJiFpR7{4d**5QuX+P>esnSDo(a-`4Z)vd zoHZL~;5Ov}$&2Zgi{(650EQl2@GzG24vNp1uWaUB?a#*|IQjt!?|6#yZi_(V00dZx zet1KgA&T?KbLDu;ks&MzCvS*gYKUS z4vUi$q`2@~Wa|m0t!NoXe@T)0%(Naj2P1HVm$zY0E+ zVsz7-O(EwzWVB}~N7#u)Enl0we+Td0y@T)HzKQ2KJ)Ldc3;zkp-?>aT?gj1e3+*r( zz>cOs!l8Z0ws%ZmS)+P;>NjN!3|>0)Kn2#_2++xKSnlnNn@?a2r&Qh%_u<9j?ZLFe zHhxbgNOf@TqCBE>uV$$Xg#P9X6dJH$^*DUtdaX$G=gH^md&oEb#a=0aA*|pVgUn&1 zkDJDtD|xzin*yjNRc1vEBjfAqE2)^2h+a4P>FH5C|NiHu{QLd4-vx>N{PQpH%P+sk zVYu0>u`pHk_17Jz9^$NNz7>rSq@eFen!|0SL_1e+wVa)`rBMDCpjIT?3#`~r_L}FU z;C9)1Bo?Hu?J)d)`}PBV+&`EHM`2LV-0i>(&aBdU`;rkx2r0fjhI}qAIGTjXv(nIj^Rk2$pGh!al-m|QtTig=B>7x3G5CV`2-86P#z0G>zUu zor0NzQ2s^8Q@{$AH@)B#6eS0Lznt6-m z+Np9Lh$0gMai?3QkiQ=dkxR(}t_7HPTRd zo_Frwy~@=iJ0px3x4P_5y_`KA0%Flp_yf}cgQ7DOcRVju5F`tOnS(vvDR^2L zMu3vx7G)pwd*>?68_b;W3hv&%-*~_uaKGsVo*o`Ux6q36IMitfRu5p>3+Ll_*>PU! zJ!d!XjXq1yp!6A$9^g#JrB0HaCR@~iKcx9P(_?cmh`QI0D_@Z5qAP=#Ni*hTu_oDL zCy|UUvkx+u5?z(TjFvwGb4{_3=^m~R>z{H^hQ#FX%Inn+=D5cj<@1Hjy3oa|yz+hR z3)o0V1Bb3|%D(%}D$1d!TqTW*cHIBi0QUR@p-3^Nn!{8$hu{16@8QkcH`Y&LKSv2 zZoUiA<4bbekshjE6k0KFW6oVTjGP%psE6<*fDoX9b24ig?UcOW#>w3B zS+UE43XfmHc$Ou`kh-f>45H=Vi^Qir53P=Q$+3}!D#wuo-CM2fr;0&gX2A9natZ-E z16Y;k7&Vi7YLDtEZ)c}%pz@=EqjT<;-NMV6ov?7U9*!gkiOq2AFP^Ag5dcsW@iyIo z;9*e+mhFh~xC^;jfAc-hA5HzF{fbgqwb}u4;vv6=4*81nsBp-{;%DJ3j1&t)qUAR1 z&S#(v8j4|b<#UQbbP+95o@0YK!Czay4_w{xQ5iYvDTrcq2_TrlKK*v?MkT04C0W*Z z8rUVUf&Fh~3b0phO$7Jg-Fyeg5572Tsk(c zd=CXZR&0(V(^We_Fa|gCZQ<@YZ+qH9IdZMGcIjmpa+vu?!voG*VZ*|ws3M*{D(>FA zgO8ix>(gccyLRN!~bo;jq3Y_#HsQatzmK@QF}JR?h#5P=2$$+-$Tn07!ff^ zm{Xosm=xTx(I=Ll`gu{qheuY(d0T=@!=VqA)42-vx+$<=wetj6LL_x$p0WpxCb`i- z3g(!iuQIJ3iRN@LqOa`!x|XWrORkmT(I)akE2(E(k#{oha4~#zcGh?lIre1dT+$v; zGr4Vnnp@V-FnW3!P&ti@u{@nEMxT!KCe0aQ<{B>*SHrta==t>NlRU%k4f(R3IkdyR z^4=MIj1SDskmg=8m-n``>?(GcBDrTUrceYs&&MD)X==Y7>~2ZJ`|!Rnr5!D|h29s& z@X87z80Js~jD_x&X|b~H?b|z9Pc9}RNOuLblv!n+KnGOD3J?TxDM^8k4tP{AG6f;8 z*Y))cT#JkSS(JXP0OSHBOaN~-1jhY*`_@H4F9}XJB`}Xsl%aNR1+leEBG2Jb9L^+) z!_D%s4lej6WW?SmJo-uZNlBQMSOFIe-zBx2EuP~K?L8-GUc>@S0p#sTGFgaF3Ji@P z!jPkaE-}~1?8IkV`%sX_T=GT#?;hG`bi98$s*4qhOq7DqNYx#VF^L3Gj0x=#g!}+= zK#adVLdwW+uV@8&^pQecOM=(c3d-ITH+Q%4mtlylwO_t`fzAZ($rbLdq=P|_8|UxS ztLvNhsotYxUdXB#ck%vvRL_h}0r+@!VGfKKXHLuL7O6IAYm1>Ety@#JziiWGyao0B z@;FUx9yfs(Zyi}TpBsX9bU55Zy?7TOta5@09c=x(+Q=UsSJepEXq+-nkI%ThJH>+ZMmNKnZ<? zIdNBab1B87R$r4cSeZRf$2f2;A@y$Zkv%UY!q#o%YR z2fj##7P)$L(fxdp0VWOqy3HkNR(H5#0g?CGl=2+ncyqhau1yK}_^|P~qS!#Ff)0>} zg>PeB7w`xLxW?f3&Y%$+%qQ_wpSJ&Ot%V`nt7?m?PZZI%-fOcxu?x|>329(G0?!e_ zp7s>c+SX^h4)~4TjSCk&|0o@Np~D~djm%SdRW^$8jl(_(o*5_=oM$%@+tcxTamM#= zfyt%`E_BcUULMcOH^<*7>6*Y+@6o^F8F0{oLBw~(O~JcWUZA%npzMzE=8(tM$?@>tR#31Ayb9aukn+F@tO=@N{2Ma1*(jk#!ln&0&`8h&eaKF*<__VakBaL^@~Kef#m~ER_7W2 zJbNgAy?|rp%eM^;9t%%~^E?k;rn>Fkobf0EkEXT7qO|uhGR|QA5j$sy-iOp+>&{*7 z<;m}z&=?obZ=ea>6@X5WGoH>EE^}*a2hr1vZmq=tOY*$=k4#N*x``jcbkHb_vy<9 z;2iqPm1f^;K+j>nA3y#i))i&&C=yNYuj6}%boOA4skotZ028&yc2B5$A`BJ!VOj7)T{Z?1H$K#Q1gCQ{BV z_;~*yN)GXGBFB1`->uU9u#$O|Bt7^p1dp!YEe^9PEEi$UZ#hJpD>%c05Zk*=(ew21 zr1LV%IxF_xyx9~$%aydZTHZnl?J_Mj5K@^!L0}zEh>QJZX_R-E z`QkTQ6CsLodU8^APR`6ZKbf$x;Xo3m`+A^y3 z_XN-Ook~#}T?foF2iYFY>qg0F9gW4uGvJThh`J3>302Jd5SQch)bY=HcBj3y4>Yyp za2mohdwW8jDH99o$fg<8xt8w8^QY7fu}pPbw^Ym-^AZ`1bp*}(zy?(rNF_f@b0tgc zOZjZiM;~Rs#x=1q#`#U}1>qma1&nf@2cZe3#i1RJa2h4EvJj!G(+#}+@CknU`B%;R z#|9k}_8s(V+f{)h=^<=al8lr!YH8Twu%S^|pDQphjDE%mM}g7=B=^$RGju%DamK!v zh3TV&CawOgo&yxL7Xk<8_$kJsd>J10=DQgJY$%};|AXPJrD-_YVoBXqnWI(sly;SB zlV+)wfJR7T?4a>9V;doSN7~g5R=Cui*j|-O0=aR!D<}>aIF|Uj( z*u+D$d9JTTEbx)nszd?kaBaMvC|~YT;QeMWtf6mNJ-MDG7x1MGhlrZXxF`T4zcW$r z94dg@_kugr(}w9jK{a%tg@BR6Oi=(T{IuW`vfv&@qmL$=?7o%HDdC$C2@48=Z zES2_U&(o8+o`W#7?0DGuI=l&n{NRtg;TJvg16}_0FS~XSj{q-v^FX2Ld=+|`m^JUu zG};xk19poSlz9lcVc(+4YZw!%XRj*}p)FPn zWK#fgN`$WAwdei>k{xDcBl=0hO|96*i8Al_^7ItxeY%}DKc}JyX^M-M@ZfXr>@|xL zIij05sbpk5b%L4&D-ZhLc?R*CVboO?mId9*+ z7X_d=!$|gtq693fafqsI7>SR{c+0rwci_0G>BhJd6#s*O+3|Ty1D)mm41URYB1yx< zjz61j=(^tw^m^=WJBMh}fD{LS!W5k~q>K;g2`>||<0)cMc2QUgmNtd{y?DJA!e2YK z7nFa0l3*M(2S?{$f7_7Yroj95{X2a5{29J}`@AXt9xPuBhyWtYdsk1f>P4^^qb|C7 zR`fhQY)VAShp;X9=PzHx3y|{zaDS*XqDW-`BiZ}!8#8>h!rkqiosn)il$zyOd$jP% z9#%M3{$1Z(%h;dHV#tcuO;5SOmmy{g*Ez3G;`Xjgc8kzb9G5vffx)CG&1_AVWlPpxpfDg)b)haD3z(p+OlX#(ya=G8xjdWW&y<(P$Z(8QENY zOgKh>0@TueRfrgs8HZTDS02@WCyD{(4o{5rs54=vs7!FMHOSofL);*$hh57ItV4l~y zkW?ET_s1Mm_X9zg-raqHfgwP}xfML@^zNT4!UF#lC9ZKw$xJ6MplB!i4P_@(Xf2>s z`zpgHD4-gPfB=-<_rB)UTPB2CET^r6Ern=Y8b8|FieF>d%Qndz5CAVxVS*haH9xuE zGRlF=898kh-!~im`*~CTeG=<0pNk%AL@+47QSqL8G(U{q$zsZjElaC|wQh_O#AlZ-eVp1pX#;4%!MHj7B@^1K-wAo?PD-s1EE z^So^0d46?@ymVfutM?wXQiW7XC6{~hXNOR|Wn5v1WxN0-jSSZLx)0jHD%!%-a8gON zu`FWvyAt8H5BoNI=V>l%&)ZNKYgB`|C=o3cg1}4UDs=A?#iLi2{X#*cO_z}Co+jC*{iOu$vS_=KSb*k zQ6S_p52ED_Wc=L;*G^Z*%zr|-?h-v_A;N3r$^1CTWotODA9p zTs;{vj4VdO`FXV-478LD*rtY*1MQybobxX|3zK(CLpDW0CtpLU9>3b2t=)3Uljxtq zr$P9{1Mj<$T{GCX6~A!sY5!{cP$)DEK5U1ue*vCtAk~l=TxrmyeAan5Z;?#7EX`Es zu7fe`d6qJc_>4g-oIi#kEe+(1Z0!0t>E43MgAo3;Ig)=o=Bpv9ZZ$R)pL*D!TXDc4I{nTmpS zJ4tHJ^2ScL8(soSuk6byW zSHD5!)q~c`e=a>H*WpMJhsee{Gq#3*`_7(!zrmL;pW$Is;t_RIm@)2BMeK$W)4C%6 zXMk;Ctw$3|SqZrICt*w(xcMM_{P+<*efkOBYzn2eczJaS#-9dm0}JzI|E=NKR8D@( z#gH&T0V}=13nw(n1R0D0VA z7F+}=4p}mq<|$O0D~Kb>aWXW%M=BI{^IU5tge3fU6PVl+Vs1&n%J}^Iz z_0PU}oN!>$K!;PM#W*zpP(7fjB!DDm8UioYHnRZ*u~Y;OUWNcgFwa59n#*shg!FtQ z8RC};!JLOrRgqykz^#WD?Tu?L*f^~sJO3l;C4;s4Vy+!c_xtcQ^Nb4oG5*{X3-3RE zf}j8XH+Z-G8_0S|t-~b;070bjOQUk_ZpsoIO7>ICW%Z~;IPZT(U24jq<{Ud+h zlX~)zDyyWL{HEmL%JL-ncGbI&X;OsZiu9~Z_-Y0<&&}sB=`>gcJ1dfb`pn{nMH#E% zup#QCoaG=Mx+dv`Z@Hd8$IQkwrsLi58bTzk_hs`iDWRK^)`B#g$b=20*%-pK!;~t> zIGVt!9YYsmt|wUsDn-<6{$XhGa2=nUDJ`j_Qc$pM5V^7-@h=r6phxm(#7}_>%yS{i z+NL}}mFHYMtaQv{*EH<=Bs3{_0Tbe+a;A9G*cdqF$+p()3HW$>FP;iQTfnj@mNaDI zHMIT4VRn4fPuC|ieri}DZEJsTIU0GHq$)6b0@A%1E?b;$DU{IEvry@)GC)m2-J}eH z;Y`P~4nxJDY4lUiwfrZ}#6(^a{$Sic7=GT%3Hjebifq{lp1(#kooxjNLdG6okOp{W zWU%waqa!3;zR~y$l?hOJg$`P9_>11A`8v*~edALr_^P!^;&{|bo-Ivb<!UtDut*J5m_P7AgGfh&Ud5HCEXSzh2t+scZ%u0#@jq(roVMHqF7bQ2~0 zHr}gsH3g>?V>&;{8JF~9rsRm!MuOK@&x%vfyV3W|InZx8Epy@13^viK#)tO9ZDITBB zg+vn4m}U4=uOsHNWbzGvRLhY4XNB9`xf%2kTT|&Uc&k zPsx9!TPM>p5KWcjPqd~Lf!}}ls1<>q&JvDpF?>oN2@CA74Nes)tN>HR+V;D4<~j^*3~coi{r?Ok&INDlUyPi zOYGMPTXf)vpq0=fUqc-XMFk(p*I|bOT?u6f!e>o@uaYzV(u*#ArQMNyzKNGN)|DsB z@O$K%@>)mDPpP2|q+QT#LgvVubgtw#Evl6io)>Ws8k9nSbes z5S4hFdB8yxf^q^ds>(4Ku~hF zdY8AeAc>#qp2mZUA{OGvdWwWj zg-1+mpgfP|kII}C5QK($s)*B5!4FU>#a63Q?v*9cXn}j~IiB_~+@J%dL{!gND)XQi zWgI0F(pBN>Maa~E!rpAlQVMYagg0)e+9)9)JT2wHmML^&S9{@$#tm~!uSLi_FIOvq zy({~0JRfZd;ajnYg$_E*m#)$-SSrE zYvjdwQv#k=4YTG#cyz}pN6zN0^8}XVTnZCBY$_1Ee=v`tB^9$K+h{IO=EpT(R9{(v zDIyWfNGNau|6PrTMW-4R3Wb@DjC(+EEZk6`L?b;ek7WNZT@z|FQXKh*y~phN(p8-^ zM&;eUBgo$edo)q7mMbWp2jLLKDrWE9H z$UpJ+b>30egDh{G7#CK|*%{7pyo^Q8^i|i2wf3A%sGB@4=_eM8>{_nUSUCWl^Rpx& zz}+LCc+RE-WG`iLjBq+C`(p||EDJf|0m>=B@edArYN(LtrU7FB9KG)Qh$cUHL99a_ z+GS}NIS+=<#bpYUzg15|P;G2}pl4-Y@IiPyHhGp_dv<+%lXBiWz=H71wT0ovJ$qO| z&Uy3iHr)uPOk&3=WhncAm#~B!`@QmK>UGw6%|m`YUUD3nSD$(->!6GhjF6CqP2ta2 z?{?PID{f3|X0%)udw%q4hqbGMM1os;lWlB zVqLO7i@g7ZivWgQhocPuc%C@S&pk&@^$A9NKQ`d|@#6owW|is7 zA=?9nWLja``41PnSQf1$)n?`2D~6iv$ovq!F!!9P=VK5r0I_<{9n*_6*98=`66}tE zgCHA&pvq6&9W>W&t7UqTHke2;v-k9LV?vNAe+(%vmj6d_f?4HItH$2%~sEsWrA(qU{^hO4sg&7&aXthzbs>V{L_QHiLc72uh-;)=G$H&V#hU&cly; z$@|#58(JWe>;t9(TJt5+NYrQ?FG&Q+YB^3kd@plajSG>)K7?O`kyMZ*ZK${^6<^tA zWlZ&T-2Yb3`b&63d*9v9xjXZa?Zr9SDOO&f1p)tU?5Z$vc^(SWBA?peedc=~!*Y0Y zwgX2vmXDM-us-7t4hJV77G_2Cfd8qEXuNimasUozkH#}rjX3(w{O&*{hJFZ+pH;?K z^NZbX%-#D>KS|!d>$^L?>De%4fqGB;pn?S!k9rvK7EF*cc=N(&BYSCafWV7Uc$l%T zjVszl#&*0M!{oN(v+RV=$J zx%Nq`gLX8u!gHVs;b|yf>laDyjfQuiopGYJ>qjTTWz6fIjboA*%M5icU@A>U^-S|H zTtC3xZmR|ID1(F<(y}tBwn+4 zSiG-Qh1sa-q1s$1fsMGdS~-HPlLtJjmnf+xAV+H4FOQpo;Xy)yn<itb zU4FEjkKK<%({uURaX9p6+NT;yJky!^dhHyadkcrZiWdsKD$k8H&CgFW=Yu7^o3&8%BSeLtPj!pJpc+P4-jS#q>BIALZ~vCWUE&;oo-d4| zV(8*23?grNvTG zD{&bATJa(#FX(%NQ0cnF#z0LcPr+Cacy4dums=A#DvKHU^JWB7!eRM)n=t20D|+#u z?7}a%pg}HMB1}vI;y}9{VqUzsT^u;G@#oqWBG7>zg91szz=I8AaXLV$K-T59>ndI8dF-i1B?Jdd*S(-Kzyq>+o_MbFUF=~&GtV}+S$JX{Bc!C1Fj)a=k>h|MVl}nj> zs5EG@OL!GnsFhbEt?fX^Vk_)q{`~kQ{*aQO9nT|3ZJtH zC(Q&B_S)4DEQ#!gr-uh4NV+qrd>;wXs5k>iF91jS>9MB*gML>5z&t-rSfIe$L^H-v zxW~&#C{;5U6vJ^(p%?>#uUD}uOaCdP(IF$6usRcq0WK@wXTwp1J&;(a)@@ta=rZ!o z1*ZpqWqo7(Av`t97eRwHj6WG0V*;!)UBWDhvTqZ@zG?o)d#S|x2z2hM{IwMl!Z)lL z1+2{%+PrEB`PFm%QHH@KuWN#m^Sr|MdMaczW(Ue&h&|WOWv)1l#Gjq19Zs-NNe4}B z5e=RXbrmjje9WQ1zK{4E)&9ozYHQhCdOc zn=_SeF#fh_U-TFr;k#-q!69VSF(F#X2#Mt|yB3QaVxy9@>LLLXp;HD!!dp?D?F zMfG4_8_G)ObK4oolIyAbhn*!?5Mmn)-(J-hW6hi8-7|dWxdOrA9fNlFsc+A*+)NSb zl{xE~aXRB3G2!cgi6a7UH!7jk7)4E*S2iX}Wq}o~Aq#^e^RSJ)&r7=T$z1FplC6t- z@ttpO;c5GKetc5TA2x_f-dpAW)Z4^x3LBy%+?VO8RtgLwUw8FzwQc`xoBxC(d06PLU zA20xV{^#=bfQA=u-};{Oa-LBCfZXQeG?AHIzvbS<(+szqpR5S`h&L;|)PTwk`fz?? zG&UVymw%bsf9A=c8_H1GP^s{?3_}*iZ?c%CL&-=W^|*zyu=+1N(8I|=ai9p)bg~E! z&HKLEAkOBs%pR+_Bu(qa^I^$B&IRV8(mFf$baO&L8@H59C(99TMFbHo2RVfkgy$~r zkrxjPziq#+nO9Tt`Yr0Y$e!M3%S-Qt3LL*RQMjpcf|V4U4uB)Zo}LzXSkGdZIh~%Y zO0eZq=Qc@=CrXno|2P+tj6A8flVTv(ISQQ;;4eycM1v`n&p03A+Sb5p4XE&t2_aPY z*o92~={<0||2y)q+eJ8GP+)lWGLV_^3nd~td@T(AyXMLnBN*7ghSQVC!xDd+}2?IK6%MP9iYBeEq60?Rg}h>xi<4 zxrFZQ_dnzeQ!LDjsQbb-Zq2jLW%qE* zJOjWV*i*S8cSycVH&BqNuIB-Z#NjaHw%jjN6FmU z8Wk+-vQJb|>W0WIX)Br)S3Q-IAl3b%L0C|Wlv?uDhbqD-Hwvl>6%8?YqNiZm{eAU@ zQ!hKT>XJ;bICn*3h1crS$6NzR(_sXAYZo5DgbZbj)0z&JU{=F``Ge=Y;c;{Ln#b`o zq2zbiYJR&RF+O*`rmGPhcK>I%V!`P4_Eri1c4j3+88V~;?Z3`|`1b~_BwNRXDBm}N z5O9>JHe(=k-4`P&RT$IB86sw@Br_tWgzbhhnkV#_VQ~OF|5Uj{1s04dlwJOz^*{5l z+B_!l!2-kNZRd)NeX4(j?^~?ETWWS7pPDatkESYsVF- zSpm#2Ms8?X#WPR|e2a!6cMUzA_=p?)^Nc^pMpuk_eZfB^v637O1I|FIM zoBPHhOC`RkL@hbURnOa@f?4z?Lyryqc2l4(eKc1$(uB_ z=px zXXTy`%@yQ~4%bpd!^be;Kwl|@9m_df$ybd`b*%tgrzb0C_@RbP1tBfxonkdgsLbfn z1yBSI=SgHvy+>{-gt?!j6SZ=r8;W~R?>=u>^8c|Hn0o+n()Y%pSm#olDA%M`00zv@ zlGb3=yj7B~cuAo*6lQ=?h@)%i$Uy;^OWnd#rz%dv+Y>UkqzjB*DVf_@i2`8UOSJCO ze5G+Nb7Kt~T=>FKk(-}lZR}+PNJXH{P4yVLW^M?I>hBKyf%a1QC(Z+QJ~dU!8n7(J zYv!NeRzj2>SqaEh>>rhX(VSJ9w<=dy&!KoVY_yLy-jbt~JeRZNY81uaz5Kpf5d@}X z5BCTyW=ff&;)GK8O)||ijecGZ(mYEk#{UiCnIc-r_ zrMehA`Wgi_C6=Un+odQWkjV$6PS3(;E(1;XbUv%`eC#;jvWypQA_EQr4V%|g8HOV) zok=H(G#jcgG{+0ab(^wY3RT+L@^f1F<4IBhNY!}GRZowCm-#-22rtXEh8j!G@b116 zu|*9ra_l-@mU|Kgt?NoyV9U3a%orpx3C|rZB?NbH3O>pUD8#w9-Aw$6ktbIFtphzx zI#Dn!!U%W>7mko|AZ|pkIaD%Tw$i{gadl}X7A(B>5RcIjI<$`G(`li_QR_MbM2~pFN872W}%gixnLYELjE5EsW@SI3z*m zDRRwe7{f9QNstO3VkXpJZNL(Wl|n}h;A~JXI6AS7CJ(6DzjD?7=@OYEn1OC0n{fCd z!eK{@{Mn1^`;Tvui%yu&$Sp$=cEJBw(JILs*R0;+3|%Xhmdx2>$iJv=^0 zKb~hM28ZRS9w3p6P`dkVI+&)w;5-Fl;oTU!o9!PHmGg$o*CCX~{sc1%Wo#Ph3XloO zJqKWLGMMYteI8iaMF@{cMpa!Hep}y1FMH2cCC&}_wEcUK0w2ppsNQT=;_KV%n4g6C znmf28nSM}@Jn_CWqT(WxJ*I-UnvNlXr>%%any-Sz-m`cJ^4g zcXhf}AYh&ggwt1-6<5mjH=Wr&Y&AWD`lDBG+s5j?ptznaDH3?866$<1al;|jK16x3>UHl1KEU@Hg7I%=lGij=NiC2v=ag5;^SyC+UpH>G zbzfsFJjgVs0?YongL{q_OCXNz~6!7^Q+d z6rKkF<^4jzZwgv#8nTG$7VCRHmf=j*Rl%dq>O$Ec&2^_95w(4czX))V_M0rAJc;L* z>J>L+d*R7SC4Io!p%z)ubQ76h&lR+_5J;?T&$V~&!9|LUEa09M;e|4JgXp^FZ2ei! z|L0XGJ#~f`it&PXPJ6{OttBeg`Po7?2%(#7E?ODU!m69D`C3vc)Ix49Xu0qTA<))w z>mM?@AiN6{h31-`ifC&X8z)6B^kXxHmhkyF`i6^gu3;z^3Xm8%p~2H74Xu!TXq+o6 zC?hT*D*(hp*0(FcZ{YU+W>W%gM$OG=jZcpnH6iO~%5gtZ#_M=k)3P+HI1R!Rxp$Pl zb2gX&;icgLa-GQ(vuJZ=hb60=Eifn@*(P_2JZrp&;&l{LAzK^W_n?mhGi~cq^GY3| zQ<`wkOBWqIi!(CL$0^UKM!)3y!D@{p`M5utF=e4mdML^-^D~qz2@up`Sczat`&8dP zn3pf>;Xp4Svg!SuXj#VNJU};8C-emTEP&!Z|z(xJQ zylTOXGFrlhb8m@+fX&kXaQLjvy{9sWp)WiDSvF)oUW~XPQb6k&(u)8^U(v9=Sc@Ls^0J82tqL?;Ao+#?UifzHj!@0M)dW)+J5+Tk(nF{~w0BGk~=zI5KRvOXO z*|Qk6yo<^)T|J1SLZr^S*}gk0tK)`XQRzx#DbjNv=dF2^M4_4!30GKBL0zI*#l4M<1Ezf^CZ;zx*8{-hfK?&L@$IZ{VCBcM zStjn-v&@kVU=TzhqLx^b6%-z3BTUvS6nC*kM9(}lZuB3zh>kzM^xBTh5Fos+Z+@0H z;PK%>){iSIYhrz7jL&%%jKMpbl`}2pVEbTJ9%ihrPHAN*3>%sPM5^nZ*THQYU%giN z%HQXsl0=S`!W{m4yD8I@S=VyZoV=A>5d^bh!rEpe+0{Br=KV*Q7HcXDar7(A;WS1J zLMm{%Dx&`JV^iQgY((cu!+1ac{uMrd;qZ|sxV_ns|1Tfavg`&r%XdTU%(v|eKmX;+ zSE=Y{s2Nm^Cc(ezNoOJT?v3W*GqMIA@@e}gOsIz@i=w?pE;q6@k{nmYP-qAgx35(s zS4^Qx8HAo*T%~erTJn9Xyc7roQF6f~!95ir{?|EjO-SBh?`7ab*W)I7Y!;c4Y_UBN zWz}N&u^Oau4$_OkSGWa(?QB@>Q~#kMKA3*p<24YXkU*rwbVTQ{BY2g;jWX{s9Gk0$ zMOkQ?wlSzzLiy+K>g&xuqjO5~-SMU)doV>&^Q7;fUV@8-Jgh9JtS*T1dFmtKDfCE{ zrajU%>?*<4^}xg-LF~X+%Bn5G7|yC)lvTyj-tMB^Z$tn)n(F7GFfe|eYAeI&XH@0u zL3D8x;lX-oq@*b@gPk+4m3kh$g1T3f^Yaq(ka3fQOc16*5HW=X?~JA~5sg!+m!-2G zc@BHpPG_NCE!%{HGXQnjaK7>4Wl(o`|8*C^$O0w= z}JA(X~2DZJ8#zABzVW zI))JJTT9wt|1gqiC0!5RlN+#9DZ2e=ia@kP<;|415NAQpq2}|u;)=l6*Wyrs56z6C zy}%HAwl~9(sswy^GDigSSmI)9w;LYlYvw#V8{7$w=65!uxX?+|oMOy(wJTJhYnA30 zZw64-j`xL%q6Z6Vrr2ZG^FczK?ttIFl%x;n@MC-_bl~XCI5CF@9utQ-z#gTNcmW~P z-2Y5F<9jOlVn_oHxirrGuwQ&Gj18}8@IoK;qJ!_r3g#C%lOD}u#eJw4fOZ{}vt@8C z@WRSJIk&6g2kHafOLDI=6<|gR$k5OgULMlk6t}dq7C^u=RF2tk>40M`>PS*XvBSuf zMmU|tJ8JuFllM6nEx(1vriOIE%?p~0bZ+%lps)ANiMP&#q4fI6$ zhZeL)P`u?nRHbeOB9(ERSAPDYR~?V>4F@|aU+j*JU1aFWClej;6Ifc5g(^)_IS{#1>&j&P{lwm4 ztPwpSgrcGuR!j!T5H<;VF=BXn=uIXho zHi>4LiSXDEPMip(QZ&X;6-WLTJ*0JOdnvgIFoiEvg6@We!fC|#)zh$q*}A7D zxp#`QCNv0CFbRf!yDF__#4wx$%Js{yluA8QBuW>(z6UWLDC5y7CR3;L^&eB13}Fv# zy^Zn7Xr(5W9ZyZKaHlG1Z2lAlu6A#gwdDNsP?Avz1>;&Im4)4fBZhHCSAW6_&m&Y! zixXw@Zy}Ty!dwZiv~swISpIv^f0>38ZC~r*DyUAzL5^cV&mTC^@8*@9NtAuPQ;2Zb zDM}#B(S{;WZWun?;ckZ(3>p!_0$4s48}zu3iKpCp?Sj5xfwDYvpMUNuGk(Q0$4}|h zT8?ZJGT6)f-N#Sx^Dn=`oA)2!bbX^0`UV)n$jj6sH)H07qeCTdu5H3HJ--xzwA ziW_YY-`Kq6p69X+&q7copStf#Q7s=_dYW~c^J00oEaZ+L`D4RdB1~HXe^-D>7u}U_ zO}y}jlVIl*3kj@YULw2`goi6wh~8qJ^+E3mrV|070N3>>ioI5$zzcm?e?Wn=oG;;f za;{t;cHCtiGr||OK^>_%$VhWJN~)MmAY&(lhx-N2Jq=qLqDWFujcl*DrTZTbqR8Vh zoHoS)D-Z7Oyttco@%`J6jo&Us5W1da9;U!g^F-(<$`XFh9se}#1{GeCOu^vB%r44Q zr#V0=bYZ7L$1w(kq0O1jDP6{iuX$!hNwoXT&cAsxAE(8DOU{G)mT5;Y3*M#yiO=#B zQ1Zm{yDlLycx7DTI~G>L&ZFnWwtiQ`=i-B=ETFN1t{hf=AY>Oi z34%w=({4R?gChWAn!&x!M9FvB&i2NKqooVd9GxxBU^iZ*EgUj@C1=K?_oA?oG}b~H z*~}}Rj1OeFf<0_RyH9nQtBngsA zvU}V!xS4TFx4l7h+ch-;TJ)6Qo94z7EV!=)7!5E-J75F%w&zIw& zZTvT%Rq-yvas{CGi?*#EsDQr=<%%-# z5rX1>ka(N+MxH;shEMPfcf5{=(U=Oqzdl<&;r6?4Mezgu8R!y0Rou9jhby`PQi&)p z9_sIS-5O61VHkuA1sxZRjA3p&bBlS^@B~V5Yn8i%fMpsKBm0Z?j~H(UG6qe12Su;Vr`UxB3;9U$LHZB?5j+->^qCESD>W;!~jmkzbMFQ9RL= zmefT_oLQ8ACeXFQ7lD26{R{tNx$}gtq~di&WN9iOaXYTOk!^FtQ@<+>hvxZsEB;nx zoT>zT9+AOcyTN`RrIz zKlqvPN$ov*nI^zR<=>7H4@O>pSH7ifk66AKzPoS@&C0a!cUjfgOMzw3bRUp^DgdOn z>_%vb0VYkZIA7%H^Bh%gUAFdW>{=A5nNd^o_qk$90$ing#c>BZs!@Y0)Gqf;R@}&c z_c)&zw&CgC5{Ld*ErJNID4JS36|6-RoMiXVMVuV590S>fki z!8TUSlNt*s^PNvRf0h^B6Ih&OSP}Sggxi}l9NSUuZ_U%snsP1Z7eE-veKEz{=-wI^AVyAlO$ngJp&I2Sq0giS9;Z=|DE#UXd)GxfCxrGJHWaqs z2CjK%g?ZKMI0n)8A%{o5_WZa^FeqQkr^JG^{NCHP2{-SOGPrUQfYuczH&56*hi}N&YZ%bibAxN`3kh%M zVxkgo71`*j!DIkeX0Ry42=3bJ0y6f+xxk|$2 z&>6NmaXx@9?BHtj<1Dw=w;rOx?5znG!J5du(o{KB1M$IR%sl)nDPCGHs92omW>)Tl zzVf`?8gM zTAhCR<#8cyFJkd}VdY=9UvUb{Jh?X)?@4FH>oTWhGwyB=%GeU5P(~VhJA0^~&!&}e zn%9Y2VZr7f6#~s&xl3NcC-FKIMy>m9sq(LzVC3GTR1|=2wbt{h=aheM!I(qLO%!j& zv`Wqo%4ERdv1Dic%*?WQ7x)o1Yo%?6=Z}+7wt0a0W<-M~t-7T`x-0W236tcg&<68( zQh?MJSrR}g0FsveJ}~DrP(EW8T&d!Fb=p2p@sQ?#yZ`VJKL79s`2G)nkP5)dmr(RY)=tjH zak2kLai+8NRcG-bZYkim?jy$S6BAfyrC)2y&z$3oJtp3W9^rPkb7`=UZX5vt%X;}> zH~Zt`+$tDd`O_(3xc8D$q&u#y?Lkx(=1UYXLa*i=S6QcQ*Sy!tM8%kFo`X76&r#dN zg{qS$aet#iEtRxxlW@dS{#!R}G^ zPP7pH0LAY??>Qiz*7oPF8A0_nEOJV&002jNyJf`TM%AG`G z|KpD_2rBTcU(ssd7nXx|`oDOa!{#N*a1J4xZd{Hx@~Gs6gv#k|R5AV5~YV}jddeC$PdIFc0uwa664+a z8$={M7Uu%Iy|L=xM|fJ~5pg_tQD+B94-nOo`#?Vhmx?DI1}-h=)2Mw5nKkf^<2u{7 z?%|S)(dsbY=vqfxBY-02R|78xU^!mfkaJkaTIe-x0WoFN6;AQ=O=UjH;RVP+4TCMi z9Q!alzE`PO%`q4}YaNeY2zmCs+AsRaDC?oW6SPP~;{!k_q+>I@%U6-7ht*jxBEi;h z26;xo@VN1i3~%=f-+2Gw0e=1UXFX@V%)1O3*76Xn`7InrMX$^$CuHOH545`H!h*qw z<#6vdnK;uykaP6>qy>D{;B~O`APT^fDi+8BFpMd5w_tv_TQbkdT!UHB!ES{f-_L!q z(&22LL&qsYe|-Z08(iA`V04W@)yY*l5|iD%UPt=iV|fMS-gT`uGj^j)!V#8R$=PDi zEU7Co)9ATWBoB@WD9w~lO+!qM$4PP}FeA)41~8aQUBGTd0lFw+7i9kR5q|#p7kF3{ zfRcFi#~-2HPU`+?LnBjCjtId9e(lT-XAcKC0}Y_rvU_`TEB$jdV1_edO+_$K!@|Yo z{kalHUFdzTFc4VWg?wd3rJCRrtZeW;jEG+(F{b9(lW13G=1oyYwwA)&PFj$IZ)OGT zr%xZCKd>_FU}0sG$OD{E^!e#UT-TZL)bVN9cC+v^u{whyT*>)nOj#>1&qJ9PrQ~ye z7Vkr^kSH?8S@UD4UPu*=QTW0IIZ9A;I8MK+66E<1asJSh5S*zL(uX)LDs2ty@?&??1z$CPW9!LLqK_+P@NZ!My$=oK7~g zhb1-fIHkcIwJ*m2V!4B~A}aK*tz08aijtjr2ya=9x)2|;jMAz&iTqb|aX~0H21g3d z7ziaDM1Ql-+k{L_P_5sex>g3(kTw)67_85?3x9^|eeGk`fqXI6&0$lAw>opOtSGsf z5Ntd&uARW>(0DGx-nGW&j$2;G^CSLprVn>pg>jIiXOk*%-@NnBG>*nKytHjtmsxR( z;{lfgLlFeYP&u>!WrfsK09yZ^H;|GkA{&Mpc@+Kz=T@@h*a6=KamRiI(X#nnS#N)) zfX=lGb4lRRR;FNGnJP3`M-BbGe}Iplzh9JpKZ^I?fh(L@UeS0QMzC*1!R@V%udpWD z7Y0}85fvb}{>WaV3T$hEBjf0;_8sKK~*49$)DwCEhA7&046fyi3+r8=%d(=SR_afKcZAjhOxv93^L}1k?X4RBtge0 zH;PY_EBc$KH!(V*-j6yCuN2%%>Q>*UTxWYn8j9M?6VLgaW^`dV*9dX$(InXTZ=hoi zKk6;$ZAL>?$a31<4oO!F?M3g{*HCgjcBq1n>^vL659$dxn=B&q+2|lgq0MHH^n9}n zKODiqRYbYS09sY(=|GZuTBg77)?e_eC=nbnu)>A;zcd2aPk3*9oCqk;ilCc=dr-1-TE zf-n6m8h-btwjI1SnDP<*-8&|ArCHi(Uy5x|PcJmEAM6sm@A>jDalG@!ksrEiQO%PT zuJP*Mnu@`3hpGNq&S1~9rt&n)Rb-9^Y;ZWRjKdB$7NQ%xM$>BCqKq>BRHM%(8>^mO zFkHvcK(nRz;@?;i$MU_)`+yK~3>bZ*W(vXXiY2hTZ-4jV=j(9nPM? zij!vYd}+%X9M77%swE^i{~L5S@GBJ%puoA|MB&CN*fKqCoJ#Vu5BqMwlJ`N_6ITNk z77yS;8~m@yoz{0*1*i(>%X|p{&}x*1AcyMW1g50y5C&I^c#9k0t*5ol_3ih@Ie?T$ zV#OF9*g@&x9v&7$G8ZdVIG^L(IxR$OmOkJx^EWr!pS&%2@!2r zgo>OCm@p#o@#d77jH>=7Ryks1(Ba04*l@Dwo8$8Py}H4xfN?YzV^oXR;R)Jf69U&Q z7f_ofv(TAEZrHOF;$sLPet=R{D;M55KT{ez^X>{JpaOrG>sJtrdNbq+Oa+a5vuVk2 zFCFe7ysYTg{tudWE_&!gZX&et*Y#{M8wLxA5@E!-g;+fGYGDKr%mc>#6#XWnN+G0r_*P|vsdyvCzQq>i=fn-ZRmLLp za+Zw{<=L|_ktyz*3Qx_F;xh=a^6xz7xOEh$#)P_{rA6=r#W+A`?#FOZ?uT6rcCW@kmj~ix5WPkLK{s(6#Qd-&rvjlID1yjfQr7i`D*9 zPeamW<)aEc3ZdEgkc2G?81=RB{8l|%u-@O}ob zR@|%r0mBR{Dq7@s!#p-+{|kZ{3@q|C=s>P{^#E6W0lkrsQR3+<_^I|)d6E2br@d91 zua9Ksi-O2EIF*u9fHI_uk{!ZuU;Y44l{T(Sc+c41bzv?c4daGLeI;o@oj3A)fR^_z zjRC%IhNoB-v-D3{O+E903hePSmg-K-0@Zs@x>9eyxwpvS%wwFyDMX9exS~nD&nCgi zFJS;#&W|WLbbP6%#vvcHIPn}cB-g6}ixWw+;!C9bi^6)#dC7#kr4;KnuY(wZ*onn) zwaYB>-}30ma{jSb7Aqb(a*T5#p2gweEIHiSINrbu*>?|T z%h5htMc@PLGLEX+-=od2Vli<=SCHyhZVQZ0`V1uhxRvi~lI1vHZnwZT9vZ z^O9T)gn7vipFW9BYvwBSv6eLA+137(hG8E-$}PkD%QGRUleqMJ?P26ORuhhWfQd3x3atg{t&a943bHdk&_hbm!k2rQhaoU@M#+UY`6-lt zU_#bJ_2BsPtHJEdY3}uB6VfDzk~$wU`i9P-=6)JhMaX?$1Jb~ANBb1S zP%A4JOF7jt2EM)OJwzs4J#%7I4v`lnAdUvL;Ywn*{+U*Dg&0~Y=-k`2EIPQdpu#AX zMrqZFe{PVt&U+T--fZ+Pk3=u$)RE2+@wCf(5GGeNPmsq%Oa zrd04TjzrnlObQIylk+f^d>;im?AdvD=9LM+i*&Qsduhay5RZ=df7r9t^&W+v^i26& z2-L9M6UPf4QtjSkjfk3KdtW}sCOXjbqjifcIF8c_LuCQLcK%kz<*WZSi=3znEKzo`Q^47SioOuXzy z7)e;}Rcz~{Za}a&bAs zTd$|c`{!w8%%h^av2%{J8)SJ*tI^E`Z4dG4)lFTHl^jZOFU8oEmRY#7?+5cmELd0qcF+6eUVWj2censm>atM61wl?P z&uWM)2*N`Y`Fjr2y*a4&CVMt8ug1!Q+q+v+P#mzK2}Pj*dD#k zPa~h=h-lS4tsdXQlLP>CVJy4KjWlxL(8%h-@)Op9jkIulK1EM1R; zZZ`F1VgoFn2cNU#i$5?f&NK*GACD@_r@zVhRfVGDZIy_>gQZ#kJ$uWacm*<_*A35F zj^S?U7rbw$3yAI&uAYmEuUp8dXx?!WX($Dwx;VN>Rp6x`)=RJsg3tJEo1)NEGoynS zK#3?QKnteRafyGTDN%J#8$|H5@#o!ABxxj+7OpxUma*J=$hOVOm7cgg(<8i5>>Vp| zyXAz1;Fn9`jrF;~W_2nGP6oTp@QW*_vOg?v>qls-1caOhgeD8iTZ57CnxplpShZuX zuI8zpgSliB(Okj1fcJ~5c@&;%b8WetaY#FAHJ6>ycuDef9=2W*-FJYy`}>8D{0Lt~ zNAfil?uiBV$3z8>Qhfrh^TvVaa&AyQR=lZjbiFAmjVAiW7^0`z;wr!|&m5)rDEhpE zr42aDWXv|kAF!(Ubdqx5$cx11xSrv%<8y9`>kO&`uEO{{XKC^KG z&ZLifniE>n%D|zU<$#UAwFNP_bavs&T3lem8}lUffP6>YIC z;xMMkM+k+5DY;^6K%VxD3njkzlL?}ege@Ut2mw~Sn=3*7>cSpC zRni|Ul%W|ch=)QbJdAi{BD;Lvi50DPHv29oyR1K~psb7;y)qw!5aNnRff)-ku7E){ zcUOLSh|+mkyXQs0`ou~=(0t>wb?KED-E+jx=v)}L$m{668>3&tzCy`^IUZkEJ)h;U z;PyPZqR-dWEpJ-+a51th3?o-fyt~!2{P_GNg=0?3Iq%s3jA3alIuboG!~pf{qG21* zfbWM>rT9pLVkMX{W_&mab)O?gbZKRaJ9e)a!SH0IA8%`?T)0M1rTrp!U=UMU5m?9& z1}tkfj1Y$P+rOwBnR-TGO#qAHIdK|pTFZU`=yKon2P}6wtoi;rY`?O=*XLL|TD{E~ zmpPkKEw=?%Ju&PqN*$&pi_-7Y_us?)$4`re{ze#lM>n+EyIeP1F&I=py5cH;#2?%F z4tZabnK=u8!Bo>UdsrT&Yk!xx&pA zX2w5-C%l2L7)U3p00?u??U~4EFhmtxGQJF)ud)Joy_d8L6_H`)7xZvHUoRbRRT`=X zqbtUG$x~S2&(}ccMi}*srj)`p!YTAQy(tTnJLHdW;V@?H_k>A@@l-VE{a$esW9~4V zFF&pccH?|`otr5Gn|Q%A2*qsSMIy%~dUfYTxv(hTCQftnQk1+z7OkP-yX>8Fb39m% z?6VloS$;X4?lnb@(N>|8H=O6`K0+-|C!K-kYSv@5C}o@G1Y~>e9rJE?cgyz&RqA+s z6Dk6=6$HUJH%4!1jeiT!7?LLAQLyk1^-L#U4nTW|C|aZ)9JRgq!sEFO6`cewf(z}? z#RgZXb^F^I%?=OwST1&4KE7p+TPk^J9qKbdn=C(;oHw$(f4)ERQ}TPF+-WAyG|Tf{ zCcKt09FI>1{F>tFpsArm_O=uW1$TOLaz7h4`XY~rRldo%#Ng!ly-e|rjZ8twvfwN{ zEdQs4gPdsDSpk$>(w@tk9Z|H{q~^}!h$q$wc=c>K$Br`%-*(G;H_#5C;0*;=p4}** zO8-fYfXB+7zd2v{?amXAdl2 zo#TBedOB;Ea-S5BvGNcZ_fFFe<*RZ(%Xs`AdY-+ z%AUg{cE(P-zpX|UZDDhTX7V#K7W>V;0PlbK1%8=+TK4^=w1MjuA)(Damoa%X;y$*) zVO?Xq6&!7^2SuvAt9xu@$Jl1)6?S?wK3^k>)Vq~+L9E-8v&@FC^I0|QC$>(Wlv&}t zf1Dic_HY-Te;&TbZpCbE<3suQ^aNiYA4Qw^@Zp12@o~$61AAyADTo^4daFj_;971c zg&Tr|b%C(_OIsoX>&3iwz3r}{-2%8-+yD&dYI$)*aHmlWt$Vw`jxjlEwZ9t+MZGKgO`D!Ub1{Cl4xmo3t7sjXTc!ak3nH zFN*@O(**bPO@tb$X8=Y1H&^gTUdClECsa!sSKU!!t*x^F!$QpRf1tM zv6n97(P{u#soUN@8}2rm6INy~h>AEEC!C&V9?x0;1jez~J^%*={`lMe`#M@d(Ln<- zHoz5m&MQ<^DGb^khi5l3`CktsCKz4ho(~ocLMF^ zRu+5@>6N01^s_LDuC0aGx*%-dbyFnuIg4;;Auoy?d{aD!rXZSAp!Kyg{>y1?R{SP~ z8{?6yg3sseyaN_~I2atCGf`p&+n)i0yF#w)xeJ%qP=(Jy*K!KDtvqZ95|A`FjAsS6 zRfLIvQD6kkaMrBw4Pd70ac|fNYUXuE6%f&s7l*DPhb(WL7DXPtkT@gcyxmi1fvwvf zg%8e)?DDkG^XJ>gWfdNv9kYKP8+$E1aD{MzvnPd{^Bj35S>|zBS$Vr#V}I{#uO}#M zNAP@cvg{u!2Tw_7#XyB;u5>lR;sEwkhj@j`UD@s{S3Wh69IAa@5@72rwqpf?ib;lc zcQt=IUyXAu$Z-_{>|=TRwUt+1dz#9S;v7eF=zwholoB!y$u%jRz&Ok9-H?25F(mMV zJs|;&cXb&_qBryh#WRu%&cgnk1vAd5lD%2JK`=a~X_LC<*E9Mivhb`*KFR6HiV0BVJC zN{-U*N=9o#>3teT6y&$(Ib~BE;R}+T38?VqSNzN#N3cE1Bb|Y@Y%sD3Fbsgoa{$|8 zN_E%>G~`7nIOhs)b)Cnd-QS!Z`#=!ZFXo{TON-m*1=G~|YkjW~0Hp#`o zKb*}&qM0}6c}@bgtcc|H$mOTOgEg z7!_}h+l@)mm2}Y0kUCW6h*$oFh0+E7o|ZkEIWairCs`gioX-J&9%jbR@}3{X8h8Y# zgmg2GmN})0=S;#^VyVJA48KDNUA99(Kh4X=U_^4Y2%KuJKTiVfgigFpUD%7B#=|vW zOLRuTRvUf4Vs`q{#|9cskv|MUfB8`bK2roXm0Y^DMVoEKQFn|CR-0Rm~k2!?XyR| zbFy4#nu6!7aBG4)-#fW7(-b`z@a1cxbV2AR<<8`i6}n%4cOlC03tM!H#_|rxDa19Zc1-m*%+}?0aQTf}iJXGQ=0hk!k4a%NZ zGAZ5|oR%rYOlC-VPremE(ISS+cPA;e0lg4b#e);5qTPfV%Lm=&$rS#R=H#C>Y`0sk zKBEN2-z4sJ55SWs+_dU1`SoVre~B{!r!a1AN(1~)@B{PPhcs$) zu}cn9mUGC;K2tLC`DU*_agJGd_2)%L!8(U1ss)O-+(Vk@tzu-4+@ffTwF+ZdtRmGT zF>un{?~JD6#ho9J%(uip7nii8~9d{M!|5occoNUmHac7ES1dFcQU@%=it)6B!TJ6e)0xC1 zRrPrm3gd$-tVen0;MVU9fh2by6JGnC5mFv~8u*FTf|dT^ZRGR)7rxpeUU~*c<;Hb- zpR4R)pYDq3Ev*#Rt6zaO-)ukcd47z~+j|;+sOyKDA>nS9 zp4&_g@`BTjT=?U|NAdo9;JkkeeLgeAKd4_UbIe73?J zw%3?=_~3Kx8dg)ipaF{UN9ba=2#}KsffFR37KT6Hi+WBm+5i~6cYJNqo6xfO+S%U) zukt)rn%Ze)+es?n|Ku7$2ve#YFrGiWjZz6H`I(!|3+vgWp&jj!bEuI#t09Y%VpCVJ zm*QRwFP^WQl^d+|J4?RulPD0lA~0B(q8LfJ-J{dj4o{EIVg&c~N6heI4k=#joQC)qGn{6VSgeqoPmsJ(-F%6U)}W|TR*J%{WxItYj{#k{D2 zQn7-T)`ri6^B$}`mjXAW>h|MwlrUED$hO>lNte+K);TUI{KrbblPbkE>=&azh_d08 zSJ(3*X{BtDy2-1K7jjkAP(=M8XO0VGu#x>jw*4BVrSSr#fM~$3}gl>HB!y$xMBP>p(|E2XSCB*G*3S9|05V^ z9#r~=JNy=I5kc&e!{3^xf}Lx1%r0Gy<1qv9U>!N(4wJRLirVr$&&?bG%+s&ZtLSUs z0qq`yOs@lF{1=XL{EQ>Lqzxjq-_1)?`3T)XWhE4EFx|PmyIT~1AK~=;1YdaH>1+e1 zI)ky@^6j8+xwrv1wzOK0+9s{uJN+HjBv^!zJ0DR}Q8|(p&cbDm>aceMqVbau(Z@$P zFUUh&ZSU_?3vb?hSwU1sEUx(HlPO*_XP{dVwL%!>NJ{c)8;|`)+wayh1Aw8tbUeCV zHQLXqd*Kk!l^?0>4qZG%*G`unf7T>FKRs!9!t#0&&p%fVQZPegrD#3xn7V&y&H)MU zK&{9GPKdgOPq+$Clg(O^YtLQKvwgiw!J}LCNlaJ8oHLHALVE|vKb6C?cj4-l5jktv zKYjcNzyA8ODm`DMR$v_D5R5SU$U{JptXpQoAz{bKip5dD2s3s^KlB6&!xsKXwYnATUqv2&F%+1z)$ceXe>HY>9giJ5sh~I~5 zL;wiT|B%X6s- zU1y_Do&df_9xD}?*7T~9%Hi#1Is=7XjoYi5wkT?o-l<#Vtl~LS7GrIl;OaW=U9^2m zuRJiUI~ArS2&aw1$<;6<`(BubeOx!f-PpG8-)q;JXkTI**7w!tarMLA>RyB1{r2B7 z`kl6XZ4Hmu3n0G!oU#k%Z9yQ#J#H2OhCTj1fByq~{_Y33;cz|?Hu6kpn2$5r>ycyN zG^&HMJqyMh2-51kJ+t;;%qN5h!^4pXz2uLIrz+jIDz3C?dCj<${?OT5ym)G8)k2K3 z5cDB*hz#dBEjL^>K2p}RC}1qO$=1mWbtt{^2ol|MrkVPAvfN2h#6teGVOO%GJ_sS! z-*8KWulm4=Vtmd&`5e4oxtr2sI%vP)NTMD>tqZw)Ib9fyt`yuD4I%;w7JFpdfnvnVY%m-aD=Qi)gQ(#N4qS?G>HE7gnQvuoid3E^WUgslv|L=I$` z*M9oH|~>1pqK zSmWCuBwGYW>lSeJ5ev?W8@A67?NQ2W)D8AVef4tOqkJ#M*-?~*pq_sdglXI*b4Q-8 z00DsNm|;mcW3DoeKGGnHvkeAcEwF8-Byc!_fFbEN&g7)B5K`pRV|;V!eM4j1Sn2d| z?mfbQ)e~{7;J~xfWjn*~l(JnMH{8no6X->x#EA-r))X!J-p*I-IuViH!*`y zF1Bn3WAHu(EIN}l-);NAw48Mt%Q@mRbi94xD90uM&kDfy@F2>j+nZZCVET|MSoA%L zW}FiVa-K8Qj5j@;i}HhA?Ik&HTdhWY>jSS0eBB9v4Wsg!@BriE7k;{OL+uT>@c6dN zP#jVqGgb(_ygZq*R6X7@^s$NS@HCNxKsbU-0Mm8n@!Z@@aCduad1tb>5~wC8S2kA= zE5y2cXrfnm5~OyxG0V)(ny(Pax2^;h(AF^E+eHb;dCMMDQHCb~&zV@d%z3TaB+Utv ziXE+pXmu@DjQddLNrm11P$;RdXozvWWhfWg?Ox2R$WuC&KWRAx%= zIC(ar3L*ku-KP^m>Uytz$*TtottCV|r=IVsknv!Sm6x^p#AL)Ic4>djbG`M~zNLTw zP*I4x!(;h5(R(14X6L8po-47jLIKOVc*sMuF$YPbWXQbftEXUG(#_ZdqYq#DcyW*F zZyMj^&kOuu&6~2?g>xq$3hd)5Z9hQShzs|i?_|FBd@G^+gK~V0yBgV$^wRWu5}&^Z z&wLY7zSv6A^0H2Q>v5@pp%MRyGEWZxtUY!rX3cymm;0j;sVSzQx6wm_(ek$g4XgX1 zFc9#{-TBO=>rFHplI~!E9dDOu*Ktw!efanZK7IafF>*YJhr=MoI3q}-(X0GC(#h3t zR5>nya>OpE+t%dKj|b`#Ba=)akfZ{zHgvi1O&5WquXzMrxdz<1-RnSGRNw}6ZGA>w z{6h>r(M~GG`2P89o+93dDewYUbS;!63LR9XQs4$iT2x=%Xz2>C9@heTr=_8{hn7%T0b z>ACI>y)8y+=U{&*qIgbMVJUY$SR}GOR3Ey>(BQ)3*vwbt3N(C6e)ie(Z)#EHGvGnOwbdBf%`nS> zx6-!bG*yU_8n0u%U8=mHVmP*xH36gM z5lDzZv+c!loqGOJjo>Gd!>6s$aSVsxI#R+dsgdWM{Qw%a4XGIe$r8r#i<@pI-7NyAZ-E$;r zuA4(Eu7v8wzItsT`%OUg&WpaW$-2i$oHU=oGp~zO_O-BwhL#7#Ja)jXxB$+fdAJHI z#zX*GFery3+}s>2;W9uKG8{aGc_C$7UAd|i==hy^BuXB@1+m@qK|z*d#|;ycz101L zS1v~qid^|gCPM6`_UY3{fuFM_eic`6Q&1X|ZFW2lt^VL)j?7$f@>R5g6FBKplH$9y zAWE*Z5CxDCM)XM&R3k$CF{Al)u_#YJ{VLk>8>wRa1Yf^A$+&M>u{SG83vaxpiK+42 zEw_-afzQi@q_tq>c!+;uqWft_P0GL`1UvNjV>sC)_v?go$q%0 zUk#-+ZD6+FKE9#7?GrhkOEe_V0~<6J-s0nTpW*u-xbVj(sRHaC5kB*gYOESX+>$XU zxR+>nq|!pN_`YgA&h@53j%@!?#l;Xz$rbXoFDj3&6eWRs3j7wu*xo9xCcE?1>bv1k z)!lPey?Rzdo&h}^HqJPewo_e3x^-VdgN+Gz;#bm;2>8?pg`|&Cpnc zgz3cDntM<^-~0@RqPuyX*)IHxthBhJfq8Kkr5>!-q$*BVFz&F*|2Y?4@h-NUYpY;k z6GekNvb~nHHFNS)UR_zo;zb8Ayb}ac$oK|>H&?dy?$v;mSJR5lHULyFy$L5<=gD%2 zfmmc^%>g85uX_Gi>LCsj+b2u-; z#lo0x_k71w)3ws#V7@1bA9Cg}yx;6g{caC2tuBI{Q%%>NxX_3n=&OwTCAzuW;P4K2 zf4$TPUHtf-OOIUA&EL?}#mnJ>;aS4YyBezrYS9n-v(Iv7P00gY(+r4Fq|d5^Xjphh zGF)U;&4C8?gU^sxBZtC&QB-fO_&j;9kyq1@Vy&9(vjV6%3JQJV|0HF>oXUF7y{b6^ zXDQ@S9fSrW5BhhI>8)_&z8r@Izlmz#0Z*48X_hEFf<@WawNNJGY#kSe2_Im&TNQ`i zX1M}4S`-I((H+8J+&MJaZN()g6dC?_^CIKuoTrehFVg`WQ7V6|aype_WTVkc_fS($ zQPLDy7b@O&tJ97pBJ0|@El1mRHsg;J=VXnf0@k1!t|J*yB-j70`2l z`19qqI;_1p?Kw$YJL5T*y&o|i-tM1FtjSo8H}e32Ei7m1DIpBQVSy zS%@kl)2X*SQTY1$;Cr@;^1!%P1S9yn+k5!D94_%7wPegvFvIfNr975%$WKt#5ke8d zZEzT@v;i&vv?y0EaBZQoM3YvkWkie^h$;><6IlV!?IR)mM?>=3ahe|b8}=K53?*0 zm{4YUC@yh~4rEY}dJzniWOdkFPF+k$5aqNfn$(GuN;nOb-nu`cp#vt6sKt{qWq?;) z^pGf2;x3PN^E}`WR*_7S{n~gmH;J|*p38VEQ{p72oR)ys@9v>VWn@RHWNo>y7gK}+ z3Y1?~#2Z}lo%$zb)&r*N`|p=kAr14G~zZH3!X z?YF>-uEA)E$u(Is1KAtm_?_Rb3{=lGyw3ALBF(bis7$l|jAiEK%$2dg>OH@< z)$j6K_u8q*ksOA`rIHV|A{_)^frYpl0z zTzVk8fQ3?_?Ec{ceE&3FvMQw%T_jFJy((ggGxjz)2Qe4gUe}(b2utDs4#GK|$jPGW4~kQIMSVvnm|X z)<@Q)2&wC<6^}^Gx#Y{LYf^Jyg~Ab`@FJ-o@Nr6LpXK`&-xRB)r5-F@au)OLlPUZ> znTqgRET~)9wHzPn7D}zVEb}uZCzicxM#MBhKAHjbpb9{SmotixqkKb#Nu5JsqP(3{ z7+&NLl00EnDVXzxSwRxchsL}T8`70$xU+}~vZxt@6T{J31OOyTMJpJi%gw|C)NZW`OA?HM0i9O>9z8?k?H?zgtTrMbKG%K6RxAzbYGEkk-p~`EVO!JI}b*z z6!)hs<%`puNR@?o=AjxJ6j>MNft?u}eitrXa`=JBO_LdKB#*qLcsQsBo6$*E^a&~i zOF;uL8XNfe-Xm3wde@$H9hd(2e9h*tayn_j%p+=1pW8(i(zFbkgPoN#Y}h|ae!zn$ z2Gv=q*;?dz^xS~q320&Y&}rDSy6+lIH`_QEkDLWBG0r%M7FhF$yC=3g7X%|JKm*Zs z$mixco<%S0)fKvLxK=DBq$etD4i!3Ijh7*x;L2GnfLZ zpmM9f)j#7u1JBb6fl|SRi3hTwq6CCAVyg$U>MbhJYyLm=*qR5#jD`%@o%wt|M*$1}(_A$qVW3B;h^E$csjAkX zC=|@unUsN%LIB2;I>YV@fS@kaQjM5HR2jKX$My6I5anc(Ilr`SS1t;ACjW4i=!Zp+ z`~3MNz{g5b&9P&7bD$gwdv54AtS;_3nb^e7fP3QAkfF#^BS@)5LT|T_87c!9h`BPr zr&EVtfBjl=7|oI7x?16nk~34S*AhZB_i!lUCJzVmk`MvkP+<%p8|8JAWVJmIT9TEz4?BB@O04}URl@b*cYje&2CG`=?-3!XaAv7FJe_oU2}D-sL^rI1gZ z3NuH;$j9}M^~6FnhiCZTUb{*Ls!V8x)zVV!yX=;IIe0s+_GP<8%D}{!UZG`-ciL%y+{+qKY2WC9Dn|8d4QGWA z^qgjRVXyTW^i>S}YVFOIE6VSoayG~9F&-%PYV3n@yW`2Y7hr(7HJJ%~FG{`^I4enJ zD|}S0iC0~2w!hqm(`9M8C{)uB?t2CrR@^74_RaE#^HHJ@U5v234%GI}`DQ5^0$@%D zAn?_9{xOLTUAl;xkyzhNhqM(X025 z=aFnyoPwNp1C2D}LW_RLU0Ef8F+N?xdmbVfJeO4{=P^s=V)Fu&ioK-HB-4SU`gg36 zDYm2!Un9tNU0#yn3i8=Js&SdSft`8;lPLe#dB&@)vpm2!#3YbQpZA#jL?`ml}v(v#jF4ST(( za18uL<&ZfK4Y-eXE}$bC|AC#4Mk>qz)$0vLH=L}I@-yx@#4;r3oc_cSoQn*}`2gqh zoHMwzoWq=V7`OE}Lw*>~(7k>jpg zYPrX{x6e8aG)C(^O2CUG^eQDw$fH35*p3r1e{;ks;lHLm_OJ(a=bo+F(Tf#~IG4O` z5LQ#Mx`<<@=KWI-wpsvNK%~F%l*~%5iY6{V@nUV6WC>|KR&O`8&MTqm^2BfE$4}}7I4dz=MWC2mBPQS0(gi0&7D87hOh&Yg2f`KD;%V2S zMy*+Flc(yixO)gRGN3kh<_+Ylwp>Xz@`Y~vvCsTukQVg zgw@}O60i$HK#?a&iSkKAl;1-GexXG zL{m-sWFChNvjU(%sO5A_VNsd70YxCKX>@$5IHUUaR05M{rzMYBZVCYsRZ$_HDZQ7r z%(4hHtj|bmlTnj#*_rW*?($2mylMaVnNocTRjQt|YFP-n4jLdre!%9H` zLX3IYDnj7tT1m0MUf`07zltClAt5W_W{`vGCXPcH83HWicVKF4zm0nPI_%EX->>oQ z1^y^E)?apL8bZN+!^<$Q-3#kHR(-B-#SN4j-gjSMD*yU3;}qnNg`NcmK79BDKm7Pd zQ34)s?*zxX*HL($lJ_(~c@A|&&fJ?Tlq*JW{VA-B*4}EyUMGNHS-`4ttY>7eoeE+X z5Jld5Y5!rQ&(TbQ-I2Vna1!oyYJ)R;IOf>1Sz?)U)5?>n^qI36qZ!J>qyjI_MQKwX z=@O!%jxZQSH=?KnxbiM}rkoE7Z{Ui?2=VF!Yj3_#&Vj?A3byL`;rVlMfN8H*Z%bp( zHYiJ>IM;EYG4g$L*4LYjSri1by6D=-lwLhLX!o#)dBO;1Sa1}Tg&MsIji2Frn=4Sw zxY@Mu*%-OvOqhH5==(JoxmOd*6?x9wD+tVMCfPmZp=uhHgXCo5VjNCi+>oSj2IjS= z_`(H$T@&qxy?2i1Lxi3SyJ&Rnc6qMfsZ)b^3(8n$D+r?LcSM;3AUyL+J8L=~fi0aZ zrvvnKK8A+IlN9oe)8S#u-*;Uj7QgqkMw*w#@NJFf%SGh6zOE4{x^V9|E|B5cO2-F9 zyzH^_{FyqyiNBv?2~S7md1~xl%Qq z7?-AT1uZ8XpUKH_C4XUT7McfjGbNtR#TEAmTY>~O4=%8AFc0iWdEQDBCOrckGsOZJ z|0sri((1VlGw0vj)xf0r{rC`_qffq%+u}3H*Ca)LjuH*!`BwQ{vr>lj4ZJoyFOhAf znA0ROO=L;LqWFyPUqhkd8JtEgH-`Eg1OkX+e^~i9v&ho{C?>+t4qO%f}*N~MdV4<19v}j=v&vB5RP(>JV zmXDONaD`L$04nT*ypefud#q4AozE5>YFe>^3_OopGQ{DZyq?0YRPuW$0p&faXUE;$ z1AO{)QV&V9e$Dgh^v6kc!ieGG?VoiS5!TtKtC%X^rQp+w zbx%Hv+d-}7>;!>Skl{HE2)zg`D_A-B@v&jT5_MeRiV^5O{J3Z2v4|0 zIf0`_!if$O)>kEz68>i4Al$bj^}Zs0m2r&jqT9@^SMPTMpi9&tJyaZp+u#7>H-SHX z#+bn27=x3TQSs=%;2`9gKo^4OxL8!@MdKRU!T=N)qxz3-c6!+DX-3Mi#NoS&{8#tx zkX`?ZVCNnxZ_rVtkH0it?yGnpkt7X68-}yq@5@7k3y;12V{S$)@%H&LxM2zR)}uVN z#;xOz@Y*$aRR#N!|JL!Duakj;hE*NeNc7$J@Ok;Wd-x#dxkq7u+DgUa1lO+|fuX3& z&yX6;HlDTv?2QU6RZ;*{&vf$6HY2$-RxItk{c^=V47g|KKKnPTm~Qc5GR%<5C0F88 z*;3SbOqRRNE9bf(t5#<9f|n3Vqh;RSx_YdDi?B*wEjN`;8HHf~pmCCw)&OO7ayzF~ zjRA�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 literal 0 HcmV?d00001 diff --git a/static/images/illustrations/standupTutorialThumb.jpg b/static/images/illustrations/standupTutorialThumb.jpg index 62c0f87467ccd4a4a580863300f9fcb79d0f4d8c..cd96d52cec0f3aecb7955b30885b68a703a58cb4 100644 GIT binary patch literal 786917 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

*SBV( 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νSL_$*1qQ0iIO>Bs&ut*@jBJL&&oX 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Π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

6z;6)YdZne*P=sTT zmsV5}4s=5$?sgWF!h7U0rg zPboboge*byX5&mrUE@TSw@x8sqJ^V4XZjD^d{CknxkjBv;DHB9qLN$j9l z>e7mz?*1hewOjeZlTy*Q>*7RUul9eT5#i6R!j5gSagQXzIE;Z#@3$}FzF2u(^%sc zHeDoT!P2nF2?`~LY_vr(fXR%FUG3kMx$(7TH{I0HZI2&Jiu;tG{>pbqa?+92jp{8_ zF&Rv_8@ux0&9GN5_{7I8a}hD$?ouaBxZ3o6d$bk(z1{r*eYq8|4mLaT`3~;>X_Y9a zg!!9TVen7<*<5Kec#jL}VxNGu`L<1cU}3(*)+5E+<3b_Md1+2~UH!2tBDR`dI=$#X zt(cMFf75Wu0%?J1w;jwRcaAuLf-5s9iV#fSoRaGwH>wpdMC@cK9nnFrF0tf0E*4?r zN$*P_%+Oly^&#{Suk0iYHuNI;jh&ap*Xx{Q1z74>#R@77%<-P(&~2x>+ln7UJ4Bhj zHz>fM9FQlYEcQ0Dk$%9@>k;%nBw_Pu#79Lh1Cl6E*#!#n9Ogi;G&micS~)3s-@B2l z7%c*e1rC2bGEo8 zBUv6wQ~!%&)LI0|%Yo~Y7s>w1$WO&)(jv;|aC2WX&@GA*a}L3qgkMf~fG1GO;t1DP z637mbC{WRw`RRi;%$t7Hg$}j;4ougaQ~x!knPcQYk=GHIYe8z>BL@zzt*yZF=S^uC zE)F(4cry_U)1UQ-;sgu=LKGp0h>HRGQ=w8kl0(?Q3QtrLRHzVkYbc+$+7w$Kpf6d} z-Uy@~CR~qb&ud9#Nvf=bWHYR=z}7jY&=wyR=nLV3#5<*Krq|+b-viX7s7AAq0LFszb>v1rr zuu8KN8QsBzC93`L0 zH5STnS=~7{khW7`{T12JHte)KVVu`!3PZ(rM*8bDuA_vfEP8PBGbUClC3$jdqrwgF zRYx*1W%zOUB?asq+*Kj_tQZn%{6N2WQux&u$OzVFk!Sc2x|8_%v!7;&d>o_4MFDzg zF-Wq~|9eyZlFCh0vb7{9lwh&Zn`cRXN28kJSL)aEj2czy?$Lw?1nhTrJ?{qa6BQRy z{+b80uZz~+@UJVwk5EW9FYm51*c$^h?=5LXeDh&(rel)}duZCGh_IHjx9ndj)dOkV zrY`t{iQ9wI1l2f&E0>EcvqkzmnY=MlBJ_jQIWteeBx)2>gia@_)#?~)=MR+x&!^#CCZLHUUWi54axR_(HO!cj<^VVj8KwaM^lDuWwiRCvf4&zo=@$ENhUJ3+EMkSa@Qr#YThkoru;en}_b{2oO z{RUzr#TGW(>q~a`*tum1=U%{g>$!wLQ141q7D(Ss~-;!^H+_f;4h@dxpuX zmNf$qC(L7ljQ;lX9nTs9)uXDma5R$zL(iuE?x@k{5*LYenW!JBza^=pP?mV#wYym* z>p?e>nbvcGR!fJke78ofUgZv?Ju7B!N9by0|IdNf&!* zZ_8fsFjw7IO&MB_ z?k{Cfo_Ch?9F+iw&qe-#95WHnIlEdh@DiCH(V;s=a7kIxwJbtW*bNZ~wUR%)XV!5& z_oOD-c3l|hx_DUl7df3bbd!3~T+v6&mc8rkGN+W%lU@?+wU6^VNqDx$c}Alft?`Ev zl_n5bl3%mlZlXEAz19@bM8wBauKf;R1+Qr8H}N}sqgc)@)jnhD&srHVcT|g8Wh$xs z=*Z|=&#_dw(_7B}H{uHvK$~{=9*bZjIXKoBU!Da6Oo#-8ozRdG?2KyPd@SGdD$00EJ^-<&eB5o=>60aXQF0IQ@tFGZ$%hb}+;<(K+6t8M-SBiUxv%?| z&X?DxuU;Xi9L(uM47mFNPNGq+GFNpOFv>WIkD?}66)9C!*x8jc?Ymv6R+Gd{#X(q9 zKww2SZyIo;%w8be-9ow|IL+5kZA*s^(|ZzR+M-1puHRZ9GI~z+D`C6$4Lgl<4DA<= zSE{7PS?9{eMp9S7zwJv49;}lC1jMonQ^YXDJX86UH3b7Ra8qhBGD=>wbFR;5OK7to}KEN#Yaq)Ux5)k zv12^0e{Nb<9gac|q;`RQO+D zdi2_PDcrjWxW>C!gt#M9VLf)ryhk++kpbJEg$tCNFChhd8?RkR)P|rVA3N2+G;nHP z#K2&U;!xP(k2#y?YO{^!SjGYk4Ba}{ytXpd=;AbShnA4a7T#1st2-~M2S>=`gdg}{ zG0u{~SqyHsXI3h6T9LnRTfYS`H_~5St{=T4=8>glwrf=|HMmZmnf&%kcG0}jQC0%j zYJ+MCG0~e|pp@4pkI!t*jX&9%!Woz0K8x-2=oxq&(A#)Y%N~``y1u9T5t+5G>Z+$U zph#%9>Y37xfKP&j#b^i1eU_KR!maFZ1eHP-PLk^>aL}6y>I9|TsWskmBi}(3OwG!p zG={+GC?}Z%&h4rr4fLnLU|zZn{TK<8%XRd9OBU1DPT{PIeUsb*8y-(q-2Ks?hIkZ* zTVqTCxrzKUNzD9{0r@xlTC~WALe9l%>1c%sxQ2}x(aqa}h5@qRT%I^++4OiTVX~9| zVqZp)s=FM!K8nVb!2p(d2hkN>)hLw%Dsko$84SQ=M%;Ff1>X`{((eB4IC?V`5L`HK znXQ#3Q2K+TSoI5A7|nl%*vSGRlay`NoFKHGHY&!$H4|?bLg-T{5DwO2x5GXYY7KVV zV1(9SEMQx2yRV1db+4@Jiv=+H1{OCkY7uuS9D{|495nI?$o&3W0Jbi|eP>*@g#SbH zPTH?^#>bpf0UI-96756fl*8wbis@H7uWJyn@SQLFcG-sum$Stty|;zO_~PE`7^U=K zBeqz=|K_P+jFQfU8HGV(lMB=M H%QpcvEp4wClO`P*SsWVUwY%4AxIW@)d+IIz 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

!kBk2`(;{#_6J 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%0A-T%q&$NM34s$8X-l%H(^<3wJ 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 V3*We=1|`d+%E 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

A@9ERU__w@IoNkhO0K&he1iMYryqg5a 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 KMNUMnLSTX`c?Ea? literal 114736 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

IYxF6EF^1%(1n_md1%QUrC$0 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%0D8r^J#(XIttcs}FV^ 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 OrE%ncmNOiOqyGV0*Gf+S From 4558e143c98c2ceab197b7c00670cc56ae7c6436 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Mon, 20 May 2024 19:11:44 +0100 Subject: [PATCH 226/529] feat: add logic that lets users favorite a template (#9713) --- codegen.json | 5 ++- .../ActivityLibrary/ActivityCardFavorite.tsx | 34 ++++++++++++--- .../ActivityDetails/TemplateDetails.tsx | 7 +++- .../ActivityLibrary/ActivityGrid.tsx | 23 ++++++++++- .../ActivityLibrary/ActivityLibrary.tsx | 11 ++++- .../ActivityLibraryEmptyState.tsx | 10 +++-- .../components/ActivityLibrary/Categories.tsx | 3 +- .../ToggleFavoriteTemplateMutation.ts | 41 +++++++++++++++++++ packages/server/database/types/User.ts | 4 ++ .../server/dataloader/customLoaderMakers.ts | 21 ++++++++++ .../mutations/toggleFavoriteTemplate.ts | 37 +++++++++++++++++ .../graphql/public/typeDefs/User.graphql | 5 +++ .../typeDefs/toggleFavoriteTemplate.graphql | 20 +++++++++ .../types/ToggleFavoriteTemplateSuccess.ts | 14 +++++++ packages/server/graphql/public/types/User.ts | 4 ++ .../1714598525167_addFavoriteTemplateIds.ts | 28 +++++++++++++ 16 files changed, 250 insertions(+), 17 deletions(-) create mode 100644 packages/client/mutations/ToggleFavoriteTemplateMutation.ts create mode 100644 packages/server/graphql/public/mutations/toggleFavoriteTemplate.ts create mode 100644 packages/server/graphql/public/typeDefs/toggleFavoriteTemplate.graphql create mode 100644 packages/server/graphql/public/types/ToggleFavoriteTemplateSuccess.ts create mode 100644 packages/server/postgres/migrations/1714598525167_addFavoriteTemplateIds.ts diff --git a/codegen.json b/codegen.json index 83f9993f249..2bfa7b6011f 100644 --- a/codegen.json +++ b/codegen.json @@ -49,9 +49,9 @@ "ActionMeeting": "../../database/types/MeetingAction#default", "ActionMeetingMember": "../../database/types/ActionMeetingMember#default as ActionMeetingMemberDB", "AddApprovedOrganizationDomainsSuccess": "./types/AddApprovedOrganizationDomainsSuccess#AddApprovedOrganizationDomainsSuccessSource", + "AddPokerTemplateSuccess": "./types/AddPokerTemplateSuccess#AddPokerTemplateSuccessSource", "AddReactjiToReactableSuccess": "./types/AddReactjiToReactableSuccess#AddReactjiToReactableSuccessSource", "AddReflectTemplateSuccess": "./types/AddReflectTemplateSuccess#AddReflectTemplateSuccessSource", - "AddPokerTemplateSuccess": "./types/AddPokerTemplateSuccess#AddPokerTemplateSuccessSource", "AddTranscriptionBotSuccess": "./types/AddTranscriptionBotSuccess#AddTranscriptionBotSuccessSource", "AddedNotification": "./types/AddedNotification#AddedNotificationSource", "AgendaItem": "../../database/types/AgendaItem#default as AgendaItemDB", @@ -132,6 +132,7 @@ "TeamPromptResponse": "../../postgres/queries/getTeamPromptResponsesByIds#TeamPromptResponse", "TemplateDimension": "../../database/types/TemplateDimension#default", "TimelineEventTeamPromptComplete": "./types/TimelineEventTeamPromptComplete#TimelineEventTeamPromptCompleteSource", + "ToggleFavoriteTemplateSuccess": "./types/ToggleFavoriteTemplateSuccess#ToggleFavoriteTemplateSuccessSource", "ToggleSummaryEmailSuccess": "./types/ToggleSummaryEmailSuccess#ToggleSummaryEmailSuccessSource", "TopRetroTemplate": "./types/TopRetroTemplate#TopRetroTemplateSource", "UpdateAutoJoinSuccess": "./types/UpdateAutoJoinSuccess#UpdateAutoJoinSuccessSource", @@ -140,9 +141,9 @@ "UpdateFeatureFlagPayload": "./types/UpdateFeatureFlagPayload#UpdateFeatureFlagPayloadSource", "UpdateGitLabDimensionFieldSuccess": "./types/UpdateGitLabDimensionFieldSuccess#UpdateGitLabDimensionFieldSuccessSource", "UpdateMeetingPromptSuccess": "./types/UpdateMeetingPromptSuccess#UpdateMeetingPromptSuccessSource", + "UpdateMeetingTemplateSuccess": "./types/UpdateMeetingTemplateSuccess#UpdateMeetingTemplateSuccessSource", "UpdateOrgPayload": "./types/UpdateOrgPayload#UpdateOrgPayloadSource", "UpdateRecurrenceSettingsSuccess": "./types/UpdateRecurrenceSettingsSuccess#UpdateRecurrenceSettingsSuccessSource", - "UpdateMeetingTemplateSuccess": "./types/UpdateMeetingTemplateSuccess#UpdateMeetingTemplateSuccessSource", "UpdateTaskPayload": "./types/UpdateTaskPayload#UpdateTaskPayloadSource", "UpdateTemplateCategorySuccess": "./types/UpdateTemplateCategorySuccess#UpdateTemplateCategorySuccessSource", "UpdateUserProfilePayload": "./types/UpdateUserProfilePayload#UpdateUserProfilePayloadSource", diff --git a/packages/client/components/ActivityLibrary/ActivityCardFavorite.tsx b/packages/client/components/ActivityLibrary/ActivityCardFavorite.tsx index 1a703d338f3..51b5cfbf60f 100644 --- a/packages/client/components/ActivityLibrary/ActivityCardFavorite.tsx +++ b/packages/client/components/ActivityLibrary/ActivityCardFavorite.tsx @@ -1,22 +1,44 @@ import {Favorite} from '@mui/icons-material' +import graphql from 'babel-plugin-relay/macro' import clsx from 'clsx' -import React, {useState} from 'react' +import React from 'react' +import {useFragment} from 'react-relay' +import {ActivityCardFavorite_user$key} from '../../__generated__/ActivityCardFavorite_user.graphql' +import useAtmosphere from '../../hooks/useAtmosphere' +import useMutationProps from '../../hooks/useMutationProps' +import ToggleFavoriteTemplateMutation from '../../mutations/ToggleFavoriteTemplateMutation' import {Tooltip} from '../../ui/Tooltip/Tooltip' import {TooltipContent} from '../../ui/Tooltip/TooltipContent' import {TooltipTrigger} from '../../ui/Tooltip/TooltipTrigger' type Props = { className?: string + templateId: string + viewerRef: ActivityCardFavorite_user$key } const ActivityCardFavorite = (props: Props) => { - const {className} = props - const [isSelected, setIsSelected] = useState(false) - const tooltipCopy = isSelected ? 'Remove from favorites' : 'Add to favorites' + const {className, templateId, viewerRef} = props + const atmosphere = useAtmosphere() + const {onError, onCompleted} = useMutationProps() + + const viewer = useFragment( + graphql` + fragment ActivityCardFavorite_user on User { + favoriteTemplates { + id + } + } + `, + viewerRef + ) + const favoriteTemplateIds = viewer.favoriteTemplates.map((template) => template.id) + const isFavorite = favoriteTemplateIds.includes(templateId) + const tooltipCopy = isFavorite ? 'Remove from favorites' : 'Add to favorites' const handleClick = (e: React.MouseEvent) => { e.preventDefault() - setIsSelected((prev) => !prev) + ToggleFavoriteTemplateMutation(atmosphere, {templateId}, {onError, onCompleted}) } return ( @@ -32,7 +54,7 @@ const ActivityCardFavorite = (props: Props) => { onClick={handleClick} className='flex h-full w-full cursor-pointer items-center justify-center bg-transparent' > - +

U>jrSQrg4<(|vm_LvNt=fi`KOwv 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

diff --git a/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx b/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx index 54ec5449623..0adf77aab6b 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetails/TemplateDetails.tsx @@ -133,6 +133,7 @@ export const TemplateDetails = (props: Props) => { const viewer = useFragment( graphql` fragment TemplateDetails_user on User { + ...ActivityCardFavorite_user preferredTeamId teams { ...TeamPickerModal_teams @@ -260,7 +261,11 @@ export const TemplateDetails = (props: Props) => {
{description}
- +
{ +const ActivityGrid = (props: ActivityGridProps) => { + const {templates, selectedCategory, viewerRef} = props + const viewer = useFragment( + graphql` + fragment ActivityGrid_user on User { + ...ActivityCardFavorite_user + } + `, + viewerRef ?? null + ) return ( <> {templates.map((template) => { @@ -43,7 +56,13 @@ const ActivityGrid = ({templates, selectedCategory}: ActivityGridProps) => { src={template.illustrationUrl} category={template.category as CategoryID} /> - + {viewer && ( + + )} { // If there's a search query, just use the search filter results return filteredTemplates } + if (categoryId === 'favorite') { + return viewer.favoriteTemplates + } return filteredTemplates.filter((template) => categoryId === QUICK_START_CATEGORY_ID @@ -344,7 +351,7 @@ export const ActivityLibrary = (props: Props) => { style={{ color: category === 'favorite' - ? category === categoryId + ? category === categoryId && searchQuery.length === 0 ? 'white' : 'red' : undefined @@ -388,6 +395,7 @@ export const ActivityLibrary = (props: Props) => {
@@ -400,6 +408,7 @@ export const ActivityLibrary = (props: Props) => {
diff --git a/packages/client/components/ActivityLibrary/ActivityLibraryEmptyState.tsx b/packages/client/components/ActivityLibrary/ActivityLibraryEmptyState.tsx index f68ac23d1fe..26b1e28183b 100644 --- a/packages/client/components/ActivityLibrary/ActivityLibraryEmptyState.tsx +++ b/packages/client/components/ActivityLibrary/ActivityLibraryEmptyState.tsx @@ -16,9 +16,13 @@ const ActivityLibraryEmptyState = (props: Props) => { if (categoryId === 'favorite') { return ( -
-
- Favorite placeholder +
+
+ Favorite placeholder
= style={{ color: 'inherit', display: 'flex', - alignItems: 'center', - fontSize: '18px' + fontSize: '22px' }} /> ), diff --git a/packages/client/mutations/ToggleFavoriteTemplateMutation.ts b/packages/client/mutations/ToggleFavoriteTemplateMutation.ts new file mode 100644 index 00000000000..c052d601311 --- /dev/null +++ b/packages/client/mutations/ToggleFavoriteTemplateMutation.ts @@ -0,0 +1,41 @@ +import graphql from 'babel-plugin-relay/macro' +import {commitMutation} from 'react-relay' +import {ToggleFavoriteTemplateMutation as TToggleFavoriteTemplateMutation} from '../__generated__/ToggleFavoriteTemplateMutation.graphql' +import {StandardMutation} from '../types/relayMutations' + +graphql` + fragment ToggleFavoriteTemplateMutation_viewer on ToggleFavoriteTemplateSuccess { + user { + id + ...ActivityCardFavorite_user + } + } +` + +const mutation = graphql` + mutation ToggleFavoriteTemplateMutation($templateId: ID!) { + toggleFavoriteTemplate(templateId: $templateId) { + ... on ErrorPayload { + error { + message + } + } + ...ToggleFavoriteTemplateMutation_viewer @relay(mask: false) + } + } +` + +const ToggleFavoriteTemplateMutation: StandardMutation = ( + atmosphere, + variables, + {onError, onCompleted} +) => { + return commitMutation(atmosphere, { + mutation, + variables, + onCompleted, + onError + }) +} + +export default ToggleFavoriteTemplateMutation diff --git a/packages/server/database/types/User.ts b/packages/server/database/types/User.ts index 0f5002becae..05431d88791 100644 --- a/packages/server/database/types/User.ts +++ b/packages/server/database/types/User.ts @@ -9,6 +9,7 @@ interface Input { id?: string preferredName: string email: string + favoriteTemplateIds?: string[] featureFlags?: string[] lastSeenAt?: Date lastSeenAtURLs?: string[] @@ -28,6 +29,7 @@ export default class User { id: string preferredName: string email: string + favoriteTemplateIds: string[] featureFlags: string[] lastSeenAt: Date lastSeenAtURLs: string[] | null @@ -55,6 +57,7 @@ export default class User { createdAt, picture, updatedAt, + favoriteTemplateIds, featureFlags, lastSeenAt, lastSeenAtURLs, @@ -73,6 +76,7 @@ export default class User { this.createdAt = createdAt || now this.picture = picture this.updatedAt = updatedAt || now + this.favoriteTemplateIds = favoriteTemplateIds || [] this.featureFlags = featureFlags || [] this.identities = identities || [] this.inactive = inactive || false diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index fb3931b1506..0ceba9e5255 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -842,6 +842,27 @@ export const isCompanyDomain = (parent: RootDataLoader) => { ) } +export const favoriteTemplateIds = (parent: RootDataLoader) => { + return new DataLoader( + async (userIds) => { + const pg = getKysely() + const users = await pg + .selectFrom('User') + .select(['id', 'favoriteTemplateIds']) + .where('id', 'in', userIds) + .execute() + + const userIdToFavoriteTemplateIds = new Map( + users.map((user) => [user.id, user.favoriteTemplateIds]) + ) + return userIds.map((userId) => userIdToFavoriteTemplateIds.get(userId) || []) + }, + { + ...parent.dataLoaderOptions + } + ) +} + export const fileStoreAsset = (parent: RootDataLoader) => { return new DataLoader( async (urls) => { diff --git a/packages/server/graphql/public/mutations/toggleFavoriteTemplate.ts b/packages/server/graphql/public/mutations/toggleFavoriteTemplate.ts new file mode 100644 index 00000000000..31874a1e1b5 --- /dev/null +++ b/packages/server/graphql/public/mutations/toggleFavoriteTemplate.ts @@ -0,0 +1,37 @@ +import getKysely from '../../../postgres/getKysely' +import {getUserId} from '../../../utils/authorization' +import {MutationResolvers} from '../resolverTypes' + +const toggleFavoriteTemplate: MutationResolvers['toggleFavoriteTemplate'] = async ( + _source, + {templateId}, + {authToken, dataLoader} +) => { + const viewerId = getUserId(authToken) + const pg = getKysely() + const userId = getUserId(authToken) + + const favoriteTemplateIds = await dataLoader.get('favoriteTemplateIds').load(viewerId) + + let updatedFavoriteTemplateIds + + const isCurrentlyFavorite = favoriteTemplateIds.includes(templateId) + + if (isCurrentlyFavorite) { + updatedFavoriteTemplateIds = favoriteTemplateIds.filter((id) => id !== templateId) + } else { + updatedFavoriteTemplateIds = [...favoriteTemplateIds, templateId] + } + + await pg + .updateTable('User') + .set({ + favoriteTemplateIds: updatedFavoriteTemplateIds + }) + .where('id', '=', userId) + .execute() + + return true +} + +export default toggleFavoriteTemplate diff --git a/packages/server/graphql/public/typeDefs/User.graphql b/packages/server/graphql/public/typeDefs/User.graphql index bd29eaf1c02..ab75ef73a4c 100644 --- a/packages/server/graphql/public/typeDefs/User.graphql +++ b/packages/server/graphql/public/typeDefs/User.graphql @@ -57,6 +57,11 @@ type User { """ email: Email! + """ + The user's favorite meeting templates + """ + favoriteTemplates: [MeetingTemplate!]! + """ Any super power given to the user via a super user """ diff --git a/packages/server/graphql/public/typeDefs/toggleFavoriteTemplate.graphql b/packages/server/graphql/public/typeDefs/toggleFavoriteTemplate.graphql new file mode 100644 index 00000000000..1e046e2becd --- /dev/null +++ b/packages/server/graphql/public/typeDefs/toggleFavoriteTemplate.graphql @@ -0,0 +1,20 @@ +extend type Mutation { + """ + Add or remove the template to the user's favorite templates + """ + toggleFavoriteTemplate( + """ + The ID of the template to be toggled as a favorite + """ + templateId: ID! + ): ToggleFavoriteTemplatePayload! +} + +union ToggleFavoriteTemplatePayload = ErrorPayload | ToggleFavoriteTemplateSuccess + +type ToggleFavoriteTemplateSuccess { + """ + The user who's favorite templates were updated + """ + user: User! +} diff --git a/packages/server/graphql/public/types/ToggleFavoriteTemplateSuccess.ts b/packages/server/graphql/public/types/ToggleFavoriteTemplateSuccess.ts new file mode 100644 index 00000000000..e722b9192a5 --- /dev/null +++ b/packages/server/graphql/public/types/ToggleFavoriteTemplateSuccess.ts @@ -0,0 +1,14 @@ +import {getUserId} from '../../../utils/authorization' +import {ToggleFavoriteTemplateSuccessResolvers} from '../resolverTypes' + +export type ToggleFavoriteTemplateSuccessSource = Record + +const ToggleFavoriteTemplateSuccess: ToggleFavoriteTemplateSuccessResolvers = { + user: async (_, _args, {dataLoader, authToken}) => { + const userId = getUserId(authToken) + const user = await dataLoader.get('users').loadNonNull(userId) + return user + } +} + +export default ToggleFavoriteTemplateSuccess diff --git a/packages/server/graphql/public/types/User.ts b/packages/server/graphql/public/types/User.ts index 8f9f0a40040..b5951a80541 100644 --- a/packages/server/graphql/public/types/User.ts +++ b/packages/server/graphql/public/types/User.ts @@ -19,6 +19,7 @@ import {getSSOMetadataFromURL} from '../../../utils/getSSOMetadataFromURL' import sendToSentry from '../../../utils/sendToSentry' import standardError from '../../../utils/standardError' import {getStripeManager} from '../../../utils/stripe' +import isValid from '../../isValid' import connectionFromTemplateArray from '../../queries/helpers/connectionFromTemplateArray' import {getFeatureTier} from '../../types/helpers/getFeatureTier' import getSignOnURL from '../mutations/helpers/SAMLHelpers/getSignOnURL' @@ -85,6 +86,9 @@ const User: UserResolvers = { } return request }, + favoriteTemplates: async ({favoriteTemplateIds}, _args, {dataLoader}) => { + return (await dataLoader.get('meetingTemplates').loadMany(favoriteTemplateIds)).filter(isValid) + }, featureFlags: ({featureFlags}) => { return Object.fromEntries(featureFlags.map((flag) => [flag as any, true])) }, diff --git a/packages/server/postgres/migrations/1714598525167_addFavoriteTemplateIds.ts b/packages/server/postgres/migrations/1714598525167_addFavoriteTemplateIds.ts new file mode 100644 index 00000000000..b2a17b04b43 --- /dev/null +++ b/packages/server/postgres/migrations/1714598525167_addFavoriteTemplateIds.ts @@ -0,0 +1,28 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import getPg from '../getPg' + +export async function up() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + await sql` + ALTER TABLE "User" + ADD COLUMN "favoriteTemplateIds" TEXT[] NOT NULL DEFAULT ARRAY[]::TEXT[]; +`.execute(pg) +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + await sql` + ALTER TABLE "User" + DROP COLUMN "favoriteTemplateIds"; +`.execute(pg) +} From ca20d75d86b467eef64c7e08419434a7f4be5946 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 21 May 2024 04:16:55 -0700 Subject: [PATCH 227/529] fix: Send correct websocket status code (#9760) --- packages/client/package.json | 2 +- packages/server/socketHandlers/handleOpen.ts | 7 ++-- yarn.lock | 39 ++++---------------- 3 files changed, 11 insertions(+), 37 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index 232f4240741..711f673ad80 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -68,7 +68,7 @@ "@emotion/styled": "^10.0.27", "@mattkrick/graphql-trebuchet-client": "^2.2.1", "@mattkrick/sanitize-svg": "0.4.0", - "@mattkrick/trebuchet-client": "3.0.2", + "@mattkrick/trebuchet-client": "3.0.1", "@mui/icons-material": "^5.8.4", "@mui/material": "^5.9.2", "@mui/x-date-pickers": "^6.3.1", diff --git a/packages/server/socketHandlers/handleOpen.ts b/packages/server/socketHandlers/handleOpen.ts index f2920fe0ff0..151ac82673f 100644 --- a/packages/server/socketHandlers/handleOpen.ts +++ b/packages/server/socketHandlers/handleOpen.ts @@ -23,13 +23,12 @@ const handleOpen: WebSocketBehavior['open'] = async (socket) => const {authToken, ip, protocol} = socket.getUserData() if (protocol !== 'trebuchet-ws') { sendToSentry(new Error(`WebSocket error: invalid protocol: ${protocol}`)) - // WS Error 1002 is roughly HTTP 412 Precondition Failed because we can't support the req header - socket.end(412, 'Invalid protocol') + socket.end(1002, 'Invalid protocol') return } if (!isAuthenticated(authToken)) { - socket.end(401, TrebuchetCloseReason.EXPIRED_SESSION) + socket.end(1008, TrebuchetCloseReason.EXPIRED_SESSION) return } @@ -37,7 +36,7 @@ const handleOpen: WebSocketBehavior['open'] = async (socket) => const {sub: userId, iat} = authToken const isBlacklistedJWT = await checkBlacklistJWT(userId, iat) if (isBlacklistedJWT) { - socket.end(401, TrebuchetCloseReason.EXPIRED_SESSION) + socket.end(1008, TrebuchetCloseReason.EXPIRED_SESSION) return } diff --git a/yarn.lock b/yarn.lock index e598f18f678..85a16d38734 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4917,10 +4917,10 @@ resolved "https://registry.yarnpkg.com/@mattkrick/sanitize-svg/-/sanitize-svg-0.4.0.tgz#388c29614cf72aa0dd9803c77c9c9d070bd3cd2d" integrity sha512-TnPI97WVAxo8SQcPy8aV3OF9/2WjXB5/+pRNVudIWR7Bhi5ZjtR/ur162So08GkvsvB914AXCW2sxFh1x6KhHA== -"@mattkrick/trebuchet-client@3.0.2": - version "3.0.2" - resolved "https://registry.yarnpkg.com/@mattkrick/trebuchet-client/-/trebuchet-client-3.0.2.tgz#336d687256fb9ac7bb407f656bf2f69f35f817e1" - integrity sha512-JLOx8gd+cGkDeHhdnns0oor2UNv5uRZ8A/Jfw3NhQcVqQe6R+x9xIHW29M2T78TDHUWqItTgntX+cdDLqVjVvg== +"@mattkrick/trebuchet-client@3.0.1": + version "3.0.1" + resolved "https://registry.yarnpkg.com/@mattkrick/trebuchet-client/-/trebuchet-client-3.0.1.tgz#3fc49f7858652a55dca92cb0c14c0313df931619" + integrity sha512-5uHCldCqmVntoyujTzRfmGtjld8k4JuBdFN0SvhvRFf83FPaNeoit6Mh8VgrcxxxKujKeYCQDMQH6LWwpsshgQ== dependencies: "@mattkrick/fast-rtc-peer" "^0.4.1" eventemitter3 "^4.0.7" @@ -20248,7 +20248,7 @@ string-similarity@^3.0.0: resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-3.0.0.tgz#07b0bc69fae200ad88ceef4983878d03793847c7" integrity sha512-7kS7LyTp56OqOI2BDWQNVnLX/rCxIQn+/5M0op1WV6P8Xx6TZNdajpuqQdiJ7Xx+p1C5CsWMvdiBp9ApMhxzEQ== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -20266,15 +20266,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.0.tgz#5ab00980cfb29f43e736b113a120a73a0fb569d3" @@ -20351,7 +20342,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -20365,13 +20356,6 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -22224,7 +22208,7 @@ workbox-window@6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.5.4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -22242,15 +22226,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From a39cd41baa3a696600af5299b281fbb2729fa7af Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 21 May 2024 04:18:10 -0700 Subject: [PATCH 228/529] fix: Update remove user from org copy (#9759) --- .../components/RemoveFromOrgModal/RemoveFromOrgModal.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/client/modules/userDashboard/components/RemoveFromOrgModal/RemoveFromOrgModal.tsx b/packages/client/modules/userDashboard/components/RemoveFromOrgModal/RemoveFromOrgModal.tsx index fe04cba1304..5d8a079bba8 100644 --- a/packages/client/modules/userDashboard/components/RemoveFromOrgModal/RemoveFromOrgModal.tsx +++ b/packages/client/modules/userDashboard/components/RemoveFromOrgModal/RemoveFromOrgModal.tsx @@ -37,9 +37,7 @@ const RemoveFromOrgModal = (props: Props) => { {'Are you sure?'} - {`This will remove ${preferredName} from the organization. Any outstanding tasks will be given - to the team leads. Any time remaining on their subscription will be refunded on the next - invoice.`} + {`This will remove ${preferredName} from all teams within the organization. Any outstanding tasks will be given to the respective team leads.`} From 7a3c567907c3704779195148f2210a149ac24fe2 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 21 May 2024 13:38:31 +0200 Subject: [PATCH 229/529] chore(release): release v7.32.0 (#9747) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 25 +++++++++++++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 37 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3a9c391c003..3f880395abc 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.31.0" + ".": "7.32.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index a962a8a3916..b2086a50df0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,31 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.32.0](https://github.com/ParabolInc/parabol/compare/v7.31.0...v7.32.0) (2024-05-21) + + +### Added + +* add favorite activities UI to activity library ([#9680](https://github.com/ParabolInc/parabol/issues/9680)) ([d6a775d](https://github.com/ParabolInc/parabol/commit/d6a775d4e8f5383588938e163b1e44025afa6624)) +* add logic that lets users favorite a template ([#9713](https://github.com/ParabolInc/parabol/issues/9713)) ([4558e14](https://github.com/ParabolInc/parabol/commit/4558e143c98c2ceab197b7c00670cc56ae7c6436)) +* saml upload ([#9750](https://github.com/ParabolInc/parabol/issues/9750)) ([5c40fcf](https://github.com/ParabolInc/parabol/commit/5c40fcfb1d6df9ac32c2f2277735169f0e1ae95d)) +* **single-tenant-host:** Embedder and Text Embeddings Inference added to the stack ([#9753](https://github.com/ParabolInc/parabol/issues/9753)) ([5ec8f45](https://github.com/ParabolInc/parabol/commit/5ec8f457f44780036da79b54136f3c68c5bb052c)) + + +### Fixed + +* close websocket with reason on invalid token ([#9744](https://github.com/ParabolInc/parabol/issues/9744)) ([a5d4bad](https://github.com/ParabolInc/parabol/commit/a5d4badf63781ddf9023fcc169d4b90f0f9d646f)) +* **dev-stack:** update text-embeddings-inference to 1.2.2 ([#9754](https://github.com/ParabolInc/parabol/issues/9754)) ([1c8fa84](https://github.com/ParabolInc/parabol/commit/1c8fa84444dc361d6bb8938d55accad31be2b6e7)) +* fix the issue where a successful upgrade won't refresh the billing page ([#9740](https://github.com/ParabolInc/parabol/issues/9740)) ([9a904d3](https://github.com/ParabolInc/parabol/commit/9a904d3a35b0f82ffd67d6e7d4853b40cfc4f234)) +* Send correct websocket status code ([#9760](https://github.com/ParabolInc/parabol/issues/9760)) ([ca20d75](https://github.com/ParabolInc/parabol/commit/ca20d75d86b467eef64c7e08419434a7f4be5946)) +* Update remove user from org copy ([#9759](https://github.com/ParabolInc/parabol/issues/9759)) ([a39cd41](https://github.com/ParabolInc/parabol/commit/a39cd41baa3a696600af5299b281fbb2729fa7af)) + + +### Changed + +* Trace RRule ([#9756](https://github.com/ParabolInc/parabol/issues/9756)) ([341772a](https://github.com/ParabolInc/parabol/commit/341772af9da5923e7c22b8a253aaca0b2aeab7c5)) +* update tutorial card thumbnail & video links ([#9746](https://github.com/ParabolInc/parabol/issues/9746)) ([28c7432](https://github.com/ParabolInc/parabol/commit/28c743274df3c8ed97e3e8dbe2677a58483a851e)) + ## [7.31.0](https://github.com/ParabolInc/parabol/compare/v7.30.4...v7.31.0) (2024-05-08) diff --git a/package.json b/package.json index a2fc3dbc5ed..36d6e1a0270 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.31.0", + "version": "7.32.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 3edb0647598..22ff9e859d0 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.31.0", + "version": "7.32.0", "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.31.0" + "parabol-server": "7.32.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 711f673ad80..c9f898a4047 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.31.0", + "version": "7.32.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 7ff5e3462f9..46eda1dbb28 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.31.0", + "version": "7.32.0", "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 2b4ac934549..52fb5f2bb8b 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.31.0", + "version": "7.32.0", "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.31.0", - "parabol-server": "7.31.0", + "parabol-client": "7.32.0", + "parabol-server": "7.32.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index da23b924d9f..d7aa0dcbc00 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.31.0", + "version": "7.32.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 6b7a62a7fc4..2050704c083 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.31.0", + "version": "7.32.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.31.0", + "parabol-client": "7.32.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 8fc0ec11f8e45033c5291feef59d298b36dbfc7c Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Wed, 22 May 2024 14:16:11 +0200 Subject: [PATCH 230/529] fix: Revert @aws-sdk/client-s3 upgrade (#9763) --- packages/server/package.json | 2 +- yarn.lock | 371 ++++++++++++++++++++--------------- 2 files changed, 218 insertions(+), 155 deletions(-) diff --git a/packages/server/package.json b/packages/server/package.json index 2050704c083..606260905d0 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -75,7 +75,7 @@ }, "dependencies": { "@amplitude/analytics-node": "^1.3.2", - "@aws-sdk/client-s3": "3.556.0", + "@aws-sdk/client-s3": "3.537.0", "@aws-sdk/s3-request-presigner": "^3.565.0", "@dicebear/core": "^8.0.1", "@dicebear/initials": "^8.0.1", diff --git a/yarn.lock b/yarn.lock index 85a16d38734..bb7170ab1de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -431,17 +431,17 @@ "@smithy/util-waiter" "^2.2.0" tslib "^2.6.2" -"@aws-sdk/client-s3@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.556.0.tgz#b32c12e7857326072df7c53c40b36bdf5d8e6169" - integrity sha512-6WF9Kuzz1/8zqX8hKBpqj9+FYwQ5uTsVcOKpTW94AMX2qtIeVRlwlnNnYyywWo61yqD3g59CMNHcqSsaqAwglg== +"@aws-sdk/client-s3@3.537.0": + version "3.537.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-s3/-/client-s3-3.537.0.tgz#887b2c743da49378104054b66aa135fae805263c" + integrity sha512-EMPN2toHz1QtSiDeLKS1zrazh+8J0g1Y5t5lCq25iTXqCSV9vB2jCKwG5+OB6L5tAKkwyl1uZofeWLmdFkztEg== dependencies: "@aws-crypto/sha1-browser" "3.0.0" "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.556.0" - "@aws-sdk/core" "3.556.0" - "@aws-sdk/credential-provider-node" "3.556.0" + "@aws-sdk/client-sts" "3.535.0" + "@aws-sdk/core" "3.535.0" + "@aws-sdk/credential-provider-node" "3.535.0" "@aws-sdk/middleware-bucket-endpoint" "3.535.0" "@aws-sdk/middleware-expect-continue" "3.535.0" "@aws-sdk/middleware-flexible-checksums" "3.535.0" @@ -449,19 +449,19 @@ "@aws-sdk/middleware-location-constraint" "3.535.0" "@aws-sdk/middleware-logger" "3.535.0" "@aws-sdk/middleware-recursion-detection" "3.535.0" - "@aws-sdk/middleware-sdk-s3" "3.556.0" - "@aws-sdk/middleware-signing" "3.556.0" + "@aws-sdk/middleware-sdk-s3" "3.535.0" + "@aws-sdk/middleware-signing" "3.535.0" "@aws-sdk/middleware-ssec" "3.537.0" - "@aws-sdk/middleware-user-agent" "3.540.0" + "@aws-sdk/middleware-user-agent" "3.535.0" "@aws-sdk/region-config-resolver" "3.535.0" - "@aws-sdk/signature-v4-multi-region" "3.556.0" + "@aws-sdk/signature-v4-multi-region" "3.535.0" "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-endpoints" "3.540.0" + "@aws-sdk/util-endpoints" "3.535.0" "@aws-sdk/util-user-agent-browser" "3.535.0" "@aws-sdk/util-user-agent-node" "3.535.0" "@aws-sdk/xml-builder" "3.535.0" "@smithy/config-resolver" "^2.2.0" - "@smithy/core" "^1.4.2" + "@smithy/core" "^1.4.0" "@smithy/eventstream-serde-browser" "^2.2.0" "@smithy/eventstream-serde-config-resolver" "^2.2.0" "@smithy/eventstream-serde-node" "^2.2.0" @@ -472,21 +472,21 @@ "@smithy/invalid-dependency" "^2.2.0" "@smithy/md5-js" "^2.2.0" "@smithy/middleware-content-length" "^2.2.0" - "@smithy/middleware-endpoint" "^2.5.1" - "@smithy/middleware-retry" "^2.3.1" + "@smithy/middleware-endpoint" "^2.5.0" + "@smithy/middleware-retry" "^2.2.0" "@smithy/middleware-serde" "^2.3.0" "@smithy/middleware-stack" "^2.2.0" "@smithy/node-config-provider" "^2.3.0" "@smithy/node-http-handler" "^2.5.0" "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.1" + "@smithy/smithy-client" "^2.5.0" "@smithy/types" "^2.12.0" "@smithy/url-parser" "^2.2.0" "@smithy/util-base64" "^2.3.0" "@smithy/util-body-length-browser" "^2.2.0" "@smithy/util-body-length-node" "^2.3.0" - "@smithy/util-defaults-mode-browser" "^2.2.1" - "@smithy/util-defaults-mode-node" "^2.3.1" + "@smithy/util-defaults-mode-browser" "^2.2.0" + "@smithy/util-defaults-mode-node" "^2.3.0" "@smithy/util-endpoints" "^1.2.0" "@smithy/util-retry" "^2.2.0" "@smithy/util-stream" "^2.2.0" @@ -541,60 +541,60 @@ tslib "^2.6.2" uuid "^9.0.1" -"@aws-sdk/client-sso-oidc@3.552.0": - version "3.552.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.552.0.tgz#3215792bbce40a4373d6fca711e4b58fbf794284" - integrity sha512-6JYTgN/n4xTm3Z+JhEZq06pyYsgo7heYDmR+0smmauQS02Eu8lvUc2jPs/0GDAmty7J4tq3gS6TRwvf7181C2w== +"@aws-sdk/client-sso-oidc@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.535.0.tgz#64666c2f7bed8510938ba2b481429fea8f97473d" + integrity sha512-M2cG4EQXDpAJQyq33ORIr6abmdX9p9zX0ssVy8XwFNB7lrgoIKxuVoGL+fX+XMgecl24x7ELz6b4QlILOevbCw== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.552.0" - "@aws-sdk/core" "3.552.0" + "@aws-sdk/client-sts" "3.535.0" + "@aws-sdk/core" "3.535.0" "@aws-sdk/middleware-host-header" "3.535.0" "@aws-sdk/middleware-logger" "3.535.0" "@aws-sdk/middleware-recursion-detection" "3.535.0" - "@aws-sdk/middleware-user-agent" "3.540.0" + "@aws-sdk/middleware-user-agent" "3.535.0" "@aws-sdk/region-config-resolver" "3.535.0" "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-endpoints" "3.540.0" + "@aws-sdk/util-endpoints" "3.535.0" "@aws-sdk/util-user-agent-browser" "3.535.0" "@aws-sdk/util-user-agent-node" "3.535.0" "@smithy/config-resolver" "^2.2.0" - "@smithy/core" "^1.4.2" + "@smithy/core" "^1.4.0" "@smithy/fetch-http-handler" "^2.5.0" "@smithy/hash-node" "^2.2.0" "@smithy/invalid-dependency" "^2.2.0" "@smithy/middleware-content-length" "^2.2.0" - "@smithy/middleware-endpoint" "^2.5.1" - "@smithy/middleware-retry" "^2.3.1" + "@smithy/middleware-endpoint" "^2.5.0" + "@smithy/middleware-retry" "^2.2.0" "@smithy/middleware-serde" "^2.3.0" "@smithy/middleware-stack" "^2.2.0" "@smithy/node-config-provider" "^2.3.0" "@smithy/node-http-handler" "^2.5.0" "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.1" + "@smithy/smithy-client" "^2.5.0" "@smithy/types" "^2.12.0" "@smithy/url-parser" "^2.2.0" "@smithy/util-base64" "^2.3.0" "@smithy/util-body-length-browser" "^2.2.0" "@smithy/util-body-length-node" "^2.3.0" - "@smithy/util-defaults-mode-browser" "^2.2.1" - "@smithy/util-defaults-mode-node" "^2.3.1" + "@smithy/util-defaults-mode-browser" "^2.2.0" + "@smithy/util-defaults-mode-node" "^2.3.0" "@smithy/util-endpoints" "^1.2.0" "@smithy/util-middleware" "^2.2.0" "@smithy/util-retry" "^2.2.0" "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@aws-sdk/client-sso-oidc@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.556.0.tgz#4c19fccc35361de046d2cd74a7a685d71aa5dd1e" - integrity sha512-AXKd2TB6nNrksu+OfmHl8uI07PdgzOo4o8AxoRO8SHlwoMAGvcT9optDGVSYoVfgOKTymCoE7h8/UoUfPc11wQ== +"@aws-sdk/client-sso-oidc@3.552.0": + version "3.552.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso-oidc/-/client-sso-oidc-3.552.0.tgz#3215792bbce40a4373d6fca711e4b58fbf794284" + integrity sha512-6JYTgN/n4xTm3Z+JhEZq06pyYsgo7heYDmR+0smmauQS02Eu8lvUc2jPs/0GDAmty7J4tq3gS6TRwvf7181C2w== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/client-sts" "3.556.0" - "@aws-sdk/core" "3.556.0" + "@aws-sdk/client-sts" "3.552.0" + "@aws-sdk/core" "3.552.0" "@aws-sdk/middleware-host-header" "3.535.0" "@aws-sdk/middleware-logger" "3.535.0" "@aws-sdk/middleware-recursion-detection" "3.535.0" @@ -631,58 +631,58 @@ "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@aws-sdk/client-sso@3.552.0": - version "3.552.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.552.0.tgz#dea1533cc74e80f9bb49f8926c21912497a08616" - integrity sha512-IAjRj5gcuyoPe/OhciMY/UyW8C1kyXSUJFagxvbeSv8q0mEfaPBVjGgz2xSYRFhhZr3gFlGCS9SiukwOL2/VoA== +"@aws-sdk/client-sso@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.535.0.tgz#c405aaf880cb695aa2f5070a8827955274fc9df2" + integrity sha512-h9eQRdFnjDRVBnPJIKXuX7D+isSAioIfZPC4PQwsL5BscTRlk4c90DX0R0uk64YUtp7LZu8TNtrosFZ/1HtTrQ== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/core" "3.552.0" + "@aws-sdk/core" "3.535.0" "@aws-sdk/middleware-host-header" "3.535.0" "@aws-sdk/middleware-logger" "3.535.0" "@aws-sdk/middleware-recursion-detection" "3.535.0" - "@aws-sdk/middleware-user-agent" "3.540.0" + "@aws-sdk/middleware-user-agent" "3.535.0" "@aws-sdk/region-config-resolver" "3.535.0" "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-endpoints" "3.540.0" + "@aws-sdk/util-endpoints" "3.535.0" "@aws-sdk/util-user-agent-browser" "3.535.0" "@aws-sdk/util-user-agent-node" "3.535.0" "@smithy/config-resolver" "^2.2.0" - "@smithy/core" "^1.4.2" + "@smithy/core" "^1.4.0" "@smithy/fetch-http-handler" "^2.5.0" "@smithy/hash-node" "^2.2.0" "@smithy/invalid-dependency" "^2.2.0" "@smithy/middleware-content-length" "^2.2.0" - "@smithy/middleware-endpoint" "^2.5.1" - "@smithy/middleware-retry" "^2.3.1" + "@smithy/middleware-endpoint" "^2.5.0" + "@smithy/middleware-retry" "^2.2.0" "@smithy/middleware-serde" "^2.3.0" "@smithy/middleware-stack" "^2.2.0" "@smithy/node-config-provider" "^2.3.0" "@smithy/node-http-handler" "^2.5.0" "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.1" + "@smithy/smithy-client" "^2.5.0" "@smithy/types" "^2.12.0" "@smithy/url-parser" "^2.2.0" "@smithy/util-base64" "^2.3.0" "@smithy/util-body-length-browser" "^2.2.0" "@smithy/util-body-length-node" "^2.3.0" - "@smithy/util-defaults-mode-browser" "^2.2.1" - "@smithy/util-defaults-mode-node" "^2.3.1" + "@smithy/util-defaults-mode-browser" "^2.2.0" + "@smithy/util-defaults-mode-node" "^2.3.0" "@smithy/util-endpoints" "^1.2.0" "@smithy/util-middleware" "^2.2.0" "@smithy/util-retry" "^2.2.0" "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@aws-sdk/client-sso@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.556.0.tgz#7beeeebb6a437f09680edefc5c998822292a528a" - integrity sha512-unXdWS7uvHqCcOyC1de+Fr8m3F2vMg2m24GPea0bg7rVGTYmiyn9mhUX11VCt+ozydrw+F50FQwL6OqoqPocmw== +"@aws-sdk/client-sso@3.552.0": + version "3.552.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sso/-/client-sso-3.552.0.tgz#dea1533cc74e80f9bb49f8926c21912497a08616" + integrity sha512-IAjRj5gcuyoPe/OhciMY/UyW8C1kyXSUJFagxvbeSv8q0mEfaPBVjGgz2xSYRFhhZr3gFlGCS9SiukwOL2/VoA== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/core" "3.556.0" + "@aws-sdk/core" "3.552.0" "@aws-sdk/middleware-host-header" "3.535.0" "@aws-sdk/middleware-logger" "3.535.0" "@aws-sdk/middleware-recursion-detection" "3.535.0" @@ -719,58 +719,58 @@ "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@aws-sdk/client-sts@3.552.0": - version "3.552.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.552.0.tgz#ae6879022644348596e822e80accb468676a2005" - integrity sha512-rOZlAj8GyFgUBESyKezes67A8Kj5+KjRhfBHMXrkcM5h9UOIz5q7QdkSQOmzWwRoPDmmAqb6t+y041/76TnPEg== +"@aws-sdk/client-sts@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.535.0.tgz#0f518fe338c6b7a8b8a897e2ccee65d06dc0040f" + integrity sha512-ii9OOm3TJwP3JmO1IVJXKWIShVKPl0VtdlgROc/SkDglO/kuAw9eDdlROgc+qbFl+gm6bBTguOVTUXt3tS3flw== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/core" "3.552.0" + "@aws-sdk/core" "3.535.0" "@aws-sdk/middleware-host-header" "3.535.0" "@aws-sdk/middleware-logger" "3.535.0" "@aws-sdk/middleware-recursion-detection" "3.535.0" - "@aws-sdk/middleware-user-agent" "3.540.0" + "@aws-sdk/middleware-user-agent" "3.535.0" "@aws-sdk/region-config-resolver" "3.535.0" "@aws-sdk/types" "3.535.0" - "@aws-sdk/util-endpoints" "3.540.0" + "@aws-sdk/util-endpoints" "3.535.0" "@aws-sdk/util-user-agent-browser" "3.535.0" "@aws-sdk/util-user-agent-node" "3.535.0" "@smithy/config-resolver" "^2.2.0" - "@smithy/core" "^1.4.2" + "@smithy/core" "^1.4.0" "@smithy/fetch-http-handler" "^2.5.0" "@smithy/hash-node" "^2.2.0" "@smithy/invalid-dependency" "^2.2.0" "@smithy/middleware-content-length" "^2.2.0" - "@smithy/middleware-endpoint" "^2.5.1" - "@smithy/middleware-retry" "^2.3.1" + "@smithy/middleware-endpoint" "^2.5.0" + "@smithy/middleware-retry" "^2.2.0" "@smithy/middleware-serde" "^2.3.0" "@smithy/middleware-stack" "^2.2.0" "@smithy/node-config-provider" "^2.3.0" "@smithy/node-http-handler" "^2.5.0" "@smithy/protocol-http" "^3.3.0" - "@smithy/smithy-client" "^2.5.1" + "@smithy/smithy-client" "^2.5.0" "@smithy/types" "^2.12.0" "@smithy/url-parser" "^2.2.0" "@smithy/util-base64" "^2.3.0" "@smithy/util-body-length-browser" "^2.2.0" "@smithy/util-body-length-node" "^2.3.0" - "@smithy/util-defaults-mode-browser" "^2.2.1" - "@smithy/util-defaults-mode-node" "^2.3.1" + "@smithy/util-defaults-mode-browser" "^2.2.0" + "@smithy/util-defaults-mode-node" "^2.3.0" "@smithy/util-endpoints" "^1.2.0" "@smithy/util-middleware" "^2.2.0" "@smithy/util-retry" "^2.2.0" "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@aws-sdk/client-sts@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.556.0.tgz#3aa20cca462839f1451f11efada2be119dd36a6b" - integrity sha512-TsK3js7Suh9xEmC886aY+bv0KdLLYtzrcmVt6sJ/W6EnDXYQhBuKYFhp03NrN2+vSvMGpqJwR62DyfKe1G0QzQ== +"@aws-sdk/client-sts@3.552.0": + version "3.552.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/client-sts/-/client-sts-3.552.0.tgz#ae6879022644348596e822e80accb468676a2005" + integrity sha512-rOZlAj8GyFgUBESyKezes67A8Kj5+KjRhfBHMXrkcM5h9UOIz5q7QdkSQOmzWwRoPDmmAqb6t+y041/76TnPEg== dependencies: "@aws-crypto/sha256-browser" "3.0.0" "@aws-crypto/sha256-js" "3.0.0" - "@aws-sdk/core" "3.556.0" + "@aws-sdk/core" "3.552.0" "@aws-sdk/middleware-host-header" "3.535.0" "@aws-sdk/middleware-logger" "3.535.0" "@aws-sdk/middleware-recursion-detection" "3.535.0" @@ -807,27 +807,27 @@ "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@aws-sdk/core@3.552.0", "@aws-sdk/core@^3.535.0": - version "3.552.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.552.0.tgz#7f744d7cd303d1fa60006d81f75a6f999b64bfb0" - integrity sha512-T7ovljf6fCvIHG9SOSZqGmbVbqZPXPywLAcU+onk/fYLZJj6kjfzKZzSAUBI0nO1OKpuP/nCHaCp51NLWNqsnw== +"@aws-sdk/core@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.535.0.tgz#f3a726c297cea9634d19a1db4e958c918c506c8b" + integrity sha512-+Yusa9HziuaEDta1UaLEtMAtmgvxdxhPn7jgfRY6PplqAqgsfa5FR83sxy5qr2q7xjQTwHtV4MjQVuOjG9JsLw== dependencies: - "@smithy/core" "^1.4.2" + "@smithy/core" "^1.4.0" "@smithy/protocol-http" "^3.3.0" - "@smithy/signature-v4" "^2.2.1" - "@smithy/smithy-client" "^2.5.1" + "@smithy/signature-v4" "^2.2.0" + "@smithy/smithy-client" "^2.5.0" "@smithy/types" "^2.12.0" fast-xml-parser "4.2.5" tslib "^2.6.2" -"@aws-sdk/core@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.556.0.tgz#d0f4431a72282b71cfbcaedfb803f7f2807cf60b" - integrity sha512-vJaSaHw2kPQlo11j/Rzuz0gk1tEaKdz+2ser0f0qZ5vwFlANjt08m/frU17ctnVKC1s58bxpctO/1P894fHLrA== +"@aws-sdk/core@3.552.0", "@aws-sdk/core@^3.535.0": + version "3.552.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/core/-/core-3.552.0.tgz#7f744d7cd303d1fa60006d81f75a6f999b64bfb0" + integrity sha512-T7ovljf6fCvIHG9SOSZqGmbVbqZPXPywLAcU+onk/fYLZJj6kjfzKZzSAUBI0nO1OKpuP/nCHaCp51NLWNqsnw== dependencies: "@smithy/core" "^1.4.2" "@smithy/protocol-http" "^3.3.0" - "@smithy/signature-v4" "^2.3.0" + "@smithy/signature-v4" "^2.2.1" "@smithy/smithy-client" "^2.5.1" "@smithy/types" "^2.12.0" fast-xml-parser "4.2.5" @@ -854,6 +854,21 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" +"@aws-sdk/credential-provider-http@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.535.0.tgz#0a42f6b1a61d927bbce9f4afd25112f486bd05da" + integrity sha512-kdj1wCmOMZ29jSlUskRqN04S6fJ4dvt0Nq9Z32SA6wO7UG8ht6Ot9h/au/eTWJM3E1somZ7D771oK7dQt9b8yw== + dependencies: + "@aws-sdk/types" "3.535.0" + "@smithy/fetch-http-handler" "^2.5.0" + "@smithy/node-http-handler" "^2.5.0" + "@smithy/property-provider" "^2.2.0" + "@smithy/protocol-http" "^3.3.0" + "@smithy/smithy-client" "^2.5.0" + "@smithy/types" "^2.12.0" + "@smithy/util-stream" "^2.2.0" + tslib "^2.6.2" + "@aws-sdk/credential-provider-http@3.552.0": version "3.552.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-http/-/credential-provider-http-3.552.0.tgz#ecc88d02cba95621887e6b85b2583e756ad29eb6" @@ -869,6 +884,23 @@ "@smithy/util-stream" "^2.2.0" tslib "^2.6.2" +"@aws-sdk/credential-provider-ini@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.535.0.tgz#b121b1aba2916e3f45745cd690b4082421a7c286" + integrity sha512-bm3XOYlyCjtAb8eeHXLrxqRxYVRw2Iqv9IufdJb4gM13TbNSYniUT1WKaHxGIZ5p+FuNlXVhvk1OpHFM13+gXA== + dependencies: + "@aws-sdk/client-sts" "3.535.0" + "@aws-sdk/credential-provider-env" "3.535.0" + "@aws-sdk/credential-provider-process" "3.535.0" + "@aws-sdk/credential-provider-sso" "3.535.0" + "@aws-sdk/credential-provider-web-identity" "3.535.0" + "@aws-sdk/types" "3.535.0" + "@smithy/credential-provider-imds" "^2.3.0" + "@smithy/property-provider" "^2.2.0" + "@smithy/shared-ini-file-loader" "^2.4.0" + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@aws-sdk/credential-provider-ini@3.552.0", "@aws-sdk/credential-provider-ini@^3.535.0": version "3.552.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.552.0.tgz#436f328ea0213efe3231354248ab0d82dade4345" @@ -886,16 +918,17 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-ini@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-ini/-/credential-provider-ini-3.556.0.tgz#bf780feb92a7920cc525cd7cb7870ea61b84c125" - integrity sha512-0Nz4ErOlXhe3muxWYMbPwRMgfKmVbBp36BAE2uv/z5wTbfdBkcgUwaflEvlKCLUTdHzuZsQk+BFS/gVyaUeOuA== +"@aws-sdk/credential-provider-node@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.535.0.tgz#6739b4b52a9cce29dc8e70c9a7290b89cdc4b904" + integrity sha512-6JXp/EuL6euUkH5k4d+lQFF6gBwukrcCOWfNHCmq14mNJf/cqT3HAX1VMtWFRSK20am0IxfYQGccb0/nZykdKg== dependencies: - "@aws-sdk/client-sts" "3.556.0" "@aws-sdk/credential-provider-env" "3.535.0" + "@aws-sdk/credential-provider-http" "3.535.0" + "@aws-sdk/credential-provider-ini" "3.535.0" "@aws-sdk/credential-provider-process" "3.535.0" - "@aws-sdk/credential-provider-sso" "3.556.0" - "@aws-sdk/credential-provider-web-identity" "3.556.0" + "@aws-sdk/credential-provider-sso" "3.535.0" + "@aws-sdk/credential-provider-web-identity" "3.535.0" "@aws-sdk/types" "3.535.0" "@smithy/credential-provider-imds" "^2.3.0" "@smithy/property-provider" "^2.2.0" @@ -921,29 +954,24 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-node@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-node/-/credential-provider-node-3.556.0.tgz#51f3dc4506053249f8593765d1ab2cef53732fa3" - integrity sha512-s1xVtKjyGc60O8qcNIzS1X3H+pWEwEfZ7TgNznVDNyuXvLrlNWiAcigPWGl2aAkc8tGcsSG0Qpyw2KYC939LFg== +"@aws-sdk/credential-provider-process@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.535.0.tgz#ea1e8a38a32e36bbdc3f75eb03352e6eafa0c659" + integrity sha512-9O1OaprGCnlb/kYl8RwmH7Mlg8JREZctB8r9sa1KhSsWFq/SWO0AuJTyowxD7zL5PkeS4eTvzFFHWCa3OO5epA== dependencies: - "@aws-sdk/credential-provider-env" "3.535.0" - "@aws-sdk/credential-provider-http" "3.552.0" - "@aws-sdk/credential-provider-ini" "3.556.0" - "@aws-sdk/credential-provider-process" "3.535.0" - "@aws-sdk/credential-provider-sso" "3.556.0" - "@aws-sdk/credential-provider-web-identity" "3.556.0" "@aws-sdk/types" "3.535.0" - "@smithy/credential-provider-imds" "^2.3.0" "@smithy/property-provider" "^2.2.0" "@smithy/shared-ini-file-loader" "^2.4.0" "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-process@3.535.0": +"@aws-sdk/credential-provider-sso@3.535.0": version "3.535.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-process/-/credential-provider-process-3.535.0.tgz#ea1e8a38a32e36bbdc3f75eb03352e6eafa0c659" - integrity sha512-9O1OaprGCnlb/kYl8RwmH7Mlg8JREZctB8r9sa1KhSsWFq/SWO0AuJTyowxD7zL5PkeS4eTvzFFHWCa3OO5epA== + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.535.0.tgz#dfc7c2f39f9ca965becd7e5b9414cd1bb2217490" + integrity sha512-2Dw0YIr8ETdFpq65CC4zK8ZIEbX78rXoNRZXUGNQW3oSKfL0tj8O8ErY6kg1IdEnYbGnEQ35q6luZ5GGNKLgDg== dependencies: + "@aws-sdk/client-sso" "3.535.0" + "@aws-sdk/token-providers" "3.535.0" "@aws-sdk/types" "3.535.0" "@smithy/property-provider" "^2.2.0" "@smithy/shared-ini-file-loader" "^2.4.0" @@ -963,16 +991,14 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-sso@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-sso/-/credential-provider-sso-3.556.0.tgz#26dfdd2c6e034f66e82985d65bd6aa3ae09d5e19" - integrity sha512-ETuBgcnpfxqadEAqhQFWpKoV1C/NAgvs5CbBc5EJbelJ8f4prTdErIHjrRtVT8c02MXj92QwczsiNYd5IoOqyw== +"@aws-sdk/credential-provider-web-identity@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.535.0.tgz#f1d3a72ff958cbd7e052c5109755379745ac35e0" + integrity sha512-t2/JWrKY0H66A7JW7CqX06/DG2YkJddikt5ymdQvx/Q7dRMJ3d+o/vgjoKr7RvEx/pNruCeyM1599HCvwrVMrg== dependencies: - "@aws-sdk/client-sso" "3.556.0" - "@aws-sdk/token-providers" "3.556.0" + "@aws-sdk/client-sts" "3.535.0" "@aws-sdk/types" "3.535.0" "@smithy/property-provider" "^2.2.0" - "@smithy/shared-ini-file-loader" "^2.4.0" "@smithy/types" "^2.12.0" tslib "^2.6.2" @@ -987,17 +1013,6 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/credential-provider-web-identity@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/credential-provider-web-identity/-/credential-provider-web-identity-3.556.0.tgz#94cd55eaee6ca96354237569102dfaf6774544f4" - integrity sha512-R/YAL8Uh8i+dzVjzMnbcWLIGeeRi2mioHVGnVF+minmaIkCiQMZg2HPrdlKm49El+RljT28Nl5YHRuiqzEIwMA== - dependencies: - "@aws-sdk/client-sts" "3.556.0" - "@aws-sdk/types" "3.535.0" - "@smithy/property-provider" "^2.2.0" - "@smithy/types" "^2.12.0" - tslib "^2.6.2" - "@aws-sdk/credential-providers@^3.535.0": version "3.552.0" resolved "https://registry.yarnpkg.com/@aws-sdk/credential-providers/-/credential-providers-3.552.0.tgz#cda713016c555a87dafad8b20bb0c881b4e5469c" @@ -1095,6 +1110,21 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" +"@aws-sdk/middleware-sdk-s3@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.535.0.tgz#3cb76342d91a5e0e94d9a380dbaba9a9ee4849e0" + integrity sha512-/dLG/E3af6ohxkQ5GBHT8tZfuPIg6eItKxCXuulvYj0Tqgf3Mb+xTsvSkxQsJF06RS4sH7Qsg/PnB8ZfrJrXpg== + dependencies: + "@aws-sdk/types" "3.535.0" + "@aws-sdk/util-arn-parser" "3.535.0" + "@smithy/node-config-provider" "^2.3.0" + "@smithy/protocol-http" "^3.3.0" + "@smithy/signature-v4" "^2.2.0" + "@smithy/smithy-client" "^2.5.0" + "@smithy/types" "^2.12.0" + "@smithy/util-config-provider" "^2.3.0" + tslib "^2.6.2" + "@aws-sdk/middleware-sdk-s3@3.556.0": version "3.556.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-sdk-s3/-/middleware-sdk-s3-3.556.0.tgz#ff135d1fbfc843a93860eb3a4000da9d721442c0" @@ -1110,15 +1140,15 @@ "@smithy/util-config-provider" "^2.3.0" tslib "^2.6.2" -"@aws-sdk/middleware-signing@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.556.0.tgz#2892d76cddf3cb956122618588d163ff7a42c43f" - integrity sha512-kWrPmU8qd3gI5qzpuW9LtWFaH80cOz1ZJDavXx6PRpYZJ5JaKdUHghwfDlVTzzFYAeJmVsWIkPcLT5d5mY5ZTQ== +"@aws-sdk/middleware-signing@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-signing/-/middleware-signing-3.535.0.tgz#cf98354e6d48e275689db6a4a513f62bd1555518" + integrity sha512-Rb4sfus1Gc5paRl9JJgymJGsb/i3gJKK/rTuFZICdd1PBBE5osIOHP5CpzWYBtc5LlyZE1a2QoxPMCyG+QUGPw== dependencies: "@aws-sdk/types" "3.535.0" "@smithy/property-provider" "^2.2.0" "@smithy/protocol-http" "^3.3.0" - "@smithy/signature-v4" "^2.3.0" + "@smithy/signature-v4" "^2.2.0" "@smithy/types" "^2.12.0" "@smithy/util-middleware" "^2.2.0" tslib "^2.6.2" @@ -1132,6 +1162,17 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" +"@aws-sdk/middleware-user-agent@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.535.0.tgz#2877ff5e42d943dd0c488e8b1ad82bd9da121227" + integrity sha512-Uvb2WJ+zdHdCOtsWVPI/M0BcfNrjOYsicDZWtaljucRJKLclY5gNWwD+RwIC+8b5TvfnVOlH+N5jhvpi5Impog== + dependencies: + "@aws-sdk/types" "3.535.0" + "@aws-sdk/util-endpoints" "3.535.0" + "@smithy/protocol-http" "^3.3.0" + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@aws-sdk/middleware-user-agent@3.540.0": version "3.540.0" resolved "https://registry.yarnpkg.com/@aws-sdk/middleware-user-agent/-/middleware-user-agent-3.540.0.tgz#4981c64c1eeb6b5c453bce02d060b8c71d44994d" @@ -1169,6 +1210,18 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" +"@aws-sdk/signature-v4-multi-region@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.535.0.tgz#6a5413ab087d984794e12b04cac5d64c1e37a53f" + integrity sha512-tqCsEsEj8icW0SAh3NvyhRUq54Gz2pu4NM2tOSrFp7SO55heUUaRLSzYteNZCTOupH//AAaZvbN/UUTO/DrOog== + dependencies: + "@aws-sdk/middleware-sdk-s3" "3.535.0" + "@aws-sdk/types" "3.535.0" + "@smithy/protocol-http" "^3.3.0" + "@smithy/signature-v4" "^2.2.0" + "@smithy/types" "^2.12.0" + tslib "^2.6.2" + "@aws-sdk/signature-v4-multi-region@3.556.0": version "3.556.0" resolved "https://registry.yarnpkg.com/@aws-sdk/signature-v4-multi-region/-/signature-v4-multi-region-3.556.0.tgz#34ff26a1617b885a845752e62aca7bc29deb33ac" @@ -1181,24 +1234,24 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/token-providers@3.552.0": - version "3.552.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.552.0.tgz#e0cfbeb1ff9fb212ab214f2ade9827e1032fdf42" - integrity sha512-5dNE2KqtgkT+DQXfkSmzmVSB72LpjSIK86lLD9LeQ1T+b0gfEd74MAl/AGC15kQdKLg5I3LlN5q32f1fkmYR8g== +"@aws-sdk/token-providers@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.535.0.tgz#0d5aa221449d5b56730427b28d3319005c5700ed" + integrity sha512-4g+l/B9h1H/SiDtFRosW3pMwc+3PTXljZit+5NUBcET2XqcdUyHmgj3lBdu+CJ9CHdIMggRalYMAFXnRFe3Psg== dependencies: - "@aws-sdk/client-sso-oidc" "3.552.0" + "@aws-sdk/client-sso-oidc" "3.535.0" "@aws-sdk/types" "3.535.0" "@smithy/property-provider" "^2.2.0" "@smithy/shared-ini-file-loader" "^2.4.0" "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@aws-sdk/token-providers@3.556.0": - version "3.556.0" - resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.556.0.tgz#96b4dd4fec67ae62f8c98ae8c2f94e4ed050073a" - integrity sha512-tvIiugNF0/+2wfuImMrpKjXMx4nCnFWQjQvouObny+wrif/PGqqQYrybwxPJDvzbd965bu1I+QuSv85/ug7xsg== +"@aws-sdk/token-providers@3.552.0": + version "3.552.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/token-providers/-/token-providers-3.552.0.tgz#e0cfbeb1ff9fb212ab214f2ade9827e1032fdf42" + integrity sha512-5dNE2KqtgkT+DQXfkSmzmVSB72LpjSIK86lLD9LeQ1T+b0gfEd74MAl/AGC15kQdKLg5I3LlN5q32f1fkmYR8g== dependencies: - "@aws-sdk/client-sso-oidc" "3.556.0" + "@aws-sdk/client-sso-oidc" "3.552.0" "@aws-sdk/types" "3.535.0" "@smithy/property-provider" "^2.2.0" "@smithy/shared-ini-file-loader" "^2.4.0" @@ -1228,6 +1281,16 @@ dependencies: tslib "^2.6.2" +"@aws-sdk/util-endpoints@3.535.0": + version "3.535.0" + resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.535.0.tgz#46f4b61b2661d6414ded8c98e4ad3c82a0bf597b" + integrity sha512-c8TlaQsiPchOOmTTR6qvHCO2O7L7NJwlKWAoQJ2GqWDZuC5es/fyuF2rp1h+ZRrUVraUomS0YdGkAmaDC7hJQg== + dependencies: + "@aws-sdk/types" "3.535.0" + "@smithy/types" "^2.12.0" + "@smithy/util-endpoints" "^1.2.0" + tslib "^2.6.2" + "@aws-sdk/util-endpoints@3.540.0": version "3.540.0" resolved "https://registry.yarnpkg.com/@aws-sdk/util-endpoints/-/util-endpoints-3.540.0.tgz#a7fea1d2a5e64623353aaa6ee32dbb86ab9cd3f8" @@ -6644,7 +6707,7 @@ "@smithy/util-middleware" "^2.2.0" tslib "^2.6.2" -"@smithy/core@^1.4.2": +"@smithy/core@^1.4.0", "@smithy/core@^1.4.2": version "1.4.2" resolved "https://registry.yarnpkg.com/@smithy/core/-/core-1.4.2.tgz#1c3ed886d403041ce5bd2d816448420c57baa19c" integrity sha512-2fek3I0KZHWJlRLvRTqxTEri+qV0GRHrJIoLFuBMZB4EMg4WgeBGfF0X6abnrNYpq55KJ6R4D6x4f0vLnhzinA== @@ -6787,7 +6850,7 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@smithy/middleware-endpoint@^2.5.1": +"@smithy/middleware-endpoint@^2.5.0", "@smithy/middleware-endpoint@^2.5.1": version "2.5.1" resolved "https://registry.yarnpkg.com/@smithy/middleware-endpoint/-/middleware-endpoint-2.5.1.tgz#1333c58304aff4d843e8ef4b85c8cb88975dd5ad" integrity sha512-1/8kFp6Fl4OsSIVTWHnNjLnTL8IqpIb/D3sTSczrKFnrE9VMNWxnrRKNvpUHOJ6zpGD5f62TPm7+17ilTJpiCQ== @@ -6800,7 +6863,7 @@ "@smithy/util-middleware" "^2.2.0" tslib "^2.6.2" -"@smithy/middleware-retry@^2.3.1": +"@smithy/middleware-retry@^2.2.0", "@smithy/middleware-retry@^2.3.1": version "2.3.1" resolved "https://registry.yarnpkg.com/@smithy/middleware-retry/-/middleware-retry-2.3.1.tgz#d6fdce94f2f826642c01b4448e97a509c4556ede" integrity sha512-P2bGufFpFdYcWvqpyqqmalRtwFUNUA8vHjJR5iGqbfR6mp65qKOLcUd6lTr4S9Gn/enynSrSf3p3FVgVAf6bXA== @@ -6915,10 +6978,10 @@ "@smithy/types" "^2.12.0" tslib "^2.6.2" -"@smithy/signature-v4@^2.2.1": - version "2.2.1" - resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-2.2.1.tgz#9b32571e9785c8f69aa4115517bf2a784f690c4d" - integrity sha512-j5fHgL1iqKTsKJ1mTcw88p0RUcidDu95AWSeZTgiYJb+QcfwWU/UpBnaqiB59FNH5MiAZuSbOBnZlwzeeY2tIw== +"@smithy/signature-v4@^2.2.0", "@smithy/signature-v4@^2.3.0": + version "2.3.0" + resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-2.3.0.tgz#c30dd4028ae50c607db99459981cce8cdab7a3fd" + integrity sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q== dependencies: "@smithy/is-array-buffer" "^2.2.0" "@smithy/types" "^2.12.0" @@ -6928,10 +6991,10 @@ "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@smithy/signature-v4@^2.3.0": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-2.3.0.tgz#c30dd4028ae50c607db99459981cce8cdab7a3fd" - integrity sha512-ui/NlpILU+6HAQBfJX8BBsDXuKSNrjTSuOYArRblcrErwKFutjrCNb/OExfVRyj9+26F9J+ZmfWT+fKWuDrH3Q== +"@smithy/signature-v4@^2.2.1": + version "2.2.1" + resolved "https://registry.yarnpkg.com/@smithy/signature-v4/-/signature-v4-2.2.1.tgz#9b32571e9785c8f69aa4115517bf2a784f690c4d" + integrity sha512-j5fHgL1iqKTsKJ1mTcw88p0RUcidDu95AWSeZTgiYJb+QcfwWU/UpBnaqiB59FNH5MiAZuSbOBnZlwzeeY2tIw== dependencies: "@smithy/is-array-buffer" "^2.2.0" "@smithy/types" "^2.12.0" @@ -6941,7 +7004,7 @@ "@smithy/util-utf8" "^2.3.0" tslib "^2.6.2" -"@smithy/smithy-client@^2.5.1": +"@smithy/smithy-client@^2.5.0", "@smithy/smithy-client@^2.5.1": version "2.5.1" resolved "https://registry.yarnpkg.com/@smithy/smithy-client/-/smithy-client-2.5.1.tgz#0fd2efff09dc65500d260e590f7541f8a387eae3" integrity sha512-jrbSQrYCho0yDaaf92qWgd+7nAeap5LtHTI51KXqmpIFCceKU3K9+vIVTUH72bOJngBMqa4kyu1VJhRcSrk/CQ== @@ -7014,7 +7077,7 @@ dependencies: tslib "^2.6.2" -"@smithy/util-defaults-mode-browser@^2.2.1": +"@smithy/util-defaults-mode-browser@^2.2.0", "@smithy/util-defaults-mode-browser@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-browser/-/util-defaults-mode-browser-2.2.1.tgz#9db31416daf575d2963c502e0528cfe8055f0c4e" integrity sha512-RtKW+8j8skk17SYowucwRUjeh4mCtnm5odCL0Lm2NtHQBsYKrNW0od9Rhopu9wF1gHMfHeWF7i90NwBz/U22Kw== @@ -7025,7 +7088,7 @@ bowser "^2.11.0" tslib "^2.6.2" -"@smithy/util-defaults-mode-node@^2.3.1": +"@smithy/util-defaults-mode-node@^2.3.0", "@smithy/util-defaults-mode-node@^2.3.1": version "2.3.1" resolved "https://registry.yarnpkg.com/@smithy/util-defaults-mode-node/-/util-defaults-mode-node-2.3.1.tgz#4613210a3d107aadb3f85bd80cb71c796dd8bf0a" integrity sha512-vkMXHQ0BcLFysBMWgSBLSk3+leMpFSyyFj8zQtv5ZyUBx8/owVh1/pPEkzmW/DR/Gy/5c8vjLDD9gZjXNKbrpA== From d5b5238402b79ac4dfe97ec26c585d7b8ceff897 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 22 May 2024 14:24:03 +0200 Subject: [PATCH 231/529] chore(release): release v7.32.1 (#9764) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 3f880395abc..6699e7e743d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.32.0" + ".": "7.32.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b2086a50df0..d7687d2ef2f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.32.1](https://github.com/ParabolInc/parabol/compare/v7.32.0...v7.32.1) (2024-05-22) + + +### Fixed + +* Revert @aws-sdk/client-s3 upgrade ([#9763](https://github.com/ParabolInc/parabol/issues/9763)) ([8fc0ec1](https://github.com/ParabolInc/parabol/commit/8fc0ec11f8e45033c5291feef59d298b36dbfc7c)) + ## [7.32.0](https://github.com/ParabolInc/parabol/compare/v7.31.0...v7.32.0) (2024-05-21) diff --git a/package.json b/package.json index 36d6e1a0270..cba8ea2cd1f 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.32.0", + "version": "7.32.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 22ff9e859d0..b3d46a4151e 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.32.0", + "version": "7.32.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.32.0" + "parabol-server": "7.32.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index c9f898a4047..f47682fd0d1 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.32.0", + "version": "7.32.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 46eda1dbb28..16150f4e259 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.32.0", + "version": "7.32.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 52fb5f2bb8b..6e8562e3918 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.32.0", + "version": "7.32.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.32.0", - "parabol-server": "7.32.0", + "parabol-client": "7.32.1", + "parabol-server": "7.32.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index d7aa0dcbc00..f8bfba3f9fc 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.32.0", + "version": "7.32.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 606260905d0..f973dcd7e2b 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.32.0", + "version": "7.32.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.32.0", + "parabol-client": "7.32.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 2699c3db7f2ca86257c6e5da475ec47af473c8a9 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Wed, 22 May 2024 14:40:47 +0100 Subject: [PATCH 232/529] fix: team lead can view teams in org settings (#9739) --- .../components/Dashboard/DashSidebar.tsx | 15 ++++++--------- .../components/Dashboard/MobileDashSidebar.tsx | 17 +++++++---------- .../components/OrgBilling/Organization.tsx | 18 +++++++----------- .../components/OrgTeams/OrgTeams.tsx | 17 +++++++++++++---- packages/server/graphql/types/Organization.ts | 11 ++++++----- 5 files changed, 39 insertions(+), 39 deletions(-) diff --git a/packages/client/components/Dashboard/DashSidebar.tsx b/packages/client/components/Dashboard/DashSidebar.tsx index f95a6d38675..b29c698ae7c 100644 --- a/packages/client/components/Dashboard/DashSidebar.tsx +++ b/packages/client/components/Dashboard/DashSidebar.tsx @@ -86,7 +86,6 @@ const DashSidebar = (props: Props) => { ...DashNavList_organization id name - isBillingLeader } } `, @@ -99,7 +98,7 @@ const DashSidebar = (props: Props) => { if (match) { const {orgId: orgIdFromParams} = match.params const currentOrg = organizations.find((org) => org.id === orgIdFromParams) - const {id: orgId, name, isBillingLeader} = currentOrg ?? {} + const {id: orgId, name} = currentOrg ?? {} return ( @@ -118,13 +117,11 @@ const DashSidebar = (props: Props) => { href={`/me/organizations/${orgId}/${BILLING_PAGE}`} label={'Plans & Billing'} /> - {isBillingLeader && ( - - )} + { ...DashNavList_organization id name - isBillingLeader } } `, @@ -110,7 +109,7 @@ const MobileDashSidebar = (props: Props) => { if (match) { const {orgId: orgIdFromParams} = match.params const currentOrg = organizations.find((org) => org.id === orgIdFromParams) - const {id: orgId, name, isBillingLeader} = currentOrg ?? {} + const {id: orgId, name} = currentOrg ?? {} return ( @@ -131,14 +130,12 @@ const MobileDashSidebar = (props: Props) => { href={`/me/organizations/${orgId}/${BILLING_PAGE}`} label={'Plans & Billing'} /> - {isBillingLeader && ( - - )} + { const match = useRouteMatch<{orgId: string}>('/me/organizations/:orgId')! const {organization} = viewer if (!organization) return null - const {id: orgId, isBillingLeader} = organization + const {id: orgId} = organization return (
@@ -90,16 +90,12 @@ const Organization = (props: Props) => { path={`${match.url}/${AUTHENTICATION_PAGE}`} render={(p) => } /> - {isBillingLeader && ( - <> - } - /> - - - )} + } + /> +
) diff --git a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx index 6bcd5cda9ad..52a8473974f 100644 --- a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx +++ b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx @@ -18,7 +18,11 @@ const OrgTeams = (props: Props) => { graphql` fragment OrgTeams_organization on Organization { id + isOrgAdmin isBillingLeader + featureFlags { + publicTeams + } allTeams { id ...OrgTeamsRow_team @@ -33,13 +37,18 @@ const OrgTeams = (props: Props) => { isOpen: isAddTeamDialogOpened } = useDialogState() - const {allTeams, isBillingLeader} = organization - if (!isBillingLeader) return null - + const {allTeams, isBillingLeader, isOrgAdmin, featureFlags} = organization + const hasPublicTeamsFlag = featureFlags.publicTeams + const showAllTeams = isBillingLeader || isOrgAdmin || hasPublicTeamsFlag return (
-

Teams

+
+

Teams

+ {!showAllTeams && ( +

Only showing teams you're a member of

+ )} +
history.push(`/me/organizations/${orgId}`)}> - {`Upgrade Team to ${TierLabel.TEAM}`} + {`Upgrade to ${TierLabel.TEAM} Plan`} From 462a7f45eac7193a3c64f5971a7ecec085dffb25 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Thu, 23 May 2024 10:53:28 +0100 Subject: [PATCH 235/529] feat: update promote team copy (#9767) --- .../PromoteTeamMemberModal.tsx | 41 +++++++++++++------ .../components/OrgTeams/OrgTeams.tsx | 1 + .../graphql/public/typeDefs/Team.graphql | 5 +++ packages/server/graphql/public/types/Team.ts | 4 ++ 4 files changed, 38 insertions(+), 13 deletions(-) diff --git a/packages/client/modules/teamDashboard/components/PromoteTeamMemberModal/PromoteTeamMemberModal.tsx b/packages/client/modules/teamDashboard/components/PromoteTeamMemberModal/PromoteTeamMemberModal.tsx index 8536b80cd54..b34901dd5ea 100644 --- a/packages/client/modules/teamDashboard/components/PromoteTeamMemberModal/PromoteTeamMemberModal.tsx +++ b/packages/client/modules/teamDashboard/components/PromoteTeamMemberModal/PromoteTeamMemberModal.tsx @@ -9,8 +9,9 @@ import DialogContent from '../../../../components/DialogContent' import DialogTitle from '../../../../components/DialogTitle' import IconLabel from '../../../../components/IconLabel' import PrimaryButton from '../../../../components/PrimaryButton' +import useMutationProps from '../../../../hooks/useMutationProps' import PromoteToTeamLeadMutation from '../../../../mutations/PromoteToTeamLeadMutation' -import withMutationProps, {WithMutationProps} from '../../../../utils/relay/withMutationProps' +import {upperFirst} from '../../../../utils/upperFirst' const StyledDialogContainer = styled(DialogContainer)({ width: 420 @@ -20,41 +21,55 @@ const StyledButton = styled(PrimaryButton)({ margin: '1.5rem auto 0' }) -interface Props extends WithMutationProps { +interface Props { closePortal: () => void teamMember: PromoteTeamMemberModal_teamMember$key } + const PromoteTeamMemberModal = (props: Props) => { const atmosphere = useAtmosphere() - const { - closePortal, - submitMutation, - submitting, - onError, - onCompleted, - teamMember: teamMemberRef - } = props + const {submitMutation, submitting, onError, onCompleted} = useMutationProps() + const {closePortal, teamMember: teamMemberRef} = props const teamMember = useFragment( graphql` fragment PromoteTeamMemberModal_teamMember on TeamMember { userId teamId preferredName + isSelf + team { + isOrgAdmin + teamLead { + isSelf + preferredName + } + } } `, teamMemberRef ) - const {preferredName, teamId, userId} = teamMember + const {preferredName, teamId, userId, team, isSelf} = teamMember + const {isOrgAdmin, teamLead} = team ?? {} + const oldLeadName = teamLead?.preferredName + const isOldLeadSelf = teamLead?.isSelf const handleClick = () => { submitMutation() PromoteToTeamLeadMutation(atmosphere, {teamId, userId}, {onError, onCompleted}) closePortal() } + + const copyStart = `Are you sure? ${isOldLeadSelf ? 'You' : oldLeadName} will be removed as the team leader and ${isSelf ? 'You' : preferredName} will be promoted.` + const copyEnd = + isOldLeadSelf && isOrgAdmin + ? '' + : `${isSelf ? 'You' : upperFirst(preferredName)} will no longer be able to change team membership.` + const copy = `${copyStart} ${copyEnd}` + return ( {'Are you sure?'} - {`You will be removed as the team leader and promote ${preferredName}. You will no longer be able to change team membership. This cannot be undone!`} + {copy} @@ -63,4 +78,4 @@ const PromoteTeamMemberModal = (props: Props) => { ) } -export default withMutationProps(PromoteTeamMemberModal) +export default PromoteTeamMemberModal diff --git a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx index 52a8473974f..964dd724e81 100644 --- a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx +++ b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx @@ -31,6 +31,7 @@ const OrgTeams = (props: Props) => { `, organizationRef ) + const { open: openAddTeamDialog, close: closeAddTeamDialog, diff --git a/packages/server/graphql/public/typeDefs/Team.graphql b/packages/server/graphql/public/typeDefs/Team.graphql index 6e95b1c602f..4ca3fb23ba3 100644 --- a/packages/server/graphql/public/typeDefs/Team.graphql +++ b/packages/server/graphql/public/typeDefs/Team.graphql @@ -189,4 +189,9 @@ type Team { Emoji that will be used for giving kudos """ kudosEmoji: String! + + """ + The team member that is the team lead + """ + teamLead: TeamMember! } diff --git a/packages/server/graphql/public/types/Team.ts b/packages/server/graphql/public/types/Team.ts index 1e27132dda9..cfb3d4122f4 100644 --- a/packages/server/graphql/public/types/Team.ts +++ b/packages/server/graphql/public/types/Team.ts @@ -47,6 +47,10 @@ const Team: TeamResolvers = { .get('organizationUsersByUserIdOrgId') .load({userId: viewerId, orgId}) return organizationUser?.role === 'ORG_ADMIN' + }, + teamLead: async ({id: teamId}, _args, {dataLoader}) => { + const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) + return teamMembers.find((teamMember) => teamMember.isLead)! } } From 224366762fcc11b1bfb3140317f444a10e5c0838 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Mon, 27 May 2024 09:12:52 -0700 Subject: [PATCH 236/529] feat(stripe): handle Stripe subscription events (#9768) --- .../server/billing/stripeWebhookHandler.ts | 35 +++++++++++++ .../mutations/stripeDeleteSubscription.ts | 49 +++++++++++++++++++ .../mutations/stripeUpdateSubscription.ts | 42 ++++++++++++++++ .../private/mutations/upgradeToTeamTier.ts | 8 ++- .../graphql/private/typeDefs/_legacy.graphql | 29 +++++++++++ 5 files changed, 161 insertions(+), 2 deletions(-) create mode 100644 packages/server/graphql/private/mutations/stripeDeleteSubscription.ts create mode 100644 packages/server/graphql/private/mutations/stripeUpdateSubscription.ts diff --git a/packages/server/billing/stripeWebhookHandler.ts b/packages/server/billing/stripeWebhookHandler.ts index 707247b0bbc..dff889d7399 100644 --- a/packages/server/billing/stripeWebhookHandler.ts +++ b/packages/server/billing/stripeWebhookHandler.ts @@ -80,6 +80,41 @@ 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, + subscriptionId: id + }), + query: ` + mutation StripeCreateSubscription($customerId: ID!, $subscriptionId: ID!) { + stripeUpdateSubscription(customerId: $customerId, subscriptionId: $subscriptionId) + } + ` + }, + deleted: { + getVars: ({customer, id}: {customer: string; id: string}) => ({ + customerId: customer, + subscriptionId: id + }), + query: ` + mutation StripeDeleteSubscription($customerId: ID!, $subscriptionId: ID!) { + stripeDeleteSubscription(customerId: $customerId, subscriptionId: $subscriptionId) + } + ` + } } } } as const diff --git a/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts b/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts new file mode 100644 index 00000000000..960bb1a01f2 --- /dev/null +++ b/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts @@ -0,0 +1,49 @@ +import getRethink from '../../../database/rethinkDriver' +import Organization from '../../../database/types/Organization' +import {isSuperUser} from '../../../utils/authorization' +import {getStripeManager} from '../../../utils/stripe' +import {MutationResolvers} from '../resolverTypes' + +const stripeDeleteSubscription: MutationResolvers['stripeDeleteSubscription'] = async ( + _source, + {customerId, subscriptionId}, + {authToken, dataLoader} +) => { + const r = await getRethink() + // AUTH + if (!isSuperUser(authToken)) { + throw new Error('Don’t be rude.') + } + + // RESOLUTION + const manager = getStripeManager() + const stripeCustomer = await manager.retrieveCustomer(customerId) + if (stripeCustomer.deleted) { + throw new Error('Customer was deleted') + } + + const { + metadata: {orgId} + } = stripeCustomer + if (!orgId) { + throw new Error(`orgId not found on metadata for customer ${customerId}`) + } + const org: Organization = await dataLoader.get('organizations').load(orgId) + + const {stripeSubscriptionId} = org + if (stripeSubscriptionId !== subscriptionId) { + throw new Error('Subscription ID does not match') + } + + await r + .table('Organization') + .get(orgId) + .update({ + stripeSubscriptionId: r.literal() + }) + .run() + + return true +} + +export default stripeDeleteSubscription diff --git a/packages/server/graphql/private/mutations/stripeUpdateSubscription.ts b/packages/server/graphql/private/mutations/stripeUpdateSubscription.ts new file mode 100644 index 00000000000..12831a9fab4 --- /dev/null +++ b/packages/server/graphql/private/mutations/stripeUpdateSubscription.ts @@ -0,0 +1,42 @@ +import getRethink from '../../../database/rethinkDriver' +import {isSuperUser} from '../../../utils/authorization' +import {getStripeManager} from '../../../utils/stripe' +import {MutationResolvers} from '../resolverTypes' + +const stripeUpdateSubscription: MutationResolvers['stripeUpdateSubscription'] = async ( + _source, + {customerId, subscriptionId}, + {authToken} +) => { + const r = await getRethink() + // AUTH + if (!isSuperUser(authToken)) { + throw new Error('Don’t be rude.') + } + + // RESOLUTION + const manager = getStripeManager() + const stripeCustomer = await manager.retrieveCustomer(customerId) + if (stripeCustomer.deleted) { + throw new Error('Customer was deleted') + } + + const { + metadata: {orgId} + } = stripeCustomer + if (!orgId) { + throw new Error(`orgId not found on metadata for customer ${customerId}`) + } + + await r + .table('Organization') + .get(orgId) + .update({ + stripeSubscriptionId: subscriptionId + }) + .run() + + return true +} + +export default stripeUpdateSubscription diff --git a/packages/server/graphql/private/mutations/upgradeToTeamTier.ts b/packages/server/graphql/private/mutations/upgradeToTeamTier.ts index d1abdb80e42..38efacb4caf 100644 --- a/packages/server/graphql/private/mutations/upgradeToTeamTier.ts +++ b/packages/server/graphql/private/mutations/upgradeToTeamTier.ts @@ -70,8 +70,12 @@ const upgradeToTeamTier: MutationResolvers['upgradeToTeamTier'] = async ( return standardError(new Error('Organization does not have a subscription'), {userId: viewerId}) } - if (tier !== 'starter') { - return standardError(new Error('Organization is not on the starter tier'), { + if (tier === 'enterprise') { + return standardError(new Error("Can not change an org's plan from enterprise to team"), { + userId: viewerId + }) + } else if (tier === 'team') { + return standardError(new Error('Org is already on team tier'), { userId: viewerId }) } diff --git a/packages/server/graphql/private/typeDefs/_legacy.graphql b/packages/server/graphql/private/typeDefs/_legacy.graphql index adac67fec23..0aeca980ef5 100644 --- a/packages/server/graphql/private/typeDefs/_legacy.graphql +++ b/packages/server/graphql/private/typeDefs/_legacy.graphql @@ -447,6 +447,35 @@ type Mutation { customerId: ID! ): Boolean + """ + When stripe tells us a subscription was updated, update the details in our own DB + """ + stripeUpdateSubscription( + """ + The stripe customer ID, or stripeId + """ + customerId: ID! + """ + The stripe subscription ID + """ + subscriptionId: ID! + ): Boolean + + """ + When stripe tells us a subscription was deleted, update the details in our own DB + """ + stripeDeleteSubscription( + """ + The stripe customer ID, or stripeId + """ + customerId: ID! + """ + The stripe subscription ID + """ + subscriptionId: ID! + ): Boolean + + """ When a new invoiceitem is sent from stripe, tag it with metadata """ From adcabbc186ebbbd124f0a596fead9b85b035f438 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 28 May 2024 17:04:25 -0700 Subject: [PATCH 237/529] feat: write equality checker to file store (#9786) Signed-off-by: Matt Krick --- packages/server/fileStorage/FileStoreManager.ts | 5 +++++ .../private/mutations/checkRethinkPgEquality.ts | 13 +++++-------- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/server/fileStorage/FileStoreManager.ts b/packages/server/fileStorage/FileStoreManager.ts index 35db10931f2..824b5063523 100644 --- a/packages/server/fileStorage/FileStoreManager.ts +++ b/packages/server/fileStorage/FileStoreManager.ts @@ -39,4 +39,9 @@ export default abstract class FileStoreManager { const partialPath = `Organization/${orgId}/template/${filename}.${dotfreeExt}` return this.putUserFile(file, partialPath) } + + async putDebugFile(file: ArrayBufferLike, nameWithExt: string) { + const partialPath = `__debug__/${nameWithExt}` + return this.putUserFile(file, partialPath) + } } diff --git a/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts b/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts index 818af10d9e6..54e4ea2713b 100644 --- a/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts +++ b/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts @@ -1,6 +1,5 @@ -import fs from 'fs' -import path from 'path' import getRethink from '../../../database/rethinkDriver' +import getFileStoreManager from '../../../fileStorage/getFileStoreManager' import getKysely from '../../../postgres/getKysely' import {checkRowCount, checkTableEq} from '../../../postgres/utils/checkEqBase' import { @@ -20,12 +19,10 @@ const handleResult = async ( const resultStr = JSON.stringify(result) if (!writeToFile) return resultStr - const fileName = `${tableName}-${new Date()}` - const fileDir = path.join(process.cwd(), '__rethinkEquality__') - const fileLocation = path.join(fileDir, fileName) - await fs.promises.mkdir(fileDir, {recursive: true}) - await fs.promises.writeFile(fileLocation, resultStr) - return `Result written to ${fileLocation}` + const fileName = `rethinkdbEquality_${tableName}_${new Date().toISOString()}.json` + const manager = getFileStoreManager() + const buffer = Buffer.from(resultStr, 'utf-8') + return manager.putDebugFile(buffer, fileName) } const checkRethinkPgEquality: MutationResolvers['checkRethinkPgEquality'] = async ( From da350e73705604277cc0faa81b6dab9010927d4c Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 29 May 2024 13:01:02 -0700 Subject: [PATCH 238/529] fix: bump trebuchet-client to latest version (#9797) Signed-off-by: Matt Krick --- packages/client/package.json | 2 +- yarn.lock | 44 +++++++++++------------------------- 2 files changed, 14 insertions(+), 32 deletions(-) diff --git a/packages/client/package.json b/packages/client/package.json index f47682fd0d1..1f8dca5f085 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -68,7 +68,7 @@ "@emotion/styled": "^10.0.27", "@mattkrick/graphql-trebuchet-client": "^2.2.1", "@mattkrick/sanitize-svg": "0.4.0", - "@mattkrick/trebuchet-client": "3.0.1", + "@mattkrick/trebuchet-client": "3.0.3", "@mui/icons-material": "^5.8.4", "@mui/material": "^5.9.2", "@mui/x-date-pickers": "^6.3.1", diff --git a/yarn.lock b/yarn.lock index bb7170ab1de..9667d8329c9 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4959,15 +4959,6 @@ resolved "https://registry.yarnpkg.com/@linaria/core/-/core-3.0.0-beta.13.tgz#049c5be5faa67e341e413a0f6b641d5d78d91056" integrity sha512-3zEi5plBCOsEzUneRVuQb+2SAx3qaC1dj0FfFAI6zIJQoDWu0dlSwKijMRack7oO9tUWrchfj3OkKQAd1LBdVg== -"@mattkrick/fast-rtc-peer@^0.4.1": - version "0.4.1" - resolved "https://registry.yarnpkg.com/@mattkrick/fast-rtc-peer/-/fast-rtc-peer-0.4.1.tgz#68a0c7053f19b088b1737f0d03c2ef7935de7dd4" - integrity sha512-BTQS3xEbFq9VD/AWYNACjQZDsh4Rczemukk0MvS9tXG4odoUZa3S1FFD8lBgnCp0pteB5rvYBggX/AaJCk2VHQ== - dependencies: - eventemitter3 "^3.1.0" - shortid "^2.2.12" - tslib "^1.9.3" - "@mattkrick/graphql-trebuchet-client@^2.2.1": version "2.2.1" resolved "https://registry.yarnpkg.com/@mattkrick/graphql-trebuchet-client/-/graphql-trebuchet-client-2.2.1.tgz#c7f8c30c6c8fbb584f812725f4ac1590a9c02c21" @@ -4980,15 +4971,13 @@ resolved "https://registry.yarnpkg.com/@mattkrick/sanitize-svg/-/sanitize-svg-0.4.0.tgz#388c29614cf72aa0dd9803c77c9c9d070bd3cd2d" integrity sha512-TnPI97WVAxo8SQcPy8aV3OF9/2WjXB5/+pRNVudIWR7Bhi5ZjtR/ur162So08GkvsvB914AXCW2sxFh1x6KhHA== -"@mattkrick/trebuchet-client@3.0.1": - version "3.0.1" - resolved "https://registry.yarnpkg.com/@mattkrick/trebuchet-client/-/trebuchet-client-3.0.1.tgz#3fc49f7858652a55dca92cb0c14c0313df931619" - integrity sha512-5uHCldCqmVntoyujTzRfmGtjld8k4JuBdFN0SvhvRFf83FPaNeoit6Mh8VgrcxxxKujKeYCQDMQH6LWwpsshgQ== +"@mattkrick/trebuchet-client@3.0.3": + version "3.0.3" + resolved "https://registry.yarnpkg.com/@mattkrick/trebuchet-client/-/trebuchet-client-3.0.3.tgz#7a396a90b4ba592f785eddff57b87f4b5a49b327" + integrity sha512-bCGUEkZqwD4XPdiJuQFa0czLJlxXMr6YqYJgVOAgzSLdf7ufLecclzGGdikVloAHG4la2MGHsnL4VY0CR7p7yA== dependencies: - "@mattkrick/fast-rtc-peer" "^0.4.1" - eventemitter3 "^4.0.7" - tslib "~2.2.0" - typescript "^4.2.4" + eventemitter3 "^5.0.1" + tslib "^2.6.2" "@motionone/animation@^10.12.0": version "10.15.1" @@ -12081,16 +12070,16 @@ eventemitter2@~0.4.14: resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab" integrity sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas= -eventemitter3@^3.1.0: - version "3.1.2" - resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-3.1.2.tgz#2d3d48f9c346698fce83a85d7d664e98535df6e7" - integrity sha512-tvtQIeLVHjDkJYnzf2dgVMxfuSGJeM/7UCG17TT4EumTfNtF+0nebF/4zWOIkCreAbtNqhGEboB6BWrwqNaw4Q== - -eventemitter3@^4.0.0, eventemitter3@^4.0.4, eventemitter3@^4.0.7: +eventemitter3@^4.0.0, eventemitter3@^4.0.4: version "4.0.7" resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== +eventemitter3@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-5.0.1.tgz#53f5ffd0a492ac800721bb42c66b841de96423c4" + integrity sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA== + eventid@^2.0.0: version "2.0.1" resolved "https://registry.yarnpkg.com/eventid/-/eventid-2.0.1.tgz#574e860149457a79a2efe788c459f0c3062d02ec" @@ -16407,7 +16396,7 @@ nan@^2.18.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0" integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw== -nanoid@^2.1.0, nanoid@^3.1.31, nanoid@^3.3.6: +nanoid@^3.1.31, nanoid@^3.3.6: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== @@ -19772,13 +19761,6 @@ shimmer@^1.1.0, shimmer@^1.2.0: resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== -shortid@^2.2.12: - version "2.2.16" - resolved "https://registry.yarnpkg.com/shortid/-/shortid-2.2.16.tgz#b742b8f0cb96406fd391c76bfc18a67a57fe5608" - integrity sha512-Ugt+GIZqvGXCIItnsL+lvFJOiN7RYqlGy7QE41O3YC1xbNSeDGIRO7xg2JJXIAj1cAGnOeC1r7/T9pgrtQbv4g== - dependencies: - nanoid "^2.1.0" - should-equal@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/should-equal/-/should-equal-2.0.0.tgz#6072cf83047360867e68e98b09d71143d04ee0c3" From c31b1c941273e37e8093b47648ab15d121d3be72 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 29 May 2024 13:25:54 -0700 Subject: [PATCH 239/529] chore(release): release v7.33.0 (#9766) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 21 +++++++++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 33 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6699e7e743d..b4e0b104703 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.32.1" + ".": "7.33.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index d7687d2ef2f..6e868c246ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.33.0](https://github.com/ParabolInc/parabol/compare/v7.32.1...v7.33.0) (2024-05-29) + + +### Added + +* **stripe:** handle Stripe subscription events ([#9768](https://github.com/ParabolInc/parabol/issues/9768)) ([2243667](https://github.com/ParabolInc/parabol/commit/224366762fcc11b1bfb3140317f444a10e5c0838)) +* update plan upgrade CTA label ([#9769](https://github.com/ParabolInc/parabol/issues/9769)) ([413f5b6](https://github.com/ParabolInc/parabol/commit/413f5b6349a3c7689d6dbf6b2b49df99d6b68412)) +* update promote team copy ([#9767](https://github.com/ParabolInc/parabol/issues/9767)) ([462a7f4](https://github.com/ParabolInc/parabol/commit/462a7f45eac7193a3c64f5971a7ecec085dffb25)) +* write equality checker to file store ([#9786](https://github.com/ParabolInc/parabol/issues/9786)) ([adcabbc](https://github.com/ParabolInc/parabol/commit/adcabbc186ebbbd124f0a596fead9b85b035f438)) + + +### Fixed + +* bump trebuchet-client to latest version ([#9797](https://github.com/ParabolInc/parabol/issues/9797)) ([da350e7](https://github.com/ParabolInc/parabol/commit/da350e73705604277cc0faa81b6dab9010927d4c)) +* team lead can view teams in org settings ([#9739](https://github.com/ParabolInc/parabol/issues/9739)) ([2699c3d](https://github.com/ParabolInc/parabol/commit/2699c3db7f2ca86257c6e5da475ec47af473c8a9)) + + +### Changed + +* Update processRecurrence tests ([#9770](https://github.com/ParabolInc/parabol/issues/9770)) ([222d6f9](https://github.com/ParabolInc/parabol/commit/222d6f94b9387dfbcff347f2bbcac8eef98bfe97)) + ## [7.32.1](https://github.com/ParabolInc/parabol/compare/v7.32.0...v7.32.1) (2024-05-22) diff --git a/package.json b/package.json index cba8ea2cd1f..078d273103f 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.32.1", + "version": "7.33.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index b3d46a4151e..1444cee8972 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.32.1", + "version": "7.33.0", "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.32.1" + "parabol-server": "7.33.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 1f8dca5f085..7f42b509ef8 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.32.1", + "version": "7.33.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 16150f4e259..0232cf845d0 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.32.1", + "version": "7.33.0", "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 6e8562e3918..07d478b8cad 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.32.1", + "version": "7.33.0", "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.32.1", - "parabol-server": "7.32.1", + "parabol-client": "7.33.0", + "parabol-server": "7.33.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index f8bfba3f9fc..99e1b54ef3e 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.32.1", + "version": "7.33.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index f973dcd7e2b..4c100b77fc6 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.32.1", + "version": "7.33.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.32.1", + "parabol-client": "7.33.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 23d48c48c01471be8e1332765f5d7cd9f0168954 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Thu, 30 May 2024 14:17:26 +0100 Subject: [PATCH 240/529] chore: remove kudos (#9785) --- codegen.json | 1 - .../components/KudosReceivedNotification.tsx | 59 ---- packages/client/components/Mentioned.tsx | 9 +- .../client/components/NotificationPicker.tsx | 4 - .../client/components/ResponseMentioned.tsx | 10 +- .../EmailResponseMentioned.tsx | 7 +- .../AddReactjiToReactableMutation.ts | 33 +-- .../mutations/CreateReflectionMutation.ts | 32 +-- .../UpsertTeamPromptResponseMutation.ts | 32 +-- .../toasts/mapKudosReceivedToToast.ts | 47 ---- .../mutations/toasts/mapMentionedToToast.ts | 12 +- .../toasts/mapResponseMentionedToToast.ts | 13 +- .../mutations/toasts/popNotificationToast.ts | 5 +- .../server/database/types/Notification.ts | 1 - .../types/NotificationKudosReceived.ts | 35 --- .../database/types/NotificationMentioned.ts | 10 +- .../types/NotificationResponseMentioned.ts | 8 +- packages/server/database/types/Team.ts | 8 - .../dataloader/primaryKeyLoaderMakers.ts | 2 - .../graphql/mutations/createReflection.ts | 35 +-- .../helpers/notifications/SlackNotifier.ts | 8 +- .../mutations/helpers/safeEndRetrospective.ts | 148 +--------- .../graphql/private/typeDefs/_legacy.graphql | 2 - .../typeDefs/updateOrgFeatureFlag.graphql | 1 - .../public/mutations/addReactjiToReactable.ts | 81 +----- .../__tests__/getKudosUserIdsFromJson.test.ts | 252 ------------------ .../helpers/getKudosUserIdsFromJson.ts | 35 --- .../helpers/publishTeamPromptMentions.ts | 22 +- .../mutations/upsertTeamPromptResponse.ts | 50 +--- .../AddReactjiToReactablePayload.graphql | 1 - .../graphql/public/typeDefs/Kudos.graphql | 30 --- .../typeDefs/NotifyKudosReceived.graphql | 52 ---- .../public/typeDefs/NotifyMentioned.graphql | 10 - .../typeDefs/NotifyResponseMentioned.graphql | 10 - .../public/typeDefs/Organization.graphql | 1 - .../graphql/public/typeDefs/Team.graphql | 10 - .../graphql/public/typeDefs/_legacy.graphql | 5 - .../typeDefs/upsertTeamPromptResponse.graphql | 5 - .../types/AddReactjiToReactableSuccess.ts | 5 - packages/server/graphql/public/types/Kudos.ts | 12 - .../public/types/NotifyKudosReceived.ts | 12 - .../public/types/OrganizationFeatureFlags.ts | 1 - .../types/UpsertTeamPromptResponseSuccess.ts | 10 - .../server/graphql/types/NotificationEnum.ts | 4 +- .../migrations/1716914102795_removeKudos.ts | 21 ++ .../postgres/queries/getKudosesByIds.ts | 6 - packages/server/utils/analytics/analytics.ts | 22 -- 47 files changed, 55 insertions(+), 1124 deletions(-) delete mode 100644 packages/client/components/KudosReceivedNotification.tsx delete mode 100644 packages/client/mutations/toasts/mapKudosReceivedToToast.ts delete mode 100644 packages/server/database/types/NotificationKudosReceived.ts delete mode 100644 packages/server/graphql/public/mutations/helpers/__tests__/getKudosUserIdsFromJson.test.ts delete mode 100644 packages/server/graphql/public/mutations/helpers/getKudosUserIdsFromJson.ts delete mode 100644 packages/server/graphql/public/typeDefs/Kudos.graphql delete mode 100644 packages/server/graphql/public/typeDefs/NotifyKudosReceived.graphql delete mode 100644 packages/server/graphql/public/types/Kudos.ts delete mode 100644 packages/server/graphql/public/types/NotifyKudosReceived.ts create mode 100644 packages/server/postgres/migrations/1716914102795_removeKudos.ts delete mode 100644 packages/server/postgres/queries/getKudosesByIds.ts diff --git a/codegen.json b/codegen.json index 2bfa7b6011f..46aa03a0329 100644 --- a/codegen.json +++ b/codegen.json @@ -75,7 +75,6 @@ "InviteToTeamPayload": "./types/InviteToTeamPayload#InviteToTeamPayloadSource", "JiraIssue": "./types/JiraIssue#JiraIssueSource", "JiraRemoteProject": "../types/JiraRemoteProject#JiraRemoteProjectSource", - "Kudos": "../../postgres/types/Kudos#Kudos", "MeetingSeries": "../../postgres/types/MeetingSeries#MeetingSeries", "MeetingTemplate": "../../database/types/MeetingTemplate#default", "NewMeeting": "../../postgres/types/Meeting#AnyMeeting", diff --git a/packages/client/components/KudosReceivedNotification.tsx b/packages/client/components/KudosReceivedNotification.tsx deleted file mode 100644 index 8215f304e6b..00000000000 --- a/packages/client/components/KudosReceivedNotification.tsx +++ /dev/null @@ -1,59 +0,0 @@ -import graphql from 'babel-plugin-relay/macro' -import React, {useEffect} from 'react' -import {useFragment} from 'react-relay' -import {Link} from 'react-router-dom' -import {KudosReceivedNotification_notification$key} from '~/__generated__/KudosReceivedNotification_notification.graphql' -import useAtmosphere from '../hooks/useAtmosphere' -import anonymousAvatar from '../styles/theme/images/anonymous-avatar.svg' -import SendClientSideEvent from '../utils/SendClientSideEvent' -import NotificationTemplate from './NotificationTemplate' - -interface Props { - notification: KudosReceivedNotification_notification$key -} - -const KudosReceivedNotification = (props: Props) => { - const atmosphere = useAtmosphere() - const {notification: notificationRef} = props - const notification = useFragment( - graphql` - fragment KudosReceivedNotification_notification on NotifyKudosReceived { - ...NotificationTemplate_notification - id - type - name - picture - meetingName - meetingId - emojiUnicode - status - } - `, - notificationRef - ) - const {type, name, picture, meetingName, emojiUnicode, meetingId, status} = notification - - useEffect(() => { - SendClientSideEvent(atmosphere, 'Notification Viewed', { - notificationType: type, - notificationStatus: status - }) - }, []) - - return ( - - {emojiUnicode} {name ?? 'Someone'} gave you kudos in{' '} - - {meetingName} - - - } - notification={notification} - avatar={picture ?? anonymousAvatar} - /> - ) -} - -export default KudosReceivedNotification diff --git a/packages/client/components/Mentioned.tsx b/packages/client/components/Mentioned.tsx index e22a97c40ba..ef23254d0b3 100644 --- a/packages/client/components/Mentioned.tsx +++ b/packages/client/components/Mentioned.tsx @@ -25,7 +25,6 @@ const Mentioned = (props: Props) => { status senderName senderPicture - kudosEmojiUnicode createdAt meetingId meetingName @@ -45,7 +44,6 @@ const Mentioned = (props: Props) => { senderPicture, meetingId, meetingName, - kudosEmojiUnicode, type, status, retroReflection, @@ -58,8 +56,7 @@ const Mentioned = (props: Props) => { useEffect(() => { SendClientSideEvent(atmosphere, 'Notification Viewed', { notificationType: type, - notificationStatus: status, - kudosEmojiUnicode + notificationStatus: status }) }, []) @@ -77,9 +74,7 @@ const Mentioned = (props: Props) => { } } - const message = !kudosEmojiUnicode - ? `${authorName} mentioned you in ${locationType} in ${meetingName}` - : `${kudosEmojiUnicode} ${authorName} gave you kudos in ${locationType} in ${meetingName}` + const message = `${authorName} mentioned you in ${locationType} in ${meetingName}` const goThere = () => { history.push(actionUrl) diff --git a/packages/client/components/NotificationPicker.tsx b/packages/client/components/NotificationPicker.tsx index 1d562f24283..dbd05100f9d 100644 --- a/packages/client/components/NotificationPicker.tsx +++ b/packages/client/components/NotificationPicker.tsx @@ -54,9 +54,6 @@ const typePicker: Record> = { MENTIONED: lazyPreload(() => import(/* webpackChunkName: 'Mentioned' */ './Mentioned')), RESPONSE_REPLIED: lazyPreload( () => import(/* webpackChunkName: 'ResponseReplied' */ './ResponseReplied') - ), - KUDOS_RECEIVED: lazyPreload( - () => import(/* webpackChunkName: 'KudosReceivedNotification' */ './KudosReceivedNotification') ) } @@ -86,7 +83,6 @@ const NotificationPicker = (props: Props) => { ...TeamsLimitReminderNotification_notification ...PromptToJoinOrgNotification_notification ...RequestToJoinOrgNotification_notification - ...KudosReceivedNotification_notification } `, notificationRef diff --git a/packages/client/components/ResponseMentioned.tsx b/packages/client/components/ResponseMentioned.tsx index 57a42589138..616c474c278 100644 --- a/packages/client/components/ResponseMentioned.tsx +++ b/packages/client/components/ResponseMentioned.tsx @@ -31,21 +31,19 @@ const ResponseMentioned = (props: Props) => { } type status - kudosEmojiUnicode } `, notificationRef ) const {history} = useRouter() const atmosphere = useAtmosphere() - const {meeting, response, kudosEmojiUnicode, type, status} = notification + const {meeting, response, type, status} = notification const {picture: authorPicture, preferredName: authorName} = response.user useEffect(() => { SendClientSideEvent(atmosphere, 'Notification Viewed', { notificationType: type, - notificationStatus: status, - kudosEmojiUnicode + notificationStatus: status }) }, []) @@ -54,9 +52,7 @@ const ResponseMentioned = (props: Props) => { history.push(`/meet/${meetingId}/responses?responseId=${encodeURIComponent(response.id)}`) } - const message = kudosEmojiUnicode - ? `${kudosEmojiUnicode} ${authorName} mentioned you and gave kudos in their response in ${meetingName}.` - : `${authorName} mentioned you in their response in ${meetingName}.` + const message = `${authorName} mentioned you in their response in ${meetingName}.` // :TODO: (jmtaber129): Show mention preview. return ( diff --git a/packages/client/modules/email/components/EmailNotifications/EmailResponseMentioned.tsx b/packages/client/modules/email/components/EmailNotifications/EmailResponseMentioned.tsx index dec6b3acf0c..7d42fb562f7 100644 --- a/packages/client/modules/email/components/EmailNotifications/EmailResponseMentioned.tsx +++ b/packages/client/modules/email/components/EmailNotifications/EmailResponseMentioned.tsx @@ -28,12 +28,11 @@ const EmailResponseMentioned = (props: Props) => { id name } - kudosEmojiUnicode } `, notificationRef ) - const {meeting, response, kudosEmojiUnicode} = notification + const {meeting, response} = notification const {rasterPicture: authorPicture, preferredName: authorName} = response.user const {id: meetingId, name: meetingName} = meeting @@ -47,9 +46,7 @@ const EmailResponseMentioned = (props: Props) => { } }) - const message = kudosEmojiUnicode - ? `${kudosEmojiUnicode} ${authorName} mentioned you and gave kudos in their response in ${meetingName}.` - : `${authorName} mentioned you in their response in ${meetingName}.` + const message = `${authorName} mentioned you in their response in ${meetingName}.` // :TODO: (jmtaber129): Show mention preview. return ( diff --git a/packages/client/mutations/AddReactjiToReactableMutation.ts b/packages/client/mutations/AddReactjiToReactableMutation.ts index 37fca00f436..d12f8c89066 100644 --- a/packages/client/mutations/AddReactjiToReactableMutation.ts +++ b/packages/client/mutations/AddReactjiToReactableMutation.ts @@ -3,7 +3,6 @@ import {commitMutation} from 'react-relay' import createProxyRecord from '~/utils/relay/createProxyRecord' import {AddReactjiToReactableMutation as TAddReactjiToReactableMutation} from '../__generated__/AddReactjiToReactableMutation.graphql' import {StandardMutation} from '../types/relayMutations' -import SendClientSideEvent from '../utils/SendClientSideEvent' graphql` fragment AddReactjiToReactableMutation_meeting on AddReactjiToReactableSuccess { @@ -38,14 +37,6 @@ const mutation = graphql` message } } - ... on AddReactjiToReactableSuccess { - addedKudos { - emojiUnicode - receiverUser { - preferredName - } - } - } ...AddReactjiToReactableMutation_meeting @relay(mask: false) } } @@ -113,29 +104,7 @@ const AddReactjiToReactableMutation: StandardMutation { - const {isRemove} = variables - const addedKudos = res.addReactjiToReactable.addedKudos - if (!isRemove && addedKudos) { - const {emojiUnicode} = addedKudos - atmosphere.eventEmitter.emit('addSnackbar', { - key: 'youGaveKudos', - message: `You gave kudos to ${addedKudos.receiverUser.preferredName} ${emojiUnicode}`, - autoDismiss: 5, - onShow: () => { - SendClientSideEvent(atmosphere, 'Snackbar Viewed', { - snackbarType: 'kudosSent' - }) - }, - onManualDismiss: () => { - SendClientSideEvent(atmosphere, 'Snackbar Clicked', { - snackbarType: 'kudosSent' - }) - } - }) - } - onCompleted(res, errors) - }, + onCompleted, onError }) } diff --git a/packages/client/mutations/CreateReflectionMutation.ts b/packages/client/mutations/CreateReflectionMutation.ts index f4974284134..a341f69653b 100644 --- a/packages/client/mutations/CreateReflectionMutation.ts +++ b/packages/client/mutations/CreateReflectionMutation.ts @@ -7,7 +7,6 @@ import {commitMutation} from 'react-relay' import {CreateReflectionMutation_meeting$data} from '~/__generated__/CreateReflectionMutation_meeting.graphql' import {CreateReflectionMutation as TCreateReflectionMutation} from '../__generated__/CreateReflectionMutation.graphql' import {SharedUpdater, StandardMutation} from '../types/relayMutations' -import SendClientSideEvent from '../utils/SendClientSideEvent' import makeEmptyStr from '../utils/draftjs/makeEmptyStr' import clientTempId from '../utils/relay/clientTempId' import createProxyRecord from '../utils/relay/createProxyRecord' @@ -34,12 +33,6 @@ graphql` id isNavigableByFacilitator } - draftKudoses { - receiverUser { - preferredName - } - emojiUnicode - } } ` @@ -66,30 +59,7 @@ const CreateReflectionMutation: StandardMutation = ( return commitMutation(atmosphere, { mutation, variables, - onCompleted: (res, errors) => { - const draftKudoses = res.createReflection?.draftKudoses - if (draftKudoses && draftKudoses.length) { - const preferredNames = draftKudoses - .map((kudos) => kudos.receiverUser.preferredName) - .join(', ') - atmosphere.eventEmitter.emit('addSnackbar', { - key: `youGaveKudos:${res.createReflection.reflectionId}`, - message: `${preferredNames} will receive kudos at the end ot the meeting ${draftKudoses[0]?.emojiUnicode}`, - autoDismiss: 5, - onShow: () => { - SendClientSideEvent(atmosphere, 'Snackbar Viewed', { - snackbarType: 'kudosSent' - }) - }, - onManualDismiss: () => { - SendClientSideEvent(atmosphere, 'Snackbar Clicked', { - snackbarType: 'kudosSent' - }) - } - }) - } - onCompleted(res, errors) - }, + onCompleted, onError, updater: (store) => { const payload = store.getRootField('createReflection') diff --git a/packages/client/mutations/UpsertTeamPromptResponseMutation.ts b/packages/client/mutations/UpsertTeamPromptResponseMutation.ts index 6abc25f0660..3f010784faa 100644 --- a/packages/client/mutations/UpsertTeamPromptResponseMutation.ts +++ b/packages/client/mutations/UpsertTeamPromptResponseMutation.ts @@ -4,7 +4,6 @@ import {UpsertTeamPromptResponseMutation_meeting$data} from '~/__generated__/Ups import clientTempId from '~/utils/relay/clientTempId' import {UpsertTeamPromptResponseMutation as TUpsertTeamPromptResponseMutation} from '../__generated__/UpsertTeamPromptResponseMutation.graphql' import {LocalHandlers, SharedUpdater, StandardMutation} from '../types/relayMutations' -import SendClientSideEvent from '../utils/SendClientSideEvent' graphql` fragment UpsertTeamPromptResponseMutation_meeting on UpsertTeamPromptResponseSuccess { @@ -18,12 +17,6 @@ graphql` createdAt ...TeamPromptResponseEmojis_response } - addedKudoses { - receiverUser { - preferredName - } - emojiUnicode - } } ` @@ -104,30 +97,7 @@ const UpsertTeamPromptResponseMutation: StandardMutation< const payload = store.getRootField('upsertTeamPromptResponse') upsertTeamPromptResponseUpdater(payload as any, {atmosphere, store}) }, - onCompleted: (res, errors) => { - const addedKudoses = res.upsertTeamPromptResponse.addedKudoses - if (addedKudoses?.length && addedKudoses[0]) { - const {emojiUnicode} = addedKudoses[0] - atmosphere.eventEmitter.emit('addSnackbar', { - key: 'youGaveKudos', - message: `You gave kudos to ${addedKudoses - .map((kudos) => kudos.receiverUser.preferredName) - .join(', ')} ${emojiUnicode}`, - autoDismiss: 5, - onShow: () => { - SendClientSideEvent(atmosphere, 'Snackbar Viewed', { - snackbarType: 'kudosSent' - }) - }, - onManualDismiss: () => { - SendClientSideEvent(atmosphere, 'Snackbar Clicked', { - snackbarType: 'kudosSent' - }) - } - }) - } - onCompleted?.(res, errors) - }, + onCompleted, onError }) } diff --git a/packages/client/mutations/toasts/mapKudosReceivedToToast.ts b/packages/client/mutations/toasts/mapKudosReceivedToToast.ts deleted file mode 100644 index d8876a5cfe8..00000000000 --- a/packages/client/mutations/toasts/mapKudosReceivedToToast.ts +++ /dev/null @@ -1,47 +0,0 @@ -import graphql from 'babel-plugin-relay/macro' -import {mapKudosReceivedToToast_notification$data} from '../../__generated__/mapKudosReceivedToToast_notification.graphql' -import {Snack} from '../../components/Snackbar' -import {OnNextHistoryContext} from '../../types/relayMutations' -import SendClientSideEvent from '../../utils/SendClientSideEvent' -import makeNotificationToastKey from './makeNotificationToastKey' - -graphql` - fragment mapKudosReceivedToToast_notification on NotifyKudosReceived { - id - name - meetingName - meetingId - emojiUnicode - } -` - -const mapKudosReceivedToToast = ( - notification: mapKudosReceivedToToast_notification$data, - {atmosphere, history}: OnNextHistoryContext -): Snack => { - const {id: notificationId, meetingName, name, emojiUnicode, meetingId} = notification - return { - autoDismiss: 5, - showDismissButton: true, - key: makeNotificationToastKey(notificationId), - message: `${emojiUnicode} ${name ?? 'Someone'} gave you kudos in`, - action: { - label: meetingName, - callback: () => { - history.push(`/meet/${meetingId}`) - } - }, - onShow: () => { - SendClientSideEvent(atmosphere, 'Snackbar Viewed', { - snackbarType: 'kudosReceived' - }) - }, - onManualDismiss: () => { - SendClientSideEvent(atmosphere, 'Snackbar Clicked', { - snackbarType: 'kudosReceived' - }) - } - } -} - -export default mapKudosReceivedToToast diff --git a/packages/client/mutations/toasts/mapMentionedToToast.ts b/packages/client/mutations/toasts/mapMentionedToToast.ts index 3962f9adf6d..8244d3c282d 100644 --- a/packages/client/mutations/toasts/mapMentionedToToast.ts +++ b/packages/client/mutations/toasts/mapMentionedToToast.ts @@ -8,7 +8,6 @@ import makeNotificationToastKey from './makeNotificationToastKey' graphql` fragment mapMentionedToToast_notification on NotifyMentioned { id - kudosEmojiUnicode senderName meetingId meetingName @@ -28,7 +27,6 @@ const mapMentionedToToast = ( id: notificationId, senderName, meetingName, - kudosEmojiUnicode, retroReflection, retroDiscussStageIdx, meetingId @@ -51,9 +49,7 @@ const mapMentionedToToast = ( } } - const message = !kudosEmojiUnicode - ? `${authorName} mentioned you in ${locationType} in ${meetingName}` - : `${kudosEmojiUnicode} ${authorName} gave you kudos in ${locationType} in ${meetingName}` + const message = `${authorName} mentioned you in ${locationType} in ${meetingName}` const goThere = () => { history.push(actionUrl) @@ -69,14 +65,12 @@ const mapMentionedToToast = ( }, onShow: () => { SendClientSideEvent(atmosphere, 'Snackbar Viewed', { - snackbarType, - kudosEmojiUnicode + snackbarType }) }, onManualDismiss: () => { SendClientSideEvent(atmosphere, 'Snackbar Clicked', { - snackbarType, - kudosEmojiUnicode + snackbarType }) } } diff --git a/packages/client/mutations/toasts/mapResponseMentionedToToast.ts b/packages/client/mutations/toasts/mapResponseMentionedToToast.ts index ccf6199d1ef..f36a13eacac 100644 --- a/packages/client/mutations/toasts/mapResponseMentionedToToast.ts +++ b/packages/client/mutations/toasts/mapResponseMentionedToToast.ts @@ -18,7 +18,6 @@ graphql` id name } - kudosEmojiUnicode } ` @@ -27,13 +26,11 @@ const mapResponseMentionedToToast = ( {atmosphere, history}: OnNextHistoryContext ): Snack | null => { if (!notification) return null - const {id: notificationId, meeting, response, kudosEmojiUnicode} = notification + const {id: notificationId, meeting, response} = notification const {preferredName: authorName} = response.user const {id: meetingId, name: meetingName} = meeting - const message = kudosEmojiUnicode - ? `${kudosEmojiUnicode} ${authorName} mentioned you and gave kudos in their response in ${meetingName}.` - : `${authorName} mentioned you in their response in ${meetingName}.` + const message = `${authorName} mentioned you in their response in ${meetingName}.` // :TODO: (jmtaber129): Check if we're already open to the relevant standup response discussion // thread, and do nothing if we are. @@ -50,14 +47,12 @@ const mapResponseMentionedToToast = ( }, onShow: () => { SendClientSideEvent(atmosphere, 'Snackbar Viewed', { - snackbarType: 'responseMentioned', - kudosEmojiUnicode + snackbarType: 'responseMentioned' }) }, onManualDismiss: () => { SendClientSideEvent(atmosphere, 'Snackbar Clicked', { - snackbarType: 'responseMentioned', - kudosEmojiUnicode + snackbarType: 'responseMentioned' }) } } diff --git a/packages/client/mutations/toasts/popNotificationToast.ts b/packages/client/mutations/toasts/popNotificationToast.ts index 9668d92814d..9c416d8643f 100644 --- a/packages/client/mutations/toasts/popNotificationToast.ts +++ b/packages/client/mutations/toasts/popNotificationToast.ts @@ -7,7 +7,6 @@ import {Snack} from '../../components/Snackbar' import {OnNextHandler, OnNextHistoryContext} from '../../types/relayMutations' import SetNotificationStatusMutation from '../SetNotificationStatusMutation' import mapDiscussionMentionedToToast from './mapDiscussionMentionedToToast' -import mapKudosReceivedToToast from './mapKudosReceivedToToast' import mapMentionedToToast from './mapMentionedToToast' import mapPromptToJoinOrgToToast from './mapPromptToJoinOrgToToast' import mapRequestToJoinOrgToToast from './mapRequestToJoinOrgToToast' @@ -26,8 +25,7 @@ const typePicker: Partial< TEAMS_LIMIT_EXCEEDED: mapTeamsLimitExceededToToast, TEAMS_LIMIT_REMINDER: mapTeamsLimitReminderToToast, PROMPT_TO_JOIN_ORG: mapPromptToJoinOrgToToast, - REQUEST_TO_JOIN_ORG: mapRequestToJoinOrgToToast, - KUDOS_RECEIVED: mapKudosReceivedToToast + REQUEST_TO_JOIN_ORG: mapRequestToJoinOrgToToast } graphql` @@ -43,7 +41,6 @@ graphql` ...mapTeamsLimitReminderToToast_notification @relay(mask: false) ...mapPromptToJoinOrgToToast_notification @relay(mask: false) ...mapRequestToJoinOrgToToast_notification @relay(mask: false) - ...mapKudosReceivedToToast_notification @relay(mask: false) } } ` diff --git a/packages/server/database/types/Notification.ts b/packages/server/database/types/Notification.ts index f49fe990c45..fcea79d8f4e 100644 --- a/packages/server/database/types/Notification.ts +++ b/packages/server/database/types/Notification.ts @@ -17,7 +17,6 @@ export type NotificationEnum = | 'TEAMS_LIMIT_REMINDER' | 'PROMPT_TO_JOIN_ORG' | 'REQUEST_TO_JOIN_ORG' - | 'KUDOS_RECEIVED' export interface NotificationInput { type: NotificationEnum diff --git a/packages/server/database/types/NotificationKudosReceived.ts b/packages/server/database/types/NotificationKudosReceived.ts deleted file mode 100644 index 04a6efcf8ec..00000000000 --- a/packages/server/database/types/NotificationKudosReceived.ts +++ /dev/null @@ -1,35 +0,0 @@ -import Notification from './Notification' - -interface Input { - userId: string - name: string | null - picture: string | null - senderUserId: string - meetingName: string - meetingId: string - emoji: string - emojiUnicode: string -} - -export default class NotificationKudosReceived extends Notification { - readonly type = 'KUDOS_RECEIVED' - name: string | null - picture: string | null - senderUserId: string - meetingName: string - meetingId: string - emoji: string - emojiUnicode: string - - constructor(input: Input) { - const {userId, name, picture, senderUserId, meetingName, meetingId, emoji, emojiUnicode} = input - super({userId, type: 'KUDOS_RECEIVED'}) - this.name = name - this.picture = picture - this.senderUserId = senderUserId - this.meetingName = meetingName - this.meetingId = meetingId - this.emoji = emoji - this.emojiUnicode = emojiUnicode - } -} diff --git a/packages/server/database/types/NotificationMentioned.ts b/packages/server/database/types/NotificationMentioned.ts index 16abf39e8c0..c35dff270bb 100644 --- a/packages/server/database/types/NotificationMentioned.ts +++ b/packages/server/database/types/NotificationMentioned.ts @@ -9,8 +9,6 @@ interface Input { meetingId: string retroReflectionId?: string | null retroDiscussStageIdx?: number | null - kudosEmoji?: string | null - kudosEmojiUnicode?: string | null } // TODO: replace NotificationResponseMentioned and NotificationResponseReplied with NotificationMentioned @@ -23,8 +21,6 @@ export default class NotificationMentioned extends Notification { meetingId: string retroReflectionId?: string | null retroDiscussStageIdx?: number | null - kudosEmoji?: string | null - kudosEmojiUnicode?: string | null constructor(input: Input) { const { @@ -35,9 +31,7 @@ export default class NotificationMentioned extends Notification { meetingName, meetingId, retroReflectionId, - retroDiscussStageIdx, - kudosEmoji, - kudosEmojiUnicode + retroDiscussStageIdx } = input super({userId, type: 'MENTIONED'}) this.senderName = senderName @@ -46,8 +40,6 @@ export default class NotificationMentioned extends Notification { this.meetingName = meetingName this.meetingId = meetingId this.retroReflectionId = retroReflectionId - this.kudosEmoji = kudosEmoji - this.kudosEmojiUnicode = kudosEmojiUnicode this.retroDiscussStageIdx = retroDiscussStageIdx } } diff --git a/packages/server/database/types/NotificationResponseMentioned.ts b/packages/server/database/types/NotificationResponseMentioned.ts index 562f40d1321..3368b54babb 100644 --- a/packages/server/database/types/NotificationResponseMentioned.ts +++ b/packages/server/database/types/NotificationResponseMentioned.ts @@ -4,23 +4,17 @@ interface Input { responseId: string meetingId: string userId: string - kudosEmoji?: string | null - kudosEmojiUnicode?: string | null } export default class NotificationResponseMentioned extends Notification { readonly type = 'RESPONSE_MENTIONED' responseId: string meetingId: string - kudosEmoji?: string | null - kudosEmojiUnicode?: string | null constructor(input: Input) { - const {responseId, meetingId, userId, kudosEmoji, kudosEmojiUnicode} = input + const {responseId, meetingId, userId} = input super({userId, type: 'RESPONSE_MENTIONED'}) this.responseId = responseId this.meetingId = meetingId - this.kudosEmoji = kudosEmoji - this.kudosEmojiUnicode = kudosEmojiUnicode } } diff --git a/packages/server/database/types/Team.ts b/packages/server/database/types/Team.ts index 0e6055d6281..b5bf6434a84 100644 --- a/packages/server/database/types/Team.ts +++ b/packages/server/database/types/Team.ts @@ -17,8 +17,6 @@ interface Input { orgId: string qualAIMeetingsCount?: number isOnboardTeam?: boolean - giveKudosWithEmoji?: boolean - kudosEmoji?: string updatedAt?: Date } @@ -36,8 +34,6 @@ export default class Team { trialStartDate?: Date | null orgId: string isOnboardTeam: boolean - giveKudosWithEmoji: boolean - kudosEmoji: string qualAIMeetingsCount: number updatedAt: Date constructor(input: Input) { @@ -48,8 +44,6 @@ export default class Team { id, isArchived, isOnboardTeam, - giveKudosWithEmoji, - kudosEmoji, lastMeetingType, isPaid, name, @@ -71,8 +65,6 @@ export default class Team { this.lastMeetingType = lastMeetingType ?? 'retrospective' this.isArchived = isArchived ?? false this.isOnboardTeam = isOnboardTeam ?? false - this.giveKudosWithEmoji = giveKudosWithEmoji ?? true - this.kudosEmoji = kudosEmoji ?? 'heart' this.isPaid = isPaid ?? true this.qualAIMeetingsCount = qualAIMeetingsCount ?? 0 } diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 09f2b463024..187d6802720 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -1,7 +1,6 @@ import getKysely from '../postgres/getKysely' import {getDiscussionsByIds} from '../postgres/queries/getDiscussionsByIds' import {getDomainJoinRequestsByIds} from '../postgres/queries/getDomainJoinRequestsByIds' -import {getKudosesByIds} from '../postgres/queries/getKudosesByIds' import getMeetingSeriesByIds from '../postgres/queries/getMeetingSeriesByIds' import getMeetingTemplatesByIds from '../postgres/queries/getMeetingTemplatesByIds' import {getTeamPromptResponsesByIds} from '../postgres/queries/getTeamPromptResponsesByIds' @@ -20,7 +19,6 @@ export const teamPromptResponses = primaryKeyLoaderMaker(getTeamPromptResponsesB export const meetingSeries = primaryKeyLoaderMaker(getMeetingSeriesByIds) export const meetingTemplates = primaryKeyLoaderMaker(getMeetingTemplatesByIds) export const domainJoinRequests = primaryKeyLoaderMaker(getDomainJoinRequestsByIds) -export const kudoses = primaryKeyLoaderMaker(getKudosesByIds) export const embeddingsMetadata = primaryKeyLoaderMaker((ids: readonly number[]) => { return getKysely().selectFrom('EmbeddingsMetadata').selectAll().where('id', 'in', ids).execute() diff --git a/packages/server/graphql/mutations/createReflection.ts b/packages/server/graphql/mutations/createReflection.ts index f9a7fd64760..7eaa94998f5 100644 --- a/packages/server/graphql/mutations/createReflection.ts +++ b/packages/server/graphql/mutations/createReflection.ts @@ -1,4 +1,3 @@ -import {RawDraftContentState} from 'draft-js' import {GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import extractTextFromDraftString from 'parabol-client/utils/draftjs/extractTextFromDraftString' @@ -67,37 +66,6 @@ export default { // RESOLUTION const plaintextContent = extractTextFromDraftString(normalizedContent) - const contentJson = JSON.parse(normalizedContent) as RawDraftContentState - const draftKudoses: { - id: string - receiverUserId: string - emoji: string - emojiUnicode: string - }[] = [] - - const {giveKudosWithEmoji, kudosEmojiUnicode, kudosEmoji} = team - if ( - giveKudosWithEmoji && - kudosEmojiUnicode && - plaintextContent.includes(kudosEmojiUnicode) && - contentJson.entityMap - ) { - const mentions = Object.values(contentJson.entityMap).filter( - (entity) => entity.type === 'MENTION' - ) - const userIds = [...new Set(mentions.map((mention) => mention.data.userId))].filter( - (userId) => userId !== viewerId - ) - - userIds.forEach((userId) => { - draftKudoses.push({ - id: 'DRAFT_KUDOS_' + generateUID(), - receiverUserId: userId, - emoji: kudosEmoji, - emojiUnicode: kudosEmojiUnicode - }) - }) - } const [entities, sentimentScore] = await Promise.all([ getReflectionEntities(plaintextContent), @@ -155,8 +123,7 @@ export default { meetingId, reflectionId: reflection.id, reflectionGroupId, - unlockedStageIds, - draftKudoses + unlockedStageIds } publish(SubscriptionChannel.MEETING, meetingId, 'CreateReflectionPayload', data, subOptions) return data diff --git a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts index 089ca295826..463eeecf935 100644 --- a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts @@ -284,9 +284,7 @@ const getSlackMessageForNotification = async ( const responseId = notification.responseId const response = await dataLoader.get('teamPromptResponses').loadNonNull(responseId) const author = await dataLoader.get('users').loadNonNull(response.userId) - const title = notification.kudosEmojiUnicode - ? `${notification.kudosEmojiUnicode} *${author.preferredName}* mentioned you and gave kudos in their response in *${meeting.name}*` - : `*${author.preferredName}* mentioned you in their response in *${meeting.name}*` + const title = `*${author.preferredName}* mentioned you in their response in *${meeting.name}*` const options = { searchParams: { @@ -331,9 +329,7 @@ const getSlackMessageForNotification = async ( ) } - const title = notification.kudosEmojiUnicode - ? `${notification.kudosEmojiUnicode} *${authorName}* mentioned you and gave kudos in their ${location} in *${meeting.name}*` - : `*${authorName}* mentioned you in their ${location} in *${meeting.name}*` + const title = `*${authorName}* mentioned you in their ${location} in *${meeting.name}*` return { buttonUrl, diff --git a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts index 9b47bbda356..2c9c282e648 100644 --- a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts @@ -1,4 +1,3 @@ -import {RawDraftContentState} from 'draft-js' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {DISCUSS, PARABOL_AI_USER_ID} from 'parabol-client/utils/constants' import getMeetingPhase from 'parabol-client/utils/getMeetingPhase' @@ -7,7 +6,6 @@ import {checkTeamsLimit} from '../../../billing/helpers/teamLimitsCheck' import getRethink from '../../../database/rethinkDriver' import {RDatum} from '../../../database/stricterR' import MeetingRetrospective from '../../../database/types/MeetingRetrospective' -import NotificationMentioned from '../../../database/types/NotificationMentioned' import TimelineEventRetroComplete from '../../../database/types/TimelineEventRetroComplete' import getKysely from '../../../postgres/getKysely' import removeSuggestedAction from '../../../safeMutations/removeSuggestedAction' @@ -20,7 +18,6 @@ import publish from '../../../utils/publish' import sendToSentry from '../../../utils/sendToSentry' import standardError from '../../../utils/standardError' import {InternalContext} from '../../graphql' -import publishNotification from '../../public/mutations/helpers/publishNotification' import sendNewMeetingSummary from './endMeeting/sendNewMeetingSummary' import gatherInsights from './gatherInsights' import generateWholeMeetingSentimentScore from './generateWholeMeetingSentimentScore' @@ -37,148 +34,6 @@ const getTranscription = async (recallBotId?: string | null) => { return await manager.getBotTranscript(recallBotId) } -const sendKudos = async ( - meeting: MeetingRetrospective, - teamId: string, - context: InternalContext -) => { - const {dataLoader, socketId: mutatorId} = context - const operationId = dataLoader.share() - const subOptions = {mutatorId, operationId} - const {id: meetingId, disableAnonymity} = meeting - const isAnonymous = !disableAnonymity - const pg = getKysely() - const r = await getRethink() - - const [reflections, team, meetingMembers] = await Promise.all([ - dataLoader.get('retroReflectionsByMeetingId').load(meetingId), - dataLoader.get('teams').loadNonNull(teamId), - dataLoader.get('meetingMembersByMeetingId').load(meetingId) - ]) - - const {phases} = meeting - const discussPhase = getPhase(phases, 'discuss') - if (!discussPhase) { - return - } - const {stages} = discussPhase - - const {giveKudosWithEmoji, kudosEmojiUnicode, kudosEmoji} = team - - if (!giveKudosWithEmoji || !kudosEmojiUnicode) { - return - } - - const kudosToInsert: { - senderUserId: string - receiverUserId: any - teamId: string - emoji: string - emojiUnicode: string - isAnonymous: boolean - reflectionId: string - }[] = [] - - const notificationsToInsert: NotificationMentioned[] = [] - - for (const reflection of reflections) { - const {id: reflectionId, content, plaintextContent, creatorId} = reflection - const senderUser = await dataLoader.get('users').loadNonNull(creatorId) - - const contentJson = JSON.parse(content) as RawDraftContentState - - const mentions = contentJson.entityMap - ? Object.values(contentJson.entityMap).filter((entity) => entity.type === 'MENTION') - : [] - - if (mentions.length) { - const userIds = [...new Set(mentions.map((mention) => mention.data.userId))].filter( - (userId) => userId !== creatorId - ) - - if (userIds.length) { - const retroDiscussStageIdx = - stages.findIndex((stage) => stage.reflectionGroupId === reflection.reflectionGroupId) + 1 - - if (plaintextContent.includes(kudosEmojiUnicode)) { - userIds.forEach((userId) => { - kudosToInsert.push({ - senderUserId: creatorId, - receiverUserId: userId, - teamId, - emoji: kudosEmoji, - emojiUnicode: kudosEmojiUnicode, - isAnonymous, - reflectionId - }) - notificationsToInsert.push( - new NotificationMentioned({ - userId, - senderUserId: creatorId, - meetingId, - retroDiscussStageIdx, - retroReflectionId: reflectionId, - kudosEmoji: team.kudosEmoji, - kudosEmojiUnicode: team.kudosEmojiUnicode, - meetingName: meeting.name, - senderName: isAnonymous ? null : senderUser.preferredName, - senderPicture: isAnonymous ? null : senderUser.picture - }) - ) - }) - } else { - const absentUserIds = userIds.filter( - (userId) => !meetingMembers.find((member) => member.userId === userId) - ) - - absentUserIds.forEach((userId) => { - notificationsToInsert.push( - new NotificationMentioned({ - userId, - senderUserId: creatorId, - meetingId, - retroDiscussStageIdx, - retroReflectionId: reflectionId, - meetingName: meeting.name, - senderName: isAnonymous ? null : senderUser.preferredName, - senderPicture: isAnonymous ? null : senderUser.picture - }) - ) - }) - } - } - } - } - - if (kudosToInsert.length) { - const insertedKudoses = await pg - .insertInto('Kudos') - .values(kudosToInsert) - .returning(['id', 'senderUserId', 'receiverUserId', 'emoji', 'emojiUnicode']) - .execute() - - insertedKudoses.forEach((kudos) => { - analytics.kudosSent( - {id: kudos.senderUserId}, - teamId, - kudos.id, - kudos.receiverUserId, - 'mention', - 'retrospective', - isAnonymous - ) - }) - } - - if (notificationsToInsert.length) { - await r.table('Notification').insert(notificationsToInsert).run() - notificationsToInsert.forEach((notification) => { - IntegrationNotifier.sendNotificationToUser?.(dataLoader, notification.id, notification.userId) - publishNotification(notification, subOptions) - }) - } -} - const summarizeRetroMeeting = async (meeting: MeetingRetrospective, context: InternalContext) => { const {dataLoader, authToken} = context const {id: meetingId, phases, facilitatorUserId, teamId, recallBotId} = meeting @@ -323,8 +178,7 @@ const safeEndRetrospective = async ({ .filter({isActive: false}) .delete() .run(), - updateTeamInsights(teamId, dataLoader), - sendKudos(completedRetrospective, teamId, context) + updateTeamInsights(teamId, dataLoader) ]) // wait for removeEmptyTasks before summarizeRetroMeeting // don't await for the OpenAI response or it'll hang for a while when ending the retro diff --git a/packages/server/graphql/private/typeDefs/_legacy.graphql b/packages/server/graphql/private/typeDefs/_legacy.graphql index 0aeca980ef5..a5aa1e4ff48 100644 --- a/packages/server/graphql/private/typeDefs/_legacy.graphql +++ b/packages/server/graphql/private/typeDefs/_legacy.graphql @@ -475,7 +475,6 @@ type Mutation { subscriptionId: ID! ): Boolean - """ When a new invoiceitem is sent from stripe, tag it with metadata """ @@ -1657,7 +1656,6 @@ enum NotificationEnum { TEAMS_LIMIT_REMINDER PROMPT_TO_JOIN_ORG REQUEST_TO_JOIN_ORG - KUDOS_RECEIVED } """ diff --git a/packages/server/graphql/private/typeDefs/updateOrgFeatureFlag.graphql b/packages/server/graphql/private/typeDefs/updateOrgFeatureFlag.graphql index 6806f1f872a..095acdd2fd7 100644 --- a/packages/server/graphql/private/typeDefs/updateOrgFeatureFlag.graphql +++ b/packages/server/graphql/private/typeDefs/updateOrgFeatureFlag.graphql @@ -12,7 +12,6 @@ enum OrganizationFeatureFlagsEnum { noTeamInsights singleColumnStandups publicTeams - kudos aiTemplate relatedDiscussions } diff --git a/packages/server/graphql/public/mutations/addReactjiToReactable.ts b/packages/server/graphql/public/mutations/addReactjiToReactable.ts index 0a343f921f4..2729d9b0d83 100644 --- a/packages/server/graphql/public/mutations/addReactjiToReactable.ts +++ b/packages/server/graphql/public/mutations/addReactjiToReactable.ts @@ -1,3 +1,4 @@ +import {sql} from 'kysely' import TeamPromptResponseId from 'parabol-client/shared/gqlIds/TeamPromptResponseId' import {SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' import {ValueOf} from 'parabol-client/types/generics' @@ -7,23 +8,16 @@ import {RDatum} from '../../../database/stricterR' import Comment from '../../../database/types/Comment' import {Reactable} from '../../../database/types/Reactable' import Reflection from '../../../database/types/Reflection' +import getKysely from '../../../postgres/getKysely' import {analytics} from '../../../utils/analytics/analytics' import {getUserId} from '../../../utils/authorization' import emojiIds from '../../../utils/emojiIds' import getGroupedReactjis from '../../../utils/getGroupedReactjis' import publish from '../../../utils/publish' import {GQLContext} from '../../graphql' - -import {sql} from 'kysely' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' -import NotificationKudosReceived from '../../../database/types/NotificationKudosReceived' -import getKysely from '../../../postgres/getKysely' -import {TeamPromptResponse} from '../../../postgres/queries/getTeamPromptResponsesByIds' -import {AnyMeeting} from '../../../postgres/types/Meeting' import {ReactableEnumType} from '../../types/ReactableEnum' import getReactableType from '../../types/getReactableType' import {MutationResolvers} from '../resolverTypes' -import publishNotification from './helpers/publishNotification' const rethinkTableLookup = { COMMENT: 'Comment', @@ -34,28 +28,6 @@ const pgDataloaderLookup = { RESPONSE: 'teamPromptResponses' } as const -const getReactableCreatorId = ( - reactableType: ReactableEnumType, - reactable: Reactable, - meeting: AnyMeeting -) => { - if (reactableType === 'COMMENT') { - if ((reactable as Comment).isAnonymous) { - return null - } - return (reactable as Comment).createdBy - } else if (reactableType === 'REFLECTION') { - if ((meeting as MeetingRetrospective).disableAnonymity) { - return (reactable as Reflection).creatorId - } - return null - } else if (reactableType === 'RESPONSE') { - return (reactable as TeamPromptResponse).userId - } - - return null -} - const addReactjiToReactable: MutationResolvers['addReactjiToReactable'] = async ( _source: unknown, { @@ -171,54 +143,9 @@ const addReactjiToReactable: MutationResolvers['addReactjiToReactable'] = async } const meeting = await dataLoader.get('newMeetings').load(meetingId) - const {meetingType, teamId} = meeting - const team = await dataLoader.get('teams').loadNonNull(teamId) - - const reactableCreatorId = getReactableCreatorId(reactableType, reactable, meeting) - - let addedKudosId = null - if ( - !isRemove && - team.giveKudosWithEmoji && - reactji === team.kudosEmoji && - reactableCreatorId && - reactableCreatorId !== viewerId - ) { - addedKudosId = (await pg - .insertInto('Kudos') - .values({ - senderUserId: viewerId, - receiverUserId: reactableCreatorId, - reactableType: reactableType, - reactableId: reactableId, - teamId, - emoji: team.kudosEmoji, - emojiUnicode: team.kudosEmojiUnicode - }) - .returning('id') - .executeTakeFirst())!.id - - const senderUser = await dataLoader.get('users').loadNonNull(viewerId) - - const notificationsToInsert = new NotificationKudosReceived({ - userId: reactableCreatorId, - senderUserId: viewerId, - meetingId, - meetingName: meeting.name, - emoji: team.kudosEmoji, - emojiUnicode: team.kudosEmojiUnicode, - name: senderUser.preferredName, - picture: senderUser.picture - }) - - await r.table('Notification').insert(notificationsToInsert).run() - - publishNotification(notificationsToInsert, subOptions) - - analytics.kudosSent(viewer, teamId, addedKudosId, reactableCreatorId, 'reaction', meetingType) - } + const {meetingType} = meeting - const data = {reactableId, reactableType, addedKudosId} + const data = {reactableId, reactableType} analytics.reactjiInteracted( viewer, diff --git a/packages/server/graphql/public/mutations/helpers/__tests__/getKudosUserIdsFromJson.test.ts b/packages/server/graphql/public/mutations/helpers/__tests__/getKudosUserIdsFromJson.test.ts deleted file mode 100644 index 9bdff492939..00000000000 --- a/packages/server/graphql/public/mutations/helpers/__tests__/getKudosUserIdsFromJson.test.ts +++ /dev/null @@ -1,252 +0,0 @@ -import {JSONContent} from '@tiptap/core' -import {getKudosUserIdsFromJson} from '../getKudosUserIdsFromJson' - -describe('findMentionsByEmoji', () => { - let doc: JSONContent - - beforeAll(() => { - doc = { - type: 'doc', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Paragraph1' - } - ] - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Paragraph2' - } - ] - }, - { - type: 'paragraph' - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Paragraph from new line' - } - ] - }, - { - type: 'paragraph' - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Paragraph from new line' - }, - { - type: 'hardBreak' - }, - { - type: 'text', - text: 'and break in the same paragraph' - } - ] - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'Just new line with mention ' - }, - { - type: 'mention', - attrs: { - id: 'user_id_1', - label: 'userone' - } - }, - { - type: 'text', - text: ' ❤️' - } - ] - }, - { - type: 'paragraph' - }, - { - type: 'paragraph', - content: [ - { - type: 'text', - text: '❤️ Another mentions ' - }, - { - type: 'mention', - attrs: { - id: 'user_id_2', - label: 'usertwo' - } - }, - { - type: 'hardBreak' - }, - { - type: 'mention', - attrs: { - id: 'user_id_3', - label: 'userthree' - } - } - ] - }, - { - type: 'paragraph', - content: [ - { - type: 'mention', - attrs: { - id: 'user_id_supermention', - label: 'supermention' - } - }, - { - type: 'text', - text: ' 🌮' - } - ] - }, - { - type: 'paragraph' - }, - { - type: 'paragraph', - content: [ - { - type: 'mention', - attrs: { - id: 'user_id_4', - label: 'userone' - } - }, - { - type: 'text', - text: ' ' - }, - { - type: 'mention', - attrs: { - id: 'user_id_5', - label: 'userfour' - } - }, - { - type: 'text', - text: ' both mentioned ❤️' - } - ] - }, - { - type: 'bulletList', - content: [ - { - type: 'listItem', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'text', - text: 'List item with mention ' - }, - { - type: 'mention', - attrs: { - id: 'user_id_list_1', - label: 'userlistone' - } - }, - { - type: 'text', - text: ' ❤️ in a list' - } - ] - }, - { - type: 'listItem', - content: [ - { - type: 'bulletList', - content: [ - { - type: 'listItem', - content: [ - { - type: 'paragraph', - content: [ - { - type: 'mention', - attrs: { - id: 'user_id_nested_list', - label: 'usernestedlist' - } - }, - { - type: 'text', - text: ' Nested mention ❤️' - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - ] - } - }) - - it('returns correct mention user IDs for emoji ❤️', () => { - const emoji = '❤️' - const result = getKudosUserIdsFromJson(doc, emoji) - expect(result).toEqual([ - 'user_id_1', - 'user_id_2', - 'user_id_3', - 'user_id_4', - 'user_id_5', - 'user_id_list_1', - 'user_id_nested_list' - ]) - }) - - it('returns correct mention user IDs for different emoji (🌮)', () => { - const emoji = '🌮' - const result = getKudosUserIdsFromJson(doc, emoji) - expect(result).toEqual(['user_id_supermention']) - }) - - it('returns an empty array for an emoji with no mentions (🔥)', () => { - const emoji = '🔥' - const result = getKudosUserIdsFromJson(doc, emoji) - expect(result).toEqual([]) - }) - - it('does not include duplicate IDs', () => { - const emoji = '❤️' - const result = getKudosUserIdsFromJson(doc, emoji) - const uniqueResult = Array.from(new Set(result)) - expect(result).toEqual(uniqueResult) - }) -}) diff --git a/packages/server/graphql/public/mutations/helpers/getKudosUserIdsFromJson.ts b/packages/server/graphql/public/mutations/helpers/getKudosUserIdsFromJson.ts deleted file mode 100644 index bfcedc7c256..00000000000 --- a/packages/server/graphql/public/mutations/helpers/getKudosUserIdsFromJson.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {JSONContent} from '@tiptap/core' - -export const getKudosUserIdsFromJson = (doc: JSONContent, emoji: string): string[] => { - const mentionedIds = new Set() - - const searchForMentionsAndEmojis = (node: JSONContent | undefined) => { - if (!node || !node.content) return - - node.content.forEach((contentNode) => { - if (contentNode.type === 'paragraph') { - const tempMentions: string[] = [] - let emojiFound = false - - contentNode.content?.forEach((item) => { - if (item.type === 'text' && item.text?.includes(emoji)) { - emojiFound = true - } - if (item.type === 'mention') { - tempMentions.push(item.attrs?.id) - } - }) - - if (emojiFound) { - tempMentions.forEach((id) => mentionedIds.add(id)) - } - } else if (contentNode.content) { - searchForMentionsAndEmojis(contentNode) - } - }) - } - - searchForMentionsAndEmojis(doc) - - return Array.from(mentionedIds) -} diff --git a/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts b/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts index b48fd4e37e7..e9c9805d0d8 100644 --- a/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts +++ b/packages/server/graphql/public/mutations/helpers/publishTeamPromptMentions.ts @@ -17,30 +17,17 @@ const getMentionedUserIdsFromContent = (content: JSONContent): string[] => { const createTeamPromptMentionNotifications = async ( oldResponse: TeamPromptResponse | undefined, - newResponse: TeamPromptResponse, - addedKudoses: - | { - id: number - emoji: string | null - emojiUnicode: string | null - receiverUserId: string - }[] - | null + newResponse: TeamPromptResponse ) => { // Get mentions from previous and new content. const newResponseMentions = getMentionedUserIdsFromContent(newResponse.content) const oldResponseMentions = oldResponse ? getMentionedUserIdsFromContent(oldResponse.content) : [] - const addedKudosesUserIds = addedKudoses?.map((kudos) => kudos.receiverUserId) ?? [] - // Create notifications that should be added. const addedMentions = Array.from( new Set( newResponseMentions.filter( - (mention) => - (!oldResponseMentions.includes(mention) && newResponse.userId !== mention) || - // Send mention notification anyway in case it is also include kudos - addedKudosesUserIds.includes(mention) + (mention) => !oldResponseMentions.includes(mention) && newResponse.userId !== mention ) ) ) @@ -50,13 +37,10 @@ const createTeamPromptMentionNotifications = async ( } const notificationsToAdd = addedMentions.map((mention) => { - const kudos = addedKudoses?.find((kudos) => kudos.receiverUserId === mention) return new NotificationResponseMentioned({ userId: mention, responseId: newResponse.id, - meetingId: newResponse.meetingId, - kudosEmoji: kudos?.emoji, - kudosEmojiUnicode: kudos?.emojiUnicode + meetingId: newResponse.meetingId }) }) diff --git a/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts b/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts index 131fe934498..e59e43c53fe 100644 --- a/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts +++ b/packages/server/graphql/public/mutations/upsertTeamPromptResponse.ts @@ -2,7 +2,6 @@ import {generateText, JSONContent} from '@tiptap/core' import {createEditorExtensions} from 'parabol-client/components/promptResponse/tiptapConfig' import TeamPromptResponseId from 'parabol-client/shared/gqlIds/TeamPromptResponseId' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getKysely from '../../../postgres/getKysely' import {TeamPromptResponse} from '../../../postgres/queries/getTeamPromptResponsesByIds' import {upsertTeamPromptResponse as upsertTeamPromptResponseQuery} from '../../../postgres/queries/upsertTeamPromptResponses' import {analytics} from '../../../utils/analytics/analytics' @@ -11,7 +10,6 @@ import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' import {IntegrationNotifier} from '../../mutations/helpers/notifications/IntegrationNotifier' import {MutationResolvers} from '../resolverTypes' -import {getKudosUserIdsFromJson} from './helpers/getKudosUserIdsFromJson' import publishNotification from './helpers/publishNotification' import createTeamPromptMentionNotifications from './helpers/publishTeamPromptMentions' @@ -20,7 +18,6 @@ const upsertTeamPromptResponse: MutationResolvers['upsertTeamPromptResponse'] = {teamPromptResponseId: inputTeamPromptResponseId, meetingId, content}, {authToken, dataLoader, socketId: mutatorId} ) => { - const pg = getKysely() const viewerId = getUserId(authToken) const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -81,47 +78,6 @@ const upsertTeamPromptResponse: MutationResolvers['upsertTeamPromptResponse'] = }) ) - const team = await dataLoader.get('teams').loadNonNull(teamId) - const {kudosEmoji, kudosEmojiUnicode} = team - - let insertedKudoses: - | { - id: number - receiverUserId: string - emoji: string | null - emojiUnicode: string - }[] - | null = null - if (team.giveKudosWithEmoji && kudosEmojiUnicode) { - const oldKudosUserIds = oldTeamPromptResponse - ? getKudosUserIdsFromJson(oldTeamPromptResponse.content, kudosEmojiUnicode) - : [] - const newKudosUserIds = getKudosUserIdsFromJson(contentJSON, kudosEmojiUnicode) - const kudosUserIds = newKudosUserIds.filter( - (userId) => !oldKudosUserIds.includes(userId) && userId !== viewerId - ) - if (kudosUserIds.length) { - const kudosRows = kudosUserIds.map((userId) => ({ - senderUserId: viewerId, - receiverUserId: userId, - teamId, - emoji: kudosEmoji, - emojiUnicode: kudosEmojiUnicode, - teamPromptResponseId: TeamPromptResponseId.split(teamPromptResponseId) - })) - - insertedKudoses = await pg - .insertInto('Kudos') - .values(kudosRows) - .returning(['id', 'receiverUserId', 'emoji', 'emojiUnicode']) - .execute() - - insertedKudoses.forEach((kudos) => { - analytics.kudosSent(user, teamId, kudos.id, kudos.receiverUserId, 'mention', 'teamPrompt') - }) - } - } - dataLoader.get('teamPromptResponses').clear(teamPromptResponseId) const newTeamPromptResponse = await dataLoader @@ -130,15 +86,13 @@ const upsertTeamPromptResponse: MutationResolvers['upsertTeamPromptResponse'] = const notifications = await createTeamPromptMentionNotifications( oldTeamPromptResponse, - newTeamPromptResponse, - insertedKudoses + newTeamPromptResponse ) const data = { meetingId, teamPromptResponseId, - addedNotificationIds: notifications.map((notification) => notification.id), - addedKudosesIds: insertedKudoses?.map((row) => row.id) + addedNotificationIds: notifications.map((notification) => notification.id) } notifications.forEach((notification) => { diff --git a/packages/server/graphql/public/typeDefs/AddReactjiToReactablePayload.graphql b/packages/server/graphql/public/typeDefs/AddReactjiToReactablePayload.graphql index 6d2d27cae4b..3c0c3daca4f 100644 --- a/packages/server/graphql/public/typeDefs/AddReactjiToReactablePayload.graphql +++ b/packages/server/graphql/public/typeDefs/AddReactjiToReactablePayload.graphql @@ -1,6 +1,5 @@ type AddReactjiToReactableSuccess { reactable: Reactable! - addedKudos: Kudos } union AddReactjiToReactablePayload = AddReactjiToReactableSuccess | ErrorPayload diff --git a/packages/server/graphql/public/typeDefs/Kudos.graphql b/packages/server/graphql/public/typeDefs/Kudos.graphql deleted file mode 100644 index 946d7f5e6ba..00000000000 --- a/packages/server/graphql/public/typeDefs/Kudos.graphql +++ /dev/null @@ -1,30 +0,0 @@ -""" -A Kudos -""" -type Kudos { - """ - Id - """ - id: Int! - - """ - User who received kudos - """ - receiverUser: User! - - """ - Use who sent kudos - Can be null if kudos is anonymous - """ - senderUser: User - - """ - emoji name - """ - emoji: String! - - """ - emoji unicode character - """ - emojiUnicode: String! -} diff --git a/packages/server/graphql/public/typeDefs/NotifyKudosReceived.graphql b/packages/server/graphql/public/typeDefs/NotifyKudosReceived.graphql deleted file mode 100644 index 57fe098d3a4..00000000000 --- a/packages/server/graphql/public/typeDefs/NotifyKudosReceived.graphql +++ /dev/null @@ -1,52 +0,0 @@ -type NotifyKudosReceived implements Notification { - """ - A shortid for the notification - """ - id: ID! - - """ - UNREAD if new, READ if viewer has seen it, CLICKED if viewed clicked it - """ - status: NotificationStatusEnum! - - """ - The datetime to activate the notification & send it to the client - """ - createdAt: DateTime! - type: NotificationEnum! - - """ - The userId that should see this notification - """ - userId: ID! - - """ - Sender name - """ - name: String - - """ - Sender picture - """ - picture: URL - - """ - Meeting name - """ - meetingName: String! - - """ - Meeting id - """ - meetingId: String! - - """ - Kudos emoji - """ - emoji: String! - - """ - Kudos emoji unicode - """ - emojiUnicode: String! -} diff --git a/packages/server/graphql/public/typeDefs/NotifyMentioned.graphql b/packages/server/graphql/public/typeDefs/NotifyMentioned.graphql index c64241b170b..e6fc11e5248 100644 --- a/packages/server/graphql/public/typeDefs/NotifyMentioned.graphql +++ b/packages/server/graphql/public/typeDefs/NotifyMentioned.graphql @@ -49,14 +49,4 @@ type NotifyMentioned implements Notification { Linked discussion stage number """ retroDiscussStageIdx: Int - - """ - Kudos emoji if mention includes kudos - """ - kudosEmoji: String - - """ - Kudos emoji unicode character - """ - kudosEmojiUnicode: String } diff --git a/packages/server/graphql/public/typeDefs/NotifyResponseMentioned.graphql b/packages/server/graphql/public/typeDefs/NotifyResponseMentioned.graphql index 6af90db36ef..cf2bb5e4d5b 100644 --- a/packages/server/graphql/public/typeDefs/NotifyResponseMentioned.graphql +++ b/packages/server/graphql/public/typeDefs/NotifyResponseMentioned.graphql @@ -39,14 +39,4 @@ type NotifyResponseMentioned implements Notification { The meeting the user was mentioned in. """ meeting: TeamPromptMeeting! - - """ - Kudos emoji if mention includes kudos - """ - kudosEmoji: String - - """ - kudos emoji unicode character - """ - kudosEmojiUnicode: String } diff --git a/packages/server/graphql/public/typeDefs/Organization.graphql b/packages/server/graphql/public/typeDefs/Organization.graphql index 5c398e3330a..eda5e8f299a 100644 --- a/packages/server/graphql/public/typeDefs/Organization.graphql +++ b/packages/server/graphql/public/typeDefs/Organization.graphql @@ -187,7 +187,6 @@ type OrganizationFeatureFlags { noTeamInsights: Boolean! singleColumnStandups: Boolean! publicTeams: Boolean! - kudos: Boolean! aiTemplate: Boolean! relatedDiscussions: Boolean! } diff --git a/packages/server/graphql/public/typeDefs/Team.graphql b/packages/server/graphql/public/typeDefs/Team.graphql index 4ca3fb23ba3..718c296f4d1 100644 --- a/packages/server/graphql/public/typeDefs/Team.graphql +++ b/packages/server/graphql/public/typeDefs/Team.graphql @@ -180,16 +180,6 @@ type Team { """ viewerTeamMember: TeamMember - """ - Enable giving kudos with emoji - """ - giveKudosWithEmoji: Boolean! - - """ - Emoji that will be used for giving kudos - """ - kudosEmoji: String! - """ The team member that is the team lead """ diff --git a/packages/server/graphql/public/typeDefs/_legacy.graphql b/packages/server/graphql/public/typeDefs/_legacy.graphql index 1f65bf27ca9..6c2322af589 100644 --- a/packages/server/graphql/public/typeDefs/_legacy.graphql +++ b/packages/server/graphql/public/typeDefs/_legacy.graphql @@ -6774,11 +6774,6 @@ type CreateReflectionPayload { The stages that were unlocked by navigating """ unlockedStages: [NewMeetingStage!] - - """ - Kudoses that might be sent by the end of the retro - """ - draftKudoses: [Kudos!] } input CreateReflectionInput { diff --git a/packages/server/graphql/public/typeDefs/upsertTeamPromptResponse.graphql b/packages/server/graphql/public/typeDefs/upsertTeamPromptResponse.graphql index d0cbd048545..7681dc4cd46 100644 --- a/packages/server/graphql/public/typeDefs/upsertTeamPromptResponse.graphql +++ b/packages/server/graphql/public/typeDefs/upsertTeamPromptResponse.graphql @@ -32,11 +32,6 @@ type UpsertTeamPromptResponseSuccess { the updated meeting """ meeting: NewMeeting - - """ - Kudos added with the response - """ - addedKudoses: [Kudos!] } union UpsertTeamPromptResponsePayload = UpsertTeamPromptResponseSuccess | ErrorPayload diff --git a/packages/server/graphql/public/types/AddReactjiToReactableSuccess.ts b/packages/server/graphql/public/types/AddReactjiToReactableSuccess.ts index 92627987433..2bb6dfee67e 100644 --- a/packages/server/graphql/public/types/AddReactjiToReactableSuccess.ts +++ b/packages/server/graphql/public/types/AddReactjiToReactableSuccess.ts @@ -4,15 +4,10 @@ import {ReactableEnumType} from '../../types/ReactableEnum' export type AddReactjiToReactableSuccessSource = { reactableId: string reactableType: ReactableEnumType - addedKudosId?: number | null } const AddReactjiToReactableSuccess: AddReactjiToReactableSuccessResolvers = { reactable: async ({reactableId, reactableType}, _args: unknown, {dataLoader}) => { return await dataLoader.get('reactables').load({id: reactableId, type: reactableType}) - }, - addedKudos: async ({addedKudosId}, _args: unknown, {dataLoader}) => { - if (!addedKudosId) return null - return dataLoader.get('kudoses').load(addedKudosId) } } diff --git a/packages/server/graphql/public/types/Kudos.ts b/packages/server/graphql/public/types/Kudos.ts deleted file mode 100644 index 26a3a42ed64..00000000000 --- a/packages/server/graphql/public/types/Kudos.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {KudosResolvers} from '../resolverTypes' - -const Kudos: KudosResolvers = { - receiverUser: async ({receiverUserId}, _args, {dataLoader}) => { - return dataLoader.get('users').loadNonNull(receiverUserId) - }, - senderUser: async ({senderUserId, isAnonymous}, _args, {dataLoader}) => { - return isAnonymous ? null : dataLoader.get('users').loadNonNull(senderUserId) - } -} - -export default Kudos diff --git a/packages/server/graphql/public/types/NotifyKudosReceived.ts b/packages/server/graphql/public/types/NotifyKudosReceived.ts deleted file mode 100644 index 53337d84bf2..00000000000 --- a/packages/server/graphql/public/types/NotifyKudosReceived.ts +++ /dev/null @@ -1,12 +0,0 @@ -import {NotifyKudosReceivedResolvers} from '../resolverTypes' - -const NotifyKudosReceived: NotifyKudosReceivedResolvers = { - __isTypeOf: ({type}) => type === 'KUDOS_RECEIVED', - emojiUnicode: ({emojiUnicode}) => emojiUnicode ?? '❤️', - picture: async ({picture}, _args, {dataLoader}) => { - if (!picture) return null - return dataLoader.get('fileStoreAsset').load(picture) - } -} - -export default NotifyKudosReceived diff --git a/packages/server/graphql/public/types/OrganizationFeatureFlags.ts b/packages/server/graphql/public/types/OrganizationFeatureFlags.ts index 6dce6268a4a..cf3ba1898a5 100644 --- a/packages/server/graphql/public/types/OrganizationFeatureFlags.ts +++ b/packages/server/graphql/public/types/OrganizationFeatureFlags.ts @@ -11,7 +11,6 @@ const OrganizationFeatureFlags: OrganizationFeatureFlagsResolvers = { noTeamInsights: ({noTeamInsights}) => !!noTeamInsights, publicTeams: ({publicTeams}) => !!publicTeams, singleColumnStandups: ({singleColumnStandups}) => !!singleColumnStandups, - kudos: ({kudos}) => !!kudos, aiTemplate: ({aiTemplate}) => !!aiTemplate, relatedDiscussions: ({relatedDiscussions}) => !!relatedDiscussions } diff --git a/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts b/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts index 425879111b9..cf51634ce03 100644 --- a/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts +++ b/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts @@ -1,10 +1,8 @@ -import isValid from '../../../graphql/isValid' import {UpsertTeamPromptResponseSuccessResolvers} from '../resolverTypes' export type UpsertTeamPromptResponseSuccessSource = { teamPromptResponseId: string meetingId: string - addedKudosesIds?: number[] } const UpsertTeamPromptResponseSuccess: UpsertTeamPromptResponseSuccessResolvers = { @@ -15,14 +13,6 @@ const UpsertTeamPromptResponseSuccess: UpsertTeamPromptResponseSuccessResolvers meeting: async (source, _args, {dataLoader}) => { const {meetingId} = source return dataLoader.get('newMeetings').load(meetingId) - }, - addedKudoses: async (source, _args, {dataLoader}) => { - const {addedKudosesIds} = source - if (!addedKudosesIds) { - return null - } - - return (await dataLoader.get('kudoses').loadMany(addedKudosesIds)).filter(isValid) } } diff --git a/packages/server/graphql/types/NotificationEnum.ts b/packages/server/graphql/types/NotificationEnum.ts index ad75a8dbe9e..df78dcedcb3 100644 --- a/packages/server/graphql/types/NotificationEnum.ts +++ b/packages/server/graphql/types/NotificationEnum.ts @@ -12,7 +12,6 @@ export type NotificationEnumType = | 'TEAMS_LIMIT_REMINDER' | 'PROMPT_TO_JOIN_ORG' | 'REQUEST_TO_JOIN_ORG' - | 'KUDOS_RECEIVED' const NotificationEnum = new GraphQLEnumType({ name: 'NotificationEnum', @@ -28,8 +27,7 @@ const NotificationEnum = new GraphQLEnumType({ TEAMS_LIMIT_EXCEEDED: {}, TEAMS_LIMIT_REMINDER: {}, PROMPT_TO_JOIN_ORG: {}, - REQUEST_TO_JOIN_ORG: {}, - KUDOS_RECEIVED: {} + REQUEST_TO_JOIN_ORG: {} } }) diff --git a/packages/server/postgres/migrations/1716914102795_removeKudos.ts b/packages/server/postgres/migrations/1716914102795_removeKudos.ts new file mode 100644 index 00000000000..8bbb41800b6 --- /dev/null +++ b/packages/server/postgres/migrations/1716914102795_removeKudos.ts @@ -0,0 +1,21 @@ +import {Kysely, PostgresDialect} from 'kysely' +import getPg from '../getPg' + +export async function up() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + await pg.schema.dropTable('Kudos').execute() + await pg.schema + .alterTable('Team') + .dropColumn('giveKudosWithEmoji') + .dropColumn('kudosEmoji') + .execute() +} + +export async function down() { + // noop +} diff --git a/packages/server/postgres/queries/getKudosesByIds.ts b/packages/server/postgres/queries/getKudosesByIds.ts deleted file mode 100644 index 97418629a70..00000000000 --- a/packages/server/postgres/queries/getKudosesByIds.ts +++ /dev/null @@ -1,6 +0,0 @@ -import getKysely from '../../postgres/getKysely' - -export const getKudosesByIds = async (kudosIds: number[] | readonly number[]) => { - const pg = getKysely() - return pg.selectFrom('Kudos').selectAll().where('id', 'in', kudosIds).execute() -} diff --git a/packages/server/utils/analytics/analytics.ts b/packages/server/utils/analytics/analytics.ts index df6d07c69ce..741e73ab6ab 100644 --- a/packages/server/utils/analytics/analytics.ts +++ b/packages/server/utils/analytics/analytics.ts @@ -165,8 +165,6 @@ export type AnalyticsEvent = | 'Reset Groups Clicked' // Conversion Tracking | 'Conversion Modal Pay Later Clicked' - // kudos - | 'Kudos Sent' | 'Icebreaker Modified' // Deprecated Events // These will be replaced with tracking plan compliant versions by the data team @@ -699,26 +697,6 @@ class Analytics { this.track(user, 'AutoJoined Team', {userId: user.id, teamId}) } - kudosSent = ( - user: AnalyticsUser, - teamId: string, - kudosId: number, - receiverUserId: string, - kudosType: 'mention' | 'reaction', - meetingType: MeetingTypeEnum, - isAnonymous = false - ) => { - this.track(user, 'Kudos Sent', { - userId: user.id, - teamId, - kudosId, - receiverUserId, - kudosType, - meetingType, - isAnonymous - }) - } - icebreakerModified = ( user: AnalyticsUser, meetingId: string, From eec025e3e22202c0c4c5630d2e6a75db76e3008f Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Thu, 30 May 2024 16:45:41 +0200 Subject: [PATCH 241/529] feat: Add Jira Server to Your Work (#9794) --- .../TeamPrompt/TeamPromptWorkDrawer.tsx | 27 ++++ .../WorkDrawer/JiraServerIntegrationPanel.tsx | 95 ++++++++++++++ .../JiraServerIntegrationResults.tsx | 115 +++++++++++++++++ .../JiraServerIntegrationResultsRoot.tsx | 31 +++++ .../WorkDrawer/JiraServerObjectCard.tsx | 121 ++++++++++++++++++ .../graphql/private/typeDefs/_legacy.graphql | 6 + .../graphql/types/JiraServerIntegration.ts | 9 +- .../server/graphql/types/JiraServerIssue.ts | 8 ++ .../jiraServer/JiraServerRestManager.ts | 2 + 9 files changed, 410 insertions(+), 4 deletions(-) create mode 100644 packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationPanel.tsx create mode 100644 packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationResults.tsx create mode 100644 packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationResultsRoot.tsx create mode 100644 packages/client/components/TeamPrompt/WorkDrawer/JiraServerObjectCard.tsx diff --git a/packages/client/components/TeamPrompt/TeamPromptWorkDrawer.tsx b/packages/client/components/TeamPrompt/TeamPromptWorkDrawer.tsx index 9f51d4e28cd..836d3aea0e2 100644 --- a/packages/client/components/TeamPrompt/TeamPromptWorkDrawer.tsx +++ b/packages/client/components/TeamPrompt/TeamPromptWorkDrawer.tsx @@ -8,12 +8,14 @@ import gcalLogo from '../../styles/theme/images/graphics/google-calendar.svg' import SendClientSideEvent from '../../utils/SendClientSideEvent' import GitHubSVG from '../GitHubSVG' import JiraSVG from '../JiraSVG' +import JiraServerSVG from '../JiraServerSVG' import ParabolLogoSVG from '../ParabolLogoSVG' import Tab from '../Tab/Tab' import Tabs from '../Tabs/Tabs' import GCalIntegrationPanel from './WorkDrawer/GCalIntegrationPanel' import GitHubIntegrationPanel from './WorkDrawer/GitHubIntegrationPanel' import JiraIntegrationPanel from './WorkDrawer/JiraIntegrationPanel' +import JiraServerIntegrationPanel from './WorkDrawer/JiraServerIntegrationPanel' import ParabolTasksPanel from './WorkDrawer/ParabolTasksPanel' interface Props { @@ -32,11 +34,26 @@ const TeamPromptWorkDrawer = (props: Props) => { ...GitHubIntegrationPanel_meeting ...JiraIntegrationPanel_meeting ...GCalIntegrationPanel_meeting + ...JiraServerIntegrationPanel_meeting + viewerMeetingMember { + teamMember { + teamId + integrations { + jiraServer { + sharedProviders { + id + } + } + } + } + } } `, meetingRef ) const atmosphere = useAtmosphere() + const hasJiraServer = + !!meeting.viewerMeetingMember?.teamMember?.integrations.jiraServer?.sharedProviders?.length useEffect(() => { SendClientSideEvent(atmosphere, 'Your Work Drawer Impression', { @@ -54,6 +71,16 @@ const TeamPromptWorkDrawer = (props: Props) => { label: 'Parabol', Component: ParabolTasksPanel }, + ...(hasJiraServer + ? [ + { + icon: , + service: 'jiraServer', + label: 'Jira Server', + Component: JiraServerIntegrationPanel + } + ] + : []), {icon: , service: 'github', label: 'GitHub', Component: GitHubIntegrationPanel}, {icon: , service: 'jira', label: 'Jira', Component: JiraIntegrationPanel}, { diff --git a/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationPanel.tsx b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationPanel.tsx new file mode 100644 index 00000000000..da6eedf1a05 --- /dev/null +++ b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationPanel.tsx @@ -0,0 +1,95 @@ +import graphql from 'babel-plugin-relay/macro' +import React from 'react' +import {useFragment} from 'react-relay' +import {JiraServerIntegrationPanel_meeting$key} from '../../../__generated__/JiraServerIntegrationPanel_meeting.graphql' +import useAtmosphere from '../../../hooks/useAtmosphere' +import useMutationProps from '../../../hooks/useMutationProps' +import jiraServerSVG from '../../../styles/theme/images/graphics/jira-software-blue.svg' +import JiraServerClientManager from '../../../utils/JiraServerClientManager' +import SendClientSideEvent from '../../../utils/SendClientSideEvent' +import JiraServerIntegrationResultsRoot from './JiraServerIntegrationResultsRoot' + +interface Props { + meetingRef: JiraServerIntegrationPanel_meeting$key +} + +const JiraServerIntegrationPanel = (props: Props) => { + const {meetingRef} = props + const meeting = useFragment( + graphql` + fragment JiraServerIntegrationPanel_meeting on TeamPromptMeeting { + id + teamId + viewerMeetingMember { + teamMember { + teamId + integrations { + jiraServer { + auth { + id + isActive + } + sharedProviders { + id + } + } + } + } + } + } + `, + meetingRef + ) + + const teamMember = meeting.viewerMeetingMember?.teamMember + const integration = teamMember?.integrations.jiraServer + const providerId = integration?.sharedProviders?.[0]?.id + const isActive = !!integration?.auth?.isActive + + const atmosphere = useAtmosphere() + const mutationProps = useMutationProps() + const {error, onError} = mutationProps + + const authJiraServer = () => { + if (!teamMember || !providerId) { + return onError(new Error('Could not find integration provider')) + } + JiraServerClientManager.openOAuth(atmosphere, providerId, teamMember.teamId, mutationProps) + + SendClientSideEvent(atmosphere, 'Your Work Drawer Integration Connected', { + teamId: meeting.teamId, + meetingId: meeting.id, + service: 'jira server' + }) + } + if (!teamMember || !teamMember) { + return null + } + + return ( + <> + {isActive ? ( + + ) : ( +
+
+ +
+ Connect to Jira Server +
+ Connect to Jira Server to view your issues. +
+ + {error &&
Error: {error.message}
} +
+ )} + + ) +} + +export default JiraServerIntegrationPanel diff --git a/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationResults.tsx b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationResults.tsx new file mode 100644 index 00000000000..3beed1d4c9c --- /dev/null +++ b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationResults.tsx @@ -0,0 +1,115 @@ +import graphql from 'babel-plugin-relay/macro' +import React from 'react' +import {PreloadedQuery, usePaginationFragment, usePreloadedQuery} from 'react-relay' +import {Link} from 'react-router-dom' +import halloweenRetrospectiveTemplate from '../../../../../static/images/illustrations/halloweenRetrospectiveTemplate.png' +import {JiraServerIntegrationResultsQuery} from '../../../__generated__/JiraServerIntegrationResultsQuery.graphql' +import {JiraServerIntegrationResultsSearchPaginationQuery} from '../../../__generated__/JiraServerIntegrationResultsSearchPaginationQuery.graphql' +import {JiraServerIntegrationResults_search$key} from '../../../__generated__/JiraServerIntegrationResults_search.graphql' +import useLoadNextOnScrollBottom from '../../../hooks/useLoadNextOnScrollBottom' +import Ellipsis from '../../Ellipsis/Ellipsis' +import JiraServerObjectCard from './JiraServerObjectCard' + +interface Props { + queryRef: PreloadedQuery + teamId: string +} + +const JiraServerIntegrationResults = (props: Props) => { + const {queryRef, teamId} = props + const query = usePreloadedQuery( + graphql` + query JiraServerIntegrationResultsQuery($teamId: ID!) { + ...JiraServerIntegrationResults_search @arguments(teamId: $teamId) + } + `, + queryRef + ) + + const paginationRes = usePaginationFragment< + JiraServerIntegrationResultsSearchPaginationQuery, + JiraServerIntegrationResults_search$key + >( + graphql` + fragment JiraServerIntegrationResults_search on Query + @argumentDefinitions( + cursor: {type: "String"} + count: {type: "Int", defaultValue: 20} + teamId: {type: "ID!"} + ) + @refetchable(queryName: "JiraServerIntegrationResultsSearchPaginationQuery") { + viewer { + teamMember(teamId: $teamId) { + integrations { + jiraServer { + issues( + first: $count + after: $cursor + isJQL: true + queryString: "assignee = currentUser() order by updated DESC" + ) @connection(key: "JiraServerScopingSearchResults_issues") { + error { + message + } + edges { + node { + ...JiraServerObjectCard_result + id + summary + url + issueKey + } + } + } + } + } + } + } + } + `, + query + ) + + const lastItem = useLoadNextOnScrollBottom(paginationRes, {}, 20) + const {data, hasNext} = paginationRes + + const jira = data.viewer.teamMember?.integrations.jiraServer + const jiraResults = jira?.issues.edges.map((edge) => edge.node) + const error = jira?.issues.error ?? null + + return ( + <> +
+ {jiraResults && jiraResults.length > 0 ? ( + jiraResults?.map((result, idx) => { + if (!result) { + return null + } + return + }) + ) : ( +
+ +
+ {error?.message ? error.message : `Looks like you don’t have any issues to display.`} +
+ + Review your Jira Server configuration + +
+ )} + {lastItem} + {hasNext && ( +
+ +
+ )} +
+ + ) +} + +export default JiraServerIntegrationResults diff --git a/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationResultsRoot.tsx b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationResultsRoot.tsx new file mode 100644 index 00000000000..c58e4170e3d --- /dev/null +++ b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerIntegrationResultsRoot.tsx @@ -0,0 +1,31 @@ +import React, {Suspense} from 'react' +import {Loader} from '~/utils/relay/renderLoader' +import jiraIntegrationResultsQuery, { + JiraServerIntegrationResultsQuery +} from '../../../__generated__/JiraServerIntegrationResultsQuery.graphql' +import useQueryLoaderNow from '../../../hooks/useQueryLoaderNow' +import ErrorBoundary from '../../ErrorBoundary' +import JiraServerIntegrationResults from './JiraServerIntegrationResults' + +interface Props { + teamId: string +} + +const JiraServerIntegrationResultsRoot = (props: Props) => { + const {teamId} = props + const queryRef = useQueryLoaderNow( + jiraIntegrationResultsQuery, + { + teamId: teamId + } + ) + return ( + + }> + {queryRef && } + + + ) +} + +export default JiraServerIntegrationResultsRoot diff --git a/packages/client/components/TeamPrompt/WorkDrawer/JiraServerObjectCard.tsx b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerObjectCard.tsx new file mode 100644 index 00000000000..dd6da759a71 --- /dev/null +++ b/packages/client/components/TeamPrompt/WorkDrawer/JiraServerObjectCard.tsx @@ -0,0 +1,121 @@ +import {Link} from '@mui/icons-material' +import graphql from 'babel-plugin-relay/macro' +import React from 'react' +import CopyToClipboard from 'react-copy-to-clipboard' +import {useFragment} from 'react-relay' +import {JiraServerObjectCard_result$key} from '../../../__generated__/JiraServerObjectCard_result.graphql' +import useAtmosphere from '../../../hooks/useAtmosphere' +import {MenuPosition} from '../../../hooks/useCoords' +import useTooltip from '../../../hooks/useTooltip' +import jiraSVG from '../../../styles/theme/images/graphics/jira.svg' +import SendClientSideEvent from '../../../utils/SendClientSideEvent' +import relativeDate from '../../../utils/date/relativeDate' +import {mergeRefs} from '../../../utils/react/mergeRefs' + +interface Props { + resultRef: JiraServerObjectCard_result$key +} + +const JiraServerObjectCard = (props: Props) => { + const {resultRef} = props + + const result = useFragment( + graphql` + fragment JiraServerObjectCard_result on JiraServerIssue { + id + summary + url + issueKey + projectKey + projectName + updatedAt + } + `, + resultRef + ) + + const atmosphere = useAtmosphere() + + const {tooltipPortal, openTooltip, closeTooltip, originRef} = useTooltip( + MenuPosition.UPPER_CENTER + ) + + const { + tooltipPortal: copiedTooltipPortal, + openTooltip: openCopiedTooltip, + closeTooltip: closeCopiedTooltip, + originRef: copiedTooltipRef + } = useTooltip(MenuPosition.LOWER_CENTER) + + const trackLinkClick = () => { + SendClientSideEvent(atmosphere, 'Your Work Drawer Card Link Clicked', { + service: 'jira' + }) + } + + const trackCopy = () => { + SendClientSideEvent(atmosphere, 'Your Work Drawer Card Copied', { + service: 'jira' + }) + } + + const handleCopy = () => { + openCopiedTooltip() + trackCopy() + setTimeout(() => { + closeCopiedTooltip() + }, 2000) + } + + const {summary, url, issueKey, projectName, updatedAt} = result + + return ( +
+
+ + {issueKey} + +
Updated {relativeDate(updatedAt)}
+
+
+
+
+
+ +
+
{projectName}
+
+ +
+ +
+
+ {tooltipPortal('Copy link')} + {copiedTooltipPortal('Copied!')} +
+
+ ) +} + +export default JiraServerObjectCard diff --git a/packages/server/graphql/private/typeDefs/_legacy.graphql b/packages/server/graphql/private/typeDefs/_legacy.graphql index a5aa1e4ff48..406fd1d39c0 100644 --- a/packages/server/graphql/private/typeDefs/_legacy.graphql +++ b/packages/server/graphql/private/typeDefs/_legacy.graphql @@ -1095,6 +1095,7 @@ type JiraServerIssue implements TaskIntegration { id: ID! issueKey: ID! projectKey: ID! + projectName: String! """ The parabol teamId this issue was fetched for @@ -1121,6 +1122,11 @@ type JiraServerIssue implements TaskIntegration { The description converted into raw HTML """ descriptionHTML: String! + + """ + The timestamp the issue was last updated + """ + updatedAt: DateTime! } """ diff --git a/packages/server/graphql/types/JiraServerIntegration.ts b/packages/server/graphql/types/JiraServerIntegration.ts index 2f4f3968b74..ec349fa98b1 100644 --- a/packages/server/graphql/types/JiraServerIntegration.ts +++ b/packages/server/graphql/types/JiraServerIntegration.ts @@ -92,7 +92,7 @@ const JiraServerIntegration = new GraphQLObjectType<{teamId: string; userId: str }, after: { type: GraphQLString, - defaultValue: '0' + defaultValue: '-1' }, queryString: { type: GraphQLString, @@ -162,21 +162,22 @@ const JiraServerIntegration = new GraphQLObjectType<{teamId: string; userId: str const {issues} = issueRes const mappedIssues = issues.map((issue) => { - const {project, issuetype, summary, description} = issue.fields + const {project, issuetype, summary, description, updated} = issue.fields return { ...issue, userId, teamId, providerId: provider.id, issueKey: issue.key, + description: description ?? '', descriptionHTML: issue.renderedFields.description, projectId: project.id, projectKey: project.key, + projectName: project.name, issueType: issuetype.id, summary, - description, service: 'jiraServer' as const, - updatedAt: new Date() + updatedAt: new Date(updated) } }) diff --git a/packages/server/graphql/types/JiraServerIssue.ts b/packages/server/graphql/types/JiraServerIssue.ts index 6d4a583c68a..3047f751c28 100644 --- a/packages/server/graphql/types/JiraServerIssue.ts +++ b/packages/server/graphql/types/JiraServerIssue.ts @@ -3,6 +3,7 @@ import JiraServerIssueId from '~/shared/gqlIds/JiraServerIssueId' import {JiraServerIssue as JiraServerRestIssue} from '../../dataloader/jiraServerLoaders' import connectionDefinitions from '../connectionDefinitions' import {GQLContext} from '../graphql' +import GraphQLISO8601Type from './GraphQLISO8601Type' import StandardMutationError from './StandardMutationError' import TaskIntegration from './TaskIntegration' @@ -40,6 +41,9 @@ const JiraServerIssue = new GraphQLObjectType projectKey: { type: new GraphQLNonNull(GraphQLID) }, + projectName: { + type: new GraphQLNonNull(GraphQLString) + }, teamId: { type: new GraphQLNonNull(GraphQLID), description: 'The parabol teamId this issue was fetched for' @@ -84,6 +88,10 @@ const JiraServerIssue = new GraphQLObjectType .map(({name}) => name) return fieldNames } + }, + updatedAt: { + type: new GraphQLNonNull(GraphQLISO8601Type), + description: 'The timestamp the issue was last updated' } }) }) diff --git a/packages/server/integrations/jiraServer/JiraServerRestManager.ts b/packages/server/integrations/jiraServer/JiraServerRestManager.ts index 52416b07746..36b024379a1 100644 --- a/packages/server/integrations/jiraServer/JiraServerRestManager.ts +++ b/packages/server/integrations/jiraServer/JiraServerRestManager.ts @@ -57,7 +57,9 @@ export interface JiraServerIssue { id: string key: string name: string + self: string } + updated: string } renderedFields: { description: string From 051e51c7746dfb50c4853b07ecc5bf548bd99a4e Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Thu, 30 May 2024 16:46:00 +0200 Subject: [PATCH 242/529] chore: Allow global Jira Server integration provider (#9796) --- .../mutations/addIntegrationProvider.ts | 11 +++++++++-- .../graphql/public/typeDefs/_legacy.graphql | 1 + .../IntegrationProviderEditableScopeEnum.ts | 5 +++-- .../graphql/types/JiraServerIntegration.ts | 8 +++++--- ...1716995191300_allowGlobalOAuth1Provider.ts | 19 +++++++++++++++++++ 5 files changed, 37 insertions(+), 7 deletions(-) create mode 100644 packages/server/postgres/migrations/1716995191300_allowGlobalOAuth1Provider.ts diff --git a/packages/server/graphql/mutations/addIntegrationProvider.ts b/packages/server/graphql/mutations/addIntegrationProvider.ts index a2da4c321f3..bafee1d0c61 100644 --- a/packages/server/graphql/mutations/addIntegrationProvider.ts +++ b/packages/server/graphql/mutations/addIntegrationProvider.ts @@ -30,12 +30,19 @@ const addIntegrationProvider = { context: GQLContext ) => { const {authToken, dataLoader, socketId: mutatorId} = context - const {teamId} = input + const {teamId, scope} = input const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} // AUTH - if (!isTeamMember(authToken, teamId) && !isSuperUser(authToken)) { + if (scope === 'global') { + if (!isSuperUser(authToken)) { + return {error: {message: 'Global scope requires su'}} + } + if (teamId !== 'aGhostTeam') { + return {error: {message: 'Global scope requires teamId to be aGhostTeam'}} + } + } else if (!isTeamMember(authToken, teamId) && !isSuperUser(authToken)) { return {error: {message: 'Must be on the team for which the provider is created'}} } diff --git a/packages/server/graphql/public/typeDefs/_legacy.graphql b/packages/server/graphql/public/typeDefs/_legacy.graphql index 6c2322af589..8b6eb1bc78b 100644 --- a/packages/server/graphql/public/typeDefs/_legacy.graphql +++ b/packages/server/graphql/public/typeDefs/_legacy.graphql @@ -8626,6 +8626,7 @@ The scope this provider was created on by a user (excluding global scope) enum IntegrationProviderEditableScopeEnum { org team + global } """ diff --git a/packages/server/graphql/types/IntegrationProviderEditableScopeEnum.ts b/packages/server/graphql/types/IntegrationProviderEditableScopeEnum.ts index 8d9f1534ec7..501c1d0b0cb 100644 --- a/packages/server/graphql/types/IntegrationProviderEditableScopeEnum.ts +++ b/packages/server/graphql/types/IntegrationProviderEditableScopeEnum.ts @@ -1,13 +1,14 @@ import {GraphQLEnumType} from 'graphql' -export type TIntegrationProviderEditableScopeEnum = 'org' | 'team' +export type TIntegrationProviderEditableScopeEnum = 'org' | 'team' | 'global' const IntegrationProviderEditableScopeEnum = new GraphQLEnumType({ name: 'IntegrationProviderEditableScopeEnum', description: 'The scope this provider was created on by a user (excluding global scope)', values: { org: {}, - team: {} + team: {}, + global: {} } }) diff --git a/packages/server/graphql/types/JiraServerIntegration.ts b/packages/server/graphql/types/JiraServerIntegration.ts index ec349fa98b1..2f10905f418 100644 --- a/packages/server/graphql/types/JiraServerIntegration.ts +++ b/packages/server/graphql/types/JiraServerIntegration.ts @@ -75,9 +75,11 @@ const JiraServerIntegration = new GraphQLObjectType<{teamId: string; userId: str const orgTeams = await dataLoader.get('teamsByOrgIds').load(orgId) const orgTeamIds = orgTeams.map(({id}) => id) - const providers = await dataLoader - .get('sharedIntegrationProviders') - .load({service: 'jiraServer', orgTeamIds, teamIds: [teamId]}) + const providers = await dataLoader.get('sharedIntegrationProviders').load({ + service: 'jiraServer', + orgTeamIds: [...orgTeamIds, 'aGhostTeam'], + teamIds: [teamId] + }) return providers } }, diff --git a/packages/server/postgres/migrations/1716995191300_allowGlobalOAuth1Provider.ts b/packages/server/postgres/migrations/1716995191300_allowGlobalOAuth1Provider.ts new file mode 100644 index 00000000000..51606b8c040 --- /dev/null +++ b/packages/server/postgres/migrations/1716995191300_allowGlobalOAuth1Provider.ts @@ -0,0 +1,19 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DO $$ + BEGIN + ALTER TABLE "IntegrationProvider" + DROP CONSTRAINT global_provider_must_be_oauth2; + END $$; + `) + await client.end() +} + +export async function down() { + //noop, the constraint was a leftover and served no purpose +} From 9487c1e350339d068d902a5e9cf0e3a67b0f4b79 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 30 May 2024 16:51:41 +0200 Subject: [PATCH 243/529] chore(release): release v7.34.0 (#9803) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b4e0b104703..bf51eb05d0e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.33.0" + ".": "7.34.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 6e868c246ea..bcdb0e60954 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.34.0](https://github.com/ParabolInc/parabol/compare/v7.33.0...v7.34.0) (2024-05-30) + + +### Added + +* Add Jira Server to Your Work ([#9794](https://github.com/ParabolInc/parabol/issues/9794)) ([eec025e](https://github.com/ParabolInc/parabol/commit/eec025e3e22202c0c4c5630d2e6a75db76e3008f)) + + +### Changed + +* Allow global Jira Server integration provider ([#9796](https://github.com/ParabolInc/parabol/issues/9796)) ([051e51c](https://github.com/ParabolInc/parabol/commit/051e51c7746dfb50c4853b07ecc5bf548bd99a4e)) +* remove kudos ([#9785](https://github.com/ParabolInc/parabol/issues/9785)) ([23d48c4](https://github.com/ParabolInc/parabol/commit/23d48c48c01471be8e1332765f5d7cd9f0168954)) + ## [7.33.0](https://github.com/ParabolInc/parabol/compare/v7.32.1...v7.33.0) (2024-05-29) diff --git a/package.json b/package.json index 078d273103f..e85bf4d6f16 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.33.0", + "version": "7.34.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 1444cee8972..40f04821182 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.33.0", + "version": "7.34.0", "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.33.0" + "parabol-server": "7.34.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 7f42b509ef8..99c6b6d9429 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.33.0", + "version": "7.34.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 0232cf845d0..43db5b13961 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.33.0", + "version": "7.34.0", "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 07d478b8cad..6da10e9c69f 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.33.0", + "version": "7.34.0", "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.33.0", - "parabol-server": "7.33.0", + "parabol-client": "7.34.0", + "parabol-server": "7.34.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 99e1b54ef3e..c280bd88eb0 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.33.0", + "version": "7.34.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 4c100b77fc6..e90a7e83586 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.33.0", + "version": "7.34.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.33.0", + "parabol-client": "7.34.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 712f79eb81087b3a86301de3e611703a8ef46826 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 30 May 2024 08:44:05 -0700 Subject: [PATCH 244/529] feat: type safety for gql perms (#9798) Signed-off-by: Matt Krick --- packages/server/graphql/public/permissions.ts | 17 +++++++--- .../public/rules/getResolverDotPath.ts | 34 +++++++++++++++++-- .../server/graphql/public/rules/isOrgTier.ts | 2 +- .../public/rules/isViewerBillingLeader.ts | 2 +- .../graphql/public/rules/isViewerOnOrg.ts | 2 +- 5 files changed, 48 insertions(+), 9 deletions(-) diff --git a/packages/server/graphql/public/permissions.ts b/packages/server/graphql/public/permissions.ts index edf2d4ccce6..c74d9bc023c 100644 --- a/packages/server/graphql/public/permissions.ts +++ b/packages/server/graphql/public/permissions.ts @@ -51,10 +51,16 @@ const permissionMap: PermissionMap = { verifyEmail: rateLimit({perMinute: 50, perHour: 100}), addApprovedOrganizationDomains: or( isSuperUser, - and(isViewerBillingLeader('args.orgId'), isOrgTier('args.orgId', 'enterprise')) + and( + isViewerBillingLeader<'Mutation.addApprovedOrganizationDomains'>('args.orgId'), + isOrgTier<'Mutation.addApprovedOrganizationDomains'>('args.orgId', 'enterprise') + ) ), - removeApprovedOrganizationDomains: or(isSuperUser, isViewerBillingLeader('args.orgId')), - uploadIdPMetadata: isViewerOnOrg('args.orgId'), + removeApprovedOrganizationDomains: or( + isSuperUser, + isViewerBillingLeader<'Mutation.removeApprovedOrganizationDomains'>('args.orgId') + ), + uploadIdPMetadata: isViewerOnOrg<'Mutation.uploadIdPMetadata'>('args.orgId'), updateTemplateCategory: isViewerOnTeam(getTeamIdFromArgTemplateId) }, Query: { @@ -63,7 +69,10 @@ const permissionMap: PermissionMap = { SAMLIdP: rateLimit({perMinute: 120, perHour: 3600}) }, Organization: { - saml: and(isViewerBillingLeader('source.id'), isOrgTier('source.id', 'enterprise')) + saml: and( + isViewerBillingLeader<'Organization.saml'>('source.id'), + isOrgTier<'Organization.saml'>('source.id', 'enterprise') + ) }, User: { domains: or(isSuperUser, isUserViewer) diff --git a/packages/server/graphql/public/rules/getResolverDotPath.ts b/packages/server/graphql/public/rules/getResolverDotPath.ts index cac815882a0..05e9098ff08 100644 --- a/packages/server/graphql/public/rules/getResolverDotPath.ts +++ b/packages/server/graphql/public/rules/getResolverDotPath.ts @@ -1,9 +1,39 @@ +import {FirstParam} from '../../../../client/types/generics' +import {Resolvers} from '../resolverTypes' + export const getResolverDotPath = ( - dotPath: ResolverDotPath, + dotPath: `${'source' | 'args'}.${string}`, source: Record, args: Record ) => { return dotPath.split('.').reduce((val: any, key) => val?.[key], {source, args}) } -export type ResolverDotPath = `source.${string}` | `args.${string}` +type SecondParam = T extends (arg1: any, arg2: infer A, ...args: any[]) => any ? A : never + +type ParseParent = T extends `${infer Parent extends string}.${string}` ? Parent : never +type ParseChild = T extends `${string}.${infer Child extends string}` ? Child : never + +type ExtractTypeof = '__isTypeOf' extends keyof NonNullable + ? NonNullable['__isTypeOf'] + : never +type ExtractParent = FirstParam>> + +type Source = + ParseParent extends keyof Resolvers + ? ExtractParent> extends never + ? never + : keyof ExtractParent> & string + : never + +type ExtractChild = TChild extends keyof TOp + ? NonNullable + : never + +type Arg = + ParseParent extends keyof Resolvers + ? keyof SecondParam]>, ParseChild>> & + string + : never + +export type ResolverDotPath = `source.${Source}` | `args.${Arg}` diff --git a/packages/server/graphql/public/rules/isOrgTier.ts b/packages/server/graphql/public/rules/isOrgTier.ts index 06e10388dac..7fb5a80eb6e 100644 --- a/packages/server/graphql/public/rules/isOrgTier.ts +++ b/packages/server/graphql/public/rules/isOrgTier.ts @@ -3,7 +3,7 @@ import {GQLContext} from '../../graphql' import {TierEnum} from '../resolverTypes' import {ResolverDotPath, getResolverDotPath} from './getResolverDotPath' -export const isOrgTier = (orgIdDotPath: ResolverDotPath, requiredTier: TierEnum) => +export const isOrgTier = (orgIdDotPath: ResolverDotPath, requiredTier: TierEnum) => rule(`isViewerOnOrg-${orgIdDotPath}-${requiredTier}`, {cache: 'strict'})( async (source, args, {dataLoader}: GQLContext) => { const orgId = getResolverDotPath(orgIdDotPath, source, args) diff --git a/packages/server/graphql/public/rules/isViewerBillingLeader.ts b/packages/server/graphql/public/rules/isViewerBillingLeader.ts index 28708113671..99391ad1baf 100644 --- a/packages/server/graphql/public/rules/isViewerBillingLeader.ts +++ b/packages/server/graphql/public/rules/isViewerBillingLeader.ts @@ -3,7 +3,7 @@ import {getUserId} from '../../../utils/authorization' import {GQLContext} from '../../graphql' import {ResolverDotPath, getResolverDotPath} from './getResolverDotPath' -export const isViewerBillingLeader = (orgIdDotPath: ResolverDotPath) => +export const isViewerBillingLeader = (orgIdDotPath: ResolverDotPath) => rule(`isViewerBillingLeader-${orgIdDotPath}`, {cache: 'strict'})( async (source, args, {authToken, dataLoader}: GQLContext) => { const orgId = getResolverDotPath(orgIdDotPath, source, args) diff --git a/packages/server/graphql/public/rules/isViewerOnOrg.ts b/packages/server/graphql/public/rules/isViewerOnOrg.ts index 01b0cbab18f..15532d23a86 100644 --- a/packages/server/graphql/public/rules/isViewerOnOrg.ts +++ b/packages/server/graphql/public/rules/isViewerOnOrg.ts @@ -3,7 +3,7 @@ import {getUserId} from '../../../utils/authorization' import {GQLContext} from '../../graphql' import {ResolverDotPath, getResolverDotPath} from './getResolverDotPath' -export const isViewerOnOrg = (orgIdDotPath: ResolverDotPath) => +export const isViewerOnOrg = (orgIdDotPath: ResolverDotPath) => rule(`isViewerOnOrg-${orgIdDotPath}`, {cache: 'strict'})( async (source, args, {authToken, dataLoader}: GQLContext) => { const orgId = getResolverDotPath(orgIdDotPath, source, args) From 74d8dbc76366959be4274bde1d12d7978a146a2c Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Thu, 30 May 2024 18:13:38 +0100 Subject: [PATCH 245/529] fix: clear kudos received notifications (#9805) --- .../1717083323369_removeKudosNotifications.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) create mode 100644 packages/server/postgres/migrations/1717083323369_removeKudosNotifications.ts diff --git a/packages/server/postgres/migrations/1717083323369_removeKudosNotifications.ts b/packages/server/postgres/migrations/1717083323369_removeKudosNotifications.ts new file mode 100644 index 00000000000..2607568be0b --- /dev/null +++ b/packages/server/postgres/migrations/1717083323369_removeKudosNotifications.ts @@ -0,0 +1,16 @@ +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' + +export async function up() { + try { + await connectRethinkDB() + await r.table('Notification').filter(r.row('type').eq('KUDOS_RECEIVED')).delete().run() + await r.getPoolMaster()?.drain() + } catch (e) { + console.log(e) + } +} + +export async function down() { + // noop +} From bf9371808c14a4ab83a9f6f157fe5c194ae30822 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 30 May 2024 20:44:09 +0200 Subject: [PATCH 246/529] chore(release): release v7.35.0 (#9806) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index bf51eb05d0e..fb632dc6f0a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.34.0" + ".": "7.35.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index bcdb0e60954..12642d0bcf8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.35.0](https://github.com/ParabolInc/parabol/compare/v7.34.0...v7.35.0) (2024-05-30) + + +### Added + +* type safety for gql perms ([#9798](https://github.com/ParabolInc/parabol/issues/9798)) ([712f79e](https://github.com/ParabolInc/parabol/commit/712f79eb81087b3a86301de3e611703a8ef46826)) + + +### Fixed + +* clear kudos received notifications ([#9805](https://github.com/ParabolInc/parabol/issues/9805)) ([74d8dbc](https://github.com/ParabolInc/parabol/commit/74d8dbc76366959be4274bde1d12d7978a146a2c)) + ## [7.34.0](https://github.com/ParabolInc/parabol/compare/v7.33.0...v7.34.0) (2024-05-30) diff --git a/package.json b/package.json index e85bf4d6f16..e4a4186934e 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.34.0", + "version": "7.35.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 40f04821182..2bdc5f14c9a 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.34.0", + "version": "7.35.0", "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.34.0" + "parabol-server": "7.35.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 99c6b6d9429..fa84db39acd 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.34.0", + "version": "7.35.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 43db5b13961..59aa95203b6 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.34.0", + "version": "7.35.0", "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 6da10e9c69f..cbbeb8e3554 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.34.0", + "version": "7.35.0", "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.34.0", - "parabol-server": "7.34.0", + "parabol-client": "7.35.0", + "parabol-server": "7.35.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index c280bd88eb0..61634e7a21e 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.34.0", + "version": "7.35.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index e90a7e83586..6cd52324516 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.34.0", + "version": "7.35.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.34.0", + "parabol-client": "7.35.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 652a9c034267f9c53f4cf9c04b4f29b6f854eb1c Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 4 Jun 2024 10:27:27 +0200 Subject: [PATCH 247/529] fix: remove custom activity badge (#9812) --- .../client/components/ActivityLibrary/ActivityCard.tsx | 8 ++------ .../ActivityLibrary/ActivityDetails/ActivityDetails.tsx | 1 - .../client/components/ActivityLibrary/ActivityGrid.tsx | 6 ------ packages/client/components/RetroDrawerTemplateCard.tsx | 7 ------- 4 files changed, 2 insertions(+), 20 deletions(-) diff --git a/packages/client/components/ActivityLibrary/ActivityCard.tsx b/packages/client/components/ActivityLibrary/ActivityCard.tsx index 260013189a5..2289c25004b 100644 --- a/packages/client/components/ActivityLibrary/ActivityCard.tsx +++ b/packages/client/components/ActivityLibrary/ActivityCard.tsx @@ -50,14 +50,13 @@ export interface ActivityCardProps { className?: string theme: CardTheme title?: string - badge?: React.ReactNode children?: React.ReactNode type?: MeetingTypeEnum templateRef?: ActivityCard_template$key } export const ActivityCard = (props: ActivityCardProps) => { - const {className, theme, title, children, type, badge, templateRef} = props + const {className, theme, title, children, type, templateRef} = props const category = type && MEETING_TYPE_TO_CATEGORY[type] const [showTooltip, setShowTooltip] = useState(false) const hoverTimeout = useRef(null) @@ -105,10 +104,7 @@ export const ActivityCard = (props: ActivityCardProps) => { className )} > -
- {children} -
{badge}
-
+
{children}
{template && ( diff --git a/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx b/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx index c9d09f21195..2fb98c8671c 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx @@ -106,7 +106,6 @@ const ActivityDetails = (props: Props) => { diff --git a/packages/client/components/ActivityLibrary/ActivityGrid.tsx b/packages/client/components/ActivityLibrary/ActivityGrid.tsx index 1ee031f248a..2564d542746 100644 --- a/packages/client/components/ActivityLibrary/ActivityGrid.tsx +++ b/packages/client/components/ActivityLibrary/ActivityGrid.tsx @@ -3,7 +3,6 @@ import React from 'react' import {useFragment} from 'react-relay' import {Link} from 'react-router-dom' import {ActivityGrid_user$key} from '../../__generated__/ActivityGrid_user.graphql' -import {ActivityBadge} from './ActivityBadge' import {ActivityCard, ActivityCardImage} from './ActivityCard' import ActivityCardFavorite from './ActivityCardFavorite' import {Template} from './ActivityLibrary' @@ -45,11 +44,6 @@ const ActivityGrid = (props: ActivityGridProps) => { title={template.name} type={template.type} templateRef={template} - badge={ - template.scope !== 'PUBLIC' ? ( - Custom - ) : null - } > { name category illustrationUrl - scope } `, templateRef @@ -54,11 +52,6 @@ const RetroDrawerTemplateCard = (props: Props) => { theme={CATEGORY_THEMES[template.category as CategoryID]} title={template.name} type='retrospective' - badge={ - template.scope !== 'PUBLIC' ? ( - Custom - ) : null - } > Date: Tue, 4 Jun 2024 09:42:36 -0700 Subject: [PATCH 248/529] chore: read ReflectionGroups from PG (#9801) Signed-off-by: Matt Krick --- codegen.json | 2 +- .../modules/demo/ClientGraphQLServer.ts | 12 +++- .../helpers/publishSimilarRetroTopics.ts | 2 +- packages/server/database/rethinkDriver.ts | 5 -- .../dataloader/foreignKeyLoaderMakers.ts | 14 +++++ .../dataloader/primaryKeyLoaderMakers.ts | 4 ++ .../rethinkForeignKeyLoaderMakers.ts | 13 ----- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../graphql/mutations/createReflection.ts | 1 - .../helpers/generateDiscussionPrompt.ts | 15 ++--- .../mutations/helpers/handleCompletedStage.ts | 21 ++----- .../helpers/notifications/SlackNotifier.ts | 2 +- .../helpers/removeEmptyReflectionGroup.ts | 18 ++---- .../helpers/removeEmptyReflections.ts | 9 +-- .../mutations/helpers/safeEndRetrospective.ts | 6 -- .../mutations/helpers/safelyCastVote.ts | 44 +++++--------- .../mutations/helpers/safelyWithdrawVote.ts | 38 +++---------- .../addReflectionToGroup.ts | 57 ++++++------------- .../removeReflectionFromGroup.ts | 38 +++++-------- .../updateSmartGroupTitle.ts | 31 +++------- .../resetRetroMeetingToGroupStage.ts | 5 -- .../mutations/updateReflectionGroupTitle.ts | 32 ++++------- .../mutations/voteForReflectionGroup.ts | 3 +- .../private/mutations/backupOrganization.ts | 17 ------ .../mutations/checkRethinkPgEquality.ts | 2 +- .../public/types/RetroReflectionGroup.ts | 9 +++ .../public/types/RetrospectiveMeeting.ts | 15 ++--- 27 files changed, 134 insertions(+), 282 deletions(-) create mode 100644 packages/server/graphql/public/types/RetroReflectionGroup.ts diff --git a/codegen.json b/codegen.json index 46aa03a0329..9354c2d6426 100644 --- a/codegen.json +++ b/codegen.json @@ -105,7 +105,7 @@ "RequestToJoinDomainSuccess": "./types/RequestToJoinDomainSuccess#RequestToJoinDomainSuccessSource", "ResetReflectionGroupsSuccess": "./types/ResetReflectionGroupsSuccess#ResetReflectionGroupsSuccessSource", "RetroReflection": "../../database/types/RetroReflection#default as RetroReflectionDB", - "RetroReflectionGroup": "../../database/types/RetroReflectionGroup#default as RetroReflectionGroupDB", + "RetroReflectionGroup": "./types/RetroReflectionGroup#RetroReflectionGroupSource", "RetrospectiveMeeting": "../../database/types/MeetingRetrospective#default", "RetrospectiveMeetingMember": "../../database/types/RetroMeetingMember#default", "RetrospectiveMeetingSettings": "../../database/types/MeetingSettingsRetrospective#default", diff --git a/packages/client/modules/demo/ClientGraphQLServer.ts b/packages/client/modules/demo/ClientGraphQLServer.ts index e58c5f10f22..dd8adfd039f 100644 --- a/packages/client/modules/demo/ClientGraphQLServer.ts +++ b/packages/client/modules/demo/ClientGraphQLServer.ts @@ -15,7 +15,6 @@ import NewMeetingStage from '../../../server/database/types/GenericMeetingStage' import GoogleAnalyzedEntity from '../../../server/database/types/GoogleAnalyzedEntity' import ReflectPhase from '../../../server/database/types/ReflectPhase' import Reflection from '../../../server/database/types/Reflection' -import ReflectionGroup from '../../../server/database/types/ReflectionGroup' import ITask from '../../../server/database/types/Task' import { ExternalLinks, @@ -69,8 +68,17 @@ export type DemoReflection = Omit & { +export type DemoReflectionGroup = { __typename: string + id: string + isActive: boolean + meetingId: string + promptId: string + sortOrder: number + smartTitle: string | null + summary: string | null + title: string | null + discussionPromptQuestion: string | null commentors: any createdAt: string | Date meeting: any diff --git a/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts b/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts index f8de026db74..0f881ee4621 100644 --- a/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts +++ b/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts @@ -22,7 +22,7 @@ const makeSimilarDiscussionLink = async ( const {meetingId, discussionTopicId: reflectionGroupId} = discussion const [meeting, reflectionGroup] = await Promise.all([ dataLoader.get('newMeetings').load(meetingId), - dataLoader.get('retroReflectionGroups').load(reflectionGroupId) + dataLoader.get('retroReflectionGroups').loadNonNull(reflectionGroupId) ]) if (!meeting || !isRetroMeeting(meeting)) throw new Error('invalid meeting type') diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index e030375d971..40621c4db4c 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -30,7 +30,6 @@ import OrganizationUser from './types/OrganizationUser' import PasswordResetRequest from './types/PasswordResetRequest' import PushInvitation from './types/PushInvitation' import Reflection from './types/Reflection' -import ReflectionGroup from './types/ReflectionGroup' import RetrospectivePrompt from './types/RetrospectivePrompt' import SAML from './types/SAML' import SuggestedActionCreateNewTeam from './types/SuggestedActionCreateNewTeam' @@ -143,10 +142,6 @@ export type RethinkSchema = { type: MeetingTemplate index: 'teamId' | 'orgId' } - RetroReflectionGroup: { - type: ReflectionGroup - index: 'meetingId' - } RetroReflection: { type: Reflection index: 'meetingId' | 'reflectionGroupId' diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 0c967c37b1a..8807ba06f17 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -23,3 +23,17 @@ export const embeddingsMetadataByRefId = foreignKeyLoaderMaker( return pg.selectFrom('EmbeddingsMetadata').selectAll().where('refId', 'in', refId).execute() } ) + +export const retroReflectionGroupsByMeetingId = foreignKeyLoaderMaker( + 'retroReflectionGroups', + 'meetingId', + async (meetingIds) => { + const pg = getKysely() + return pg + .selectFrom('RetroReflectionGroup') + .selectAll() + .where('meetingId', 'in', meetingIds) + .where('isActive', '=', true) + .execute() + } +) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 187d6802720..87c8adafc0a 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -23,3 +23,7 @@ export const domainJoinRequests = primaryKeyLoaderMaker(getDomainJoinRequestsByI export const embeddingsMetadata = primaryKeyLoaderMaker((ids: readonly number[]) => { return getKysely().selectFrom('EmbeddingsMetadata').selectAll().where('id', 'in', ids).execute() }) + +export const retroReflectionGroups = primaryKeyLoaderMaker((ids: readonly string[]) => { + return getKysely().selectFrom('RetroReflectionGroup').selectAll().where('id', 'in', ids).execute() +}) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index 70a038b712c..34ad30aca6f 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -152,19 +152,6 @@ export const organizationUsersByUserId = new RethinkForeignKeyLoaderMaker( } ) -export const retroReflectionGroupsByMeetingId = new RethinkForeignKeyLoaderMaker( - 'retroReflectionGroups', - 'meetingId', - async (meetingIds) => { - const r = await getRethink() - return r - .table('RetroReflectionGroup') - .getAll(r.args(meetingIds), {index: 'meetingId'}) - .filter({isActive: true}) - .run() - } -) - export const scalesByTeamId = new RethinkForeignKeyLoaderMaker( 'templateScales', 'teamId', diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index 1806bdd3efe..1e4f2d32e95 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -16,7 +16,6 @@ export const notifications = new RethinkPrimaryKeyLoaderMaker('Notification') export const organizations = new RethinkPrimaryKeyLoaderMaker('Organization') export const organizationUsers = new RethinkPrimaryKeyLoaderMaker('OrganizationUser') export const templateScales = new RethinkPrimaryKeyLoaderMaker('TemplateScale') -export const retroReflectionGroups = new RethinkPrimaryKeyLoaderMaker('RetroReflectionGroup') export const retroReflections = new RethinkPrimaryKeyLoaderMaker('RetroReflection') export const slackAuths = new RethinkPrimaryKeyLoaderMaker('SlackAuth') export const slackNotifications = new RethinkPrimaryKeyLoaderMaker('SlackNotification') diff --git a/packages/server/graphql/mutations/createReflection.ts b/packages/server/graphql/mutations/createReflection.ts index 7eaa94998f5..67e7420f3c6 100644 --- a/packages/server/graphql/mutations/createReflection.ts +++ b/packages/server/graphql/mutations/createReflection.ts @@ -99,7 +99,6 @@ export default { await Promise.all([ pg.insertInto('RetroReflectionGroup').values(reflectionGroup).execute(), - r.table('RetroReflectionGroup').insert(reflectionGroup).run(), r.table('RetroReflection').insert(reflection).run() ]) diff --git a/packages/server/graphql/mutations/helpers/generateDiscussionPrompt.ts b/packages/server/graphql/mutations/helpers/generateDiscussionPrompt.ts index bb7a245ca2f..2c8e3a094f6 100644 --- a/packages/server/graphql/mutations/helpers/generateDiscussionPrompt.ts +++ b/packages/server/graphql/mutations/helpers/generateDiscussionPrompt.ts @@ -1,4 +1,3 @@ -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import OpenAIServerManager from '../../../utils/OpenAIServerManager' import sendToSentry from '../../../utils/sendToSentry' @@ -26,7 +25,6 @@ const generateDiscussionPrompt = async ( dataLoader.get('retroReflectionsByMeetingId').load(meetingId), dataLoader.get('retroReflectionGroupsByMeetingId').load(meetingId) ]) - const r = await getRethink() const pg = getKysely() const manager = new OpenAIServerManager() if (!reflectionGroups.length) { @@ -46,14 +44,11 @@ const generateDiscussionPrompt = async ( ) if (!fullQuestion) return const discussionPromptQuestion = fullQuestion?.slice(0, 2000) - return Promise.all([ - pg - .updateTable('RetroReflectionGroup') - .set({discussionPromptQuestion}) - .where('id', '=', group.id) - .execute(), - r.table('RetroReflectionGroup').get(group.id).update({discussionPromptQuestion}).run() - ]) + return pg + .updateTable('RetroReflectionGroup') + .set({discussionPromptQuestion}) + .where('id', '=', group.id) + .execute() }) ) } diff --git a/packages/server/graphql/mutations/helpers/handleCompletedStage.ts b/packages/server/graphql/mutations/helpers/handleCompletedStage.ts index 8227ec6fe69..94f44d7f401 100644 --- a/packages/server/graphql/mutations/helpers/handleCompletedStage.ts +++ b/packages/server/graphql/mutations/helpers/handleCompletedStage.ts @@ -35,7 +35,6 @@ const handleCompletedRetrospectiveStage = async ( if (stage.phaseType === REFLECT) { const r = await getRethink() const pg = getKysely() - const now = new Date() const [reflectionGroups, reflections] = await Promise.all([ dataLoader.get('retroReflectionGroupsByMeetingId').load(meeting.id), @@ -60,21 +59,11 @@ const handleCompletedRetrospectiveStage = async ( await Promise.all( sortedReflectionGroups.map((group, index) => { group.sortOrder = index - return Promise.all([ - pg - .updateTable('RetroReflectionGroup') - .set({sortOrder: index}) - .where('id', '=', group.id) - .execute(), - r - .table('RetroReflectionGroup') - .get(group.id) - .update({ - sortOrder: index, - updatedAt: now - } as any) - .run() - ]) + return pg + .updateTable('RetroReflectionGroup') + .set({sortOrder: index}) + .where('id', '=', group.id) + .execute() }) ) diff --git a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts index 463eeecf935..7ebc7bdcf61 100644 --- a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts @@ -597,7 +597,7 @@ export const SlackNotifier = { dataLoader.get('teams').loadNonNull(teamId), dataLoader.get('users').loadNonNull(userId), dataLoader.get('newMeetings').load(meetingId), - dataLoader.get('retroReflectionGroups').load(reflectionGroupId), + dataLoader.get('retroReflectionGroups').loadNonNull(reflectionGroupId), r.table('RetroReflection').getAll(reflectionGroupId, {index: 'reflectionGroupId'}).run(), r .table('SlackAuth') diff --git a/packages/server/graphql/mutations/helpers/removeEmptyReflectionGroup.ts b/packages/server/graphql/mutations/helpers/removeEmptyReflectionGroup.ts index b605ffd4bf2..7958457a558 100644 --- a/packages/server/graphql/mutations/helpers/removeEmptyReflectionGroup.ts +++ b/packages/server/graphql/mutations/helpers/removeEmptyReflectionGroup.ts @@ -7,7 +7,6 @@ const removeEmptyReflectionGroup = async ( ) => { const r = await getRethink() const pg = getKysely() - const now = new Date() if (!reflectionGroupId) return false const reflectionCount = await r .table('RetroReflection') @@ -17,18 +16,11 @@ const removeEmptyReflectionGroup = async ( .run() if (reflectionCount > 0) return - return Promise.all([ - pg - .updateTable('RetroReflectionGroup') - .set({isActive: false}) - .where('id', '=', oldReflectionGroupId) - .execute(), - r - .table('RetroReflectionGroup') - .get(oldReflectionGroupId) - .update({isActive: false, updatedAt: now}) - .run() - ]) + return pg + .updateTable('RetroReflectionGroup') + .set({isActive: false}) + .where('id', '=', oldReflectionGroupId) + .execute() } export default removeEmptyReflectionGroup diff --git a/packages/server/graphql/mutations/helpers/removeEmptyReflections.ts b/packages/server/graphql/mutations/helpers/removeEmptyReflections.ts index c3867b2f4da..1745e5d97f8 100644 --- a/packages/server/graphql/mutations/helpers/removeEmptyReflections.ts +++ b/packages/server/graphql/mutations/helpers/removeEmptyReflections.ts @@ -34,14 +34,7 @@ const removeEmptyReflections = async (meeting: Meeting) => { .updateTable('RetroReflectionGroup') .set({isActive: false}) .where('id', 'in', emptyReflectionGroupIds) - .execute(), - r - .table('RetroReflectionGroup') - .getAll(r.args(emptyReflectionGroupIds), {index: 'id'}) - .update({ - isActive: false - }) - .run() + .execute() ]) } return {emptyReflectionGroupIds} diff --git a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts index 2c9c282e648..b902b66c3a0 100644 --- a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts @@ -172,12 +172,6 @@ const safeEndRetrospective = async ({ .where('meetingId', '=', meetingId) .where('isActive', '=', false) .execute(), - r - .table('RetroReflectionGroup') - .getAll(meetingId, {index: 'meetingId'}) - .filter({isActive: false}) - .delete() - .run(), updateTeamInsights(teamId, dataLoader) ]) // wait for removeEmptyTasks before summarizeRetroMeeting diff --git a/packages/server/graphql/mutations/helpers/safelyCastVote.ts b/packages/server/graphql/mutations/helpers/safelyCastVote.ts index 86d2db1780d..d0e8e030ee9 100644 --- a/packages/server/graphql/mutations/helpers/safelyCastVote.ts +++ b/packages/server/graphql/mutations/helpers/safelyCastVote.ts @@ -5,7 +5,6 @@ import {RValue} from '../../../database/stricterR' import AuthToken from '../../../database/types/AuthToken' import getKysely from '../../../postgres/getKysely' import {getUserId} from '../../../utils/authorization' -import sendToSentry from '../../../utils/sendToSentry' import standardError from '../../../utils/standardError' const safelyCastVote = async ( @@ -40,36 +39,19 @@ const safelyCastVote = async ( return standardError(new Error('No votes remaining'), {userId: viewerId}) } - const [isVoteAddedToGroup, voteAddedResult] = await Promise.all([ - r - .table('RetroReflectionGroup') - .get(reflectionGroupId) - .update((group: RValue) => { - return r.branch( - group('voterIds').count(userId).lt(maxVotesPerGroup), - { - updatedAt: now, - voterIds: group('voterIds').append(userId) - }, - {} - ) - })('replaced') - .eq(1) - .run(), - pg - .updateTable('RetroReflectionGroup') - .set({voterIds: sql`ARRAY_APPEND("voterIds",${userId})`}) - .where('id', '=', reflectionGroupId) - .where( - sql`COALESCE(array_length(array_positions("voterIds", ${userId}),1),0)`, - '<', - maxVotesPerGroup - ) - .executeTakeFirst() - ]) - const isVoteAddedToGroupPG = voteAddedResult.numUpdatedRows === BigInt(1) - if (isVoteAddedToGroupPG !== isVoteAddedToGroup) - sendToSentry(new Error('MISMATCH VOTE CAST LOGIC')) + const voteAddedResult = await pg + .updateTable('RetroReflectionGroup') + .set({voterIds: sql`ARRAY_APPEND("voterIds",${userId})`}) + .where('id', '=', reflectionGroupId) + .where( + sql`COALESCE(array_length(array_positions("voterIds", ${userId}),1),0)`, + '<', + maxVotesPerGroup + ) + .executeTakeFirst() + + const isVoteAddedToGroup = voteAddedResult.numUpdatedRows === BigInt(1) + if (!isVoteAddedToGroup) { await r .table('MeetingMember') diff --git a/packages/server/graphql/mutations/helpers/safelyWithdrawVote.ts b/packages/server/graphql/mutations/helpers/safelyWithdrawVote.ts index 939e0519dcd..b78559ee901 100644 --- a/packages/server/graphql/mutations/helpers/safelyWithdrawVote.ts +++ b/packages/server/graphql/mutations/helpers/safelyWithdrawVote.ts @@ -5,7 +5,6 @@ import {RValue} from '../../../database/stricterR' import AuthToken from '../../../database/types/AuthToken' import getKysely from '../../../postgres/getKysely' import {getUserId} from '../../../utils/authorization' -import sendToSentry from '../../../utils/sendToSentry' import standardError from '../../../utils/standardError' const safelyWithdrawVote = async ( @@ -19,37 +18,18 @@ const safelyWithdrawVote = async ( const pg = getKysely() const now = new Date() const viewerId = getUserId(authToken) - const [isVoteRemovedFromGroup, voteRemovedResult] = await Promise.all([ - r - .table('RetroReflectionGroup') - .get(reflectionGroupId) - .update((group: RValue) => { - return r.branch( - group('voterIds').offsetsOf(userId).count().ge(1), - { - updatedAt: now, - voterIds: group('voterIds').deleteAt(group('voterIds').offsetsOf(userId).nth(0)) - }, - {} - ) - })('replaced') - .eq(1) - .run(), - pg - .updateTable('RetroReflectionGroup') - .set({ - voterIds: sql`array_cat( + const voteRemovedResult = await pg + .updateTable('RetroReflectionGroup') + .set({ + voterIds: sql`array_cat( "voterIds"[1:array_position("voterIds",${userId})-1], "voterIds"[array_position("voterIds",${userId})+1:] )` - }) - .where('id', '=', reflectionGroupId) - .where(sql`${userId}`, '=', sql`ANY("voterIds")`) - .executeTakeFirst() - ]) - const isVoteRemovedFromGroupPG = voteRemovedResult.numUpdatedRows === BigInt(1) - if (isVoteRemovedFromGroup !== isVoteRemovedFromGroupPG) - sendToSentry(new Error('MISMATCH VOTE REMOVED LOGIC')) + }) + .where('id', '=', reflectionGroupId) + .where(sql`${userId}`, '=', sql`ANY("voterIds")`) + .executeTakeFirst() + const isVoteRemovedFromGroup = voteRemovedResult.numUpdatedRows === BigInt(1) if (!isVoteRemovedFromGroup) { return standardError(new Error('Already removed vote'), {userId: viewerId}) } diff --git a/packages/server/graphql/mutations/helpers/updateReflectionLocation/addReflectionToGroup.ts b/packages/server/graphql/mutations/helpers/updateReflectionLocation/addReflectionToGroup.ts index a6d151f0127..5d51532369c 100644 --- a/packages/server/graphql/mutations/helpers/updateReflectionLocation/addReflectionToGroup.ts +++ b/packages/server/graphql/mutations/helpers/updateReflectionLocation/addReflectionToGroup.ts @@ -2,7 +2,6 @@ import dndNoise from 'parabol-client/utils/dndNoise' import getGroupSmartTitle from 'parabol-client/utils/smartGroup/getGroupSmartTitle' import getRethink from '../../../../database/rethinkDriver' import Reflection from '../../../../database/types/Reflection' -import ReflectionGroup from '../../../../database/types/ReflectionGroup' import getKysely from '../../../../postgres/getKysely' import {GQLContext} from './../../../graphql' import updateSmartGroupTitle from './updateSmartGroupTitle' @@ -19,14 +18,13 @@ const addReflectionToGroup = async ( const reflection = await dataLoader.get('retroReflections').load(reflectionId) if (!reflection) throw new Error('Reflection not found') const {reflectionGroupId: oldReflectionGroupId, meetingId: reflectionMeetingId} = reflection - const {reflectionGroup, oldReflectionGroup} = await r({ - reflectionGroup: r - .table('RetroReflectionGroup') - .get(reflectionGroupId) as unknown as ReflectionGroup, - oldReflectionGroup: r - .table('RetroReflectionGroup') - .get(oldReflectionGroupId) as unknown as ReflectionGroup - }).run() + const [reflectionGroup, oldReflectionGroup] = await Promise.all([ + dataLoader.get('retroReflectionGroups').loadNonNull(reflectionGroupId), + dataLoader.get('retroReflectionGroups').loadNonNull(oldReflectionGroupId) + ]) + dataLoader.get('retroReflectionGroups').clear(reflectionGroupId) + dataLoader.get('retroReflectionGroups').clear(oldReflectionGroupId) + if (!reflectionGroup || !reflectionGroup.isActive) { throw new Error('Reflection group not found') } @@ -79,22 +77,11 @@ const addReflectionToGroup = async ( const newGroupHasSmartTitle = reflectionGroup.title === reflectionGroup.smartTitle if (oldGroupHasSingleReflectionCustomTitle && newGroupHasSmartTitle) { // Edge case of dragging a single card with a custom group name on a group with smart name - await Promise.all([ - pg - .updateTable('RetroReflectionGroup') - .set({title: oldReflectionGroup.title, smartTitle: nextTitle}) - .where('id', '=', reflectionGroupId) - .execute(), - r - .table('RetroReflectionGroup') - .get(reflectionGroupId) - .update({ - title: oldReflectionGroup.title, - smartTitle: nextTitle, - updatedAt: now - }) - .run() - ]) + await pg + .updateTable('RetroReflectionGroup') + .set({title: oldReflectionGroup.title, smartTitle: nextTitle}) + .where('id', '=', reflectionGroupId) + .execute() } else { await updateSmartGroupTitle(reflectionGroupId, nextTitle) } @@ -103,21 +90,11 @@ const addReflectionToGroup = async ( const oldTitle = getGroupSmartTitle(oldReflections) await updateSmartGroupTitle(oldReflectionGroupId, oldTitle) } else { - await Promise.all([ - pg - .updateTable('RetroReflectionGroup') - .set({isActive: false}) - .where('id', '=', oldReflectionGroupId) - .execute(), - r - .table('RetroReflectionGroup') - .get(oldReflectionGroupId) - .update({ - isActive: false, - updatedAt: now - }) - .run() - ]) + await pg + .updateTable('RetroReflectionGroup') + .set({isActive: false}) + .where('id', '=', oldReflectionGroupId) + .execute() } } return reflectionGroupId diff --git a/packages/server/graphql/mutations/helpers/updateReflectionLocation/removeReflectionFromGroup.ts b/packages/server/graphql/mutations/helpers/updateReflectionLocation/removeReflectionFromGroup.ts index 66cbb18c44f..c7166d817dc 100644 --- a/packages/server/graphql/mutations/helpers/updateReflectionLocation/removeReflectionFromGroup.ts +++ b/packages/server/graphql/mutations/helpers/updateReflectionLocation/removeReflectionFromGroup.ts @@ -14,17 +14,16 @@ const removeReflectionFromGroup = async (reflectionId: string, {dataLoader}: GQL const reflection = await dataLoader.get('retroReflections').load(reflectionId) if (!reflection) throw new Error('Reflection not found') const {reflectionGroupId: oldReflectionGroupId, meetingId, promptId} = reflection - const [oldReflectionGroup, reflectionGroupsInColumn, meeting] = await Promise.all([ - dataLoader.get('retroReflectionGroups').load(oldReflectionGroupId), - r - .table('RetroReflectionGroup') - .getAll(meetingId, {index: 'meetingId'}) - .filter({isActive: true, promptId}) - .orderBy('sortOrder') - .run(), + const [meetingReflectionGroups, meeting] = await Promise.all([ + dataLoader.get('retroReflectionGroupsByMeetingId').load(meetingId), dataLoader.get('newMeetings').load(meetingId) ]) - + dataLoader.get('retroReflectionGroupsByMeetingId').clear(meetingId) + dataLoader.get('retroReflectionGroups').clearAll() + const oldReflectionGroup = meetingReflectionGroups.find((g) => g.id === oldReflectionGroupId)! + const reflectionGroupsInColumn = meetingReflectionGroups + .filter((g) => g.promptId === promptId) + .sort((a, b) => (a.sortOrder < b.sortOrder ? -1 : 1)) let newSortOrder = 1e6 const oldReflectionGroupIdx = reflectionGroupsInColumn.findIndex( (group) => group.id === oldReflectionGroup.id @@ -47,7 +46,6 @@ const removeReflectionFromGroup = async (reflectionId: string, {dataLoader}: GQL const {id: reflectionGroupId} = reflectionGroup await Promise.all([ pg.insertInto('RetroReflectionGroup').values(reflectionGroup).execute(), - r.table('RetroReflectionGroup').insert(reflectionGroup).run(), r .table('RetroReflection') .get(reflectionId) @@ -77,21 +75,11 @@ const removeReflectionFromGroup = async (reflectionId: string, {dataLoader}: GQL const oldTitle = getGroupSmartTitle(oldReflections) await updateSmartGroupTitle(oldReflectionGroupId, oldTitle) } else { - await Promise.all([ - pg - .updateTable('RetroReflectionGroup') - .set({isActive: false}) - .where('id', '=', oldReflectionGroupId) - .execute(), - r - .table('RetroReflectionGroup') - .get(oldReflectionGroupId) - .update({ - isActive: false, - updatedAt: now - }) - .run() - ]) + await pg + .updateTable('RetroReflectionGroup') + .set({isActive: false}) + .where('id', '=', oldReflectionGroupId) + .execute() } return reflectionGroupId } diff --git a/packages/server/graphql/mutations/helpers/updateReflectionLocation/updateSmartGroupTitle.ts b/packages/server/graphql/mutations/helpers/updateReflectionLocation/updateSmartGroupTitle.ts index da34c3ab2fd..90aab3da6cc 100644 --- a/packages/server/graphql/mutations/helpers/updateReflectionLocation/updateSmartGroupTitle.ts +++ b/packages/server/graphql/mutations/helpers/updateReflectionLocation/updateSmartGroupTitle.ts @@ -1,31 +1,16 @@ import {sql} from 'kysely' -import getRethink from '../../../../database/rethinkDriver' -import {RValue} from '../../../../database/stricterR' import getKysely from '../../../../postgres/getKysely' const updateSmartGroupTitle = async (reflectionGroupId: string, smartTitle: string) => { - const r = await getRethink() const pg = getKysely() - const now = new Date() - await Promise.all([ - pg - .updateTable('RetroReflectionGroup') - .set({ - smartTitle, - title: sql`CASE WHEN "smartTitle" = "title" THEN ${smartTitle} ELSE "title" END` - }) - .where('id', '=', reflectionGroupId) - .execute(), - r - .table('RetroReflectionGroup') - .get(reflectionGroupId) - .update((g: RValue) => ({ - smartTitle, - title: r.branch(g('smartTitle').eq(g('title')), smartTitle, g('title')), - updatedAt: now - })) - .run() - ]) + await pg + .updateTable('RetroReflectionGroup') + .set({ + smartTitle, + title: sql`CASE WHEN "smartTitle" = "title" THEN ${smartTitle} ELSE "title" END` + }) + .where('id', '=', reflectionGroupId) + .execute() } export default updateSmartGroupTitle diff --git a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts index 721182c7242..acb1a07e001 100644 --- a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts +++ b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts @@ -115,11 +115,6 @@ const resetRetroMeetingToGroupStage = { .set({voterIds: [], summary: null, discussionPromptQuestion: null}) .where('id', 'in', reflectionGroupIds) .execute(), - r - .table('RetroReflectionGroup') - .getAll(r.args(reflectionGroupIds)) - .update({voterIds: [], summary: null, discussionPromptQuestion: null}) - .run(), r.table('NewMeeting').get(meetingId).update({phases: newPhases}).run(), (r.table('MeetingMember').getAll(meetingId, {index: 'meetingId'}) as any) .update({votesRemaining: meeting.totalVotes}) diff --git a/packages/server/graphql/mutations/updateReflectionGroupTitle.ts b/packages/server/graphql/mutations/updateReflectionGroupTitle.ts index 24e612d8615..41a3ca4978c 100644 --- a/packages/server/graphql/mutations/updateReflectionGroupTitle.ts +++ b/packages/server/graphql/mutations/updateReflectionGroupTitle.ts @@ -2,7 +2,6 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' import stringSimilarity from 'string-similarity' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' @@ -32,14 +31,14 @@ export default { {reflectionGroupId, title}: UpdateReflectionGroupTitleMutationVariables, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { - const r = await getRethink() const pg = getKysely() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} // AUTH const viewerId = getUserId(authToken) - const reflectionGroup = await r.table('RetroReflectionGroup').get(reflectionGroupId).run() + const reflectionGroup = await dataLoader.get('retroReflectionGroups').load(reflectionGroupId) + if (!reflectionGroup) { return standardError(new Error('Reflection group not found'), {userId: viewerId}) } @@ -70,30 +69,19 @@ export default { return {error: {message: 'Title is too long'}} } - const allTitles = await r - .table('RetroReflectionGroup') - .getAll(meetingId, {index: 'meetingId'}) - .filter({isActive: true})('title') - .run() + const allGroups = await dataLoader.get('retroReflectionGroupsByMeetingId').load(meetingId) + const allTitles = allGroups.map((g) => g.title) if (allTitles.includes(normalizedTitle)) { return standardError(new Error('Group titles must be unique'), {userId: viewerId}) } // RESOLUTION - await Promise.all([ - r - .table('RetroReflectionGroup') - .get(reflectionGroupId) - .update({ - title: normalizedTitle - }) - .run(), - pg - .updateTable('RetroReflectionGroup') - .set({title: normalizedTitle}) - .where('id', '=', reflectionGroupId) - .execute() - ]) + dataLoader.get('retroReflectionGroups').clear(reflectionGroupId) + await pg + .updateTable('RetroReflectionGroup') + .set({title: normalizedTitle}) + .where('id', '=', reflectionGroupId) + .execute() if (smartTitle && smartTitle === oldTitle) { // let's see how smart those smart titles really are. A high similarity means very helpful. Not calling this mutation means perfect! diff --git a/packages/server/graphql/mutations/voteForReflectionGroup.ts b/packages/server/graphql/mutations/voteForReflectionGroup.ts index fa2d0882c1a..fe193c618c9 100644 --- a/packages/server/graphql/mutations/voteForReflectionGroup.ts +++ b/packages/server/graphql/mutations/voteForReflectionGroup.ts @@ -35,7 +35,7 @@ export default { // AUTH const viewerId = getUserId(authToken) - const reflectionGroup = await r.table('RetroReflectionGroup').get(reflectionGroupId).run() + const reflectionGroup = await dataLoader.get('retroReflectionGroups').load(reflectionGroupId) if (!reflectionGroup || !reflectionGroup.isActive) { return standardError(new Error('Reflection group not found'), { userId: viewerId, @@ -66,6 +66,7 @@ export default { } // RESOLUTION + dataLoader.get('retroReflectionGroups').clear(reflectionGroupId) if (isUnvote) { const votingError = await safelyWithdrawVote( authToken, diff --git a/packages/server/graphql/private/mutations/backupOrganization.ts b/packages/server/graphql/private/mutations/backupOrganization.ts index 5938ccbfbd4..f41038a198d 100644 --- a/packages/server/graphql/private/mutations/backupOrganization.ts +++ b/packages/server/graphql/private/mutations/backupOrganization.ts @@ -280,23 +280,6 @@ const backupOrganization: MutationResolvers['backupOrganization'] = async (_sour ) .coerceTo('array') .do((items: RValue) => r.db(DESTINATION).table('RetroReflection').insert(items)), - retroReflectionGroup: ( - r.table('RetroReflectionGroup').getAll(r.args(meetingIds), {index: 'meetingId'}) as any - ) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('RetroReflectionGroup').insert(items)), - // really hard things to clone - reflectionGroupComments: r - .table('RetroReflectionGroup') - .getAll(r.args(meetingIds), {index: 'meetingId'})('id') - .coerceTo('array') - .do((discussionIds: RValue) => { - return ( - r.table('Comment').getAll(r.args(discussionIds), {index: 'discussionId'}) as any - ) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('Comment').insert(items)) - }), agendaItemComments: r .table('AgendaItem') .getAll(r.args(meetingIds), {index: 'meetingId'})('id') diff --git a/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts b/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts index 54e4ea2713b..932c9bccf40 100644 --- a/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts +++ b/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts @@ -35,7 +35,7 @@ const checkRethinkPgEquality: MutationResolvers['checkRethinkPgEquality'] = asyn const rowCountResult = await checkRowCount(tableName) const rethinkQuery = (updatedAt: Date, id: string | number) => { return r - .table('RetroReflectionGroup') + .table('RetroReflectionGroup' as any) .between([updatedAt, id], [r.maxval, r.maxval], { index: 'updatedAtId', leftBound: 'open', diff --git a/packages/server/graphql/public/types/RetroReflectionGroup.ts b/packages/server/graphql/public/types/RetroReflectionGroup.ts new file mode 100644 index 00000000000..404834dcc57 --- /dev/null +++ b/packages/server/graphql/public/types/RetroReflectionGroup.ts @@ -0,0 +1,9 @@ +import {Selectable} from 'kysely' +import {RetroReflectionGroup as TRetroReflectionGroup} from '../../../postgres/pg' +import {RetroReflectionGroupResolvers} from '../resolverTypes' + +export interface RetroReflectionGroupSource extends Selectable {} + +const RetroReflectionGroup: RetroReflectionGroupResolvers = {} + +export default RetroReflectionGroup diff --git a/packages/server/graphql/public/types/RetrospectiveMeeting.ts b/packages/server/graphql/public/types/RetrospectiveMeeting.ts index a4ad2df6f03..1501e3805e7 100644 --- a/packages/server/graphql/public/types/RetrospectiveMeeting.ts +++ b/packages/server/graphql/public/types/RetrospectiveMeeting.ts @@ -1,5 +1,4 @@ import toTeamMemberId from '../../../../client/utils/relay/toTeamMemberId' -import ReflectionGroupType from '../../../database/types/ReflectionGroup' import RetroMeetingMember from '../../../database/types/RetroMeetingMember' import {getUserId} from '../../../utils/authorization' import filterTasksByMeeting from '../../../utils/filterTasksByMeeting' @@ -20,7 +19,7 @@ const RetrospectiveMeeting: RetrospectiveMeetingResolvers = { reflectionCount: ({reflectionCount}) => reflectionCount || 0, reflectionGroup: async ({id: meetingId}, {reflectionGroupId}, {dataLoader}) => { const reflectionGroup = await dataLoader.get('retroReflectionGroups').load(reflectionGroupId) - if (reflectionGroup.meetingId !== meetingId) return null + if (reflectionGroup?.meetingId !== meetingId) return null return reflectionGroup }, reflectionGroups: async ({id: meetingId}, {sortBy}, {dataLoader}) => { @@ -28,9 +27,7 @@ const RetrospectiveMeeting: RetrospectiveMeetingResolvers = { .get('retroReflectionGroupsByMeetingId') .load(meetingId) if (sortBy === 'voteCount') { - reflectionGroups.sort((a: ReflectionGroupType, b: ReflectionGroupType) => - a.voterIds.length < b.voterIds.length ? 1 : -1 - ) + reflectionGroups.sort((a, b) => (a.voterIds.length < b.voterIds.length ? 1 : -1)) return reflectionGroups } else if (sortBy === 'stageOrder') { const meeting = await dataLoader.get('newMeetings').load(meetingId) @@ -40,18 +37,16 @@ const RetrospectiveMeeting: RetrospectiveMeetingResolvers = { const {stages} = discussPhase // for early terminations the stages may not exist const sortLookup = {} as {[reflectionGroupId: string]: number} - reflectionGroups.forEach((group: ReflectionGroupType) => { + reflectionGroups.forEach((group) => { const idx = stages.findIndex((stage) => stage.reflectionGroupId === group.id) sortLookup[group.id] = idx }) - reflectionGroups.sort((a: ReflectionGroupType, b: ReflectionGroupType) => { + reflectionGroups.sort((a, b) => { return sortLookup[a.id]! < sortLookup[b.id]! ? -1 : 1 }) return reflectionGroups } - reflectionGroups.sort((a: ReflectionGroupType, b: ReflectionGroupType) => - a.sortOrder < b.sortOrder ? -1 : 1 - ) + reflectionGroups.sort((a, b) => (a.sortOrder < b.sortOrder ? -1 : 1)) return reflectionGroups }, taskCount: ({taskCount}) => taskCount || 0, From 698444534c97a6d682a4d9b7e267941520a49564 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 6 Jun 2024 12:36:02 +0100 Subject: [PATCH 249/529] chore(release): release v7.35.1 (#9817) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fb632dc6f0a..245678114e4 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.35.0" + ".": "7.35.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 12642d0bcf8..d240965d649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.35.1](https://github.com/ParabolInc/parabol/compare/v7.35.0...v7.35.1) (2024-06-04) + + +### Fixed + +* remove custom activity badge ([#9812](https://github.com/ParabolInc/parabol/issues/9812)) ([652a9c0](https://github.com/ParabolInc/parabol/commit/652a9c034267f9c53f4cf9c04b4f29b6f854eb1c)) + + +### Changed + +* read ReflectionGroups from PG ([#9801](https://github.com/ParabolInc/parabol/issues/9801)) ([52b80b5](https://github.com/ParabolInc/parabol/commit/52b80b5cdb7fa4834004516bc8fc997fda9c3369)) + ## [7.35.0](https://github.com/ParabolInc/parabol/compare/v7.34.0...v7.35.0) (2024-05-30) diff --git a/package.json b/package.json index e4a4186934e..449b3a9e751 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.35.0", + "version": "7.35.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 2bdc5f14c9a..ad91f7f652f 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.35.0", + "version": "7.35.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.35.0" + "parabol-server": "7.35.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index fa84db39acd..b25bda684ea 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.35.0", + "version": "7.35.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 59aa95203b6..1d0c780f207 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.35.0", + "version": "7.35.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 cbbeb8e3554..5abedd96382 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.35.0", + "version": "7.35.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.35.0", - "parabol-server": "7.35.0", + "parabol-client": "7.35.1", + "parabol-server": "7.35.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 61634e7a21e..33be471cd93 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.35.0", + "version": "7.35.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 6cd52324516..dee1b548dfa 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.35.0", + "version": "7.35.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.35.0", + "parabol-client": "7.35.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From b683dc83426bfa96eee20b0ceb4d90640b9eb9b6 Mon Sep 17 00:00:00 2001 From: Terry Acker Date: Thu, 6 Jun 2024 10:14:52 -0500 Subject: [PATCH 250/529] feat: security banner concept (#9780) --- .env.example | 6 ++ packages/client/components/Action/Action.tsx | 90 +++++++++++-------- .../ActivityDetails/ActivityDetails.tsx | 4 +- .../ActivityDetailsSidebar.tsx | 2 +- .../client/components/AuthPage/Header.tsx | 4 +- .../Dashboard/MobileDashSidebar.tsx | 5 +- .../EstimatePhaseDiscussionDrawer.tsx | 11 ++- packages/client/components/GlobalBanner.tsx | 50 +++++++++++ .../client/components/NewMeetingSidebar.tsx | 33 ++++--- .../TeamInvitationMeetingAbstract.tsx | 8 +- .../components/TeamInvitationWrapper.tsx | 2 +- .../TeamPrompt/TeamPromptDrawer.tsx | 11 ++- .../client/components/TimelineRightDrawer.tsx | 2 +- .../summary/components/NewMeetingSummary.tsx | 4 +- .../components/TeamDashMain/TeamDrawer.tsx | 9 +- .../components/OrgBilling/OrgPlanDrawer.tsx | 11 ++- .../OrgBilling/OrgPlanDrawerContent.tsx | 5 +- packages/client/types/constEnums.ts | 4 + packages/client/types/modules.d.ts | 4 + .../toolboxSrc/applyEnvVarsToClientAssets.ts | 6 +- scripts/webpack/dev.client.config.js | 10 ++- tailwind.config.js | 2 +- 22 files changed, 208 insertions(+), 75 deletions(-) create mode 100644 packages/client/components/GlobalBanner.tsx diff --git a/.env.example b/.env.example index 7b1a61ae04c..71aed091304 100644 --- a/.env.example +++ b/.env.example @@ -146,3 +146,9 @@ MAIL_PROVIDER='debug' # PGAdmin - used only with yarn db:start command PGADMIN_DEFAULT_EMAIL='pgadmin4@pgadmin.org' PGADMIN_DEFAULT_PASSWORD='admin' + +# GLOBAL BANNER +# GLOBAL_BANNER_ENABLED='true' +# GLOBAL_BANNER_TEXT='UNCLASSIFIED CUI (IL4)' +# GLOBAL_BANNER_BG_COLOR='#007A33' +# GLOBAL_BANNER_COLOR='#FFFFFF' diff --git a/packages/client/components/Action/Action.tsx b/packages/client/components/Action/Action.tsx index c015ad8960b..037263b1ba9 100644 --- a/packages/client/components/Action/Action.tsx +++ b/packages/client/components/Action/Action.tsx @@ -3,9 +3,10 @@ import 'react-day-picker/dist/style.css' import {Route, Switch} from 'react-router' import useServiceWorkerUpdater from '../../hooks/useServiceWorkerUpdater' import useTrebuchetEvents from '../../hooks/useTrebuchetEvents' -import {LoaderSize} from '../../types/constEnums' +import {GlobalBanner, LoaderSize} from '../../types/constEnums' import {CREATE_ACCOUNT_SLUG, SIGNIN_SLUG} from '../../utils/constants' import ErrorBoundary from '../ErrorBoundary' +import Banner from '../GlobalBanner' import LoadingComponent from '../LoadingComponent/LoadingComponent' import PrivateRoutes from '../PrivateRoutes' import Snackbar from '../Snackbar' @@ -33,53 +34,68 @@ const Action = memo(() => { useTrebuchetEvents() useServiceWorkerUpdater() const isInternalAuthEnabled = window.__ACTION__.AUTH_INTERNAL_ENABLED + // Global Banner + const isGlobalBannerEnabled = window.__ACTION__.GLOBAL_BANNER_ENABLED + const bannerText = window.__ACTION__.GLOBAL_BANNER_TEXT + const bannerBgColor = window.__ACTION__.GLOBAL_BANNER_BG_COLOR + const bannerColor = window.__ACTION__.GLOBAL_BANNER_COLOR return ( <> }> - - } /> - } - /> - } - /> - - - - - {isInternalAuthEnabled && ( + {isGlobalBannerEnabled && ( + + )} +
+ + } /> } + path={`/${SIGNIN_SLUG}`} + render={(p) => } /> - )} - {isInternalAuthEnabled && ( } + exact + path={`/${CREATE_ACCOUNT_SLUG}`} + render={(p) => } + /> + + + + + {isInternalAuthEnabled && ( + } + /> + )} + {isInternalAuthEnabled && ( + } + /> + )} + - )} - - - - - - + + + + + +
diff --git a/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx b/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx index 2fb98c8671c..3f5eeede290 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx @@ -89,7 +89,7 @@ const ActivityDetails = (props: Props) => { const isOwner = viewerLowestScope === 'TEAM' return ( -
+
@@ -130,7 +130,7 @@ const ActivityDetails = (props: Props) => {
-
+
{ return ( <> -
+
Settings diff --git a/packages/client/components/AuthPage/Header.tsx b/packages/client/components/AuthPage/Header.tsx index 64064404601..b96c07da245 100644 --- a/packages/client/components/AuthPage/Header.tsx +++ b/packages/client/components/AuthPage/Header.tsx @@ -7,6 +7,7 @@ import React from 'react' import {Link} from 'react-router-dom' import {PALETTE} from '../../styles/paletteV3' import parabolLogo from '../../styles/theme/images/brand/lockup_color_mark_white_type.svg' +import {AppBar} from '../../types/constEnums' const HeaderContainer = styled('div')({ alignItems: 'center', @@ -14,8 +15,9 @@ const HeaderContainer = styled('div')({ color: '#FFFFFF', display: 'flex', flexDirection: 'row', + height: AppBar.HEIGHT, justifyContent: 'center', - minHeight: 56, + minHeight: AppBar.HEIGHT, width: '100%' }) diff --git a/packages/client/components/Dashboard/MobileDashSidebar.tsx b/packages/client/components/Dashboard/MobileDashSidebar.tsx index 6e8571bd7d9..2c2e7b1fdad 100644 --- a/packages/client/components/Dashboard/MobileDashSidebar.tsx +++ b/packages/client/components/Dashboard/MobileDashSidebar.tsx @@ -5,7 +5,7 @@ import {useFragment} from 'react-relay' import {useRouteMatch} from 'react-router' import {DashSidebar_viewer$key} from '../../__generated__/DashSidebar_viewer.graphql' import {PALETTE} from '../../styles/paletteV3' -import {NavSidebar} from '../../types/constEnums' +import {GlobalBanner, NavSidebar} from '../../types/constEnums' import { AUTHENTICATION_PAGE, BILLING_PAGE, @@ -18,6 +18,8 @@ import StandardHub from '../StandardHub/StandardHub' import LeftDashNavItem from './LeftDashNavItem' import LeftDashParabol from './LeftDashNavParabol' +const isGlobalBannerEnabled = window.__ACTION__.GLOBAL_BANNER_ENABLED + interface Props { handleMenuClick: () => void viewerRef: DashSidebar_viewer$key | null @@ -32,6 +34,7 @@ const DashSidebarStyles = styled('div')({ maxWidth: NavSidebar.WIDTH, minWidth: NavSidebar.WIDTH, overflow: 'hidden', + paddingTop: isGlobalBannerEnabled ? GlobalBanner.HEIGHT : 0, userSelect: 'none' }) diff --git a/packages/client/components/EstimatePhaseDiscussionDrawer.tsx b/packages/client/components/EstimatePhaseDiscussionDrawer.tsx index 49d9b65a1b1..e8cfd92bfbe 100644 --- a/packages/client/components/EstimatePhaseDiscussionDrawer.tsx +++ b/packages/client/components/EstimatePhaseDiscussionDrawer.tsx @@ -6,13 +6,21 @@ import {useFragment} from 'react-relay' import {EstimatePhaseDiscussionDrawer_meeting$key} from '~/__generated__/EstimatePhaseDiscussionDrawer_meeting.graphql' import {desktopSidebarShadow} from '~/styles/elevation' import {PALETTE} from '~/styles/paletteV3' -import {BezierCurve, Breakpoint, DiscussionThreadEnum, ZIndex} from '../types/constEnums' +import { + BezierCurve, + Breakpoint, + DiscussionThreadEnum, + GlobalBanner, + ZIndex +} from '../types/constEnums' import {DiscussionThreadables} from './DiscussionThreadList' import DiscussionThreadListEmptyState from './DiscussionThreadListEmptyState' import DiscussionThreadRoot from './DiscussionThreadRoot' import LabelHeading from './LabelHeading/LabelHeading' import PlainButton from './PlainButton/PlainButton' +const isGlobalBannerEnabled = window.__ACTION__.GLOBAL_BANNER_ENABLED + const Drawer = styled('div')<{isDesktop: boolean; isOpen: boolean}>(({isDesktop, isOpen}) => ({ boxShadow: isDesktop ? desktopSidebarShadow : undefined, backgroundColor: '#FFFFFF', @@ -21,6 +29,7 @@ const Drawer = styled('div')<{isDesktop: boolean; isOpen: boolean}>(({isDesktop, height: '100vh', justifyContent: 'flex-start', overflow: 'hidden', + paddingTop: isGlobalBannerEnabled ? GlobalBanner.HEIGHT : 0, position: isDesktop ? 'fixed' : 'static', bottom: 0, top: 0, diff --git a/packages/client/components/GlobalBanner.tsx b/packages/client/components/GlobalBanner.tsx new file mode 100644 index 00000000000..2821a48413a --- /dev/null +++ b/packages/client/components/GlobalBanner.tsx @@ -0,0 +1,50 @@ +import clsx from 'clsx' +import React from 'react' + +interface Props { + bgColor: string + color: string + text: string +} + +// https://www.astrouxds.com/components/classification-markings/#banner-examples + +// Note: banner height is 24px per Tailwind settings (Scale of 6: h-6, leading-6, p-6, etc.) +// This height value is also in constEnums GlobalBanner + +const GlobalBanner = (props: Props) => { + const {bgColor, color, text} = props + return ( +
+ {/* Container div creates natural height to push down + container wrapping in Action.tsx. + Meanwhile the main div here is postion: fixed. */} +
+ {text} +
+
+ ) +} + +export default GlobalBanner diff --git a/packages/client/components/NewMeetingSidebar.tsx b/packages/client/components/NewMeetingSidebar.tsx index 22b76fa85f9..0591e5461a6 100644 --- a/packages/client/components/NewMeetingSidebar.tsx +++ b/packages/client/components/NewMeetingSidebar.tsx @@ -6,8 +6,9 @@ import {Link} from 'react-router-dom' import {NewMeetingSidebar_meeting$key} from '~/__generated__/NewMeetingSidebar_meeting.graphql' import useAtmosphere from '~/hooks/useAtmosphere' import {useRenameMeeting} from '~/hooks/useRenameMeeting' +import useBreakpoint from '../hooks/useBreakpoint' import {PALETTE} from '../styles/paletteV3' -import {NavSidebar} from '../types/constEnums' +import {Breakpoint, GlobalBanner, NavSidebar} from '../types/constEnums' import isDemoRoute from '../utils/isDemoRoute' import EditableText from './EditableText' import Facilitator from './Facilitator' @@ -16,6 +17,22 @@ import NewMeetingSidebarUpgradeBlock from './NewMeetingSidebarUpgradeBlock' import SidebarToggle from './SidebarToggle' import InactiveTag from './Tag/InactiveTag' +const isGlobalBannerEnabled = window.__ACTION__.GLOBAL_BANNER_ENABLED +const sidebarHeight = isGlobalBannerEnabled ? `calc(100vh - ${GlobalBanner.HEIGHT}px)` : '100vh' +const sidebarPaddingTop = isGlobalBannerEnabled ? GlobalBanner.HEIGHT : 0 + +const SidebarParent = styled('div')<{isDesktop: boolean}>(({isDesktop}) => ({ + backgroundColor: '#FFFFFF', + display: 'flex', + flex: 1, + flexDirection: 'column', + height: isDesktop ? sidebarHeight : '100vh', + maxWidth: NavSidebar.WIDTH, + minWidth: NavSidebar.WIDTH, + paddingTop: isDesktop ? 0 : sidebarPaddingTop, + userSelect: 'none' +})) + const MeetingName = styled('div')({ fontSize: 20, fontWeight: 600, @@ -39,17 +56,6 @@ const StyledToggle = styled(SidebarToggle)({ paddingRight: 16 }) -const SidebarParent = styled('div')({ - backgroundColor: '#FFFFFF', - display: 'flex', - flex: 1, - flexDirection: 'column', - height: '100vh', - maxWidth: NavSidebar.WIDTH, - minWidth: NavSidebar.WIDTH, - userSelect: 'none' -}) - const TeamDashboardLink = styled(Link)({ color: PALETTE.SKY_500, display: 'block', @@ -106,9 +112,10 @@ const NewMeetingSidebar = (props: Props) => { const atmosphere = useAtmosphere() const {viewerId} = atmosphere const isFacilitator = viewerId === facilitatorUserId + const isDesktop = useBreakpoint(Breakpoint.SIDEBAR_LEFT) return ( - +
diff --git a/packages/client/components/TeamInvitationMeetingAbstract.tsx b/packages/client/components/TeamInvitationMeetingAbstract.tsx index 171edaa8575..2c9ba6c37fe 100644 --- a/packages/client/components/TeamInvitationMeetingAbstract.tsx +++ b/packages/client/components/TeamInvitationMeetingAbstract.tsx @@ -10,7 +10,8 @@ const PageContainer = styled('div')({ display: 'flex', flexDirection: 'column', maxWidth: '100%', - minHeight: '100vh', + height: '100%', + maxHeight: '100%', position: 'relative' }) @@ -28,7 +29,8 @@ const CenteredBlock = styled('div')({ const Backdrop = styled('div')({ backgroundColor: PALETTE.SLATE_700_30, - height: '100vh', + bottom: 0, + height: '100%', left: 0, position: 'absolute', top: 0, @@ -53,7 +55,7 @@ const AbstractSidebar = styled('div')({ display: 'block', backgroundColor: 'white', flexShrink: 0, - height: '100vh', + height: '100%', width: 240 } }) diff --git a/packages/client/components/TeamInvitationWrapper.tsx b/packages/client/components/TeamInvitationWrapper.tsx index 1748381e3ed..f2dfd2b8195 100644 --- a/packages/client/components/TeamInvitationWrapper.tsx +++ b/packages/client/components/TeamInvitationWrapper.tsx @@ -8,7 +8,7 @@ interface Props { function TeamInvitationWrapper(props: Props) { const {children} = props return ( -
+
{children}
diff --git a/packages/client/components/TeamPrompt/TeamPromptDrawer.tsx b/packages/client/components/TeamPrompt/TeamPromptDrawer.tsx index 3392a34bcb7..a52bf4888f5 100644 --- a/packages/client/components/TeamPrompt/TeamPromptDrawer.tsx +++ b/packages/client/components/TeamPrompt/TeamPromptDrawer.tsx @@ -6,13 +6,21 @@ import {TeamPromptDrawer_meeting$key} from '~/__generated__/TeamPromptDrawer_mee import useAtmosphere from '~/hooks/useAtmosphere' import useBreakpoint from '../../hooks/useBreakpoint' import {desktopSidebarShadow} from '../../styles/elevation' -import {BezierCurve, Breakpoint, DiscussionThreadEnum, ZIndex} from '../../types/constEnums' +import { + BezierCurve, + Breakpoint, + DiscussionThreadEnum, + GlobalBanner, + ZIndex +} from '../../types/constEnums' import SendClientSideEvent from '../../utils/SendClientSideEvent' import findStageById from '../../utils/meetings/findStageById' import ResponsiveDashSidebar from '../ResponsiveDashSidebar' import TeamPromptDiscussionDrawer from './TeamPromptDiscussionDrawer' import TeamPromptWorkDrawer from './TeamPromptWorkDrawer' +const isGlobalBannerEnabled = window.__ACTION__.GLOBAL_BANNER_ENABLED + export const Drawer = styled('div')<{isDesktop: boolean; isMobile: boolean; isOpen: boolean}>( ({isDesktop, isMobile, isOpen}) => ({ boxShadow: isDesktop ? desktopSidebarShadow : undefined, @@ -22,6 +30,7 @@ export const Drawer = styled('div')<{isDesktop: boolean; isMobile: boolean; isOp flexDirection: 'column', justifyContent: 'stretch', overflow: 'hidden', + paddingTop: isGlobalBannerEnabled ? GlobalBanner.HEIGHT : 0, position: isDesktop ? 'fixed' : 'static', bottom: 0, top: 0, diff --git a/packages/client/components/TimelineRightDrawer.tsx b/packages/client/components/TimelineRightDrawer.tsx index f634ecc9387..ba23d3479d6 100644 --- a/packages/client/components/TimelineRightDrawer.tsx +++ b/packages/client/components/TimelineRightDrawer.tsx @@ -24,7 +24,7 @@ export const RightDrawer = styled('div')({ minWidth: DashTimeline.TIMELINE_DRAWER_WIDTH, maxWidth: DashTimeline.TIMELINE_DRAWER_WIDTH, borderLeft: `1px solid ${PALETTE.SLATE_400}`, - height: 'fit-content', + height: '100%', padding: 16, [makeMinWidthMediaQuery(MIN_WIDTH)]: { display: 'block' diff --git a/packages/client/modules/summary/components/NewMeetingSummary.tsx b/packages/client/modules/summary/components/NewMeetingSummary.tsx index cb42c5637a8..15674979357 100644 --- a/packages/client/modules/summary/components/NewMeetingSummary.tsx +++ b/packages/client/modules/summary/components/NewMeetingSummary.tsx @@ -80,13 +80,13 @@ const NewMeetingSummary = (props: Props) => {
)} -
+
{!isDemoRoute() && (
)} -
+
(({isDesktop}) => ({ backgroundColor: PALETTE.WHITE, display: 'flex', overflow: 'hidden', - padding: `0 0 ${isDesktop ? 58 : 0}px`, + padding: `0 0 ${isDesktop ? bottomPadding : 0}px`, + paddingTop: !isDesktop && isGlobalBannerEnabled ? GlobalBanner.HEIGHT : 0, height: '100vh', flexDirection: 'column', justifyContent: 'space-between', diff --git a/packages/client/modules/userDashboard/components/OrgBilling/OrgPlanDrawer.tsx b/packages/client/modules/userDashboard/components/OrgBilling/OrgPlanDrawer.tsx index 39766aa6e5e..e808c6e831e 100644 --- a/packages/client/modules/userDashboard/components/OrgBilling/OrgPlanDrawer.tsx +++ b/packages/client/modules/userDashboard/components/OrgBilling/OrgPlanDrawer.tsx @@ -13,9 +13,17 @@ import useBreakpoint from '../../../../hooks/useBreakpoint' import {desktopSidebarShadow} from '../../../../styles/elevation' import {PALETTE} from '../../../../styles/paletteV3' import {ICON_SIZE} from '../../../../styles/typographyV2' -import {BezierCurve, Breakpoint, DiscussionThreadEnum, ZIndex} from '../../../../types/constEnums' +import { + BezierCurve, + Breakpoint, + DiscussionThreadEnum, + GlobalBanner, + ZIndex +} from '../../../../types/constEnums' import OrgPlanDrawerContent from './OrgPlanDrawerContent' +const isGlobalBannerEnabled = window.__ACTION__.GLOBAL_BANNER_ENABLED + const DrawerHeader = styled('div')({ alignItems: 'center', display: 'flex', @@ -32,6 +40,7 @@ const Drawer = styled('div')<{isDesktop: boolean; isOpen: boolean}>(({isDesktop, flexDirection: 'column', justifyContent: 'stretch', overflow: 'hidden', + paddingTop: isGlobalBannerEnabled ? GlobalBanner.HEIGHT : 0, position: isDesktop ? 'fixed' : 'static', bottom: 0, top: 0, diff --git a/packages/client/modules/userDashboard/components/OrgBilling/OrgPlanDrawerContent.tsx b/packages/client/modules/userDashboard/components/OrgBilling/OrgPlanDrawerContent.tsx index dfd3bbe1089..ddd78c4a89c 100644 --- a/packages/client/modules/userDashboard/components/OrgBilling/OrgPlanDrawerContent.tsx +++ b/packages/client/modules/userDashboard/components/OrgBilling/OrgPlanDrawerContent.tsx @@ -16,10 +16,9 @@ const List = styled('div')({ const DrawerContent = styled('div')({ backgroundColor: PALETTE.WHITE, display: 'flex', + flexDirection: 'column', overflowY: 'auto', - padding: 16, - height: '100vh', - flexDirection: 'column' + padding: 16 }) const Title = styled('span')({ diff --git a/packages/client/types/constEnums.ts b/packages/client/types/constEnums.ts index 28bbd744b85..5414c339757 100644 --- a/packages/client/types/constEnums.ts +++ b/packages/client/types/constEnums.ts @@ -161,6 +161,10 @@ export const enum Filter { BENEATH_DIALOG = 'blur(1.5px)' } +export const enum GlobalBanner { + HEIGHT = 24 +} + export const enum Gutters { COLUMN_INNER_GUTTER = '12px', DASH_GUTTER = '20px', diff --git a/packages/client/types/modules.d.ts b/packages/client/types/modules.d.ts index 28b06e27aa7..5408bedab4a 100644 --- a/packages/client/types/modules.d.ts +++ b/packages/client/types/modules.d.ts @@ -48,6 +48,10 @@ interface Window { AMPLITUDE_WRITE_KEY: string microsoftTenantId: string microsoft: string + GLOBAL_BANNER_ENABLED: boolean + GLOBAL_BANNER_TEXT: string + GLOBAL_BANNER_BG_COLOR: string + GLOBAL_BANNER_COLOR: string } } declare type Json = null | boolean | number | string | Json[] | {[key: string]: Json} diff --git a/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts b/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts index 1723bab9515..2395ed1a87a 100644 --- a/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts +++ b/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts @@ -67,7 +67,11 @@ const rewriteIndexHTML = () => { AUTH_SSO_ENABLED: process.env.AUTH_SSO_DISABLED !== 'true', AMPLITUDE_WRITE_KEY: process.env.AMPLITUDE_WRITE_KEY, microsoftTenantId: process.env.MICROSOFT_TENANT_ID, - microsoft: process.env.MICROSOFT_CLIENT_ID + microsoft: process.env.MICROSOFT_CLIENT_ID, + GLOBAL_BANNER_ENABLED: process.env.GLOBAL_BANNER_ENABLED === 'true', + GLOBAL_BANNER_TEXT: process.env.GLOBAL_BANNER_TEXT, + GLOBAL_BANNER_BG_COLOR: process.env.GLOBAL_BANNER_BG_COLOR, + GLOBAL_BANNER_COLOR: process.env.GLOBAL_BANNER_COLOR } const skeleton = fs.readFileSync(path.join(clientDir, 'skeleton.html'), 'utf8') diff --git a/scripts/webpack/dev.client.config.js b/scripts/webpack/dev.client.config.js index e3d6766bf59..183106f1d1e 100644 --- a/scripts/webpack/dev.client.config.js +++ b/scripts/webpack/dev.client.config.js @@ -60,9 +60,9 @@ module.exports = { // important terminating / so saml-redirect doesn't get targeted, too 'saml/' ].map((name) => ({ - context: [`/${name}`], - target: `http://localhost:${SOCKET_PORT}`, - })) + context: [`/${name}`], + target: `http://localhost:${SOCKET_PORT}` + })) }, infrastructureLogging: {level: 'warn'}, watchOptions: { @@ -131,6 +131,10 @@ module.exports = { AMPLITUDE_WRITE_KEY: process.env.AMPLITUDE_WRITE_KEY, microsoftTenantId: process.env.MICROSOFT_TENANT_ID, microsoft: process.env.MICROSOFT_CLIENT_ID, + GLOBAL_BANNER_ENABLED: process.env.GLOBAL_BANNER_ENABLED === 'true', + GLOBAL_BANNER_TEXT: process.env.GLOBAL_BANNER_TEXT, + GLOBAL_BANNER_BG_COLOR: process.env.GLOBAL_BANNER_BG_COLOR, + GLOBAL_BANNER_COLOR: process.env.GLOBAL_BANNER_COLOR }) }), new ReactRefreshWebpackPlugin(), diff --git a/tailwind.config.js b/tailwind.config.js index e5b1db332a9..73d7ba98197 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -22,7 +22,7 @@ module.exports = { 'icon-md-48': '48px' }, boxShadow: { - 'card': 'rgba(0,0,0,.2) 0px 2px 1px -1px, rgba(0,0,0,.14) 0px 1px 1px 0px, rgba(0,0,0,.12) 0px 1px 3px 0px', + card: 'rgba(0,0,0,.2) 0px 2px 1px -1px, rgba(0,0,0,.14) 0px 1px 1px 0px, rgba(0,0,0,.12) 0px 1px 3px 0px', 'card-1': '0px 6px 10px rgba(68, 66, 88, 0.14), 0px 1px 18px rgba(68, 66, 88, 0.12), 0px 3px 5px rgba(68, 66, 88, 0.2)', dialog: From 695646c6885c31e2049a1ac76fd3f4b9b589f5da Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 6 Jun 2024 12:53:45 -0700 Subject: [PATCH 251/529] chore: refactor ReflectionGroup to SDL pattern (#9807) Signed-off-by: Matt Krick --- .../client/mutations/AutogroupMutation.ts | 1 - .../EndDraggingReflectionMutation.ts | 3 +- .../ResetReflectionGroupsMutation.ts | 1 - packages/server/graphql/composeResolvers.ts | 7 +- packages/server/graphql/public/permissions.ts | 4 + .../public/types/RetroReflectionGroup.ts | 38 ++++- .../graphql/types/RetroReflectionGroup.ts | 140 +----------------- 7 files changed, 47 insertions(+), 147 deletions(-) diff --git a/packages/client/mutations/AutogroupMutation.ts b/packages/client/mutations/AutogroupMutation.ts index 3a66969a2e1..8f05c4d042c 100644 --- a/packages/client/mutations/AutogroupMutation.ts +++ b/packages/client/mutations/AutogroupMutation.ts @@ -13,7 +13,6 @@ graphql` reflectionGroups { id title - smartTitle reflections { id plaintextContent diff --git a/packages/client/mutations/EndDraggingReflectionMutation.ts b/packages/client/mutations/EndDraggingReflectionMutation.ts index 174d2634aa6..eb9491b5619 100644 --- a/packages/client/mutations/EndDraggingReflectionMutation.ts +++ b/packages/client/mutations/EndDraggingReflectionMutation.ts @@ -179,8 +179,7 @@ const EndDraggingReflectionMutation: SimpleMutation(resolverMap: T, permissionMap: nextResolverFieldMap[resolverFieldName] = wrapResolve(resolve as Resolver, rule) }) } else { - const unwrappedResolver = nextResolverFieldMap[fieldName] - if (!unwrappedResolver) { - throw new Error(`No resolver exists for field: ${fieldName}`) - } + // use default if a resolver isn't provided, e.g. a field exists in the DB but only available to superusers via GQL + const unwrappedResolver = nextResolverFieldMap[fieldName] || defaultFieldResolver nextResolverFieldMap[fieldName] = wrapResolve(unwrappedResolver, rule) } }) diff --git a/packages/server/graphql/public/permissions.ts b/packages/server/graphql/public/permissions.ts index c74d9bc023c..41bcc679ea7 100644 --- a/packages/server/graphql/public/permissions.ts +++ b/packages/server/graphql/public/permissions.ts @@ -74,6 +74,10 @@ const permissionMap: PermissionMap = { isOrgTier<'Organization.saml'>('source.id', 'enterprise') ) }, + RetroReflectionGroup: { + smartTitle: isSuperUser, + voterIds: isSuperUser + }, User: { domains: or(isSuperUser, isUserViewer) } diff --git a/packages/server/graphql/public/types/RetroReflectionGroup.ts b/packages/server/graphql/public/types/RetroReflectionGroup.ts index 404834dcc57..fbde33b5789 100644 --- a/packages/server/graphql/public/types/RetroReflectionGroup.ts +++ b/packages/server/graphql/public/types/RetroReflectionGroup.ts @@ -1,9 +1,45 @@ import {Selectable} from 'kysely' +import MeetingRetrospective from '../../../database/types/MeetingRetrospective' +import Reflection from '../../../database/types/Reflection' import {RetroReflectionGroup as TRetroReflectionGroup} from '../../../postgres/pg' +import {getUserId} from '../../../utils/authorization' import {RetroReflectionGroupResolvers} from '../resolverTypes' export interface RetroReflectionGroupSource extends Selectable {} -const RetroReflectionGroup: RetroReflectionGroupResolvers = {} +const RetroReflectionGroup: RetroReflectionGroupResolvers = { + meeting: async ({meetingId}, _args, {dataLoader}) => { + const retroMeeting = await dataLoader.get('newMeetings').load(meetingId) + return retroMeeting as MeetingRetrospective + }, + prompt: ({promptId}, _args, {dataLoader}) => { + return dataLoader.get('reflectPrompts').load(promptId) + }, + reflections: async ({id: reflectionGroupId, meetingId}, _args, {dataLoader}) => { + // use meetingId so we only hit the DB once instead of once per group + const reflections = await dataLoader.get('retroReflectionsByMeetingId').load(meetingId) + const filteredReflections = reflections.filter( + (reflection: Reflection) => reflection.reflectionGroupId === reflectionGroupId + ) + filteredReflections.sort((a: Reflection, b: Reflection) => (a.sortOrder < b.sortOrder ? 1 : -1)) + return filteredReflections + }, + team: async ({meetingId}, _args, {dataLoader}) => { + const meeting = await dataLoader.get('newMeetings').load(meetingId) + return dataLoader.get('teams').loadNonNull(meeting.teamId) + }, + titleIsUserDefined: ({title, smartTitle}) => { + return title ? title !== smartTitle : false + }, + voteCount: ({voterIds}) => { + return voterIds ? voterIds.length : 0 + }, + viewerVoteCount: ({voterIds}, _args, {authToken}) => { + const viewerId = getUserId(authToken) + return voterIds + ? voterIds.reduce((sum, voterId) => (voterId === viewerId ? sum + 1 : sum), 0) + : 0 + } +} export default RetroReflectionGroup diff --git a/packages/server/graphql/types/RetroReflectionGroup.ts b/packages/server/graphql/types/RetroReflectionGroup.ts index 7fb07754d0c..faee66c3037 100644 --- a/packages/server/graphql/types/RetroReflectionGroup.ts +++ b/packages/server/graphql/types/RetroReflectionGroup.ts @@ -1,145 +1,9 @@ -import { - GraphQLBoolean, - GraphQLFloat, - GraphQLID, - GraphQLInt, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLString -} from 'graphql' -import Reflection from '../../database/types/Reflection' -import {getUserId} from '../../utils/authorization' +import {GraphQLObjectType} from 'graphql' import {GQLContext} from '../graphql' -import {resolveForSU} from '../resolvers' -import CommentorDetails from './CommentorDetails' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import ReflectPrompt from './ReflectPrompt' -import RetroReflection from './RetroReflection' -import RetrospectiveMeeting from './RetrospectiveMeeting' -import Team from './Team' const RetroReflectionGroup: GraphQLObjectType = new GraphQLObjectType({ name: 'RetroReflectionGroup', - description: 'A reflection group created during the group phase of a retrospective', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'shortid' - }, - commentors: { - type: new GraphQLList(new GraphQLNonNull(CommentorDetails)), - description: 'A list of users currently commenting', - deprecationReason: 'Moved to ThreadConnection. Can remove Jun-01-2021', - resolve: ({commentor = []}) => { - return commentor - } - }, - createdAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the meeting was created' - }, - isActive: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'True if the group has not been removed, else false' - }, - meetingId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The foreign key to link a reflection group to its meeting' - }, - meeting: { - type: new GraphQLNonNull(RetrospectiveMeeting), - description: 'The retrospective meeting this reflection was created in', - resolve: ({meetingId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('newMeetings').load(meetingId) - } - }, - prompt: { - type: new GraphQLNonNull(ReflectPrompt), - resolve: ({promptId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('reflectPrompts').load(promptId) - } - }, - promptId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The foreign key to link a reflection group to its prompt. Immutable.' - }, - reflections: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(RetroReflection))), - resolve: async ({id: reflectionGroupId, meetingId}, _args: unknown, {dataLoader}) => { - // use meetingId so we only hit the DB once instead of once per group - const reflections = await dataLoader.get('retroReflectionsByMeetingId').load(meetingId) - const filteredReflections = reflections.filter( - (reflection: Reflection) => reflection.reflectionGroupId === reflectionGroupId - ) - filteredReflections.sort((a: Reflection, b: Reflection) => - a.sortOrder < b.sortOrder ? 1 : -1 - ) - return filteredReflections - } - }, - smartTitle: { - type: GraphQLString, - description: 'Our auto-suggested title, to be compared to the actual title for analytics', - resolve: resolveForSU('smartTitle') - }, - sortOrder: { - type: new GraphQLNonNull(GraphQLFloat), - description: 'The sort order of the reflection group' - }, - discussionPromptQuestion: { - type: GraphQLString, - description: `The AI generated question to prompt and engage the discussion of this reflection group` - }, - team: { - type: Team, - description: 'The team that is running the retro', - resolve: async ({meetingId}, _args: unknown, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) - return dataLoader.get('teams').load(meeting.teamId) - } - }, - title: { - type: GraphQLString, - description: 'The title of the grouping of the retrospective reflections' - }, - titleIsUserDefined: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'true if a user wrote the title, else false', - resolve: ({title, smartTitle}) => { - return title ? title !== smartTitle : false - } - }, - updatedAt: { - type: GraphQLISO8601Type, - description: 'The timestamp the meeting was updated at' - }, - voterIds: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLID))), - description: 'A list of voterIds (userIds). Not available to team to preserve anonymity', - resolve: resolveForSU('voterIds') - }, - voteCount: { - type: new GraphQLNonNull(GraphQLInt), - description: 'The number of votes this group has received', - resolve: ({voterIds}) => { - return voterIds ? voterIds.length : 0 - } - }, - viewerVoteCount: { - type: GraphQLInt, - description: 'The number of votes the viewer has given this group', - resolve: ({voterIds}, _args: unknown, {authToken}) => { - const viewerId = getUserId(authToken) - return voterIds - ? voterIds.reduce( - (sum: number, voterId: string) => (voterId === viewerId ? sum + 1 : sum), - 0 - ) - : 0 - } - } - }) + fields: {} }) export default RetroReflectionGroup From eb88af6fbafb19e748803fd6423ba17be0e51e74 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Mon, 10 Jun 2024 09:10:58 +0200 Subject: [PATCH 252/529] fix: Add teamId index to SuggestedAction (#9831) --- packages/server/database/rethinkDriver.ts | 2 +- ...685812675_addSuggestedActionTeamIdIndex.ts | 21 +++++++++++++++++++ .../server/safeMutations/safeArchiveTeam.ts | 2 +- 3 files changed, 23 insertions(+), 2 deletions(-) create mode 100644 packages/server/postgres/migrations/1717685812675_addSuggestedActionTeamIdIndex.ts diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index 40621c4db4c..e52e2dd4ea8 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -166,7 +166,7 @@ export type RethinkSchema = { // tryRetroMeeting = 'tryRetroMeeting', // tryActionMeeting = 'tryActionMeeting' type: SuggestedActionCreateNewTeam | SuggestedActionInviteYourTeam | SuggestedActionTryTheDemo - index: 'userId' + index: 'userId' | 'teamId' } Task: { type: Task diff --git a/packages/server/postgres/migrations/1717685812675_addSuggestedActionTeamIdIndex.ts b/packages/server/postgres/migrations/1717685812675_addSuggestedActionTeamIdIndex.ts new file mode 100644 index 00000000000..adbdc0e3534 --- /dev/null +++ b/packages/server/postgres/migrations/1717685812675_addSuggestedActionTeamIdIndex.ts @@ -0,0 +1,21 @@ +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' + +export async function up() { + await connectRethinkDB() + try { + await r.table('SuggestedAction').indexCreate('teamId').run() + await r.table('SuggestedAction').indexWait().run() + } catch { + // index already exists + } +} + +export async function down() { + await connectRethinkDB() + try { + await r.table('SuggestedAction').indexDrop('teamId').run() + } catch { + // index already dropped + } +} diff --git a/packages/server/safeMutations/safeArchiveTeam.ts b/packages/server/safeMutations/safeArchiveTeam.ts index 5283349838d..a0c2522e4ff 100644 --- a/packages/server/safeMutations/safeArchiveTeam.ts +++ b/packages/server/safeMutations/safeArchiveTeam.ts @@ -26,7 +26,7 @@ const safeArchiveTeam = async (teamId: string, dataLoader: DataLoaderWorker) => })) as unknown as null, removedSuggestedActionIds: r .table('SuggestedAction') - .filter({teamId}) + .getAll(teamId, {index: 'teamId'}) .update( { removedAt: now From d5d1fd734434fab96e3ae6216df666b57c1c16e4 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Mon, 10 Jun 2024 12:58:52 +0100 Subject: [PATCH 253/529] chore(release): release v7.36.0 (#9832) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 17 +++++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 29 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 245678114e4..e9443cfb68e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.35.1" + ".": "7.36.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index d240965d649..7fa93344e30 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.36.0](https://github.com/ParabolInc/parabol/compare/v7.35.1...v7.36.0) (2024-06-10) + + +### Added + +* security banner concept ([#9780](https://github.com/ParabolInc/parabol/issues/9780)) ([b683dc8](https://github.com/ParabolInc/parabol/commit/b683dc83426bfa96eee20b0ceb4d90640b9eb9b6)) + + +### Fixed + +* Add teamId index to SuggestedAction ([#9831](https://github.com/ParabolInc/parabol/issues/9831)) ([eb88af6](https://github.com/ParabolInc/parabol/commit/eb88af6fbafb19e748803fd6423ba17be0e51e74)) + + +### Changed + +* refactor ReflectionGroup to SDL pattern ([#9807](https://github.com/ParabolInc/parabol/issues/9807)) ([695646c](https://github.com/ParabolInc/parabol/commit/695646c6885c31e2049a1ac76fd3f4b9b589f5da)) + ## [7.35.1](https://github.com/ParabolInc/parabol/compare/v7.35.0...v7.35.1) (2024-06-04) diff --git a/package.json b/package.json index 449b3a9e751..2a9f045c25d 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.35.1", + "version": "7.36.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index ad91f7f652f..ac7bf7c07af 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.35.1", + "version": "7.36.0", "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.35.1" + "parabol-server": "7.36.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index b25bda684ea..15a2985aa7d 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.35.1", + "version": "7.36.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 1d0c780f207..d99f9a662d0 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.35.1", + "version": "7.36.0", "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 5abedd96382..0083f0d95ee 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.35.1", + "version": "7.36.0", "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.35.1", - "parabol-server": "7.35.1", + "parabol-client": "7.36.0", + "parabol-server": "7.36.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 33be471cd93..dced586c0c2 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.35.1", + "version": "7.36.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index dee1b548dfa..766918b0526 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.35.1", + "version": "7.36.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.35.1", + "parabol-client": "7.36.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From bd37d85ed95f4d0186b129421c2b1e5dedef3ea8 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Mon, 10 Jun 2024 17:17:35 +0200 Subject: [PATCH 254/529] fix: Retry S3 upload after cloudflare error (#9819) --- .../server/fileStorage/S3FileStoreManager.ts | 27 ++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/packages/server/fileStorage/S3FileStoreManager.ts b/packages/server/fileStorage/S3FileStoreManager.ts index eeb5b17da60..f68cde04464 100644 --- a/packages/server/fileStorage/S3FileStoreManager.ts +++ b/packages/server/fileStorage/S3FileStoreManager.ts @@ -1,9 +1,32 @@ import {GetObjectCommand, HeadObjectCommand, PutObjectCommand, S3Client} from '@aws-sdk/client-s3' import {getSignedUrl} from '@aws-sdk/s3-request-presigner' +import type {RetryErrorInfo, StandardRetryToken} from '@smithy/types' +import {StandardRetryStrategy} from '@smithy/util-retry' import mime from 'mime-types' import path from 'path' import FileStoreManager, {FileAssetDir} from './FileStoreManager' +class CloudflareRetry extends StandardRetryStrategy { + public async refreshRetryTokenForRetry( + tokenToRenew: StandardRetryToken, + errorInfo: RetryErrorInfo + ): Promise { + const status = errorInfo.error?.$response?.statusCode + if (status && status >= 520 && status < 530) { + const date = errorInfo.error?.$response?.headers?.date + console.log('Cloudflare error', { + status, + date: date && new Date(date).toISOString(), + path: errorInfo.error?.$response?.body?.req?.path + }) + // Cloudflare swallows the error, so let's treat it as a transient and retry + errorInfo.errorType = 'TRANSIENT' + } + const token = await super.refreshRetryTokenForRetry(tokenToRenew, errorInfo) + return token + } +} + export default class S3Manager extends FileStoreManager { // e.g. development, production private envSubDir: string @@ -38,7 +61,9 @@ export default class S3Manager extends FileStoreManager { credentials, // The bucket is inferred from the CDN_BASE_URL bucketEndpoint: true, - region: AWS_REGION + region: AWS_REGION, + followRegionRedirects: true, + retryStrategy: new CloudflareRetry(3) }) } From e614253d55aef39cb14f8c73ba160ee74701be1d Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Mon, 10 Jun 2024 17:18:46 +0200 Subject: [PATCH 255/529] feat: Update MeetingTemplate.updatedAt on prompt changes (#9829) --- packages/server/database/rethinkDriver.ts | 2 +- .../mutations/addPokerTemplateDimension.ts | 11 ++++++++- .../mutations/addPokerTemplateScaleValue.ts | 14 +++++++++++ .../mutations/addReflectTemplatePrompt.ts | 11 ++++++++- .../mutations/movePokerTemplateDimension.ts | 24 ++++++++++++------- .../mutations/movePokerTemplateScaleValue.ts | 13 ++++++++++ .../mutations/moveReflectTemplatePrompt.ts | 24 ++++++++++++------- .../reflectTemplatePromptUpdateDescription.ts | 23 +++++++++++------- .../reflectTemplatePromptUpdateGroupColor.ts | 23 +++++++++++------- .../mutations/removePokerTemplateDimension.ts | 7 +++++- .../mutations/removePokerTemplateScale.ts | 9 +++++++ .../removePokerTemplateScaleValue.ts | 14 +++++++++++ .../mutations/removeReflectTemplatePrompt.ts | 21 +++++++++------- .../mutations/renamePokerTemplateDimension.ts | 21 +++++++++------- .../mutations/renamePokerTemplateScale.ts | 14 +++++++++++ .../mutations/renameReflectTemplatePrompt.ts | 21 +++++++++------- .../updatePokerTemplateDimensionScale.ts | 8 ++++++- .../updatePokerTemplateScaleValue.ts | 14 +++++++++++ ...PokerTemplateScaleDimensionScaleIdIndex.ts | 21 ++++++++++++++++ 19 files changed, 230 insertions(+), 65 deletions(-) create mode 100644 packages/server/postgres/migrations/1717685812676_addPokerTemplateScaleDimensionScaleIdIndex.ts diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index e52e2dd4ea8..50f42952ff7 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -197,7 +197,7 @@ export type RethinkSchema = { } TemplateDimension: { type: TemplateDimension - index: 'teamId' | 'templateId' + index: 'teamId' | 'templateId' | 'scaleId' } TemplateScale: { type: TemplateScale diff --git a/packages/server/graphql/mutations/addPokerTemplateDimension.ts b/packages/server/graphql/mutations/addPokerTemplateDimension.ts index 676392cd947..ff1d5a1e2ef 100644 --- a/packages/server/graphql/mutations/addPokerTemplateDimension.ts +++ b/packages/server/graphql/mutations/addPokerTemplateDimension.ts @@ -4,6 +4,7 @@ import dndNoise from 'parabol-client/utils/dndNoise' import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' import TemplateDimension from '../../database/types/TemplateDimension' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -24,6 +25,7 @@ const addPokerTemplateDimension = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const template = await dataLoader.get('meetingTemplates').load(templateId) @@ -73,7 +75,14 @@ const addPokerTemplateDimension = { templateId }) - await r.table('TemplateDimension').insert(newDimension).run() + await Promise.all([ + r.table('TemplateDimension').insert(newDimension).run(), + pg + .updateTable('MeetingTemplate') + .set({updatedAt: new Date()}) + .where('id', '=', templateId) + .execute() + ]) const dimensionId = newDimension.id const data = {dimensionId} diff --git a/packages/server/graphql/mutations/addPokerTemplateScaleValue.ts b/packages/server/graphql/mutations/addPokerTemplateScaleValue.ts index 8e20ccb812b..3470e9418dc 100644 --- a/packages/server/graphql/mutations/addPokerTemplateScaleValue.ts +++ b/packages/server/graphql/mutations/addPokerTemplateScaleValue.ts @@ -3,6 +3,7 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' import TemplateScale from '../../database/types/TemplateScale' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -32,6 +33,7 @@ const addPokerTemplateScaleValue = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -84,6 +86,18 @@ const addPokerTemplateScaleValue = { }) } + // mark all templates using this scale as updated + const updatedDimensions = await r + .table('TemplateDimension') + .getAll(scaleId, {index: 'scaleId'}) + .run() + const updatedTemplateIds = updatedDimensions.map(({templateId}) => templateId) + await pg + .updateTable('MeetingTemplate') + .set({updatedAt: now}) + .where('id', 'in', updatedTemplateIds) + .execute() + const data = {scaleId} publish( SubscriptionChannel.TEAM, diff --git a/packages/server/graphql/mutations/addReflectTemplatePrompt.ts b/packages/server/graphql/mutations/addReflectTemplatePrompt.ts index 7f8b4c3c9ad..e0867254b79 100644 --- a/packages/server/graphql/mutations/addReflectTemplatePrompt.ts +++ b/packages/server/graphql/mutations/addReflectTemplatePrompt.ts @@ -5,6 +5,7 @@ import palettePickerOptions from '../../../client/styles/palettePickerOptions' import {PALETTE} from '../../../client/styles/paletteV3' import getRethink from '../../database/rethinkDriver' import RetrospectivePrompt from '../../database/types/RetrospectivePrompt' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -25,6 +26,7 @@ const addReflectTemplatePrompt = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const template = await dataLoader.get('meetingTemplates').load(templateId) @@ -69,7 +71,14 @@ const addReflectTemplatePrompt = { removedAt: null }) - await r.table('ReflectPrompt').insert(reflectPrompt).run() + await Promise.all([ + await r.table('ReflectPrompt').insert(reflectPrompt).run(), + pg + .updateTable('MeetingTemplate') + .set({updatedAt: new Date()}) + .where('id', '=', templateId) + .execute() + ]) const promptId = reflectPrompt.id const data = {promptId} diff --git a/packages/server/graphql/mutations/movePokerTemplateDimension.ts b/packages/server/graphql/mutations/movePokerTemplateDimension.ts index 7d5523cbcd4..3f037e6e223 100644 --- a/packages/server/graphql/mutations/movePokerTemplateDimension.ts +++ b/packages/server/graphql/mutations/movePokerTemplateDimension.ts @@ -1,6 +1,7 @@ import {GraphQLFloat, GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -24,6 +25,7 @@ const movePokerTemplateDimension = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -39,16 +41,20 @@ const movePokerTemplateDimension = { } // RESOLUTION - await r - .table('TemplateDimension') - .get(dimensionId) - .update({ - sortOrder, - updatedAt: now - }) - .run() + const {teamId, templateId} = dimension + + await Promise.all([ + r + .table('TemplateDimension') + .get(dimensionId) + .update({ + sortOrder, + updatedAt: now + }) + .run(), + pg.updateTable('MeetingTemplate').set({updatedAt: now}).where('id', '=', templateId).execute() + ]) - const {teamId} = dimension const data = {dimensionId} publish(SubscriptionChannel.TEAM, teamId, 'MovePokerTemplateDimensionPayload', data, subOptions) return data diff --git a/packages/server/graphql/mutations/movePokerTemplateScaleValue.ts b/packages/server/graphql/mutations/movePokerTemplateScaleValue.ts index 5fd63247107..6da4d67829d 100644 --- a/packages/server/graphql/mutations/movePokerTemplateScaleValue.ts +++ b/packages/server/graphql/mutations/movePokerTemplateScaleValue.ts @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLInt, GraphQLNonNull, GraphQLString} 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 {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -30,6 +31,7 @@ const movePokerTemplateScaleValue = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { const r = await getRethink() + const pg = getKysely() const viewerId = getUserId(authToken) const now = new Date() const operationId = dataLoader.share() @@ -66,6 +68,17 @@ const movePokerTemplateScaleValue = { updatedAt: now })) .run() + // mark all templates using this scale as updated + const updatedDimensions = await r + .table('TemplateDimension') + .getAll(scaleId, {index: 'scaleId'}) + .run() + const updatedTemplateIds = updatedDimensions.map(({templateId}) => templateId) + await pg + .updateTable('MeetingTemplate') + .set({updatedAt: now}) + .where('id', 'in', updatedTemplateIds) + .execute() const data = {scaleId} publish( diff --git a/packages/server/graphql/mutations/moveReflectTemplatePrompt.ts b/packages/server/graphql/mutations/moveReflectTemplatePrompt.ts index 21ed63083cd..2a02c13f9d7 100644 --- a/packages/server/graphql/mutations/moveReflectTemplatePrompt.ts +++ b/packages/server/graphql/mutations/moveReflectTemplatePrompt.ts @@ -1,6 +1,7 @@ import {GraphQLFloat, GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -24,6 +25,7 @@ const moveReflectTemplate = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -39,16 +41,20 @@ const moveReflectTemplate = { } // RESOLUTION - await r - .table('ReflectPrompt') - .get(promptId) - .update({ - sortOrder, - updatedAt: now - }) - .run() + const {teamId, templateId} = prompt + + await Promise.all([ + r + .table('ReflectPrompt') + .get(promptId) + .update({ + sortOrder, + updatedAt: now + }) + .run(), + pg.updateTable('MeetingTemplate').set({updatedAt: now}).where('id', '=', templateId).execute() + ]) - const {teamId} = prompt const data = {promptId} publish(SubscriptionChannel.TEAM, teamId, 'MoveReflectTemplatePromptPayload', data, subOptions) return data diff --git a/packages/server/graphql/mutations/reflectTemplatePromptUpdateDescription.ts b/packages/server/graphql/mutations/reflectTemplatePromptUpdateDescription.ts index 23c00074fbd..73254ea5522 100644 --- a/packages/server/graphql/mutations/reflectTemplatePromptUpdateDescription.ts +++ b/packages/server/graphql/mutations/reflectTemplatePromptUpdateDescription.ts @@ -1,6 +1,7 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -24,6 +25,7 @@ const reflectTemplatePromptUpdateDescription = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -39,18 +41,21 @@ const reflectTemplatePromptUpdateDescription = { } // VALIDATION - const {teamId} = prompt + const {teamId, templateId} = prompt const normalizedDescription = description.trim().slice(0, 256) || '' // RESOLUTION - await r - .table('ReflectPrompt') - .get(promptId) - .update({ - description: normalizedDescription, - updatedAt: now - }) - .run() + await Promise.all([ + r + .table('ReflectPrompt') + .get(promptId) + .update({ + description: normalizedDescription, + updatedAt: now + }) + .run(), + pg.updateTable('MeetingTemplate').set({updatedAt: now}).where('id', '=', templateId).execute() + ]) const data = {promptId} publish( diff --git a/packages/server/graphql/mutations/reflectTemplatePromptUpdateGroupColor.ts b/packages/server/graphql/mutations/reflectTemplatePromptUpdateGroupColor.ts index 1b1b381a590..c7c397d1a99 100644 --- a/packages/server/graphql/mutations/reflectTemplatePromptUpdateGroupColor.ts +++ b/packages/server/graphql/mutations/reflectTemplatePromptUpdateGroupColor.ts @@ -1,6 +1,7 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -24,6 +25,7 @@ const reflectTemplatePromptUpdateGroupColor = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -40,17 +42,20 @@ const reflectTemplatePromptUpdateGroupColor = { } // VALIDATION - const {teamId} = prompt + const {teamId, templateId} = prompt // RESOLUTION - await r - .table('ReflectPrompt') - .get(promptId) - .update({ - groupColor, - updatedAt: now - }) - .run() + await Promise.all([ + r + .table('ReflectPrompt') + .get(promptId) + .update({ + groupColor, + updatedAt: now + }) + .run(), + pg.updateTable('MeetingTemplate').set({updatedAt: now}).where('id', '=', templateId).execute() + ]) const data = {promptId} publish( diff --git a/packages/server/graphql/mutations/removePokerTemplateDimension.ts b/packages/server/graphql/mutations/removePokerTemplateDimension.ts index 02393715f2e..308ba0850b0 100644 --- a/packages/server/graphql/mutations/removePokerTemplateDimension.ts +++ b/packages/server/graphql/mutations/removePokerTemplateDimension.ts @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -22,6 +23,7 @@ const removePokerTemplateDimension = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -52,7 +54,10 @@ const removePokerTemplateDimension = { } // RESOLUTION - await r.table('TemplateDimension').get(dimensionId).update({removedAt: now}).run() + await Promise.all([ + r.table('TemplateDimension').get(dimensionId).update({removedAt: now}).run(), + pg.updateTable('MeetingTemplate').set({updatedAt: now}).where('id', '=', templateId).execute() + ]) const data = {dimensionId, templateId} publish( diff --git a/packages/server/graphql/mutations/removePokerTemplateScale.ts b/packages/server/graphql/mutations/removePokerTemplateScale.ts index 59bc5460c65..b56255592f4 100644 --- a/packages/server/graphql/mutations/removePokerTemplateScale.ts +++ b/packages/server/graphql/mutations/removePokerTemplateScale.ts @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SprintPokerDefaults, SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -22,6 +23,7 @@ const removePokerTemplateScale = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -56,6 +58,13 @@ const removePokerTemplateScale = { )('changes')('new_val') .default([]) .run() + // mark templates as updated + const updatedTemplateIds = dimensions.map(({templateId}: any) => templateId) + await pg + .updateTable('MeetingTemplate') + .set({updatedAt: now}) + .where('id', 'in', updatedTemplateIds) + .execute() const data = {scaleId, dimensions} publish(SubscriptionChannel.TEAM, teamId, 'RemovePokerTemplateScalePayload', data, subOptions) diff --git a/packages/server/graphql/mutations/removePokerTemplateScaleValue.ts b/packages/server/graphql/mutations/removePokerTemplateScaleValue.ts index 5ce74911a47..9caecbfeab5 100644 --- a/packages/server/graphql/mutations/removePokerTemplateScaleValue.ts +++ b/packages/server/graphql/mutations/removePokerTemplateScaleValue.ts @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} 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 {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -25,6 +26,7 @@ const removePokerTemplateScaleValue = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -58,6 +60,18 @@ const removePokerTemplateScaleValue = { })) .run() + // mark all templates using this scale as updated + const updatedDimensions = await r + .table('TemplateDimension') + .getAll(scaleId, {index: 'scaleId'}) + .run() + const updatedTemplateIds = updatedDimensions.map(({templateId}) => templateId) + await pg + .updateTable('MeetingTemplate') + .set({updatedAt: now}) + .where('id', 'in', updatedTemplateIds) + .execute() + const data = {scaleId} publish( SubscriptionChannel.TEAM, diff --git a/packages/server/graphql/mutations/removeReflectTemplatePrompt.ts b/packages/server/graphql/mutations/removeReflectTemplatePrompt.ts index 6737181ccc2..b0f2f1eebaf 100644 --- a/packages/server/graphql/mutations/removeReflectTemplatePrompt.ts +++ b/packages/server/graphql/mutations/removeReflectTemplatePrompt.ts @@ -1,6 +1,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -21,6 +22,7 @@ const removeReflectTemplatePrompt = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -53,14 +55,17 @@ const removeReflectTemplatePrompt = { } // RESOLUTION - await r - .table('ReflectPrompt') - .get(promptId) - .update({ - removedAt: now, - updatedAt: now - }) - .run() + await Promise.all([ + r + .table('ReflectPrompt') + .get(promptId) + .update({ + removedAt: now, + updatedAt: now + }) + .run(), + pg.updateTable('MeetingTemplate').set({updatedAt: now}).where('id', '=', templateId).execute() + ]) const data = {promptId, templateId} publish( diff --git a/packages/server/graphql/mutations/renamePokerTemplateDimension.ts b/packages/server/graphql/mutations/renamePokerTemplateDimension.ts index 49665377f50..21b76b33471 100644 --- a/packages/server/graphql/mutations/renamePokerTemplateDimension.ts +++ b/packages/server/graphql/mutations/renamePokerTemplateDimension.ts @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -25,6 +26,7 @@ const renamePokerTemplateDimension = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -55,14 +57,17 @@ const renamePokerTemplateDimension = { } // RESOLUTION - await r - .table('TemplateDimension') - .get(dimensionId) - .update({ - name: normalizedName, - updatedAt: now - }) - .run() + await Promise.all([ + r + .table('TemplateDimension') + .get(dimensionId) + .update({ + name: normalizedName, + updatedAt: now + }) + .run(), + pg.updateTable('MeetingTemplate').set({updatedAt: now}).where('id', '=', templateId).execute() + ]) const data = {dimensionId} publish( diff --git a/packages/server/graphql/mutations/renamePokerTemplateScale.ts b/packages/server/graphql/mutations/renamePokerTemplateScale.ts index 4aca5cc53f5..f9b42c50778 100644 --- a/packages/server/graphql/mutations/renamePokerTemplateScale.ts +++ b/packages/server/graphql/mutations/renamePokerTemplateScale.ts @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -25,6 +26,7 @@ const renamePokerTemplateScale = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -63,6 +65,18 @@ const renamePokerTemplateScale = { }) .run() + // mark all templates using this scale as updated + const updatedDimensions = await r + .table('TemplateDimension') + .getAll(scaleId, {index: 'scaleId'}) + .run() + const updatedTemplateIds = updatedDimensions.map(({templateId}) => templateId) + await pg + .updateTable('MeetingTemplate') + .set({updatedAt: now}) + .where('id', 'in', updatedTemplateIds) + .execute() + const data = {scaleId} publish(SubscriptionChannel.TEAM, teamId, 'RenamePokerTemplateScalePayload', data, subOptions) return data diff --git a/packages/server/graphql/mutations/renameReflectTemplatePrompt.ts b/packages/server/graphql/mutations/renameReflectTemplatePrompt.ts index f1c4f0ee736..b1a74470ebc 100644 --- a/packages/server/graphql/mutations/renameReflectTemplatePrompt.ts +++ b/packages/server/graphql/mutations/renameReflectTemplatePrompt.ts @@ -1,6 +1,7 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -24,6 +25,7 @@ const renameReflectTemplatePrompt = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -56,14 +58,17 @@ const renameReflectTemplatePrompt = { } // RESOLUTION - await r - .table('ReflectPrompt') - .get(promptId) - .update({ - question: normalizedQuestion, - updatedAt: now - }) - .run() + await Promise.all([ + r + .table('ReflectPrompt') + .get(promptId) + .update({ + question: normalizedQuestion, + updatedAt: now + }) + .run(), + pg.updateTable('MeetingTemplate').set({updatedAt: now}).where('id', '=', templateId).execute() + ]) const data = {promptId} publish( diff --git a/packages/server/graphql/mutations/updatePokerTemplateDimensionScale.ts b/packages/server/graphql/mutations/updatePokerTemplateDimensionScale.ts index ea35b3ec46a..479eba4abfb 100644 --- a/packages/server/graphql/mutations/updatePokerTemplateDimensionScale.ts +++ b/packages/server/graphql/mutations/updatePokerTemplateDimensionScale.ts @@ -1,6 +1,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -24,6 +25,7 @@ const updatePokerTemplateDimensionScale = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -45,7 +47,11 @@ const updatePokerTemplateDimensionScale = { return standardError(new Error('Scale not found'), {userId: viewerId}) } - await r.table('TemplateDimension').get(dimensionId).update({scaleId, updatedAt: now}).run() + const {templateId} = dimension + await Promise.all([ + r.table('TemplateDimension').get(dimensionId).update({scaleId, updatedAt: now}).run(), + pg.updateTable('MeetingTemplate').set({updatedAt: now}).where('id', '=', templateId).execute() + ]) const data = {dimensionId} publish( diff --git a/packages/server/graphql/mutations/updatePokerTemplateScaleValue.ts b/packages/server/graphql/mutations/updatePokerTemplateScaleValue.ts index 546d30c348a..64ce07cab26 100644 --- a/packages/server/graphql/mutations/updatePokerTemplateScaleValue.ts +++ b/packages/server/graphql/mutations/updatePokerTemplateScaleValue.ts @@ -3,6 +3,7 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import isSpecialPokerLabel from 'parabol-client/utils/isSpecialPokerLabel' import getRethink from '../../database/rethinkDriver' import {RValue} from '../../database/stricterR' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -43,6 +44,7 @@ const updatePokerTemplateScaleValue = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -107,6 +109,18 @@ const updatePokerTemplateScaleValue = { }) } + // mark all templates using this scale as updated + const updatedDimensions = await r + .table('TemplateDimension') + .getAll(scaleId, {index: 'scaleId'}) + .run() + const updatedTemplateIds = updatedDimensions.map(({templateId}) => templateId) + await pg + .updateTable('MeetingTemplate') + .set({updatedAt: now}) + .where('id', 'in', updatedTemplateIds) + .execute() + const data = {scaleId} publish( SubscriptionChannel.TEAM, diff --git a/packages/server/postgres/migrations/1717685812676_addPokerTemplateScaleDimensionScaleIdIndex.ts b/packages/server/postgres/migrations/1717685812676_addPokerTemplateScaleDimensionScaleIdIndex.ts new file mode 100644 index 00000000000..384ec9f1c3f --- /dev/null +++ b/packages/server/postgres/migrations/1717685812676_addPokerTemplateScaleDimensionScaleIdIndex.ts @@ -0,0 +1,21 @@ +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' + +export async function up() { + await connectRethinkDB() + try { + await r.table('TemplateDimension').indexCreate('scaleId').run() + await r.table('TemplateDimension').indexWait().run() + } catch { + // index already exists + } +} + +export async function down() { + await connectRethinkDB() + try { + await r.table('TemplateDimension').indexDrop('scaleId').run() + } catch { + // index already dropped + } +} From 6aec87f7ec4d4abc1b395552161f4cbaf85334de Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 11 Jun 2024 14:38:44 +0200 Subject: [PATCH 256/529] chore(deps): bump @grpc/grpc-js from 1.10.6 to 1.10.9 (#9840) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 49 +++++++++++++++++++++++++++++++++++++------------ 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/yarn.lock b/yarn.lock index 9667d8329c9..99f8e2a34c3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3896,11 +3896,11 @@ integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ== "@grpc/grpc-js@~1.10.0": - version "1.10.6" - resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.10.6.tgz#1e3eb1af911dc888fbef7452f56a7573b8284d54" - integrity sha512-xP58G7wDQ4TCmN/cMUHh00DS7SRDv/+lC+xFLrTkMIN8h55X5NhZMLYbvy7dSELP15qlI6hPhNCRWVMtZMwqLA== + version "1.10.9" + resolved "https://registry.yarnpkg.com/@grpc/grpc-js/-/grpc-js-1.10.9.tgz#468cc1549a3fe37b760a16745fb7685d91f4f10c" + integrity sha512-5tcgUctCG0qoNyfChZifz2tJqbRbXVO9J7X6duFcOjY3HUNCxg5D0ZCK7EP9vIcZ0zRpLU9bWkyCqVCLZ46IbQ== dependencies: - "@grpc/proto-loader" "^0.7.10" + "@grpc/proto-loader" "^0.7.13" "@js-sdsl/ordered-map" "^4.4.2" "@grpc/proto-loader@^0.7.0": @@ -3913,14 +3913,14 @@ protobufjs "^7.2.4" yargs "^17.7.2" -"@grpc/proto-loader@^0.7.10": - version "0.7.12" - resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.12.tgz#787b58e3e3771df30b1567c057b6ab89e3a42911" - integrity sha512-DCVwMxqYzpUCiDMl7hQ384FqP4T3DbNpXU8pt681l3UWCip1WUiD5JrkImUwCB9a7f2cq4CUTmi5r/xIMRPY1Q== +"@grpc/proto-loader@^0.7.13": + version "0.7.13" + resolved "https://registry.yarnpkg.com/@grpc/proto-loader/-/proto-loader-0.7.13.tgz#f6a44b2b7c9f7b609f5748c6eac2d420e37670cf" + integrity sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw== dependencies: lodash.camelcase "^4.3.0" long "^5.0.0" - protobufjs "^7.2.4" + protobufjs "^7.2.5" yargs "^17.7.2" "@headlessui/react@^1.7.15": @@ -20293,7 +20293,7 @@ string-similarity@^3.0.0: resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-3.0.0.tgz#07b0bc69fae200ad88ceef4983878d03793847c7" integrity sha512-7kS7LyTp56OqOI2BDWQNVnLX/rCxIQn+/5M0op1WV6P8Xx6TZNdajpuqQdiJ7Xx+p1C5CsWMvdiBp9ApMhxzEQ== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -20311,6 +20311,15 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.0.tgz#5ab00980cfb29f43e736b113a120a73a0fb569d3" @@ -20387,7 +20396,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -20401,6 +20410,13 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -22253,7 +22269,7 @@ workbox-window@6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.5.4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -22271,6 +22287,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 6cf4098e572494e7db59d20fe9faaa72e9285568 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Tue, 11 Jun 2024 15:57:47 +0100 Subject: [PATCH 257/529] fix: hide ai icebreaker ui for non ai users (#9824) --- .../components/MeetingCheckInPrompt/NewCheckInQuestion.tsx | 6 ++++-- packages/client/types/modules.d.ts | 1 + scripts/toolboxSrc/applyEnvVarsToClientAssets.ts | 1 + scripts/webpack/dev.client.config.js | 1 + 4 files changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/client/modules/meeting/components/MeetingCheckInPrompt/NewCheckInQuestion.tsx b/packages/client/modules/meeting/components/MeetingCheckInPrompt/NewCheckInQuestion.tsx index 1bfa7f930cc..76c9d130f83 100644 --- a/packages/client/modules/meeting/components/MeetingCheckInPrompt/NewCheckInQuestion.tsx +++ b/packages/client/modules/meeting/components/MeetingCheckInPrompt/NewCheckInQuestion.tsx @@ -60,6 +60,7 @@ const QuestionBlock = styled('div')({ } } }) + interface Props { meeting: NewCheckInQuestion_meeting$key } @@ -227,7 +228,8 @@ const NewCheckInQuestion = (props: Props) => { } }) } - const hideAiIcebreaker = featureFlags.noAISummary || !isFacilitating + const showAiIcebreaker = + !featureFlags.noAISummary && isFacilitating && window.__ACTION__.hasOpenAI return ( <> @@ -269,7 +271,7 @@ const NewCheckInQuestion = (props: Props) => {
)} - {!hideAiIcebreaker && ( + {showAiIcebreaker && (
diff --git a/packages/client/types/modules.d.ts b/packages/client/types/modules.d.ts index 5408bedab4a..5e3bbb80bf0 100644 --- a/packages/client/types/modules.d.ts +++ b/packages/client/types/modules.d.ts @@ -40,6 +40,7 @@ interface Window { slack: string stripe: string oauth2Redirect: string + hasOpenAI: boolean prblIn: string | undefined AUTH_INTERNAL_ENABLED: boolean AUTH_GOOGLE_ENABLED: boolean diff --git a/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts b/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts index 2395ed1a87a..3227fae1507 100644 --- a/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts +++ b/scripts/toolboxSrc/applyEnvVarsToClientAssets.ts @@ -60,6 +60,7 @@ const rewriteIndexHTML = () => { stripe: process.env.STRIPE_PUBLISHABLE_KEY, publicPath: __webpack_public_path__, oauth2Redirect: process.env.OAUTH2_REDIRECT, + hasOpenAI: !!process.env.OPEN_AI_API_KEY, prblIn: process.env.INVITATION_SHORTLINK, AUTH_INTERNAL_ENABLED: process.env.AUTH_INTERNAL_DISABLED !== 'true', AUTH_GOOGLE_ENABLED: process.env.AUTH_GOOGLE_DISABLED !== 'true', diff --git a/scripts/webpack/dev.client.config.js b/scripts/webpack/dev.client.config.js index 183106f1d1e..e695115b3e3 100644 --- a/scripts/webpack/dev.client.config.js +++ b/scripts/webpack/dev.client.config.js @@ -123,6 +123,7 @@ module.exports = { slack: process.env.SLACK_CLIENT_ID, stripe: process.env.STRIPE_PUBLISHABLE_KEY, oauth2Redirect: process.env.OAUTH2_REDIRECT, + hasOpenAI: !!process.env.OPEN_AI_API_KEY, prblIn: process.env.INVITATION_SHORTLINK, AUTH_INTERNAL_ENABLED: process.env.AUTH_INTERNAL_DISABLED !== 'true', AUTH_GOOGLE_ENABLED: process.env.AUTH_GOOGLE_DISABLED !== 'true', From 095cf71348bd486a71e1f19f48be0718cbca9840 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Thu, 13 Jun 2024 11:27:06 +0200 Subject: [PATCH 258/529] feat: Create embeddings for meeting templates (#9776) --- ...addEmbeddingsMetadataForMeetingTemplate.ts | 54 +++++++++++++++ .../importHistoricalMeetingTemplates.ts | 36 ++++++++++ packages/embedder/importHistoricalMetadata.ts | 5 +- .../indexing/createEmbeddingTextFrom.ts | 3 + packages/embedder/indexing/meetingTemplate.ts | 67 +++++++++++++++++++ ...ertMeetingTemplatesIntoMetadataAndQueue.ts | 57 ++++++++++++++++ ...17685812677_addMeetingTemplateEmbedding.ts | 37 ++++++++++ 7 files changed, 258 insertions(+), 1 deletion(-) create mode 100644 packages/embedder/addEmbeddingsMetadataForMeetingTemplate.ts create mode 100644 packages/embedder/importHistoricalMeetingTemplates.ts create mode 100644 packages/embedder/indexing/meetingTemplate.ts create mode 100644 packages/embedder/insertMeetingTemplatesIntoMetadataAndQueue.ts create mode 100644 packages/server/postgres/migrations/1717685812677_addMeetingTemplateEmbedding.ts diff --git a/packages/embedder/addEmbeddingsMetadataForMeetingTemplate.ts b/packages/embedder/addEmbeddingsMetadataForMeetingTemplate.ts new file mode 100644 index 00000000000..e333f1764b4 --- /dev/null +++ b/packages/embedder/addEmbeddingsMetadataForMeetingTemplate.ts @@ -0,0 +1,54 @@ +import {ExpressionOrFactory, SqlBool, sql} from 'kysely' +import {DB} from 'parabol-server/postgres/pg' +import {Logger} from 'parabol-server/utils/Logger' +import getKysely from '../server/postgres/getKysely' +import {AddEmbeddingsMetadataParams} from './addEmbeddingsMetadata' +import {insertMeetingTemplatesIntoMetadataAndQueue} from './insertMeetingTemplatesIntoMetadataAndQueue' + +export const addEmbeddingsMetadataForMeetingTemplate = async ({ + startAt, + endAt +}: AddEmbeddingsMetadataParams) => { + const pg = getKysely() + // PG only accepts 65K parameters (inserted columns * number of rows + query params). Make the batches as big as possible + const PG_MAX_PARAMS = 65535 + const QUERY_PARAMS = 10 + const METADATA_COLS_PER_ROW = 4 + const BATCH_SIZE = Math.floor((PG_MAX_PARAMS - QUERY_PARAMS) / METADATA_COLS_PER_ROW) + const pgStartAt = startAt || new Date(0) + const pgEndAt = (endAt || new Date('4000')).getTime() / 1000 + + let curEndAt = pgEndAt + let curEndId = '' + for (let i = 0; i < 1e6; i++) { + // preserve microsecond resolution to keep timestamps equal + // so we can use the ID as a tiebreaker when count(createdAt) > BATCH_SIZE + const pgTime = sql`to_timestamp(${curEndAt})` + const lessThanTimeOrId: ExpressionOrFactory = curEndId + ? ({eb}) => + eb('updatedAt', '<', pgTime).or(eb('updatedAt', '=', pgTime).and('id', '>', curEndId)) + : ({eb}) => eb('updatedAt', '<=', pgTime) + const templates = await pg + .selectFrom('MeetingTemplate') + .select([ + 'id', + 'teamId', + 'updatedAt', + sql`extract(epoch from "updatedAt")`.as('updatedAtEpoch') + ]) + .where('updatedAt', '>', pgStartAt) + .where(lessThanTimeOrId) + .orderBy('updatedAt', 'desc') + .orderBy('id') + .limit(BATCH_SIZE) + .execute() + const earliestInBatch = templates.at(-1) + if (!earliestInBatch) break + const {updatedAtEpoch, id} = earliestInBatch + curEndId = curEndAt === updatedAtEpoch ? id : '' + curEndAt = updatedAtEpoch + await insertMeetingTemplatesIntoMetadataAndQueue(templates, 5) + const jsTime = new Date(updatedAtEpoch * 1000) + Logger.log(`Inserted ${templates.length} meetingtemplates in metadata ending at ${jsTime}`) + } +} diff --git a/packages/embedder/importHistoricalMeetingTemplates.ts b/packages/embedder/importHistoricalMeetingTemplates.ts new file mode 100644 index 00000000000..59c5ccbe5a4 --- /dev/null +++ b/packages/embedder/importHistoricalMeetingTemplates.ts @@ -0,0 +1,36 @@ +import getKysely from 'parabol-server/postgres/getKysely' +import {Logger} from 'parabol-server/utils/Logger' +import {addEmbeddingsMetadataForMeetingTemplate} from './addEmbeddingsMetadataForMeetingTemplate' + +// Check to see if the oldest discussion topic exists in the metadata table +// If not, get the date of the oldest discussion topic in the metadata table and import all items before that date +export const importHistoricalMeetingTemplates = async () => { + const pg = getKysely() + const isEarliestMetadataImported = await pg + .selectFrom('EmbeddingsMetadata') + .select('id') + .where(({eb, selectFrom}) => + eb( + 'EmbeddingsMetadata.refId', + '=', + selectFrom('MeetingTemplate') + .select('MeetingTemplate.id') + .orderBy(['updatedAt', 'id']) + .limit(1) + ) + ) + .limit(1) + .executeTakeFirst() + + if (isEarliestMetadataImported) return + const earliestImportedTemplate = await pg + .selectFrom('EmbeddingsMetadata') + .select(['id', 'refUpdatedAt', 'refId']) + .where('objectType', '=', 'meetingTemplate') + .orderBy('refUpdatedAt') + .limit(1) + .executeTakeFirst() + const endAt = earliestImportedTemplate?.refUpdatedAt ?? undefined + Logger.log(`Importing meeting template history up to ${endAt || 'now'}`) + return addEmbeddingsMetadataForMeetingTemplate({endAt}) +} diff --git a/packages/embedder/importHistoricalMetadata.ts b/packages/embedder/importHistoricalMetadata.ts index 0b805f18888..9d97370a896 100644 --- a/packages/embedder/importHistoricalMetadata.ts +++ b/packages/embedder/importHistoricalMetadata.ts @@ -1,13 +1,16 @@ import {EmbeddingObjectType} from './custom' +import {importHistoricalMeetingTemplates} from './importHistoricalMeetingTemplates' import {importHistoricalRetrospectiveDiscussionTopic} from './importHistoricalRetrospectiveDiscussionTopic' export const importHistoricalMetadata = async () => { - const OBJECT_TYPES: EmbeddingObjectType[] = ['retrospectiveDiscussionTopic'] + const OBJECT_TYPES: EmbeddingObjectType[] = ['retrospectiveDiscussionTopic', 'meetingTemplate'] return Promise.all( OBJECT_TYPES.map(async (objectType) => { switch (objectType) { case 'retrospectiveDiscussionTopic': return importHistoricalRetrospectiveDiscussionTopic() + case 'meetingTemplate': + return importHistoricalMeetingTemplates() default: throw new Error(`Invalid object type: ${objectType}`) } diff --git a/packages/embedder/indexing/createEmbeddingTextFrom.ts b/packages/embedder/indexing/createEmbeddingTextFrom.ts index 26083bd024c..59d15ca64e8 100644 --- a/packages/embedder/indexing/createEmbeddingTextFrom.ts +++ b/packages/embedder/indexing/createEmbeddingTextFrom.ts @@ -2,6 +2,7 @@ import {Selectable} from 'kysely' import {DB} from 'parabol-server/postgres/pg' import {DataLoaderInstance} from '../../server/dataloader/RootDataLoader' +import {createTextFromMeetingTemplate} from './meetingTemplate' import {createTextFromRetrospectiveDiscussionTopic} from './retrospectiveDiscussionTopic' export const createEmbeddingTextFrom = async ( @@ -16,6 +17,8 @@ export const createEmbeddingTextFrom = async ( dataLoader, isRerank ) + case 'meetingTemplate': + return createTextFromMeetingTemplate(embeddingsMetadata.refId, dataLoader) default: throw new Error(`Unexcepted objectType: ${embeddingsMetadata.objectType}`) } diff --git a/packages/embedder/indexing/meetingTemplate.ts b/packages/embedder/indexing/meetingTemplate.ts new file mode 100644 index 00000000000..a1b0ef3a5ed --- /dev/null +++ b/packages/embedder/indexing/meetingTemplate.ts @@ -0,0 +1,67 @@ +import {DataLoaderInstance} from 'parabol-server/dataloader/RootDataLoader' +import MeetingTemplate from '../../server/database/types/MeetingTemplate' +import PokerTemplate from '../../server/database/types/PokerTemplate' +import ReflectTemplate from '../../server/database/types/ReflectTemplate' +import {inferLanguage} from '../inferLanguage' + +const createTextFromRetrospectiveMeetingTemplate = async ( + template: ReflectTemplate, + dataLoader: DataLoaderInstance +) => { + const prompts = await dataLoader.get('reflectPromptsByTemplateId').load(template.id) + const promptText = prompts + .map(({question, description}) => { + return `${question}\n${description}` + }) + .join('\n') + return `${template.name}\nRetrospective\n${promptText}` +} + +const createTextFromTeamPromptMeetingTemplate = async (template: MeetingTemplate) => { + return `${template.name}\nteam prompt, daily standup, status update` +} + +const createTextFromActionMeetingTemplate = async (template: MeetingTemplate) => { + return `${template.name}\ncheck-in, action, task, todo, follow-up` +} + +const createTextFromPokerMeetingTemplate = async ( + template: PokerTemplate, + dataLoader: DataLoaderInstance +) => { + const dimensions = await dataLoader.get('templateDimensionsByTemplateId').load(template.id) + const dimensionsText = ( + await Promise.all( + dimensions.map(async ({name, description, scaleId}) => { + const scale = await dataLoader.get('templateScales').load(scaleId) + const scaleValues = scale.values.map(({label}) => label).join(', ') + return `${name}\n${description}\n${scale.name}\n${scaleValues}` + }) + ) + ).join('\n') + return `${template.name}\nplanning poker, sprint poker, estimation\n${dimensionsText}` +} + +export const createTextFromMeetingTemplate = async ( + templateId: string, + dataLoader: DataLoaderInstance +) => { + const template = await dataLoader.get('meetingTemplates').load(templateId) + const body = await (() => { + switch (template?.type) { + case 'retrospective': + return createTextFromRetrospectiveMeetingTemplate(template, dataLoader) + case 'teamPrompt': + return createTextFromTeamPromptMeetingTemplate(template) + case 'action': + return createTextFromActionMeetingTemplate(template) + case 'poker': + return createTextFromPokerMeetingTemplate(template, dataLoader) + default: + return '' + } + })() + + const language = inferLanguage(body) + return {body, language} +} diff --git a/packages/embedder/insertMeetingTemplatesIntoMetadataAndQueue.ts b/packages/embedder/insertMeetingTemplatesIntoMetadataAndQueue.ts new file mode 100644 index 00000000000..0f8e9397381 --- /dev/null +++ b/packages/embedder/insertMeetingTemplatesIntoMetadataAndQueue.ts @@ -0,0 +1,57 @@ +import {sql} from 'kysely' +import getKysely from 'parabol-server/postgres/getKysely' +import getModelManager from './ai_models/ModelManager' +import {getEmbedderPriority} from './getEmbedderPriority' + +export interface MeetingTemplateMeta { + id: string + teamId: string + updatedAt: Date +} + +export const insertMeetingTemplatesIntoMetadataAndQueue = async ( + meetingTemplates: MeetingTemplateMeta[], + maxDelayInDays: number +) => { + const pg = getKysely() + const metadataRows = meetingTemplates.map(({id, teamId, updatedAt}) => ({ + refId: id, + objectType: 'meetingTemplate' as const, + teamId, + refUpdatedAt: updatedAt + })) + if (!metadataRows[0]) return + + const modelManager = getModelManager() + const tableNames = [...modelManager.embeddingModels.keys()] + const priority = getEmbedderPriority(maxDelayInDays) + // This is ugly but it runs fast, which is what we need for historical data + return pg + .with('Insert', (qc) => + qc + .insertInto('EmbeddingsMetadata') + .values(metadataRows) + .onConflict((oc) => oc.doNothing()) + .returning('id') + ) + .with('Metadata', (qc) => + qc + .selectFrom('Insert') + .fullJoin( + sql<{model: string}>`UNNEST(ARRAY[${sql.join(tableNames)}])`.as('model'), + (join) => join.onTrue() + ) + .select(['id', 'model']) + ) + .insertInto('EmbeddingsJobQueue') + .columns(['jobType', 'priority', 'embeddingsMetadataId', 'model']) + .expression(({selectFrom}) => + selectFrom('Metadata').select(({lit, ref}) => [ + sql.lit('embed:start').as('jobType'), + lit(priority).as('priority'), + ref('Metadata.id').as('embeddingsMetadataId'), + ref('Metadata.model').as('model') + ]) + ) + .execute() +} diff --git a/packages/server/postgres/migrations/1717685812677_addMeetingTemplateEmbedding.ts b/packages/server/postgres/migrations/1717685812677_addMeetingTemplateEmbedding.ts new file mode 100644 index 00000000000..f3515f42b1f --- /dev/null +++ b/packages/server/postgres/migrations/1717685812677_addMeetingTemplateEmbedding.ts @@ -0,0 +1,37 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DO $$ + BEGIN + ALTER TYPE "EmbeddingsObjectTypeEnum" ADD VALUE IF NOT EXISTS 'meetingTemplate'; + END $$; + `) + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DO $$ + BEGIN + DELETE FROM "EmbeddingsMetadata" WHERE "objectType" = 'meetingTemplate'; + + ALTER TYPE "EmbeddingsObjectTypeEnum" RENAME TO "EmbeddingsObjectTypeEnum_delete"; + + CREATE TYPE "EmbeddingsObjectTypeEnum" AS ENUM ( + 'retrospectiveDiscussionTopic' + ); + + ALTER TABLE "EmbeddingsMetadata" + ALTER COLUMN "objectType" TYPE "EmbeddingsObjectTypeEnum" USING "objectType"::text::"EmbeddingsObjectTypeEnum"; + + DROP TYPE "EmbeddingsObjectTypeEnum_delete"; + END $$; + `) + await client.end() +} From 71b17c2d05d1f75e6eff98e94d45953ad9907c6b Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Fri, 14 Jun 2024 18:35:49 +0100 Subject: [PATCH 259/529] feat: update dashboard nav item styles (#9795) Co-authored-by: Terry Acker --- .../components/DashNavList/DashNavList.tsx | 165 +++++++----------- .../DashNavList/DashNavListTeams.tsx | 93 ++++++++++ .../components/DashNavList/DashNavMenu.tsx | 98 +++++++++++ .../components/DashNavList/PublicTeamItem.tsx | 71 ++++++++ .../DashNavList/PublicTeamsModal.tsx | 53 ++++++ .../components/Dashboard/DashSidebar.tsx | 96 +++++----- .../components/Dashboard/LeftDashNavItem.tsx | 76 ++++---- .../Dashboard/LeftDashNavParabol.tsx | 9 +- .../Dashboard/MobileDashSidebar.tsx | 124 +++++++------ packages/client/components/MeetingOptions.tsx | 25 +-- .../NewMeetingActionsCurrentMeetings.tsx | 5 +- .../components/SideBarStartMeetingButton.tsx | 25 +-- .../components/StandardHub/StandardHub.tsx | 56 +----- .../NextPeriodChargesLineItem.tsx | 2 +- .../GcalModal/VideoConferencing.tsx | 2 +- .../components/OrgTeams/OrgTeams.tsx | 2 +- .../Organization/OrganizationDetails.tsx | 2 +- .../mutations/fragments/PublicTeamsFrag.ts | 6 + packages/client/types/constEnums.ts | 4 +- packages/client/ui/Menu/Menu.tsx | 31 +--- packages/client/ui/Menu/MenuContent.tsx | 25 +++ packages/client/utils/sortByTier.ts | 2 +- packages/server/graphql/types/Organization.ts | 16 ++ 23 files changed, 637 insertions(+), 351 deletions(-) create mode 100644 packages/client/components/DashNavList/DashNavListTeams.tsx create mode 100644 packages/client/components/DashNavList/DashNavMenu.tsx create mode 100644 packages/client/components/DashNavList/PublicTeamItem.tsx create mode 100644 packages/client/components/DashNavList/PublicTeamsModal.tsx create mode 100644 packages/client/ui/Menu/MenuContent.tsx diff --git a/packages/client/components/DashNavList/DashNavList.tsx b/packages/client/components/DashNavList/DashNavList.tsx index 2c5890bc643..1134178d628 100644 --- a/packages/client/components/DashNavList/DashNavList.tsx +++ b/packages/client/components/DashNavList/DashNavList.tsx @@ -1,32 +1,17 @@ import styled from '@emotion/styled' import graphql from 'babel-plugin-relay/macro' -import React, {Fragment, useMemo} from 'react' +import React from 'react' import {useFragment} from 'react-relay' import {PALETTE} from '~/styles/paletteV3' -import {Breakpoint} from '~/types/constEnums' -import makeMinWidthMediaQuery from '~/utils/makeMinWidthMediaQuery' -import { - DashNavList_organization$data, - DashNavList_organization$key -} from '../../__generated__/DashNavList_organization.graphql' +import {DashNavList_organization$key} from '../../__generated__/DashNavList_organization.graphql' +import {TierEnum} from '../../__generated__/InvoiceHeader_invoice.graphql' +import useBreakpoint from '../../hooks/useBreakpoint' +import {Breakpoint} from '../../types/constEnums' +import {upperFirst} from '../../utils/upperFirst' import LeftDashNavItem from '../Dashboard/LeftDashNavItem' - -const DashNavListStyles = styled('div')({ - paddingRight: 8, - width: '100%' -}) - -const OrgName = styled('div')({ - paddingTop: 8, - paddingLeft: 8, - fontWeight: 600, - fontSize: 12, - lineHeight: '24px', - color: PALETTE.SLATE_500, - [makeMinWidthMediaQuery(Breakpoint.SIDEBAR_LEFT)]: { - paddingLeft: 16 - } -}) +import BaseTag from '../Tag/BaseTag' +import DashNavListTeams from './DashNavListTeams' +import DashNavMenu from './DashNavMenu' const EmptyTeams = styled('div')({ fontSize: 16, @@ -35,111 +20,81 @@ const EmptyTeams = styled('div')({ textAlign: 'center' }) -const DashHR = styled('div')({ - borderBottom: `1px solid ${PALETTE.SLATE_300}`, - width: 'calc(100% + 8px)' -}) - const StyledLeftDashNavItem = styled(LeftDashNavItem)<{isViewerOnTeam: boolean}>( ({isViewerOnTeam}) => ({ - color: isViewerOnTeam ? PALETTE.SLATE_700 : PALETTE.SLATE_600 + color: isViewerOnTeam ? PALETTE.SLATE_700 : PALETTE.SLATE_600, + borderRadius: 44, + paddingLeft: 15 }) ) +const Tag = styled(BaseTag)<{tier: TierEnum | null}>(({tier}) => ({ + backgroundColor: + tier === 'enterprise' ? PALETTE.SKY_500 : tier === 'team' ? PALETTE.GOLD_300 : PALETTE.JADE_400, + color: tier === 'team' ? PALETTE.GRAPE_700 : PALETTE.WHITE +})) + interface Props { - className?: string organizationsRef: DashNavList_organization$key | null onClick?: () => void } -type Team = DashNavList_organization$data[0]['allTeams'][0] - const DashNavList = (props: Props) => { - const {className, onClick, organizationsRef} = props + const {onClick, organizationsRef} = props const organizations = useFragment( graphql` fragment DashNavList_organization on Organization @relay(plural: true) { - allTeams { - ...DashNavListTeam @relay(mask: false) - } + ...DashNavListTeams_organization + ...DashNavMenu_organization + id + name + tier viewerTeams { - ...DashNavListTeam @relay(mask: false) - } - featureFlags { - publicTeams + id } } `, organizationsRef ) - const teams = organizations?.flatMap((org) => { - // if the user is a billing leader, allTeams will return all teams even if they don't have the publicTeams flag - const hasPublicTeamsFlag = org.featureFlags.publicTeams - return hasPublicTeamsFlag ? org.allTeams : org.viewerTeams - }) + const isDesktop = useBreakpoint(Breakpoint.SIDEBAR_LEFT) + const teams = organizations?.flatMap((org) => org.viewerTeams) - const teamsByOrgKey = useMemo(() => { - if (!teams) return null - const teamsByOrgId = {} as {[key: string]: Team[]} - teams.forEach((team) => { - const {organization} = team - const {id: orgId, name: orgName} = organization - const key = `${orgName}:${orgId}` - teamsByOrgId[key] = teamsByOrgId[key] || [] - teamsByOrgId[key]!.push(team) - }) - return Object.entries(teamsByOrgId).sort((a, b) => - a[0].toLowerCase() < b[0].toLowerCase() ? -1 : 1 - ) - }, [teams]) - if (!teams || !teamsByOrgKey) return null - - if (teams.length === 0) { + if (teams?.length === 0) { return It appears you are not a member of any team! } - const isSingleOrg = teamsByOrgKey.length === 1 - - const getIcon = (team: Team) => (team.organization.lockedAt || !team.isPaid ? 'warning' : 'group') - return ( - - {isSingleOrg - ? teams.map((team) => ( - - )) - : teamsByOrgKey.map((entry, idx) => { - const [key, teams] = entry - const name = key.slice(0, key.lastIndexOf(':')) - return ( - - {name} - {teams.map((team) => ( - - ))} - {idx !== teamsByOrgKey.length - 1 && } - - ) - })} - +
+ {organizations?.map((org) => ( +
+
+
+
+ + {org.name} + +
+ {upperFirst(org.tier)} +
+
+
+ {isDesktop ? ( + + ) : ( + + )} +
+ +
+ ))} +
) } @@ -149,6 +104,7 @@ graphql` isPaid name isViewerOnTeam + tier organization { id name @@ -156,4 +112,5 @@ graphql` } } ` + export default DashNavList diff --git a/packages/client/components/DashNavList/DashNavListTeams.tsx b/packages/client/components/DashNavList/DashNavListTeams.tsx new file mode 100644 index 00000000000..9f9f867d94e --- /dev/null +++ b/packages/client/components/DashNavList/DashNavListTeams.tsx @@ -0,0 +1,93 @@ +import styled from '@emotion/styled' +import graphql from 'babel-plugin-relay/macro' +import React, {useState} from 'react' +import {useFragment} from 'react-relay' +import {DashNavListTeams_organization$key} from '../../__generated__/DashNavListTeams_organization.graphql' +import {PALETTE} from '../../styles/paletteV3' +import plural from '../../utils/plural' +import LeftDashNavItem from '../Dashboard/LeftDashNavItem' +import PublicTeamsModal from './PublicTeamsModal' + +const StyledLeftDashNavItem = styled(LeftDashNavItem)<{isPublicTeams?: boolean}>( + ({isPublicTeams}) => ({ + color: isPublicTeams ? PALETTE.SLATE_600 : PALETTE.SLATE_700, + borderRadius: 44, + paddingLeft: 15 + }) +) + +type Props = { + organizationRef: DashNavListTeams_organization$key + onClick?: () => void +} + +const DashNavListTeams = (props: Props) => { + const {organizationRef, onClick} = props + const organization = useFragment( + graphql` + fragment DashNavListTeams_organization on Organization { + id + name + tier + featureFlags { + publicTeams + } + viewerTeams { + ...DashNavListTeam @relay(mask: false) + } + publicTeams { + ...PublicTeamsModal_team + } + } + `, + organizationRef + ) + const [showModal, setShowModal] = useState(false) + const {publicTeams, viewerTeams, featureFlags} = organization + const publicTeamsEnabled = featureFlags?.publicTeams + const publicTeamsCount = publicTeamsEnabled ? publicTeams.length : 0 + + const handleClose = () => { + setShowModal(false) + } + + const handleClick = () => { + setShowModal(true) + onClick && onClick() + } + + const getIcon = (lockedAt: string | null, isPaid: boolean | null) => + lockedAt || !isPaid ? 'warning' : 'group' + + return ( +
+ {viewerTeams.map((team) => { + return ( + + ) + })} + {publicTeamsCount > 0 && ( + + )} + +
+ ) +} + +export default DashNavListTeams diff --git a/packages/client/components/DashNavList/DashNavMenu.tsx b/packages/client/components/DashNavList/DashNavMenu.tsx new file mode 100644 index 00000000000..43fdb660221 --- /dev/null +++ b/packages/client/components/DashNavList/DashNavMenu.tsx @@ -0,0 +1,98 @@ +import styled from '@emotion/styled' +import graphql from 'babel-plugin-relay/macro' +import React from 'react' +import {useFragment} from 'react-relay' +import {useHistory} from 'react-router' +import {DashNavMenu_organization$key} from '../../__generated__/DashNavMenu_organization.graphql' +import {PALETTE} from '../../styles/paletteV3' +import {Menu} from '../../ui/Menu/Menu' +import {MenuContent} from '../../ui/Menu/MenuContent' +import {MenuItem} from '../../ui/Menu/MenuItem' +import LeftDashNavItem from '../Dashboard/LeftDashNavItem' + +const StyledLeftDashNavItem = styled(LeftDashNavItem)<{isViewerOnTeam: boolean}>( + ({isViewerOnTeam}) => ({ + color: isViewerOnTeam ? PALETTE.SLATE_700 : PALETTE.SLATE_600, + borderRadius: 44, + paddingLeft: 15 + }) +) + +type Props = { + organizationRef: DashNavMenu_organization$key +} + +const DashNavMenu = (props: Props) => { + const {organizationRef} = props + const history = useHistory() + const org = useFragment( + graphql` + fragment DashNavMenu_organization on Organization { + id + tier + } + `, + organizationRef + ) + const {id: orgId, tier} = org + const menuItems = [ + { + label: ( + <> + Plans & Billing{' '} + {tier === 'starter' && ( + <> + • Upgrade + + )} + + ), + href: `/me/organizations/${orgId}/billing` + }, + { + label: 'Teams', + href: `/me/organizations/${orgId}/teams` + }, + { + label: 'Members', + href: `/me/organizations/${orgId}/members` + }, + { + label: 'Organization Settings', + href: `/me/organizations/${orgId}/settings` + }, + { + label: 'Authentication', + href: `/me/organizations/${orgId}/authentication` + } + ] + + const handleMenuItemClick = (href: string) => { + history.push(href) + } + + return ( + + +
+ } + > + + {menuItems.map((item) => ( + handleMenuItemClick(item.href)}> + {item.label} + + ))} + + + ) +} + +export default DashNavMenu diff --git a/packages/client/components/DashNavList/PublicTeamItem.tsx b/packages/client/components/DashNavList/PublicTeamItem.tsx new file mode 100644 index 00000000000..3fad783ac63 --- /dev/null +++ b/packages/client/components/DashNavList/PublicTeamItem.tsx @@ -0,0 +1,71 @@ +import graphql from 'babel-plugin-relay/macro' +import React, {useState} from 'react' +import {useFragment} from 'react-relay' +import {PublicTeamItem_team$key} from '../../__generated__/PublicTeamItem_team.graphql' +import {PushInvitationMutation$data} from '../../__generated__/PushInvitationMutation.graphql' +import useAtmosphere from '../../hooks/useAtmosphere' +import useMutationProps from '../../hooks/useMutationProps' +import PushInvitationMutation from '../../mutations/PushInvitationMutation' +import SendClientSideEvent from '../../utils/SendClientSideEvent' +import SecondaryButton from '../SecondaryButton' + +type Props = { + teamRef: PublicTeamItem_team$key +} + +const PublicTeamItem = (props: Props) => { + const {teamRef} = props + const [isRequestSent, setIsRequestSent] = useState(false) + const atmosphere = useAtmosphere() + const {onError, onCompleted} = useMutationProps() + + const team = useFragment( + graphql` + fragment PublicTeamItem_team on Team { + id + name + } + `, + teamRef + ) + + const handleRequestToJoin = (teamId: string) => { + if (!teamId) return + SendClientSideEvent(atmosphere, 'Sent request to join from public teams', {teamId}) + PushInvitationMutation( + atmosphere, + {teamId}, + { + onError, + onCompleted: (res: PushInvitationMutation$data) => { + if (res.pushInvitation?.error) { + const error = new Error(res.pushInvitation?.error?.message) + onError(error) + } else { + setIsRequestSent(true) + onCompleted() + } + } + } + ) + } + + return ( +
+ {team.name} +
+ {isRequestSent ? ( + + Request Sent! + + ) : ( + handleRequestToJoin(team.id)} size='medium'> + Request to Join + + )} +
+
+ ) +} + +export default PublicTeamItem diff --git a/packages/client/components/DashNavList/PublicTeamsModal.tsx b/packages/client/components/DashNavList/PublicTeamsModal.tsx new file mode 100644 index 00000000000..169785c566f --- /dev/null +++ b/packages/client/components/DashNavList/PublicTeamsModal.tsx @@ -0,0 +1,53 @@ +import graphql from 'babel-plugin-relay/macro' +import React from 'react' +import {useFragment} from 'react-relay' +import {PublicTeamsModal_team$key} from '../../__generated__/PublicTeamsModal_team.graphql' +import {Dialog} from '../../ui/Dialog/Dialog' +import {DialogContent} from '../../ui/Dialog/DialogContent' +import {DialogDescription} from '../../ui/Dialog/DialogDescription' +import {DialogTitle} from '../../ui/Dialog/DialogTitle' +import plural from '../../utils/plural' +import PublicTeamItem from './PublicTeamItem' + +type Props = { + isOpen: boolean + onClose: () => void + orgName: string + teamsRef: PublicTeamsModal_team$key +} + +const PublicTeamsModal = (props: Props) => { + const {isOpen, onClose, teamsRef, orgName} = props + + const publicTeams = useFragment( + graphql` + fragment PublicTeamsModal_team on Team @relay(plural: true) { + id + ...PublicTeamItem_team + } + `, + teamsRef + ) + const publicTeamsCount = publicTeams.length + + return ( + + + {`${publicTeamsCount} ${plural(publicTeamsCount, 'Public Team', 'Public Teams')}`} + + Request to join as a Team Member on any public teams at{' '} + {orgName} + +
+ {publicTeams.map((team, index) => ( + + + {index < publicTeams.length - 1 &&
} +
+ ))} +
+
+ ) +} + +export default PublicTeamsModal diff --git a/packages/client/components/Dashboard/DashSidebar.tsx b/packages/client/components/Dashboard/DashSidebar.tsx index b29c698ae7c..23e6b276450 100644 --- a/packages/client/components/Dashboard/DashSidebar.tsx +++ b/packages/client/components/Dashboard/DashSidebar.tsx @@ -38,13 +38,9 @@ const NavMain = styled('div')({ overflowY: 'auto' }) -const DashHR = styled('div')({ - borderBottom: `solid ${PALETTE.SLATE_300} 1px`, - width: '100%' -}) - const NavItem = styled(LeftDashNavItem)({ - paddingLeft: 16 + borderRadius: 44, + paddingLeft: 15 }) const NavList = styled(DashNavList)({ @@ -61,12 +57,11 @@ const Wrapper = styled('div')({ }) const OrgName = styled('div')({ - paddingTop: 8, + color: PALETTE.SLATE_600, fontWeight: 600, fontSize: 12, - lineHeight: '24px', - color: PALETTE.SLATE_500, - paddingLeft: 16 + lineHeight: '16px', + padding: '8px 16px' }) interface Props { @@ -104,40 +99,42 @@ const DashSidebar = (props: Props) => { @@ -149,20 +146,15 @@ const DashSidebar = (props: Props) => { diff --git a/packages/client/components/Dashboard/LeftDashNavItem.tsx b/packages/client/components/Dashboard/LeftDashNavItem.tsx index 86f80537001..9cb7ac3b6c7 100644 --- a/packages/client/components/Dashboard/LeftDashNavItem.tsx +++ b/packages/client/components/Dashboard/LeftDashNavItem.tsx @@ -1,5 +1,6 @@ import styled from '@emotion/styled' import { + AccountBox, Add, ArrowBack, AutoAwesome, @@ -9,6 +10,7 @@ import { Group, Groups, Key, + ManageAccounts, PlaylistAddCheck, Timeline, Warning, @@ -18,40 +20,38 @@ import React from 'react' import {useHistory, useRouteMatch} from 'react-router' import PlainButton from '~/components/PlainButton/PlainButton' import {PALETTE} from '~/styles/paletteV3' -import {Breakpoint, NavSidebar} from '~/types/constEnums' -import makeMinWidthMediaQuery from '~/utils/makeMinWidthMediaQuery' +import {NavSidebar} from '~/types/constEnums' const NavItem = styled(PlainButton)<{isActive: boolean}>(({isActive}) => ({ alignItems: 'center', backgroundColor: isActive ? PALETTE.SLATE_300 : undefined, borderRadius: 4, - color: PALETTE.SLATE_700, + color: isActive ? PALETTE.SLATE_900 : PALETTE.SLATE_800, display: 'flex', fontSize: NavSidebar.FONT_SIZE, fontWeight: 600, lineHeight: NavSidebar.LINE_HEIGHT, - marginBottom: 8, - marginTop: 8, - padding: 8, + marginBottom: 2, + marginTop: 2, + paddingBottom: 5, + paddingRight: 8, + paddingTop: 5, textDecoration: 'none', transition: `background-color 100ms ease-in`, userSelect: 'none', width: '100%', - [makeMinWidthMediaQuery(Breakpoint.SIDEBAR_LEFT)]: { - borderRadius: '0 4px 4px 0' - }, ':hover': { backgroundColor: PALETTE.SLATE_300 } })) -const StyledIcon = styled('div')({ - height: 24, - width: 24, - color: PALETTE.SLATE_700, - marginRight: 16, - opacity: 0.5 -}) +const StyledIcon = styled('div')<{isActive: boolean}>(({isActive}) => ({ + fontSize: 18, + height: 18, + width: 18, + color: isActive ? PALETTE.SLATE_700 : PALETTE.SLATE_600, + marginRight: 11 +})) const Label = styled('div')({ flex: 1, @@ -59,38 +59,42 @@ const Label = styled('div')({ }) const iconLookup = { - magic: , - arrowBack: , - creditScore: , - forum: , - playlist_add_check: , - add: , - exit_to_app: , - group: , - groups: , - warning: , - work: , - timeline: , - key: + userSettings: , + magic: , + arrowBack: , + creditScore: , + forum: , + playlist_add_check: , + add: , + exit_to_app: , + manageAccounts: , + group: , + groups: , + warning: , + work: , + timeline: , + key: } interface Props { className?: string onClick?: () => void label: string - href: string + href?: string navState?: unknown //FIXME 6062: change to React.ComponentType - icon: keyof typeof iconLookup + icon?: keyof typeof iconLookup exact?: boolean } const LeftDashNavItem = (props: Props) => { - const {className, label, icon, href, navState, onClick} = props + const {className, label, icon, href = '', navState, onClick} = props const history = useHistory() const match = useRouteMatch(href) const handleClick = () => { - history.push(href, navState) + if (href) { + history.push(href, navState) + } onClick?.() } return ( @@ -99,7 +103,11 @@ const LeftDashNavItem = (props: Props) => { onClick={handleClick} isActive={!!match && (match?.isExact || !props.exact)} > - {iconLookup[icon]} + {icon && ( + + {iconLookup[icon]} + + )} ) diff --git a/packages/client/components/Dashboard/LeftDashNavParabol.tsx b/packages/client/components/Dashboard/LeftDashNavParabol.tsx index 09cba9d3ebd..f9bd0d68df0 100644 --- a/packages/client/components/Dashboard/LeftDashNavParabol.tsx +++ b/packages/client/components/Dashboard/LeftDashNavParabol.tsx @@ -1,9 +1,9 @@ import styled from '@emotion/styled' import React from 'react' -import parabolLogo from 'static/images/brand/mark-color.svg' import PlainButton from '~/components/PlainButton/PlainButton' import {PALETTE} from '~/styles/paletteV3' import {NavSidebar} from '~/types/constEnums' +import parabolLogo from '../../styles/theme/images/brand/lockup_color_mark_dark_type.svg' const Parabol = styled(PlainButton)({ alignItems: 'center', @@ -17,15 +17,10 @@ const Parabol = styled(PlainButton)({ width: '100%' }) -const Label = styled('div')({ - paddingLeft: 8 -}) - const LeftDashParabol = () => { return ( - Parabol - + Parabol logo ) } diff --git a/packages/client/components/Dashboard/MobileDashSidebar.tsx b/packages/client/components/Dashboard/MobileDashSidebar.tsx index 2c2e7b1fdad..ae214705b9e 100644 --- a/packages/client/components/Dashboard/MobileDashSidebar.tsx +++ b/packages/client/components/Dashboard/MobileDashSidebar.tsx @@ -41,7 +41,8 @@ const DashSidebarStyles = styled('div')({ const NavBlock = styled('div')({ flex: 1, position: 'relative', - padding: 8 + padding: 8, + overflowY: 'auto' }) const Nav = styled('nav')({ @@ -50,7 +51,7 @@ const Nav = styled('nav')({ left: 0, height: '100%', maxHeight: '100%', - padding: '0 0 8px 8px', + padding: 0, position: 'absolute', top: 0, width: '100%' @@ -58,23 +59,24 @@ const Nav = styled('nav')({ const OrgName = styled('div')({ paddingTop: 8, - paddingLeft: 8, + paddingLeft: 15, fontWeight: 600, fontSize: 12, lineHeight: '24px', color: PALETTE.SLATE_500 }) -const NavMain = styled('div')({ - overflowY: 'auto' +const NavItemsWrap = styled('div')({ + padding: '8px 17px' // add 1 to 16 to match nav section border }) -const NavItemsWrap = styled('div')({ - paddingRight: 8 +const NavItem = styled(LeftDashNavItem)({ + borderRadius: 44, + paddingLeft: 15 }) const DashHR = styled('div')({ - borderBottom: `solid ${PALETTE.SLATE_300} 1px`, + borderBottom: `solid ${PALETTE.SLATE_400} 1px`, marginLeft: -8, width: 'calc(100% + 8px)' }) @@ -82,9 +84,10 @@ const DashHR = styled('div')({ const Footer = styled('div')({ display: 'flex', // safari flexbox bug: https://stackoverflow.com/a/58720054/3155110 - flex: '1 0 auto', flexDirection: 'column', - justifyContent: 'space-between' + justifyContent: 'flex-end', + marginTop: 'auto', + padding: 8 }) const FooterBottom = styled('div')({}) @@ -119,7 +122,23 @@ const MobileDashSidebar = (props: Props) => { + +
+ + + +
) } @@ -170,60 +195,57 @@ const MobileDashSidebar = (props: Props) => { + +
+ + + +
) } diff --git a/packages/client/components/MeetingOptions.tsx b/packages/client/components/MeetingOptions.tsx index 7d471eba77b..2d9a65c750a 100644 --- a/packages/client/components/MeetingOptions.tsx +++ b/packages/client/components/MeetingOptions.tsx @@ -4,6 +4,7 @@ import React, {useEffect, useState} from 'react' import {useLazyLoadQuery} from 'react-relay' import {MeetingOptionsQuery} from '../__generated__/MeetingOptionsQuery.graphql' import {Menu} from '../ui/Menu/Menu' +import {MenuContent} from '../ui/Menu/MenuContent' import {MenuItem} from '../ui/Menu/MenuItem' import {Tooltip} from '../ui/Tooltip/Tooltip' import {TooltipContent} from '../ui/Tooltip/TooltipContent' @@ -92,17 +93,19 @@ const MeetingOptions = (props: Props) => { } > - -
- - -
{}
- Change template -
-
-
- {tooltipCopy} -
+ + +
+ + +
{}
+ Change template +
+
+
+ {tooltipCopy} +
+
) } diff --git a/packages/client/components/NewMeetingActionsCurrentMeetings.tsx b/packages/client/components/NewMeetingActionsCurrentMeetings.tsx index 63272a48dfd..6658f3c3b4b 100644 --- a/packages/client/components/NewMeetingActionsCurrentMeetings.tsx +++ b/packages/client/components/NewMeetingActionsCurrentMeetings.tsx @@ -8,6 +8,7 @@ import useSnacksForNewMeetings from '~/hooks/useSnacksForNewMeetings' import {PALETTE} from '~/styles/paletteV3' import plural from '~/utils/plural' import {Menu} from '../ui/Menu/Menu' +import {MenuContent} from '../ui/Menu/MenuContent' import FlatButton from './FlatButton' import SelectMeetingDropdown from './SelectMeetingDropdown' @@ -56,7 +57,9 @@ const NewMeetingActionsCurrentMeetings = (props: Props) => { } > - + + + ) } diff --git a/packages/client/components/SideBarStartMeetingButton.tsx b/packages/client/components/SideBarStartMeetingButton.tsx index ce67aa73bc8..265058ebf7a 100644 --- a/packages/client/components/SideBarStartMeetingButton.tsx +++ b/packages/client/components/SideBarStartMeetingButton.tsx @@ -6,24 +6,25 @@ import {BezierCurve} from '../types/constEnums' import FlatPrimaryButton from './FlatPrimaryButton' const Button = styled(FlatPrimaryButton)<{isOpen: boolean}>(({isOpen}) => ({ - height: 48, + height: 40, overflow: 'hidden', padding: 0, - width: isOpen ? 160 : 48, - marginLeft: 7, - marginTop: 15, - marginBottom: 15, + width: isOpen ? '100%' : 40, + marginTop: 16, + marginBottom: 14, // account for nav margin 2px transition: `all 300ms ${BezierCurve.DECELERATE}`, - justifyContent: 'flex-start' + justifyContent: isOpen ? 'center' : 'flex-start' })) const MeetingIcon = styled(Add)({ - margin: '0px 11px' + margin: '0px 0px 0px 7px' }) const MeetingLabel = styled('div')<{isOpen: boolean}>(({isOpen}) => ({ fontSize: 16, fontWeight: 600, + paddingLeft: 4, + paddingRight: 7, transition: `all 300ms ${BezierCurve.DECELERATE}`, opacity: isOpen ? 1 : 0 })) @@ -35,10 +36,12 @@ const SideBarStartMeetingButton = ({isOpen}: {isOpen: boolean}) => { history.push('/activity-library') } return ( - +
+ +
) } diff --git a/packages/client/components/StandardHub/StandardHub.tsx b/packages/client/components/StandardHub/StandardHub.tsx index d275019c553..3bb47e353ec 100644 --- a/packages/client/components/StandardHub/StandardHub.tsx +++ b/packages/client/components/StandardHub/StandardHub.tsx @@ -1,27 +1,18 @@ import styled from '@emotion/styled' -import {VerifiedUser as VerifiedUserIcon} from '@mui/icons-material' import graphql from 'babel-plugin-relay/macro' import React from 'react' import {useFragment} from 'react-relay' -import WaveWhiteSVG from 'static/images/waveWhite.svg' -import PlainButton from '~/components/PlainButton/PlainButton' -import TierTag from '~/components/Tag/TierTag' import useRouter from '~/hooks/useRouter' -import {StandardHub_viewer$key, TierEnum} from '../../__generated__/StandardHub_viewer.graphql' +import {StandardHub_viewer$key} from '../../__generated__/StandardHub_viewer.graphql' import {PALETTE} from '../../styles/paletteV3' import defaultUserAvatar from '../../styles/theme/images/avatar-user.svg' import Avatar from '../Avatar/Avatar' const StandardHubRoot = styled('div')({ - backgroundRepeat: 'no-repeat', - backgroundSize: '100%', - backgroundPositionY: '101%', - backgroundPositionX: '0', - backgroundImage: `url('${WaveWhiteSVG}'), linear-gradient(90deg, ${PALETTE.GRAPE_700} 0%, ${PALETTE.SLATE_700} 100%)`, + backgroundColor: PALETTE.GRAPE_700, display: 'flex', flexDirection: 'column', - minHeight: 56, - padding: 16, + padding: 8, width: '100%' }) @@ -53,28 +44,6 @@ const Email = styled('div')({ lineHeight: '16px' }) -const Upgrade = styled(PlainButton)({ - background: 'transparent', - color: PALETTE.GOLD_300, - display: 'flex', - fontWeight: 600, - paddingTop: 16, - paddingBottom: 16 -}) - -const UpgradeCTA = styled('span')({ - fontSize: 14, - lineHeight: '24px', - paddingLeft: 16 -}) - -const Tier = styled(TierTag)({ - marginLeft: 64, - marginBottom: 16, - padding: '0 16px', - width: 'fit-content' -}) - interface Props { handleMenuClick: () => void viewer: StandardHub_viewer$key | null @@ -96,19 +65,14 @@ const StandardHub = (props: Props) => { email picture preferredName - tier - billingTier } `, viewerRef ) - const {email, picture, preferredName, tier, billingTier} = viewer || DEFAULT_VIEWER + const {email, picture, preferredName} = viewer || DEFAULT_VIEWER const userAvatar = picture || defaultUserAvatar const {history} = useRouter() - const handleUpgradeClick = () => { - history.push(`/me/organizations`) - handleMenuClick() - } + const gotoUserSettings = () => { history.push('/me/profile') handleMenuClick() @@ -116,20 +80,12 @@ const StandardHub = (props: Props) => { return ( - + {preferredName} {email} - {billingTier === 'starter' ? ( - - - {'Upgrade'} - - ) : ( - - )} ) } diff --git a/packages/client/modules/invoice/components/InvoiceLineItem/NextPeriodChargesLineItem.tsx b/packages/client/modules/invoice/components/InvoiceLineItem/NextPeriodChargesLineItem.tsx index d903f61d354..ccbe9e7bb42 100644 --- a/packages/client/modules/invoice/components/InvoiceLineItem/NextPeriodChargesLineItem.tsx +++ b/packages/client/modules/invoice/components/InvoiceLineItem/NextPeriodChargesLineItem.tsx @@ -2,7 +2,7 @@ import graphql from 'babel-plugin-relay/macro' import React from 'react' import {useFragment} from 'react-relay' import {NextPeriodChargesLineItem_item$key} from '~/__generated__/NextPeriodChargesLineItem_item.graphql' -import {TierEnum} from '~/__generated__/StandardHub_viewer.graphql' +import {TierEnum} from '../../../../__generated__/DowngradeToStarterMutation.graphql' import plural from '../../../../utils/plural' import invoiceLineFormat from '../../helpers/invoiceLineFormat' import InvoiceLineItemContent from './InvoiceLineItemContent' diff --git a/packages/client/modules/userDashboard/components/GcalModal/VideoConferencing.tsx b/packages/client/modules/userDashboard/components/GcalModal/VideoConferencing.tsx index f941de9d740..3c14476ced4 100644 --- a/packages/client/modules/userDashboard/components/GcalModal/VideoConferencing.tsx +++ b/packages/client/modules/userDashboard/components/GcalModal/VideoConferencing.tsx @@ -26,7 +26,7 @@ const VideoConferencing = (props: Props) => { {videoType ? (
{videoType === 'meet' ? : } - + {selectedOptionLabel} { -
- ) + : undefined const meetingNamePlaceholder = type === 'retrospective' @@ -255,11 +227,10 @@ const ActivityDetailsSidebar = (props: Props) => { >
{type === 'retrospective' && ( <> diff --git a/packages/client/components/ActivityLibrary/TeamPickerModal.tsx b/packages/client/components/ActivityLibrary/TeamPickerModal.tsx index 07ab1ba8d2c..9f9a3083622 100644 --- a/packages/client/components/ActivityLibrary/TeamPickerModal.tsx +++ b/packages/client/components/ActivityLibrary/TeamPickerModal.tsx @@ -9,10 +9,11 @@ import {TeamPickerModal_teams$key} from '~/__generated__/TeamPickerModal_teams.g import {MeetingTypeEnum} from '~/__generated__/TemplateDetails_activity.graphql' import {AddPokerTemplateMutation$data} from '../../__generated__/AddPokerTemplateMutation.graphql' import useAtmosphere from '../../hooks/useAtmosphere' -import {MenuPosition} from '../../hooks/useCoords' import useMutationProps from '../../hooks/useMutationProps' import AddPokerTemplateMutation from '../../mutations/AddPokerTemplateMutation' import AddReflectTemplateMutation from '../../mutations/AddReflectTemplateMutation' +import {Dialog} from '../../ui/Dialog/Dialog' +import {DialogContent} from '../../ui/Dialog/DialogContent' import SendClientSideEvent from '../../utils/SendClientSideEvent' import sortByTier from '../../utils/sortByTier' import NewMeetingTeamPicker from '../NewMeetingTeamPicker' @@ -23,14 +24,15 @@ const ACTION_BUTTON_CLASSES = interface Props { preferredTeamId: string | null teamsRef: TeamPickerModal_teams$key - closePortal: () => void category: string parentTemplateId: string type: MeetingTypeEnum + isOpen: boolean + closeModal: () => void } const TeamPickerModal = (props: Props) => { - const {teamsRef, closePortal, category, parentTemplateId, type, preferredTeamId} = props + const {teamsRef, category, parentTemplateId, type, preferredTeamId, isOpen, closeModal} = props const teams = useFragment( graphql` fragment TeamPickerModal_teams on Team @relay(plural: true) { @@ -71,7 +73,7 @@ const TeamPickerModal = (props: Props) => { { onError, onCompleted: (res: AddReflectTemplateMutation$data) => { - closePortal() + closeModal() const templateId = res.addReflectTemplate?.reflectTemplate?.id if (templateId) { history.push(`/activity-library/details/${templateId}`, { @@ -91,7 +93,7 @@ const TeamPickerModal = (props: Props) => { { onError, onCompleted: (res: AddPokerTemplateMutation$data) => { - closePortal() + closeModal() const templateId = res.addPokerTemplate?.pokerTemplate?.id if (templateId) { history.push(`/activity-library/details/${templateId}`, { @@ -115,63 +117,64 @@ const TeamPickerModal = (props: Props) => { } return ( -
-
-
- Select the team to manage this cloned template -
- { - const newTeam = teams.find((team) => team.id === teamId) - newTeam && setSelectedTeam(newTeam) - }} - selectedTeamRef={selectedTeam} - teamsRef={teams} - /> - {selectedTeam.tier === 'starter' && ( + + +
- This team is on the Starter plan. Upgrade to clone and edit templates on - this team. + Select the team to manage this cloned template
- )} - {error?.message &&
{error.message}
} -
- - {selectedTeam.tier === 'starter' ? ( - - ) : ( + { + const newTeam = teams.find((team) => team.id === teamId) + newTeam && setSelectedTeam(newTeam) + }} + selectedTeamRef={selectedTeam} + teamsRef={teams} + /> + {selectedTeam.tier === 'starter' && ( +
+ This team is on the Starter plan. Upgrade to clone and edit templates on + this team. +
+ )} + {error?.message &&
{error.message}
} +
- )} + {selectedTeam.tier === 'starter' ? ( + + ) : ( + + )} +
-
-
+ + ) } diff --git a/packages/client/components/DropdownToggleInner.tsx b/packages/client/components/DropdownToggleInner.tsx deleted file mode 100644 index 673493093eb..00000000000 --- a/packages/client/components/DropdownToggleInner.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import styled from '@emotion/styled' -import React, {forwardRef} from 'react' -import {PALETTE} from '~/styles/paletteV3' - -const Container = styled('div')({ - alignItems: 'center', - display: 'flex', - flex: 1, - minWidth: 0 -}) - -const IconContainer = styled('div')({ - marginRight: 16 -}) - -const MenuToggleLabelContainer = styled('div')({ - overflow: 'hidden' -}) - -const MenuToggleLabel = styled('div')({ - flex: 1, - overflow: 'hidden', - textOverflow: 'ellipsis', - whiteSpace: 'nowrap', - fontSize: 20, - fontWeight: 600, - color: PALETTE.SLATE_900 -}) - -const MenuToggleTitle = styled('div')({ - fontSize: 14, - lineHeight: '16px', - fontWeight: 400, - color: PALETTE.SLATE_900 -}) - -interface Props { - label: string - icon?: React.ReactElement - title?: string -} -const DropdownToggleInner = forwardRef((props: Props, ref: any) => { - const {icon, label, title} = props - return ( - - {icon && {icon}} - - {title && {title}} - {label} - - - ) -}) - -export default DropdownToggleInner diff --git a/packages/client/components/DropdownToggleV2.tsx b/packages/client/components/DropdownToggleV2.tsx deleted file mode 100644 index b7111cd7384..00000000000 --- a/packages/client/components/DropdownToggleV2.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import styled from '@emotion/styled' -import {ExpandMore as ExpandMoreIcon, KeyboardArrowRight} from '@mui/icons-material' -import React, {ReactNode, Ref, forwardRef} from 'react' -import useMenu from '../hooks/useMenu' -import {PALETTE} from '../styles/paletteV3' - -const DropdownIcon = styled('div')<{opened: boolean | undefined}>(({opened}) => ({ - color: PALETTE.SLATE_600, - height: 36, - width: 36, - svg: { - fontSize: 36 - }, - alignSelf: 'center', - transform: opened ? 'rotateX(180deg)' : 'none', - transition: 'transform 0.2s' -})) - -const DropdownBlock = styled('div')<{disabled: boolean | undefined}>(({disabled}) => ({ - background: PALETTE.SLATE_200, - borderRadius: '8px', - cursor: disabled ? 'not-allowed' : 'pointer', - display: 'flex', - fontSize: 14, - lineHeight: '24px', - fontWeight: 600, - userSelect: 'none', - width: '100%', - ':hover': { - backgroundColor: PALETTE.SLATE_300 - }, - padding: 16 -})) - -interface Props { - className?: string - disabled?: boolean - icon?: string - onClick: ReturnType['togglePortal'] - onMouseEnter?: () => void - children: ReactNode - opened?: boolean -} - -const DropdownToggleV2 = forwardRef((props: Props, ref: Ref) => { - const {className, children, icon, onClick, onMouseEnter, disabled, opened} = props - return ( - - {children} - {!disabled && ( - - {icon ? : } - - )} - - ) -}) - -export default DropdownToggleV2 diff --git a/packages/client/components/NewMeetingDropdown.tsx b/packages/client/components/NewMeetingDropdown.tsx deleted file mode 100644 index c27f812c293..00000000000 --- a/packages/client/components/NewMeetingDropdown.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import React, {forwardRef} from 'react' -import useMenu from '../hooks/useMenu' -import DropdownToggleInner from './DropdownToggleInner' -import DropdownToggleV2 from './DropdownToggleV2' - -interface Props { - className?: string - icon?: React.ReactElement - dropdownIcon?: string - label: string - disabled?: boolean - onClick: ReturnType['togglePortal'] - onMouseEnter?: () => void - title?: string - opened?: boolean -} - -const NewMeetingDropdown = forwardRef((props: Props, ref: any) => { - const {className, icon, dropdownIcon, label, disabled, onClick, onMouseEnter, title, opened} = - props - return ( - - - - ) -}) - -export default NewMeetingDropdown diff --git a/packages/client/components/NewMeetingTeamPicker.tsx b/packages/client/components/NewMeetingTeamPicker.tsx index 199499dc2dc..9ce8af3b5bb 100644 --- a/packages/client/components/NewMeetingTeamPicker.tsx +++ b/packages/client/components/NewMeetingTeamPicker.tsx @@ -1,41 +1,25 @@ +import {ExpandMore, LockOpen} from '@mui/icons-material' +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import graphql from 'babel-plugin-relay/macro' import React from 'react' import {useFragment} from 'react-relay' import {NewMeetingTeamPicker_selectedTeam$key} from '~/__generated__/NewMeetingTeamPicker_selectedTeam.graphql' import {NewMeetingTeamPicker_teams$key} from '~/__generated__/NewMeetingTeamPicker_teams.graphql' import useAtmosphere from '../hooks/useAtmosphere' -import {MenuPosition} from '../hooks/useCoords' -import useMenu from '../hooks/useMenu' -import {PortalStatus} from '../hooks/usePortal' -import lazyPreload from '../utils/lazyPreload' +import {PALETTE} from '../styles/paletteV3' +import {Menu} from '../ui/Menu/Menu' import setPreferredTeamId from '../utils/relay/setPreferredTeamId' -import NewMeetingDropdown from './NewMeetingDropdown' import NewMeetingTeamPickerAvatars from './NewMeetingTeamPickerAvatars' -const SelectTeamDropdown = lazyPreload( - () => - import( - /* webpackChunkName: 'SelectTeamDropdown' */ - './SelectTeamDropdown' - ) -) - interface Props { selectedTeamRef: NewMeetingTeamPicker_selectedTeam$key teamsRef: NewMeetingTeamPicker_teams$key onSelectTeam: (teamId: string) => void - positionOverride?: MenuPosition - customPortal?: React.ReactNode + onShareToOrg?: () => void } const NewMeetingTeamPicker = (props: Props) => { - const {selectedTeamRef, teamsRef, onSelectTeam, positionOverride, customPortal} = props - const {togglePortal, menuPortal, originRef, menuProps, portalStatus} = useMenu( - positionOverride ?? MenuPosition.LOWER_RIGHT, - { - isDropdown: true - } - ) + const {selectedTeamRef, teamsRef, onSelectTeam, onShareToOrg} = props const atmosphere = useAtmosphere() @@ -49,6 +33,9 @@ const NewMeetingTeamPicker = (props: Props) => { fragment NewMeetingTeamPicker_selectedTeam on Team { ...NewMeetingTeamPickerAvatars_team name + organization { + name + } } `, selectedTeamRef @@ -66,30 +53,72 @@ const NewMeetingTeamPicker = (props: Props) => { ) const {name} = selectedTeam + return ( - <> - } - label={name} - onClick={togglePortal} - onMouseEnter={SelectTeamDropdown.preload} - disabled={teams.length === 0} - ref={originRef} - title={'Team'} - opened={[PortalStatus.Entering, PortalStatus.Entered].includes(portalStatus)} - /> - {menuPortal( - customPortal ? ( - customPortal + +
+ +
+
+
Team
+
{name}
+
+
+ +
+
+ } + > +
+ {onShareToOrg ? ( +
+
+ This custom activity is private to the {selectedTeam.name} team. +
+
+
+ As a member of the team you can share this activity with other teams at the{' '} + {selectedTeam.organization.name} organization so that they can also use the + activity. +
+ +
) : ( - - ) - )} - + <> + + Select Team: + + +
+ {teams.map((team) => { + return ( + { + handleSelectTeam(team.id) + }} + > + {team.name} + + ) + })} +
+ + )} +
+ ) } diff --git a/packages/client/components/SelectMeetingDropdown.tsx b/packages/client/components/SelectMeetingDropdown.tsx index fde41ff30f1..c7c23b172f2 100644 --- a/packages/client/components/SelectMeetingDropdown.tsx +++ b/packages/client/components/SelectMeetingDropdown.tsx @@ -24,14 +24,14 @@ const SelectMeetingDropdown = (props: Props) => { const meetingCount = meetings.length const label = `${meetingCount} Active ${plural(meetingCount, 'Meeting')}` return ( - <> +
{label} {meetings.map((meeting) => ( ))} - +
) } diff --git a/packages/client/package.json b/packages/client/package.json index 5cfb38cc7cd..89a59fc4d9c 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -76,7 +76,7 @@ "@radix-ui/react-avatar": "^1.0.4", "@radix-ui/react-collapsible": "^1.0.3", "@radix-ui/react-dialog": "^1.0.4", - "@radix-ui/react-dropdown-menu": "^2.0.4", + "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-radio-group": "^1.1.2", "@radix-ui/react-scroll-area": "^1.0.3", "@radix-ui/react-select": "^1.2.2", diff --git a/packages/client/tailwindTheme.ts b/packages/client/tailwindTheme.ts index 8e27c1efdfa..ad30c0d94b7 100644 --- a/packages/client/tailwindTheme.ts +++ b/packages/client/tailwindTheme.ts @@ -168,12 +168,34 @@ export default { opacity: 0, transform: 'scale(0)' } + }, + slideUp: { + from: { + opacity: 0, + transform: 'translateY(10px)' + }, + to: { + opacity: 1, + transform: 'translateY(0)' + } + }, + slideDown: { + from: { + opacity: 0, + transform: 'translateY(-10px)' + }, + to: { + opacity: 1, + transform: 'translateY(0)' + } } }, animation: { overlayShow: 'overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1)', contentShow: 'contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1)', - scaleIn: 'scaleIn 150ms cubic-bezier(0, 0, .2, 1)' + scaleIn: 'scaleIn 150ms cubic-bezier(0, 0, .2, 1)', + slideUp: 'slideUp 200ms cubic-bezier(0, 0, 0.2, 1)', + slideDown: 'slideDown 200ms cubic-bezier(0, 0, 0.2, 1)' } } } as const diff --git a/yarn.lock b/yarn.lock index 7183e30abd7..6345afc23c8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5970,7 +5970,7 @@ "@radix-ui/react-use-callback-ref" "1.0.1" "@radix-ui/react-use-escape-keydown" "1.0.3" -"@radix-ui/react-dropdown-menu@^2.0.4", "@radix-ui/react-dropdown-menu@^2.0.5": +"@radix-ui/react-dropdown-menu@^2.0.5": version "2.0.5" resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.5.tgz#19bf4de8ffa348b4eb6a86842f14eff93d741170" integrity sha512-xdOrZzOTocqqkCkYo8yRPCib5OkTkqN7lqNCdxwPOdE466DOaNl4N8PkUIlsXthQvW5Wwkd+aEmWpfWlBoDPEw== @@ -5984,6 +5984,20 @@ "@radix-ui/react-primitive" "1.0.3" "@radix-ui/react-use-controllable-state" "1.0.1" +"@radix-ui/react-dropdown-menu@^2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz#cdf13c956c5e263afe4e5f3587b3071a25755b63" + integrity sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-menu" "2.0.6" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-use-controllable-state" "1.0.1" + "@radix-ui/react-focus-guards@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz#1ea7e32092216b946397866199d892f71f7f98ad" @@ -6052,6 +6066,31 @@ aria-hidden "^1.1.1" react-remove-scroll "2.5.5" +"@radix-ui/react-menu@2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@radix-ui/react-menu/-/react-menu-2.0.6.tgz#2c9e093c1a5d5daa87304b2a2f884e32288ae79e" + integrity sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA== + dependencies: + "@babel/runtime" "^7.13.10" + "@radix-ui/primitive" "1.0.1" + "@radix-ui/react-collection" "1.0.3" + "@radix-ui/react-compose-refs" "1.0.1" + "@radix-ui/react-context" "1.0.1" + "@radix-ui/react-direction" "1.0.1" + "@radix-ui/react-dismissable-layer" "1.0.5" + "@radix-ui/react-focus-guards" "1.0.1" + "@radix-ui/react-focus-scope" "1.0.4" + "@radix-ui/react-id" "1.0.1" + "@radix-ui/react-popper" "1.1.3" + "@radix-ui/react-portal" "1.0.4" + "@radix-ui/react-presence" "1.0.1" + "@radix-ui/react-primitive" "1.0.3" + "@radix-ui/react-roving-focus" "1.0.4" + "@radix-ui/react-slot" "1.0.2" + "@radix-ui/react-use-callback-ref" "1.0.1" + aria-hidden "^1.1.1" + react-remove-scroll "2.5.5" + "@radix-ui/react-popper@1.1.2": version "1.1.2" resolved "https://registry.yarnpkg.com/@radix-ui/react-popper/-/react-popper-1.1.2.tgz#4c0b96fcd188dc1f334e02dba2d538973ad842e9" From c6a028bc357ee411e42f68e0f5beeddadf6fe6fd Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 26 Jun 2024 12:56:37 -0700 Subject: [PATCH 281/529] chore(rethinkdb): TimelineEvent: Phase 1 (#9871) Signed-off-by: Matt Krick --- .../components/SelectMeetingDropdownItem.tsx | 2 +- .../server/dataloader/customLoaderMakers.ts | 4 +- .../graphql/mutations/archiveTimelineEvent.ts | 11 +++- .../server/graphql/mutations/endCheckIn.ts | 7 ++- .../graphql/mutations/endSprintPoker.ts | 7 ++- .../mutations/helpers/bootstrapNewUser.ts | 21 +++++-- .../mutations/helpers/createTeamAndLeader.ts | 11 ++-- .../helpers/notifications/MSTeamsNotifier.ts | 4 +- .../notifications/MattermostNotifier.ts | 2 +- .../helpers/notifications/SlackNotifier.ts | 4 +- .../mutations/helpers/safeEndRetrospective.ts | 7 ++- .../mutations/helpers/safeEndTeamPrompt.ts | 7 ++- .../1719348524673_TimelineEvent-phase1.ts | 57 +++++++++++++++++++ .../server/postgres/queries/insertUser.ts | 21 ------- .../postgres/queries/src/insertTeamQuery.sql | 30 ---------- .../postgres/queries/src/insertUserQuery.sql | 36 ------------ 16 files changed, 121 insertions(+), 110 deletions(-) create mode 100644 packages/server/postgres/migrations/1719348524673_TimelineEvent-phase1.ts delete mode 100644 packages/server/postgres/queries/insertUser.ts delete mode 100644 packages/server/postgres/queries/src/insertTeamQuery.sql delete mode 100644 packages/server/postgres/queries/src/insertUserQuery.sql diff --git a/packages/client/components/SelectMeetingDropdownItem.tsx b/packages/client/components/SelectMeetingDropdownItem.tsx index b6ee8e6fb42..8d5c9f340d5 100644 --- a/packages/client/components/SelectMeetingDropdownItem.tsx +++ b/packages/client/components/SelectMeetingDropdownItem.tsx @@ -55,7 +55,7 @@ const SelectMeetingDropdownItem = (props: Props) => { history.push(`/meet/${meetingId}`) } //FIXME 6062: change to React.ComponentType - const IconOrSVG = meetingTypeToIcon[meetingType] + const IconOrSVG = meetingTypeToIcon[meetingType]! const meetingPhase = getMeetingPhase(phases) const meetingPhaseLabel = (meetingPhase && phaseLabelLookup[meetingPhase.phaseType]) || 'Complete' diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 0ceba9e5255..1743d3959d9 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -318,7 +318,7 @@ export const meetingSettingsByType = (parent: RootDataLoader) => { keys.forEach((key) => { const {meetingType} = key types[meetingType] = types[meetingType] || [] - types[meetingType].push(key.teamId) + types[meetingType]!.push(key.teamId) }) const entries = Object.entries(types) as [MeetingTypeEnum, string[]][] const resultsByType = await Promise.all( @@ -422,7 +422,7 @@ export const meetingTemplatesByType = (parent: RootDataLoader) => { keys.forEach((key) => { const {meetingType} = key types[meetingType] = types[meetingType] || [] - types[meetingType].push(key.teamId) + types[meetingType]!.push(key.teamId) }) const entries = Object.entries(types) as [MeetingTypeEnum, string[]][] const resultsByType = await Promise.all( diff --git a/packages/server/graphql/mutations/archiveTimelineEvent.ts b/packages/server/graphql/mutations/archiveTimelineEvent.ts index 7a30e9f89b6..f116011eece 100644 --- a/packages/server/graphql/mutations/archiveTimelineEvent.ts +++ b/packages/server/graphql/mutations/archiveTimelineEvent.ts @@ -4,6 +4,7 @@ import TimelineEventCheckinComplete from 'parabol-server/database/types/Timeline import TimelineEventRetroComplete from 'parabol-server/database/types/TimelineEventRetroComplete' import getRethink from '../../database/rethinkDriver' import {TimelineEventEnum} from '../../database/types/TimelineEvent' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -57,7 +58,15 @@ const archiveTimelineEvent = { .get('timelineEventsByMeetingId') .load(meetingId) const eventIds = meetingTimelineEvents.map(({id}) => id) - await r.table('TimelineEvent').getAll(r.args(eventIds)).update({isActive: false}).run() + const pg = getKysely() + await Promise.all([ + pg + .updateTable('TimelineEvent') + .set({isActive: false}) + .where('id', 'in', eventIds) + .execute(), + r.table('TimelineEvent').getAll(r.args(eventIds)).update({isActive: false}).run() + ]) meetingTimelineEvents.map((event) => { const {id: timelineEventId, userId} = event publish( diff --git a/packages/server/graphql/mutations/endCheckIn.ts b/packages/server/graphql/mutations/endCheckIn.ts index 38b63ef12d0..71a0b38313e 100644 --- a/packages/server/graphql/mutations/endCheckIn.ts +++ b/packages/server/graphql/mutations/endCheckIn.ts @@ -11,6 +11,7 @@ import MeetingAction from '../../database/types/MeetingAction' import Task from '../../database/types/Task' import TimelineEventCheckinComplete from '../../database/types/TimelineEventCheckinComplete' import generateUID from '../../generateUID' +import getKysely from '../../postgres/getKysely' import archiveTasksForDB from '../../safeMutations/archiveTasksForDB' import removeSuggestedAction from '../../safeMutations/removeSuggestedAction' import {Logger} from '../../utils/Logger' @@ -244,7 +245,11 @@ export default { }) ) const timelineEventId = events[0]!.id - await r.table('TimelineEvent').insert(events).run() + const pg = getKysely() + await Promise.all([ + pg.insertInto('TimelineEvent').values(events).execute(), + r.table('TimelineEvent').insert(events).run() + ]) if (team.isOnboardTeam) { const teamLeadUserId = await r .table('TeamMember') diff --git a/packages/server/graphql/mutations/endSprintPoker.ts b/packages/server/graphql/mutations/endSprintPoker.ts index 14fa0fbb449..250fc524130 100644 --- a/packages/server/graphql/mutations/endSprintPoker.ts +++ b/packages/server/graphql/mutations/endSprintPoker.ts @@ -7,6 +7,7 @@ import getRethink from '../../database/rethinkDriver' import Meeting from '../../database/types/Meeting' import MeetingPoker from '../../database/types/MeetingPoker' import TimelineEventPokerComplete from '../../database/types/TimelineEventPokerComplete' +import getKysely from '../../postgres/getKysely' import {Logger} from '../../utils/Logger' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isSuperUser, isTeamMember} from '../../utils/authorization' @@ -127,7 +128,11 @@ export default { meetingId }) ) - await r.table('TimelineEvent').insert(events).run() + const pg = getKysely() + await Promise.all([ + pg.insertInto('TimelineEvent').values(events).execute(), + r.table('TimelineEvent').insert(events).run() + ]) const data = { meetingId, diff --git a/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts b/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts index b3a79ed611f..b2100882ef4 100644 --- a/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts +++ b/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts @@ -6,8 +6,8 @@ import SuggestedActionTryTheDemo from '../../../database/types/SuggestedActionTr import TimelineEventJoinedParabol from '../../../database/types/TimelineEventJoinedParabol' import User from '../../../database/types/User' import generateUID from '../../../generateUID' +import getKysely from '../../../postgres/getKysely' import getUsersbyDomain from '../../../postgres/queries/getUsersByDomain' -import insertUser from '../../../postgres/queries/insertUser' import IUser from '../../../postgres/types/IUser' import acceptTeamInvitation from '../../../safeMutations/acceptTeamInvitation' import {analytics} from '../../../utils/analytics/analytics' @@ -57,13 +57,22 @@ const bootstrapNewUser = async ( const hasSAMLURL = !!(await getSAMLURLFromEmail(email, dataLoader, false)) const isQualifiedForAutoJoin = (isVerified || hasSAMLURL) && isCompanyDomain const orgIds = organizations.map(({id}) => id) - + const pg = getKysely() const [teamsWithAutoJoinRes] = await Promise.all([ isQualifiedForAutoJoin ? dataLoader.get('autoJoinTeamsByOrgId').loadMany(orgIds) : [], - insertUser({...newUser, isPatient0, featureFlags: experimentalFlags}), - r({ - event: r.table('TimelineEvent').insert(joinEvent) - }).run() + pg + .with('User', (qc) => + qc.insertInto('User').values({ + ...newUser, + isPatient0, + featureFlags: experimentalFlags, + identities: newUser.identities.map((identity) => JSON.stringify(identity)) + }) + ) + .insertInto('TimelineEvent') + .values(joinEvent) + .execute(), + r.table('TimelineEvent').insert(joinEvent).run() ]) // Identify the user so user properties are set before any events are sent diff --git a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts index 2e972dd3e6f..c46773ded75 100644 --- a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts +++ b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts @@ -4,10 +4,8 @@ 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 getPg from '../../../postgres/getPg' -import {insertTeamQuery} from '../../../postgres/queries/generated/insertTeamQuery' +import getKysely from '../../../postgres/getKysely' import IUser from '../../../postgres/types/IUser' -import catchAndLog from '../../../postgres/utils/catchAndLog' import addTeamIdToTMS from '../../../safeMutations/addTeamIdToTMS' import insertNewTeamMember from '../../../safeMutations/insertNewTeamMember' @@ -38,8 +36,13 @@ export default async function createTeamAndLeader(user: IUser, newTeam: ValidNew orgId }) + const pg = getKysely() await Promise.all([ - catchAndLog(() => insertTeamQuery.run(verifiedTeam, getPg())), + pg + .with('Team', (qc) => qc.insertInto('Team').values(verifiedTeam)) + .insertInto('TimelineEvent') + .values(timelineEvent) + .execute(), // add meeting settings r.table('MeetingSettings').insert(meetingSettings).run(), // denormalize common fields to team member diff --git a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts index 452791fb341..23159529393 100644 --- a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts @@ -92,7 +92,7 @@ export const MSTeamsNotificationHelper: NotificationIntegrationHelper { - identities: AuthIdentity[] -} - -type Diff = T extends U ? never : T - -type RequiredExceptFor = Pick> & - Partial - -type InsertUserQueryParamsWithoutPseudoId = RequiredExceptFor - -const insertUser = async (user: InsertUserQueryParamsWithoutPseudoId) => { - await catchAndLog(() => insertUserQuery.run(user as unknown as IInsertUserQueryParams, getPg())) -} - -export default insertUser diff --git a/packages/server/postgres/queries/src/insertTeamQuery.sql b/packages/server/postgres/queries/src/insertTeamQuery.sql deleted file mode 100644 index e2caacf620c..00000000000 --- a/packages/server/postgres/queries/src/insertTeamQuery.sql +++ /dev/null @@ -1,30 +0,0 @@ -/* - @name insertTeamQuery -*/ -INSERT INTO "Team" ( - "id", - "autoJoin", - "name", - "createdAt", - "createdBy", - "isArchived", - "isPaid", - "lastMeetingType", - "tier", - "orgId", - "isOnboardTeam", - "updatedAt" -) VALUES ( - :id, - :autoJoin, - :name, - :createdAt, - :createdBy, - :isArchived, - :isPaid, - :lastMeetingType, - :tier, - :orgId, - :isOnboardTeam, - :updatedAt -); diff --git a/packages/server/postgres/queries/src/insertUserQuery.sql b/packages/server/postgres/queries/src/insertUserQuery.sql deleted file mode 100644 index 6833ba3dc6d..00000000000 --- a/packages/server/postgres/queries/src/insertUserQuery.sql +++ /dev/null @@ -1,36 +0,0 @@ -/* - @name insertUserQuery -*/ -INSERT INTO "User" ( - "id", - "email", - "createdAt", - "updatedAt", - "inactive", - "lastSeenAt", - "preferredName", - "tier", - "picture", - "tms", - "featureFlags", - "lastSeenAtURLs", - "pseudoId", - "identities", - "isPatient0" -) VALUES ( - :id, - :email, - :createdAt, - :updatedAt, - :inactive, - :lastSeenAt, - :preferredName, - :tier, - :picture, - :tms, - :featureFlags, - :lastSeenAtURLs, - :pseudoId, - :identities, - :isPatient0 -); From a966b0adf89f453ace1bbc0bfd70f511f27c59b7 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 14:01:31 -0700 Subject: [PATCH 282/529] chore(release): release v7.37.4 (#9878) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 14 ++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 26 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 1c5a1e7ed78..b429059c575 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.37.3" + ".": "7.37.4" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 26df6611a79..b20e4030b0b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.37.4](https://github.com/ParabolInc/parabol/compare/v7.37.3...v7.37.4) (2024-06-26) + + +### Fixed + +* Don't reset failed embedding jobs ([#9877](https://github.com/ParabolInc/parabol/issues/9877)) ([882443c](https://github.com/ParabolInc/parabol/commit/882443c60c80ddc74e45b9ba7a375db43a0d4494)) +* refactor new meeting team dropdown ([#9679](https://github.com/ParabolInc/parabol/issues/9679)) ([0300ce5](https://github.com/ParabolInc/parabol/commit/0300ce5241e2b4dee848ad5978e97e1c802510cd)) + + +### Changed + +* **rethinkdb:** RetroReflection: Phase 3 ([#9867](https://github.com/ParabolInc/parabol/issues/9867)) ([7b8f505](https://github.com/ParabolInc/parabol/commit/7b8f50549df0b1e1251fe8275c41c602d072e441)) +* **rethinkdb:** TimelineEvent: Phase 1 ([#9871](https://github.com/ParabolInc/parabol/issues/9871)) ([c6a028b](https://github.com/ParabolInc/parabol/commit/c6a028bc357ee411e42f68e0f5beeddadf6fe6fd)) + ## [7.37.3](https://github.com/ParabolInc/parabol/compare/v7.37.2...v7.37.3) (2024-06-25) diff --git a/package.json b/package.json index d8382f5c9b0..97b8fcf8299 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.37.3", + "version": "7.37.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index a1ed96da53d..b9963a162cd 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.37.3", + "version": "7.37.4", "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.37.3" + "parabol-server": "7.37.4" } } diff --git a/packages/client/package.json b/packages/client/package.json index 89a59fc4d9c..0275f5b71e9 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.37.3", + "version": "7.37.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index bad6dff56d3..c3f2f30950f 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.37.3", + "version": "7.37.4", "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 81cb16995ae..6e05436f578 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.37.3", + "version": "7.37.4", "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.37.3", - "parabol-server": "7.37.3", + "parabol-client": "7.37.4", + "parabol-server": "7.37.4", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 93e5817014a..f9a9f1298a3 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.37.3", + "version": "7.37.4", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 5943d5d41f2..d0d7108d1a8 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.37.3", + "version": "7.37.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.37.3", + "parabol-client": "7.37.4", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 87363fa4168ac97869bd5b71571c7ecc5c7fb903 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 26 Jun 2024 15:21:31 -0700 Subject: [PATCH 283/529] fix: handle sql null equalities (#9884) Signed-off-by: Matt Krick --- .../helpers/updateReflectionLocation/updateSmartGroupTitle.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/graphql/mutations/helpers/updateReflectionLocation/updateSmartGroupTitle.ts b/packages/server/graphql/mutations/helpers/updateReflectionLocation/updateSmartGroupTitle.ts index 90aab3da6cc..d21c7dd713d 100644 --- a/packages/server/graphql/mutations/helpers/updateReflectionLocation/updateSmartGroupTitle.ts +++ b/packages/server/graphql/mutations/helpers/updateReflectionLocation/updateSmartGroupTitle.ts @@ -7,7 +7,7 @@ const updateSmartGroupTitle = async (reflectionGroupId: string, smartTitle: stri .updateTable('RetroReflectionGroup') .set({ smartTitle, - title: sql`CASE WHEN "smartTitle" = "title" THEN ${smartTitle} ELSE "title" END` + title: sql`CASE WHEN "smartTitle" = "title" OR "title" IS NULL THEN ${smartTitle} ELSE "title" END` }) .where('id', '=', reflectionGroupId) .execute() From 1c8b116c15818f328e448467b789c9f3f3c11c12 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 26 Jun 2024 15:23:55 -0700 Subject: [PATCH 284/529] chore(rethinkdb): TimelineEvent: Phase 2 (#9875) Signed-off-by: Matt Krick --- .../1719351990570_TimelineEvent-phase2.ts | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 packages/server/postgres/migrations/1719351990570_TimelineEvent-phase2.ts diff --git a/packages/server/postgres/migrations/1719351990570_TimelineEvent-phase2.ts b/packages/server/postgres/migrations/1719351990570_TimelineEvent-phase2.ts new file mode 100644 index 00000000000..5f70015a119 --- /dev/null +++ b/packages/server/postgres/migrations/1719351990570_TimelineEvent-phase2.ts @@ -0,0 +1,103 @@ +import {Kysely, PostgresDialect} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + try { + console.log('Adding index') + await r + .table('TimelineEvent') + .indexCreate('createdAtId', (row: any) => [row('createdAt'), row('id')]) + .run() + await r.table('TimelineEvent').indexWait().run() + } catch { + // index already exists + } + console.log('Adding index complete') + const MAX_PG_PARAMS = 65545 + const PG_COLS = [ + 'id', + 'createdAt', + 'interactionCount', + 'seenCount', + 'type', + 'userId', + 'teamId', + 'orgId', + 'meetingId', + 'isActive' + ] as const + type TimelineEvent = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + + let curUpdatedAt = r.minval + let curId = r.minval + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, curUpdatedAt, curId) + const rowsToInsert = (await r + .table('TimelineEvent') + .between([curUpdatedAt, curId], [r.maxval, r.maxval], { + index: 'createdAtId', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'createdAtId'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as TimelineEvent[] + + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curUpdatedAt = lastRow.createdAt + curId = lastRow.id + try { + await pg + .insertInto('TimelineEvent') + .values(rowsToInsert) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + await Promise.all( + rowsToInsert.map(async (row) => { + try { + const res = await pg + .insertInto('TimelineEvent') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_userId' || e.constraint === 'fk_teamId') { + // console.log(`Skipping ${row.id} because it has no user/team`) + return + } + console.log(e, row) + } + }) + ) + } + } +} + +export async function down() { + await connectRethinkDB() + try { + await r.table('TimelineEvent').indexDrop('createdAtId').run() + } catch { + // index already dropped + } + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await pg.deleteFrom('TimelineEvent').execute() +} From f1697cd42f5029117f8acb1d5021771df3a447d0 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 26 Jun 2024 15:30:22 -0700 Subject: [PATCH 285/529] chore(release): release v7.37.5 (#9885) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b429059c575..08b26b3f567 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.37.4" + ".": "7.37.5" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b20e4030b0b..f044b5e8aaa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.37.5](https://github.com/ParabolInc/parabol/compare/v7.37.4...v7.37.5) (2024-06-26) + + +### Fixed + +* handle sql null equalities ([#9884](https://github.com/ParabolInc/parabol/issues/9884)) ([87363fa](https://github.com/ParabolInc/parabol/commit/87363fa4168ac97869bd5b71571c7ecc5c7fb903)) + + +### Changed + +* **rethinkdb:** TimelineEvent: Phase 2 ([#9875](https://github.com/ParabolInc/parabol/issues/9875)) ([1c8b116](https://github.com/ParabolInc/parabol/commit/1c8b116c15818f328e448467b789c9f3f3c11c12)) + ## [7.37.4](https://github.com/ParabolInc/parabol/compare/v7.37.3...v7.37.4) (2024-06-26) diff --git a/package.json b/package.json index 97b8fcf8299..eaa1e975b1a 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.37.4", + "version": "7.37.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index b9963a162cd..0b11e11d2a0 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.37.4", + "version": "7.37.5", "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.37.4" + "parabol-server": "7.37.5" } } diff --git a/packages/client/package.json b/packages/client/package.json index 0275f5b71e9..7782ecc67d3 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.37.4", + "version": "7.37.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index c3f2f30950f..b3d6394345c 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.37.4", + "version": "7.37.5", "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 6e05436f578..3e3f90ce78e 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.37.4", + "version": "7.37.5", "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.37.4", - "parabol-server": "7.37.4", + "parabol-client": "7.37.5", + "parabol-server": "7.37.5", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index f9a9f1298a3..fe30753156d 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.37.4", + "version": "7.37.5", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index d0d7108d1a8..a3957da42c2 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.37.4", + "version": "7.37.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.37.4", + "parabol-client": "7.37.5", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From b4231b5e249b853c49bf2ac7bc736b7e47447956 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Thu, 27 Jun 2024 09:57:39 +0100 Subject: [PATCH 286/529] fix: can scroll public teams modal (#9880) --- packages/client/components/DashNavList/PublicTeamsModal.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/components/DashNavList/PublicTeamsModal.tsx b/packages/client/components/DashNavList/PublicTeamsModal.tsx index 169785c566f..a3e77fe6b57 100644 --- a/packages/client/components/DashNavList/PublicTeamsModal.tsx +++ b/packages/client/components/DashNavList/PublicTeamsModal.tsx @@ -32,7 +32,7 @@ const PublicTeamsModal = (props: Props) => { return ( - + {`${publicTeamsCount} ${plural(publicTeamsCount, 'Public Team', 'Public Teams')}`} Request to join as a Team Member on any public teams at{' '} From edb7e5882a0fcf0be6bae7fc21fa174b2d9b6195 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Thu, 27 Jun 2024 15:05:17 +0100 Subject: [PATCH 287/529] fix: download pdf shows multiple pages (#9889) Co-authored-by: Georg Bremer --- .../components/SummaryEmail/ExportToCSV.tsx | 28 ++++----- .../EmailReflectionCard.tsx | 1 + .../MeetingSummaryEmail/RetroTopic.tsx | 2 +- .../MeetingSummaryEmail/SummarySheet.tsx | 63 ++++++++++++++++++- 4 files changed, 75 insertions(+), 19 deletions(-) diff --git a/packages/client/modules/email/components/SummaryEmail/ExportToCSV.tsx b/packages/client/modules/email/components/SummaryEmail/ExportToCSV.tsx index fc69ed57121..4a9e733998f 100644 --- a/packages/client/modules/email/components/SummaryEmail/ExportToCSV.tsx +++ b/packages/client/modules/email/components/SummaryEmail/ExportToCSV.tsx @@ -351,21 +351,19 @@ const ExportToCSV = (props: Props) => { const {emailCSVUrl, referrer, corsOptions} = props return ( - <> -
- - {label} - {label} - -
+ + {label} + {label} + +
- From ce7e8bb6aea40d053e707b40c2b9b5bfdc981702 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Thu, 27 Jun 2024 16:17:56 +0200 Subject: [PATCH 288/529] fix: User can change team in Activity Library (#9893) --- .../NewMeetingActionsCurrentMeetings.tsx | 2 +- .../components/NewMeetingTeamPicker.tsx | 93 ++++++++++--------- 2 files changed, 49 insertions(+), 46 deletions(-) diff --git a/packages/client/components/NewMeetingActionsCurrentMeetings.tsx b/packages/client/components/NewMeetingActionsCurrentMeetings.tsx index 6658f3c3b4b..4c75e29d853 100644 --- a/packages/client/components/NewMeetingActionsCurrentMeetings.tsx +++ b/packages/client/components/NewMeetingActionsCurrentMeetings.tsx @@ -57,7 +57,7 @@ const NewMeetingActionsCurrentMeetings = (props: Props) => { } > - + diff --git a/packages/client/components/NewMeetingTeamPicker.tsx b/packages/client/components/NewMeetingTeamPicker.tsx index 9ce8af3b5bb..3fe1a6b1ada 100644 --- a/packages/client/components/NewMeetingTeamPicker.tsx +++ b/packages/client/components/NewMeetingTeamPicker.tsx @@ -8,6 +8,7 @@ import {NewMeetingTeamPicker_teams$key} from '~/__generated__/NewMeetingTeamPick import useAtmosphere from '../hooks/useAtmosphere' import {PALETTE} from '../styles/paletteV3' import {Menu} from '../ui/Menu/Menu' +import {MenuContent} from '../ui/Menu/MenuContent' import setPreferredTeamId from '../utils/relay/setPreferredTeamId' import NewMeetingTeamPickerAvatars from './NewMeetingTeamPickerAvatars' @@ -72,52 +73,54 @@ const NewMeetingTeamPicker = (props: Props) => { } > -
- {onShareToOrg ? ( -
-
- This custom activity is private to the {selectedTeam.name} team. + +
+ {onShareToOrg ? ( +
+
+ This custom activity is private to the {selectedTeam.name} team. +
+
+
+ As a member of the team you can share this activity with other teams at the{' '} + {selectedTeam.organization.name} organization so that they can also use the + activity. +
+
-
-
- As a member of the team you can share this activity with other teams at the{' '} - {selectedTeam.organization.name} organization so that they can also use the - activity. -
- -
- ) : ( - <> - - Select Team: - - -
- {teams.map((team) => { - return ( - { - handleSelectTeam(team.id) - }} - > - {team.name} - - ) - })} -
- - )} -
+ ) : ( + <> + + Select Team: + + +
+ {teams.map((team) => { + return ( + { + handleSelectTeam(team.id) + }} + > + {team.name} + + ) + })} +
+ + )} +
+ ) } From 6796d8c43e3bb545ba72bf5314d267e68eebc33e Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 16:23:55 +0200 Subject: [PATCH 289/529] chore(release): release v7.37.6 (#9888) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 9 +++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 21 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 08b26b3f567..10a5df74e1a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.37.5" + ".": "7.37.6" } diff --git a/CHANGELOG.md b/CHANGELOG.md index f044b5e8aaa..d9dd7494367 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.37.6](https://github.com/ParabolInc/parabol/compare/v7.37.5...v7.37.6) (2024-06-27) + + +### Fixed + +* can scroll public teams modal ([#9880](https://github.com/ParabolInc/parabol/issues/9880)) ([b4231b5](https://github.com/ParabolInc/parabol/commit/b4231b5e249b853c49bf2ac7bc736b7e47447956)) +* download pdf shows multiple pages ([#9889](https://github.com/ParabolInc/parabol/issues/9889)) ([edb7e58](https://github.com/ParabolInc/parabol/commit/edb7e5882a0fcf0be6bae7fc21fa174b2d9b6195)) +* User can change team in Activity Library ([#9893](https://github.com/ParabolInc/parabol/issues/9893)) ([ce7e8bb](https://github.com/ParabolInc/parabol/commit/ce7e8bb6aea40d053e707b40c2b9b5bfdc981702)) + ## [7.37.5](https://github.com/ParabolInc/parabol/compare/v7.37.4...v7.37.5) (2024-06-26) diff --git a/package.json b/package.json index eaa1e975b1a..e247eb15498 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.37.5", + "version": "7.37.6", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 0b11e11d2a0..86c0006f54d 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.37.5", + "version": "7.37.6", "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.37.5" + "parabol-server": "7.37.6" } } diff --git a/packages/client/package.json b/packages/client/package.json index 7782ecc67d3..c218236cb4d 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.37.5", + "version": "7.37.6", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index b3d6394345c..30aa64b6536 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.37.5", + "version": "7.37.6", "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 3e3f90ce78e..39fb6a3563d 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.37.5", + "version": "7.37.6", "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.37.5", - "parabol-server": "7.37.5", + "parabol-client": "7.37.6", + "parabol-server": "7.37.6", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index fe30753156d..71460560979 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.37.5", + "version": "7.37.6", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index a3957da42c2..314625b2803 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.37.5", + "version": "7.37.6", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.37.5", + "parabol-client": "7.37.6", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 77b56ad296bd3ff69f2a8f3440a34a7b5f34c0cf Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 27 Jun 2024 12:56:03 -0700 Subject: [PATCH 290/529] chore(rethinkdb): TimelineEvent: Phase 3 (#9876) Signed-off-by: Matt Krick --- packages/server/database/rethinkDriver.ts | 5 --- .../dataloader/foreignKeyLoaderMakers.ts | 14 ++++++++ .../dataloader/primaryKeyLoaderMakers.ts | 4 +++ .../rethinkForeignKeyLoaderMakers.ts | 14 -------- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../graphql/mutations/archiveTimelineEvent.ts | 15 +++----- .../server/graphql/mutations/endCheckIn.ts | 5 +-- .../graphql/mutations/endSprintPoker.ts | 5 +-- .../mutations/helpers/bootstrapNewUser.ts | 3 +- .../mutations/helpers/createTeamAndLeader.ts | 1 - .../mutations/helpers/safeEndRetrospective.ts | 5 +-- .../mutations/helpers/safeEndTeamPrompt.ts | 5 +-- .../private/mutations/backupOrganization.ts | 12 +------ .../private/mutations/hardDeleteUser.ts | 6 ---- packages/server/graphql/types/User.ts | 35 +++++++++---------- 15 files changed, 46 insertions(+), 84 deletions(-) diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index d66cde79213..d88791af467 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -36,7 +36,6 @@ import SuggestedActionTryTheDemo from './types/SuggestedActionTryTheDemo' import Task from './types/Task' import TemplateDimension from './types/TemplateDimension' import TemplateScale from './types/TemplateScale' -import TimelineEvent from './types/TimelineEvent' export type RethinkSchema = { AgendaItem: { @@ -187,10 +186,6 @@ export type RethinkSchema = { type: TemplateScale index: 'teamId' } - TimelineEvent: { - type: TimelineEvent - index: 'userIdCreatedAt' | 'meetingId' - } } export type DBType = { diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index dc57123c89a..9bfccba0e94 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -60,3 +60,17 @@ export const retroReflectionsByGroupId = foreignKeyLoaderMaker( .execute() } ) + +export const timelineEventsByMeetingId = foreignKeyLoaderMaker( + 'timelineEvents', + 'meetingId', + async (meetingIds) => { + const pg = getKysely() + return pg + .selectFrom('TimelineEvent') + .selectAll() + .where('meetingId', 'in', meetingIds) + .where('isActive', '=', true) + .execute() + } +) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index a3f096efbda..dcb6eb8ac0a 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -53,3 +53,7 @@ export const selectRetroReflections = () => export const retroReflections = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectRetroReflections().where('id', 'in', ids).execute() }) + +export const timelineEvents = primaryKeyLoaderMaker((ids: readonly string[]) => { + return getKysely().selectFrom('TimelineEvent').selectAll().where('id', 'in', ids).execute() +}) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index 24a4abe50d0..461c58dd606 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -1,5 +1,3 @@ -import TimelineEventCheckinComplete from 'parabol-server/database/types/TimelineEventCheckinComplete' -import TimelineEventRetroComplete from 'parabol-server/database/types/TimelineEventRetroComplete' import getRethink from '../database/rethinkDriver' import {RDatum} from '../database/stricterR' import RethinkForeignKeyLoaderMaker from './RethinkForeignKeyLoaderMaker' @@ -182,18 +180,6 @@ export const templateDimensionsByTemplateId = new RethinkForeignKeyLoaderMaker( ) } ) -export const timelineEventsByMeetingId = new RethinkForeignKeyLoaderMaker( - 'timelineEvents', - 'meetingId', - async (meetingIds) => { - const r = await getRethink() - return r - .table('TimelineEvent') - .getAll(r.args(meetingIds), {index: 'meetingId'}) - .filter({isActive: true}) - .run() as Promise - } -) export const slackAuthByUserId = new RethinkForeignKeyLoaderMaker( 'slackAuths', diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index 1a4efd5417a..d5dbd237079 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -23,4 +23,3 @@ export const tasks = new RethinkPrimaryKeyLoaderMaker('Task') export const teamMembers = new RethinkPrimaryKeyLoaderMaker('TeamMember') export const teamInvitations = new RethinkPrimaryKeyLoaderMaker('TeamInvitation') export const templateDimensions = new RethinkPrimaryKeyLoaderMaker('TemplateDimension') -export const timelineEvents = new RethinkPrimaryKeyLoaderMaker('TimelineEvent') diff --git a/packages/server/graphql/mutations/archiveTimelineEvent.ts b/packages/server/graphql/mutations/archiveTimelineEvent.ts index f116011eece..bbada88ed8b 100644 --- a/packages/server/graphql/mutations/archiveTimelineEvent.ts +++ b/packages/server/graphql/mutations/archiveTimelineEvent.ts @@ -2,7 +2,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import TimelineEventCheckinComplete from 'parabol-server/database/types/TimelineEventCheckinComplete' import TimelineEventRetroComplete from 'parabol-server/database/types/TimelineEventRetroComplete' -import getRethink from '../../database/rethinkDriver' import {TimelineEventEnum} from '../../database/types/TimelineEvent' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' @@ -25,7 +24,6 @@ const archiveTimelineEvent = { {timelineEventId}: {timelineEventId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const viewerId = getUserId(authToken) @@ -59,14 +57,11 @@ const archiveTimelineEvent = { .load(meetingId) const eventIds = meetingTimelineEvents.map(({id}) => id) const pg = getKysely() - await Promise.all([ - pg - .updateTable('TimelineEvent') - .set({isActive: false}) - .where('id', 'in', eventIds) - .execute(), - r.table('TimelineEvent').getAll(r.args(eventIds)).update({isActive: false}).run() - ]) + await pg + .updateTable('TimelineEvent') + .set({isActive: false}) + .where('id', 'in', eventIds) + .execute() meetingTimelineEvents.map((event) => { const {id: timelineEventId, userId} = event publish( diff --git a/packages/server/graphql/mutations/endCheckIn.ts b/packages/server/graphql/mutations/endCheckIn.ts index 71a0b38313e..c97e2f7cd03 100644 --- a/packages/server/graphql/mutations/endCheckIn.ts +++ b/packages/server/graphql/mutations/endCheckIn.ts @@ -246,10 +246,7 @@ export default { ) const timelineEventId = events[0]!.id const pg = getKysely() - await Promise.all([ - pg.insertInto('TimelineEvent').values(events).execute(), - r.table('TimelineEvent').insert(events).run() - ]) + await pg.insertInto('TimelineEvent').values(events).execute() if (team.isOnboardTeam) { const teamLeadUserId = await r .table('TeamMember') diff --git a/packages/server/graphql/mutations/endSprintPoker.ts b/packages/server/graphql/mutations/endSprintPoker.ts index 250fc524130..267aa3eb4cf 100644 --- a/packages/server/graphql/mutations/endSprintPoker.ts +++ b/packages/server/graphql/mutations/endSprintPoker.ts @@ -129,10 +129,7 @@ export default { }) ) const pg = getKysely() - await Promise.all([ - pg.insertInto('TimelineEvent').values(events).execute(), - r.table('TimelineEvent').insert(events).run() - ]) + await pg.insertInto('TimelineEvent').values(events).execute() const data = { meetingId, diff --git a/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts b/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts index b2100882ef4..9fb461504f6 100644 --- a/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts +++ b/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts @@ -71,8 +71,7 @@ const bootstrapNewUser = async ( ) .insertInto('TimelineEvent') .values(joinEvent) - .execute(), - r.table('TimelineEvent').insert(joinEvent).run() + .execute() ]) // Identify the user so user properties are set before any events are sent diff --git a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts index c46773ded75..f76a2540d27 100644 --- a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts +++ b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts @@ -47,7 +47,6 @@ export default async function createTeamAndLeader(user: IUser, newTeam: ValidNew r.table('MeetingSettings').insert(meetingSettings).run(), // denormalize common fields to team member insertNewTeamMember(user, teamId), - r.table('TimelineEvent').insert(timelineEvent).run(), addTeamIdToTMS(userId, teamId) ]) } diff --git a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts index 0467adf031b..a76ba6fe7c8 100644 --- a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts @@ -165,10 +165,7 @@ const safeEndRetrospective = async ({ ) const timelineEventId = events[0]!.id const pg = getKysely() - await Promise.all([ - pg.insertInto('TimelineEvent').values(events).execute(), - r.table('TimelineEvent').insert(events).run() - ]) + await pg.insertInto('TimelineEvent').values(events).execute() if (team.isOnboardTeam) { const teamLeadUserId = await r diff --git a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts index 57cc38f293f..e88ed41daf1 100644 --- a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts +++ b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts @@ -104,10 +104,7 @@ const safeEndTeamPrompt = async ({ ) const timelineEventId = events[0]!.id const pg = getKysely() - await Promise.all([ - pg.insertInto('TimelineEvent').values(events).execute(), - r.table('TimelineEvent').insert(events).run() - ]) + await pg.insertInto('TimelineEvent').values(events).execute() summarizeTeamPrompt(meeting, context) analytics.teamPromptEnd(completedTeamPrompt, meetingMembers, responses, dataLoader) checkTeamsLimit(team.orgId, dataLoader) diff --git a/packages/server/graphql/private/mutations/backupOrganization.ts b/packages/server/graphql/private/mutations/backupOrganization.ts index 4a2687025e0..cfc321db1ad 100644 --- a/packages/server/graphql/private/mutations/backupOrganization.ts +++ b/packages/server/graphql/private/mutations/backupOrganization.ts @@ -256,17 +256,7 @@ const backupOrganization: MutationResolvers['backupOrganization'] = async (_sour r.or(row('teamId').default(null).eq(null), r(teamIds).contains(row('teamId'))) ) .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('SuggestedAction').insert(items)), - timelineEvent: ( - r - .table('TimelineEvent') - .filter((row: RDatum) => r(userIds).contains(row('userId'))) as any - ) - .filter((row: RValue) => - r.branch(row('teamId'), r(teamIds).contains(row('teamId')), true) - ) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('TimelineEvent').insert(items)) + .do((items: RValue) => r.db(DESTINATION).table('SuggestedAction').insert(items)) }) }), meetingIds: r diff --git a/packages/server/graphql/private/mutations/hardDeleteUser.ts b/packages/server/graphql/private/mutations/hardDeleteUser.ts index da6a190260c..22d3084aba1 100644 --- a/packages/server/graphql/private/mutations/hardDeleteUser.ts +++ b/packages/server/graphql/private/mutations/hardDeleteUser.ts @@ -117,12 +117,6 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( .getAll(r.args(teamIds), {index: 'teamId'}) .filter((row: RValue) => row('createdBy').eq(userIdToDelete)) .delete(), - timelineEvent: r - .table('TimelineEvent') - .between([userIdToDelete, r.minval], [userIdToDelete, r.maxval], { - index: 'userIdCreatedAt' - }) - .delete(), agendaItem: r .table('AgendaItem') .getAll(r.args(teamIds), {index: 'teamId'}) diff --git a/packages/server/graphql/types/User.ts b/packages/server/graphql/types/User.ts index f26db140ccb..68147575957 100644 --- a/packages/server/graphql/types/User.ts +++ b/packages/server/graphql/types/User.ts @@ -15,13 +15,11 @@ import { MAX_RESULT_GROUP_SIZE } from '../../../client/utils/constants' import groupReflections from '../../../client/utils/smartGroup/groupReflections' -import getRethink from '../../database/rethinkDriver' -import {RDatum} from '../../database/stricterR' import MeetingMemberType from '../../database/types/MeetingMember' import OrganizationType from '../../database/types/Organization' import OrganizationUserType from '../../database/types/OrganizationUser' import SuggestedActionType from '../../database/types/SuggestedAction' -import TimelineEvent from '../../database/types/TimelineEvent' +import getKysely from '../../postgres/getKysely' import {getUserId, isSuperUser, isTeamMember} from '../../utils/authorization' import getMonthlyStreak from '../../utils/getMonthlyStreak' import getRedis from '../../utils/getRedis' @@ -217,7 +215,6 @@ const User: GraphQLObjectType = new GraphQLObjectType { - const r = await getRethink() const viewerId = getUserId(authToken) // VALIDATE @@ -244,21 +241,23 @@ const User: GraphQLObjectType = new GraphQLObjectType) => - eventTypes ? r.expr(eventTypes).contains(t('type')) : true - ) - .filter((t: RDatum) => r.expr(validTeamIds).contains(t('teamId'))) - .orderBy(r.desc('createdAt')) + const dbAfter = after ? new Date(after) : new Date('3000-01-01') + const minVal = new Date(0) + + const pg = getKysely() + const events = await pg + .selectFrom('TimelineEvent') + .selectAll() + .where('userId', '=', viewerId) + .where('createdAt', '>', minVal) + .where('createdAt', '<=', dbAfter) + .where('isActive', '=', true) + .where('teamId', 'in', validTeamIds) + .$if(!!eventTypes, (db) => db.where('type', 'in', eventTypes)) + .orderBy('createdAt') .limit(first + 1) - .coerceTo('array') - .run() + .execute() + const edges = events.slice(0, first).map((node) => ({ cursor: node.createdAt, node From 1b0fcef83a71457df7c39cb21358ae13f32506f2 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 13:36:46 -0700 Subject: [PATCH 291/529] chore(release): release v7.37.7 (#9896) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 10a5df74e1a..fb4ab51b1c3 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.37.6" + ".": "7.37.7" } diff --git a/CHANGELOG.md b/CHANGELOG.md index d9dd7494367..b7101c113c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.37.7](https://github.com/ParabolInc/parabol/compare/v7.37.6...v7.37.7) (2024-06-27) + + +### Changed + +* **rethinkdb:** TimelineEvent: Phase 3 ([#9876](https://github.com/ParabolInc/parabol/issues/9876)) ([77b56ad](https://github.com/ParabolInc/parabol/commit/77b56ad296bd3ff69f2a8f3440a34a7b5f34c0cf)) + ## [7.37.6](https://github.com/ParabolInc/parabol/compare/v7.37.5...v7.37.6) (2024-06-27) diff --git a/package.json b/package.json index e247eb15498..6ec7dbedaee 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.37.6", + "version": "7.37.7", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 86c0006f54d..291b5e6edbd 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.37.6", + "version": "7.37.7", "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.37.6" + "parabol-server": "7.37.7" } } diff --git a/packages/client/package.json b/packages/client/package.json index c218236cb4d..b4a9629b232 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.37.6", + "version": "7.37.7", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 30aa64b6536..f7b035102a2 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.37.6", + "version": "7.37.7", "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 39fb6a3563d..5f707425ce7 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.37.6", + "version": "7.37.7", "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.37.6", - "parabol-server": "7.37.6", + "parabol-client": "7.37.7", + "parabol-server": "7.37.7", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 71460560979..1983eb3a6fa 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.37.6", + "version": "7.37.7", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 314625b2803..c8317835529 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.37.6", + "version": "7.37.7", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.37.6", + "parabol-client": "7.37.7", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 9f5f38c250fbb1d0af4aaed8526c19f0fc6fd111 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 27 Jun 2024 14:09:31 -0700 Subject: [PATCH 292/529] fix: timeline ordering (#9898) Signed-off-by: Matt Krick --- packages/server/graphql/types/User.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/server/graphql/types/User.ts b/packages/server/graphql/types/User.ts index 68147575957..78b6b2380dd 100644 --- a/packages/server/graphql/types/User.ts +++ b/packages/server/graphql/types/User.ts @@ -249,15 +249,13 @@ const User: GraphQLObjectType = new GraphQLObjectType', minVal) - .where('createdAt', '<=', dbAfter) + .where((eb) => eb.between('createdAt', minVal, dbAfter)) .where('isActive', '=', true) .where('teamId', 'in', validTeamIds) .$if(!!eventTypes, (db) => db.where('type', 'in', eventTypes)) - .orderBy('createdAt') + .orderBy('createdAt', 'desc') .limit(first + 1) .execute() - const edges = events.slice(0, first).map((node) => ({ cursor: node.createdAt, node From cb20ddd38dc41cff1c0647dc438889adbba77721 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 27 Jun 2024 14:16:33 -0700 Subject: [PATCH 293/529] chore(release): release v7.37.8 (#9899) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index fb4ab51b1c3..6bdf9ff88da 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.37.7" + ".": "7.37.8" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b7101c113c2..79484a06a85 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.37.8](https://github.com/ParabolInc/parabol/compare/v7.37.7...v7.37.8) (2024-06-27) + + +### Fixed + +* timeline ordering ([#9898](https://github.com/ParabolInc/parabol/issues/9898)) ([9f5f38c](https://github.com/ParabolInc/parabol/commit/9f5f38c250fbb1d0af4aaed8526c19f0fc6fd111)) + ## [7.37.7](https://github.com/ParabolInc/parabol/compare/v7.37.6...v7.37.7) (2024-06-27) diff --git a/package.json b/package.json index 6ec7dbedaee..8d0e796e8f0 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.37.7", + "version": "7.37.8", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 291b5e6edbd..a88696f59fd 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.37.7", + "version": "7.37.8", "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.37.7" + "parabol-server": "7.37.8" } } diff --git a/packages/client/package.json b/packages/client/package.json index b4a9629b232..64822b443f4 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.37.7", + "version": "7.37.8", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index f7b035102a2..ccb4172b3cd 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.37.7", + "version": "7.37.8", "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 5f707425ce7..1aaf90bc810 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.37.7", + "version": "7.37.8", "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.37.7", - "parabol-server": "7.37.7", + "parabol-client": "7.37.8", + "parabol-server": "7.37.8", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 1983eb3a6fa..16caa3e64cd 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.37.7", + "version": "7.37.8", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index c8317835529..1ec29f34195 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.37.7", + "version": "7.37.8", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", "oy-vey": "^0.12.1", - "parabol-client": "7.37.7", + "parabol-client": "7.37.8", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 7e90ac2d1ba2144271779982272160c7c1b0df33 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Fri, 28 Jun 2024 19:40:15 +0100 Subject: [PATCH 294/529] chore: remove contact us message for team users that want to downgrade (#9903) --- .../OrgBilling/OrgBillingDangerZone.tsx | 53 +++++++++++-------- 1 file changed, 30 insertions(+), 23 deletions(-) diff --git a/packages/client/modules/userDashboard/components/OrgBilling/OrgBillingDangerZone.tsx b/packages/client/modules/userDashboard/components/OrgBilling/OrgBillingDangerZone.tsx index 4a8eafe5035..6b9761f088a 100644 --- a/packages/client/modules/userDashboard/components/OrgBilling/OrgBillingDangerZone.tsx +++ b/packages/client/modules/userDashboard/components/OrgBilling/OrgBillingDangerZone.tsx @@ -6,6 +6,7 @@ import {useFragment} from 'react-relay' import {OrgBillingDangerZone_organization$key} from '~/__generated__/OrgBillingDangerZone_organization.graphql' import ArchiveOrganization from '~/modules/teamDashboard/components/ArchiveTeam/ArchiveOrganization' import Panel from '../../../../components/Panel/Panel' +import useRouter from '../../../../hooks/useRouter' import {PALETTE} from '../../../../styles/paletteV3' import {ElementWidth, Layout} from '../../../../types/constEnums' @@ -24,25 +25,6 @@ const PanelRow = styled('div')({ textAlign: 'center' }) -const Unsubscribe = styled('div')({ - alignItems: 'center', - color: PALETTE.SLATE_700, - display: 'flex', - justifyContent: 'center', - '& a': { - alignItems: 'center', - color: PALETTE.SKY_500, - display: 'flex', - marginLeft: 8, - '& > u': { - textDecoration: 'none' - }, - '&:hover > u, &:focus > u': { - textDecoration: 'underline' - } - } -}) - const StyledPanel = styled(Panel)<{isWide: boolean}>(({isWide}) => ({ maxWidth: isWide ? ElementWidth.PANEL_WIDTH : 'inherit' })) @@ -58,33 +40,58 @@ const OrgBillingDangerZone = (props: Props) => { graphql` fragment OrgBillingDangerZone_organization on Organization { ...ArchiveOrganization_organization + id isBillingLeader billingTier } `, organizationRef ) - const {isBillingLeader, billingTier} = organization + const {history} = useRouter() + const {id, isBillingLeader, billingTier} = organization if (!isBillingLeader) return null const isStarter = billingTier === 'starter' + const isTeam = billingTier === 'team' + + const handleDowngrade = () => { + history.push(`/me/organizations/${id}/billing`) + } + return ( {isStarter ? ( + ) : isTeam ? ( +
+ {'Need to cancel? '} + + + {'Downgrade'} + + + {' to the Starter tier'} +
) : ( - +
{'Need to cancel? It’s painless. '} - {'Contact us'} + + {'Contact us'} + - +
)}
From 4e2fec1ea0e1b9eb442a799b0d3a2d66ef40772b Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Mon, 1 Jul 2024 08:57:04 +0200 Subject: [PATCH 295/529] fix: Avoid adding embedding jobs without metadata id (#9881) --- packages/embedder/insertDiscussionsIntoMetadataAndQueue.ts | 4 ++-- .../embedder/insertMeetingTemplatesIntoMetadataAndQueue.ts | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/embedder/insertDiscussionsIntoMetadataAndQueue.ts b/packages/embedder/insertDiscussionsIntoMetadataAndQueue.ts index 4383a670ff3..20b505880e6 100644 --- a/packages/embedder/insertDiscussionsIntoMetadataAndQueue.ts +++ b/packages/embedder/insertDiscussionsIntoMetadataAndQueue.ts @@ -35,9 +35,9 @@ export const insertDiscussionsIntoMetadataAndQueue = async ( .with('Metadata', (qc) => qc .selectFrom('Insert') - .fullJoin( + .innerJoin( sql<{model: string}>`UNNEST(ARRAY[${sql.join(tableNames)}])`.as('model'), - (join) => join.onTrue() + (join) => join.on('Insert.id', 'is not', null) ) .select(['id', 'model']) ) diff --git a/packages/embedder/insertMeetingTemplatesIntoMetadataAndQueue.ts b/packages/embedder/insertMeetingTemplatesIntoMetadataAndQueue.ts index 8e20cf6a370..082ede0cfd7 100644 --- a/packages/embedder/insertMeetingTemplatesIntoMetadataAndQueue.ts +++ b/packages/embedder/insertMeetingTemplatesIntoMetadataAndQueue.ts @@ -37,9 +37,9 @@ export const insertMeetingTemplatesIntoMetadataAndQueue = async ( .with('Metadata', (qc) => qc .selectFrom('Insert') - .fullJoin( + .innerJoin( sql<{model: string}>`UNNEST(ARRAY[${sql.join(tableNames)}])`.as('model'), - (join) => join.onTrue() + (join) => join.on('Insert.id', 'is not', null) ) .select(['id', 'model']) ) From 486f6703a997c827ffcd157c13e3fa1321e977ed Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Mon, 1 Jul 2024 10:25:50 +0200 Subject: [PATCH 296/529] feat: Add search template mutation (#9802) --- .../ActivityLibrary/ActivityLibrary.tsx | 115 ++++++++++++++---- .../ActivityLibraryEmptyState.tsx | 2 +- packages/client/hooks/useDebouncedSearch.ts | 37 ++++++ packages/client/package.json | 3 +- .../ai_models/TextEmbeddingsInference.ts | 2 +- ...tHistoricalRetrospectiveDiscussionTopic.ts | 2 +- packages/server/__tests__/user.test.ts | 64 +++++++++- .../graphql/public/typeDefs/User.graphql | 10 ++ packages/server/graphql/public/types/User.ts | 68 +++++++++++ packages/server/package.json | 1 + yarn.lock | 23 ++-- 11 files changed, 288 insertions(+), 39 deletions(-) create mode 100644 packages/client/hooks/useDebouncedSearch.ts diff --git a/packages/client/components/ActivityLibrary/ActivityLibrary.tsx b/packages/client/components/ActivityLibrary/ActivityLibrary.tsx index b9cf5847c0b..ec937970738 100644 --- a/packages/client/components/ActivityLibrary/ActivityLibrary.tsx +++ b/packages/client/components/ActivityLibrary/ActivityLibrary.tsx @@ -2,19 +2,28 @@ import * as ScrollArea from '@radix-ui/react-scroll-area' import graphql from 'babel-plugin-relay/macro' import clsx from 'clsx' import React, {Fragment, useEffect, useMemo} from 'react' -import {PreloadedQuery, commitLocalUpdate, usePreloadedQuery} from 'react-relay' +import { + PreloadedQuery, + commitLocalUpdate, + fetchQuery, + usePreloadedQuery, + useRefetchableFragment +} from 'react-relay' import {Redirect} from 'react-router' import {Link} from 'react-router-dom' -import {useDebounce} from 'use-debounce' import {ActivityLibraryQuery} from '~/__generated__/ActivityLibraryQuery.graphql' +import {ActivityLibraryTemplateSearchRefetchQuery} from '~/__generated__/ActivityLibraryTemplateSearchRefetchQuery.graphql' +import {ActivityLibraryTemplateSearch_query$key} from '~/__generated__/ActivityLibraryTemplateSearch_query.graphql' import {ActivityLibrary_template$data} from '~/__generated__/ActivityLibrary_template.graphql' import {ActivityLibrary_templateSearchDocument$data} from '~/__generated__/ActivityLibrary_templateSearchDocument.graphql' import useAtmosphere from '../../hooks/useAtmosphere' +import {useDebouncedSearch} from '../../hooks/useDebouncedSearch' import useRouter from '../../hooks/useRouter' import useSearchFilter from '../../hooks/useSearchFilter' import logoMarkPurple from '../../styles/theme/images/brand/mark-color.svg' import SendClientSideEvent from '../../utils/SendClientSideEvent' import IconLabel from '../IconLabel' +import LoadingComponent from '../LoadingComponent/LoadingComponent' import AISearch from './AISearch' import ActivityGrid from './ActivityGrid' import ActivityLibraryEmptyState from './ActivityLibraryEmptyState' @@ -75,8 +84,27 @@ graphql` } ` +const templateSearchFragment = graphql` + fragment ActivityLibraryTemplateSearch_query on Query + @argumentDefinitions(search: {type: "String!"}) + @refetchable(queryName: "ActivityLibraryTemplateSearchRefetchQuery") { + viewer { + templateSearch(search: $search) { + ...ActivityLibrary_template @relay(mask: false) + } + } + } +` + +const templateSearchQuery = graphql` + query ActivityLibraryTemplateSearchQuery($search: String!) { + ...ActivityLibraryTemplateSearch_query @arguments(search: $search) + } +` + const query = graphql` query ActivityLibraryQuery { + ...ActivityLibraryTemplateSearch_query @arguments(search: "") viewer { ...ActivityGrid_user favoriteTemplates { @@ -203,6 +231,12 @@ export const ActivityLibrary = (props: Props) => { const {availableTemplates, organizations} = viewer const hasAITemplateFeatureFlag = !!organizations.find((org) => org.featureFlags.aiTemplate) + const [isSearching, setIsSearching] = React.useState(true) + const [templateSearch, refetchTemplateSearch] = useRefetchableFragment< + ActivityLibraryTemplateSearchRefetchQuery, + ActivityLibraryTemplateSearch_query$key + >(templateSearchFragment, data) + const setSearch = (value: string) => { commitLocalUpdate(atmosphere, (store) => { const viewer = store.getRoot().getLinkedRecord('viewer') @@ -224,13 +258,29 @@ export const ActivityLibrary = (props: Props) => { onQueryChange, resetQuery } = useSearchFilter(templates, getTemplateDocumentValue) - const [debouncedSearchQuery] = useDebounce(searchQuery, 500) + const {debouncedSearch: debouncedSearchQuery, dirty} = useDebouncedSearch(searchQuery) + const showLoading = dirty || isSearching useEffect(() => { if (debouncedSearchQuery) { + setIsSearching(true) + // Avoid suspense while refreshing the search results, see + // https://relay.dev/docs/guided-tour/refetching/refetching-fragments-with-different-data/#if-you-need-to-avoid-suspense + fetchQuery(atmosphere, templateSearchQuery, {search: debouncedSearchQuery}).subscribe({ + complete: () => { + refetchTemplateSearch({search: debouncedSearchQuery}, {fetchPolicy: 'store-only'}) + setIsSearching(false) + }, + error: () => { + setIsSearching(false) + } + }) SendClientSideEvent(atmosphere, 'Activity Library Searched', { debouncedSearchQuery }) + } else { + refetchTemplateSearch({search: ''}, {fetchPolicy: 'store-only'}) + setIsSearching(false) } }, [debouncedSearchQuery]) @@ -241,8 +291,21 @@ export const ActivityLibrary = (props: Props) => { const templatesToRender = useMemo(() => { if (searchQuery.length > 0) { - // If there's a search query, just use the search filter results - return filteredTemplates + // If there's a search query, combine the filtered templates with the search results + const searchResults = templateSearch.viewer.templateSearch + const doubleMatches = searchResults.filter((searchResult) => + filteredTemplates.find((template) => template.id === searchResult.id) + ) + + return [ + ...doubleMatches, + ...filteredTemplates.filter( + (template) => !doubleMatches.find((doubleMatch) => doubleMatch.id === template.id) + ), + ...searchResults.filter( + (searchResult) => !doubleMatches.find((doubleMatch) => doubleMatch.id === searchResult.id) + ) + ] } if (categoryId === 'favorite') { return viewer.favoriteTemplates @@ -255,7 +318,7 @@ export const ActivityLibrary = (props: Props) => { ? template.scope !== 'PUBLIC' : template.category === categoryId ) - }, [searchQuery, filteredTemplates, categoryId]) + }, [searchQuery, filteredTemplates, templateSearch, categoryId]) const sectionedTemplates = useMemo(() => { // Show the teams on search as well, because you can search by team name @@ -337,7 +400,7 @@ export const ActivityLibrary = (props: Props) => { (category) => ( {
)} - {templatesToRender.length === 0 ? ( + {templatesToRender.length === 0 && !showLoading ? ( { <> {sectionedTemplates ? ( <> - {Object.entries(sectionedTemplates).map( - ([subCategory, subCategoryTemplates]) => - subCategoryTemplates.length > 0 && ( - - {subCategory && ( -
- {subCategory} -
- )} -
- + {Object.entries(sectionedTemplates).map(([subCategory, subCategoryTemplates]) => + subCategoryTemplates.length > 0 ? ( + + {subCategory && ( +
+ {subCategory}
-
- ) + )} +
+ +
+ + ) : ( +
+ ) )} ) : ( @@ -415,6 +479,7 @@ export const ActivityLibrary = (props: Props) => { )} )} + {showLoading && } { const {categoryId, searchQuery} = props const showResultsNotFound = categoryId !== 'custom' || searchQuery !== '' - if (categoryId === 'favorite') { + if (!searchQuery && categoryId === 'favorite') { return (
diff --git a/packages/client/hooks/useDebouncedSearch.ts b/packages/client/hooks/useDebouncedSearch.ts new file mode 100644 index 00000000000..f6af61c3724 --- /dev/null +++ b/packages/client/hooks/useDebouncedSearch.ts @@ -0,0 +1,37 @@ +import {useEffect, useRef, useState} from 'react' + +/** + * Debounces a search string. It will wait for the user to stop typing or finishing a word before returning the debounced search string. + */ +export const useDebouncedSearch = (search: string) => { + const wordEndChars = /[\s,.\!?:;\-\(\)\[\]\{\}<>"'\\|&*+=#%@$]/ + const timer = useRef() + const [debouncedSearch, setDebouncedSearch] = useState('') + + useEffect(() => { + if (!search) { + setDebouncedSearch(search) + return + } + // if they finished a word, send it now + if (wordEndChars.test(search.at(-1)!)) { + setDebouncedSearch(search) + return + } + // assuming 40 wpm with 5 characters per word we get 300ms between characters + // give some wiggle room for slow typers + timer.current = setTimeout(() => { + setDebouncedSearch(search) + timer.current = undefined + }, 500) + return () => { + clearTimeout(timer.current) + timer.current = undefined + } + }, [search]) + + return { + debouncedSearch, + dirty: !!timer.current + } +} diff --git a/packages/client/package.json b/packages/client/package.json index 64822b443f4..b53839335aa 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -144,7 +144,6 @@ "tayden-clusterfck": "^0.7.0", "tlds": "^1.192.0", "tslib": "^2.4.0", - "unicode-substring": "^1.0.0", - "use-debounce": "^10.0.0" + "unicode-substring": "^1.0.0" } } diff --git a/packages/embedder/ai_models/TextEmbeddingsInference.ts b/packages/embedder/ai_models/TextEmbeddingsInference.ts index d044c481ea2..0ed12614cd4 100644 --- a/packages/embedder/ai_models/TextEmbeddingsInference.ts +++ b/packages/embedder/ai_models/TextEmbeddingsInference.ts @@ -77,7 +77,7 @@ export class TextEmbeddingsInference extends AbstractEmbeddingsModel { body: {inputs: content} }) if (error) { - if (response.status !== 429 || retries < 1) return new Error(error.error) + if (response?.status !== 429 || retries < 1) return new Error(error.error) await sleep(2000) return this.getEmbedding(content, retries - 1) } diff --git a/packages/embedder/importHistoricalRetrospectiveDiscussionTopic.ts b/packages/embedder/importHistoricalRetrospectiveDiscussionTopic.ts index 2469327a18a..04d8caf96f2 100644 --- a/packages/embedder/importHistoricalRetrospectiveDiscussionTopic.ts +++ b/packages/embedder/importHistoricalRetrospectiveDiscussionTopic.ts @@ -27,7 +27,7 @@ export const importHistoricalRetrospectiveDiscussionTopic = async () => { const earliestImportedDiscussion = await pg .selectFrom('EmbeddingsMetadata') .select(['id', 'refUpdatedAt', 'refId']) - .where('objectType', '=', 'retrospectiveDiscussionTopic') + .where('objectType', '=', 'meetingTemplate') .orderBy('refUpdatedAt') .limit(1) .executeTakeFirst() diff --git a/packages/server/__tests__/user.test.ts b/packages/server/__tests__/user.test.ts index c467cf96855..31f4838a6aa 100644 --- a/packages/server/__tests__/user.test.ts +++ b/packages/server/__tests__/user.test.ts @@ -1,5 +1,5 @@ import faker from 'faker' -import {sendIntranet, signUp, signUpWithEmail} from './common' +import {sendIntranet, sendPublic, signUp, signUpWithEmail} from './common' test('Get user by id', async () => { const {email, userId} = await signUp() @@ -123,3 +123,65 @@ test('First user is patientZero', async () => { } }) }) + +// The tests only work after the embeddings were generated, which is not the case on CI +test.skip.each([ + ['card game', ['moscowPrioritizationTemplate', 'estimatedEffortTemplate', 'wsjfTemplate']], + ['winning loosing', ['winningStreakTemplate']], + [ + 'project failure', + [ + 'whyDidTheProjectFailPremortemTemplate', + 'successAndFailurePremortemTemplate', + 'postmortemAnalysisTemplate', + 'blamelessPostmortemTemplate', + 'simplePostmortemTemplate', + 'softwareProjectPostmortemTemplate', + 'movieDirectorPostmortemTemplate', + 'fortuneTellerPremortemTemplate', + 'timelinePremortemTemplate', + 'howLikelyToFailPremortemTemplate', + 'engineeringPostmortemTemplate', + 'resourceAllocationPremortemTemplate', + 'processImprovementPostmortemTemplate', + 'incidentImpactPostmortemTemplate', + 'bestworstCaseScenarioPremortemTemplate', + 'gameShowPostmortemTemplate', + 'timeTravelPostmortemTemplate', + 'teamEfficiencyPremortemTemplate', + 'risksAndPrecautionsPremortemTemplate', + 'obstacleCoursePremortemTemplate', + 'stakeholderSatisfactionPostmortemTemplate' + ] + ], + ['christmas', ['aChristmasCarolRetrospectiveTemplate']], + ['risk management', ['riskManagementPostmortemTemplate', 'risksAndPrecautionsPremortemTemplate']], + ['animals', ['iguanaCrocodileKomodoDragonPremortemTemplate']], + ['plants', ['roseThornBudTemplate']] +])('Template search - %s', async (search, templateIds) => { + const {authToken} = await signUp() + + const user = await sendPublic({ + query: ` + query Template($search: String!) { + viewer { + templateSearch(search: $search) { + id + } + } + } + `, + variables: { + search + }, + authToken + }) + + expect(user).toMatchObject({ + data: { + viewer: { + templateSearch: expect.arrayContaining(templateIds.map((id) => ({id}))) + } + } + }) +}) diff --git a/packages/server/graphql/public/typeDefs/User.graphql b/packages/server/graphql/public/typeDefs/User.graphql index ab75ef73a4c..8a6f9a76dcf 100644 --- a/packages/server/graphql/public/typeDefs/User.graphql +++ b/packages/server/graphql/public/typeDefs/User.graphql @@ -444,6 +444,16 @@ type User { type: MeetingTypeEnum ): MeetingTemplateConnection! + """ + Activities available to the user matching the search + """ + templateSearch( + """ + Search query + """ + search: String! + ): [MeetingTemplate!]! + """ A prototype for a team experience. Includes meeting templates and meeting types that have no templates """ diff --git a/packages/server/graphql/public/types/User.ts b/packages/server/graphql/public/types/User.ts index b5951a80541..7947deed713 100644 --- a/packages/server/graphql/public/types/User.ts +++ b/packages/server/graphql/public/types/User.ts @@ -8,6 +8,7 @@ import generateInvoice from '../../../billing/helpers/generateInvoice' import generateUpcomingInvoice from '../../../billing/helpers/generateUpcomingInvoice' import getRethink from '../../../database/rethinkDriver' import MeetingTemplate from '../../../database/types/MeetingTemplate' +import getKysely from '../../../postgres/getKysely' import { getUserId, isSuperUser, @@ -184,6 +185,73 @@ const User: UserResolvers = { return connectionFromTemplateArray(allActivities, first, after) }, + templateSearch: async ({id: userId}, {search}, {authToken, dataLoader}) => { + if (!search) return [] + const viewerId = getUserId(authToken) + const user = await dataLoader.get('users').loadNonNull(userId) + const teamIds = + viewerId === userId || isSuperUser(authToken) + ? user.tms + : user.tms.filter((teamId: string) => authToken.tms.includes(teamId)) + + const organizationUsers = await dataLoader.get('organizationUsersByUserId').load(viewerId) + const userOrgIds = organizationUsers.map(({orgId}) => orgId) + + const allOrgTeams = (await dataLoader.get('teamsByOrgIds').loadMany(userOrgIds)) + .filter(isValid) + .flat() + // all team ids which could have accessible templates + const allTeamIds = ['aGhostTeam', ...allOrgTeams.map(({id}) => id)] + + const response = await fetch('http://localhost:3040/embed', { + method: 'POST', + body: JSON.stringify({inputs: search}), + headers: {'Content-Type': 'application/json'} + }) + const data = await response.json() + + const MODEL = 'Embeddings_ember_1' + const SIMILARITY_THRESHOLD = 0.5 + + const pg = getKysely() + const similarEmbeddings = await pg + .with('Model', (qc) => + qc + .selectFrom(MODEL) + .innerJoin('EmbeddingsMetadata', 'EmbeddingsMetadata.id', `${MODEL}.embeddingsMetadataId`) + .select([`${MODEL}.id`, 'embeddingsMetadataId', 'embedding', 'refId']) + .where('objectType', '=', 'meetingTemplate') + .where('teamId', 'in', allTeamIds) + ) + .with('CosineSimilarity', (pg) => + pg + .selectFrom(['Model']) + .select(({eb, val, parens, ref}) => [ + ref('Model.id').as('embeddingId'), + ref('Model.embeddingsMetadataId').as('embeddingsMetadataId'), + ref('Model.refId').as('refId'), + eb( + val(1), + '-', + parens('Model.embedding' as any, '<=>' as any, JSON.stringify(data[0])) + ).as('similarity') + ]) + ) + .selectFrom('CosineSimilarity') + .select(['embeddingId', 'similarity', 'embeddingsMetadataId', 'refId']) + .where('similarity', '>=', SIMILARITY_THRESHOLD) + .orderBy('similarity', 'desc') + .execute() + + const aiActivityIds = similarEmbeddings.map(({refId}) => refId) + + // TODO filter out seasonal templates + const activities = await dataLoader.get('meetingTemplates').loadMany(aiActivityIds) + const accessibleActivities = activities.filter(isValid).filter((activity) => { + return activity.scope !== 'TEAM' || teamIds.includes(activity.teamId) + }) + return accessibleActivities + }, parseSAMLMetadata: async (_source, {metadataURL, domain}) => { const metadata = await getSSOMetadataFromURL(metadataURL) if (metadata instanceof Error) return {error: {message: metadata.message}} diff --git a/packages/server/package.json b/packages/server/package.json index 1ec29f34195..04b87f3a2a6 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -122,6 +122,7 @@ "nodemailer": "^6.9.9", "oauth-1.0a": "^2.2.6", "openai": "^4.24.1", + "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", "parabol-client": "7.37.8", "pg": "^8.5.1", diff --git a/yarn.lock b/yarn.lock index 6345afc23c8..8d7b138ae27 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9941,9 +9941,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001580, caniuse-lite@~1.0.0: - version "1.0.30001614" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001614.tgz#f894b4209376a0bf923d67d9c361d96b1dfebe39" - integrity sha512-jmZQ1VpmlRwHgdP1/uiKzgiAuGOfLEJsYFP4+GBou/QQ4U6IOJCB4NP1c+1p9RGLpwObcT94jA5/uO+F1vBbog== + version "1.0.30001623" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001623.tgz#e982099dcb229bb6ab35f5aebe2f8d79ccf6e8a8" + integrity sha512-X/XhAVKlpIxWPpgRTnlgZssJrF0m6YtRA0QDWgsBNT12uZM6LPRydR7ip405Y3t1LamD8cP2TZFEDZFBf5ApcA== capital-case@^1.0.4: version "1.0.4" @@ -17204,6 +17204,13 @@ openapi-fetch@^0.9.3: dependencies: openapi-typescript-helpers "^0.0.7" +openapi-fetch@^0.9.7: + version "0.9.7" + resolved "https://registry.yarnpkg.com/openapi-fetch/-/openapi-fetch-0.9.7.tgz#e224d1176e11ec68671caf0df6b736d649be97ed" + integrity sha512-NMp/GEmWSGO0b2d731IdGXcMP2PF85Rk1q+oy2Mx/DYMdP3pgTZTRamKxgZpkHhM4iOVsyD1iP5HKL9Fr6CH2Q== + dependencies: + openapi-typescript-helpers "^0.0.8" + openapi-types@^12.1.0: version "12.1.1" resolved "https://registry.yarnpkg.com/openapi-types/-/openapi-types-12.1.1.tgz#0aface4e05ba60efbf51153ed6af23988796617d" @@ -17214,6 +17221,11 @@ openapi-typescript-helpers@^0.0.7: resolved "https://registry.yarnpkg.com/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.7.tgz#1d0ead67c35864d189c2cb2d0556854ccbb16c38" integrity sha512-7nwlAtdA1fULipibFRBWE/rnF114q6ejRYzNvhdA/x+qTWAZhXGLc/368dlwMlyJDvCQMCnADjpzb5BS5ZmNSA== +openapi-typescript-helpers@^0.0.8: + version "0.0.8" + resolved "https://registry.yarnpkg.com/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.8.tgz#460f395362cc16e4a5de56264b7b1c5a03746e35" + integrity sha512-1eNjQtbfNi5Z/kFhagDIaIRj6qqDzhjNJKz8cmMW0CVdGwT6e1GLbAfgI0d28VTJa1A8jz82jm/4dG8qNoNS8g== + opener@^1.5.2: version "1.5.2" resolved "https://registry.yarnpkg.com/opener/-/opener-1.5.2.tgz#5d37e1f35077b9dcac4301372271afdeb2a13598" @@ -21624,11 +21636,6 @@ use-composed-ref@^1.3.0: resolved "https://registry.yarnpkg.com/use-composed-ref/-/use-composed-ref-1.3.0.tgz#3d8104db34b7b264030a9d916c5e94fbe280dbda" integrity sha512-GLMG0Jc/jiKov/3Ulid1wbv3r54K9HlMW29IWcDFPEqFkSO2nS0MuefWgMJpeHQ9YJeXDL3ZUF+P3jdXlZX/cQ== -use-debounce@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/use-debounce/-/use-debounce-10.0.0.tgz#5091b18d6c16292605f588bae3c0d2cfae756ff2" - integrity sha512-XRjvlvCB46bah9IBXVnq/ACP2lxqXyZj0D9hj4K5OzNroMDpTEBg8Anuh1/UfRTRs7pLhQ+RiNxxwZu9+MVl1A== - use-isomorphic-layout-effect@^1.1.1: version "1.1.2" resolved "https://registry.yarnpkg.com/use-isomorphic-layout-effect/-/use-isomorphic-layout-effect-1.1.2.tgz#497cefb13d863d687b08477d9e5a164ad8c1a6fb" From 4413142e55c94388e1cfe78503cc420d2945c334 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Mon, 1 Jul 2024 10:11:47 +0100 Subject: [PATCH 297/529] fix: remove ai from summary url if no ai env var (#9895) --- packages/client/mutations/EndRetrospectiveMutation.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/client/mutations/EndRetrospectiveMutation.ts b/packages/client/mutations/EndRetrospectiveMutation.ts index f087fbe1197..c44f0b85d88 100644 --- a/packages/client/mutations/EndRetrospectiveMutation.ts +++ b/packages/client/mutations/EndRetrospectiveMutation.ts @@ -128,7 +128,10 @@ export const endRetrospectiveTeamOnNext: OnNextHandler< } else { const reflections = reflectionGroups.flatMap((group) => group.reflections) // reflectionCount hasn't been calculated yet so check reflections length const hasMoreThanOneReflection = reflections.length > 1 - const hasOpenAISummary = hasMoreThanOneReflection && !organization.featureFlags.noAISummary + const hasOpenAISummary = + hasMoreThanOneReflection && + !organization.featureFlags.noAISummary && + window.__ACTION__.hasOpenAI const hasTeamHealth = phases.some((phase) => phase.phaseType === 'TEAM_HEALTH') const pathname = `/new-summary/${meetingId}` const search = new URLSearchParams() From d91f649918ed471bc37332abdefcc6e07d737e6d Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 1 Jul 2024 12:27:37 -0700 Subject: [PATCH 298/529] fix: speed up team upgrade (#9902) Signed-off-by: Matt Krick --- .../components/OrgBilling/BillingForm.tsx | 8 ++++ .../fragments/UpgradeToTeamTierFrag.ts | 13 ++----- .../client/subscriptions/TeamSubscription.ts | 3 -- .../mutations/helpers/oldUpgradeToTeamTier.ts | 2 +- .../mutations/stripeDeleteSubscription.ts | 4 +- .../private/mutations/upgradeToTeamTier.ts | 33 ++++------------ .../mutations/createStripeSubscription.ts | 39 ++++++------------- packages/server/utils/defaultTier.ts | 2 +- packages/server/utils/stripe/StripeManager.ts | 13 ++++++- 9 files changed, 49 insertions(+), 68 deletions(-) diff --git a/packages/client/modules/userDashboard/components/OrgBilling/BillingForm.tsx b/packages/client/modules/userDashboard/components/OrgBilling/BillingForm.tsx index a70094f3c44..eb8404d8fdf 100644 --- a/packages/client/modules/userDashboard/components/OrgBilling/BillingForm.tsx +++ b/packages/client/modules/userDashboard/components/OrgBilling/BillingForm.tsx @@ -8,6 +8,7 @@ import { } from '@stripe/react-stripe-js' import {StripeElementChangeEvent} from '@stripe/stripe-js' import React, {useState} from 'react' +import {commitLocalUpdate} from 'relay-runtime' import {CreateStripeSubscriptionMutation$data} from '../../../../__generated__/CreateStripeSubscriptionMutation.graphql' import Ellipsis from '../../../../components/Ellipsis/Ellipsis' import PrimaryButton from '../../../../components/PrimaryButton' @@ -15,8 +16,10 @@ import StyledError from '../../../../components/StyledError' import useAtmosphere from '../../../../hooks/useAtmosphere' import useMutationProps from '../../../../hooks/useMutationProps' import CreateStripeSubscriptionMutation from '../../../../mutations/CreateStripeSubscriptionMutation' +import upgradeToTeamTierSuccessUpdater from '../../../../mutations/handlers/upgradeToTeamTierSuccessUpdater' import {PALETTE} from '../../../../styles/paletteV3' import SendClientSideEvent from '../../../../utils/SendClientSideEvent' +import createProxyRecord from '../../../../utils/relay/createProxyRecord' const ButtonBlock = styled('div')({ display: 'flex', @@ -131,6 +134,11 @@ const BillingForm = (props: Props) => { setIsLoading(false) return } + commitLocalUpdate(atmosphere, (store) => { + const payload = createProxyRecord(store, 'payload', {}) + payload.setLinkedRecord(store.get(orgId)!, 'organization') + upgradeToTeamTierSuccessUpdater(payload) + }) onCompleted() } diff --git a/packages/client/mutations/fragments/UpgradeToTeamTierFrag.ts b/packages/client/mutations/fragments/UpgradeToTeamTierFrag.ts index f1f5fcb6226..d3d93ce6e8c 100644 --- a/packages/client/mutations/fragments/UpgradeToTeamTierFrag.ts +++ b/packages/client/mutations/fragments/UpgradeToTeamTierFrag.ts @@ -16,18 +16,13 @@ graphql` periodStart updatedAt lockedAt + teams { + isPaid + tier + } } meetings { showConversionModal } } ` - -graphql` - fragment UpgradeToTeamTierFrag_team on UpgradeToTeamTierSuccess { - teams { - isPaid - tier - } - } -` diff --git a/packages/client/subscriptions/TeamSubscription.ts b/packages/client/subscriptions/TeamSubscription.ts index ec2ff7aa1e6..c5bc699bf2a 100644 --- a/packages/client/subscriptions/TeamSubscription.ts +++ b/packages/client/subscriptions/TeamSubscription.ts @@ -181,9 +181,6 @@ const subscription = graphql` OldUpgradeToTeamTierPayload { ...OldUpgradeToTeamTierMutation_team @relay(mask: false) } - UpgradeToTeamTierSuccess { - ...UpgradeToTeamTierFrag_team @relay(mask: false) - } UpdateIntegrationProviderSuccess { ...UpdateIntegrationProviderMutation_team @relay(mask: false) } diff --git a/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts b/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts index 53d4f9552e0..361416e52ab 100644 --- a/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts +++ b/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts @@ -32,7 +32,7 @@ const oldUpgradeToTeamTier = async ( const manager = getStripeManager() const customer = stripeId ? await manager.updatePayment(stripeId, source) - : await manager.createCustomer(orgId, email, source) + : await manager.createCustomer(orgId, email, undefined, source) let subscriptionFields = {} if (!stripeSubscriptionId) { diff --git a/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts b/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts index 960bb1a01f2..b42338b2371 100644 --- a/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts +++ b/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts @@ -31,8 +31,10 @@ const stripeDeleteSubscription: MutationResolvers['stripeDeleteSubscription'] = const org: Organization = await dataLoader.get('organizations').load(orgId) const {stripeSubscriptionId} = org + if (!stripeSubscriptionId) return false + if (stripeSubscriptionId !== subscriptionId) { - throw new Error('Subscription ID does not match') + throw new Error(`Subscription ID does not match: ${stripeSubscriptionId} vs ${subscriptionId}`) } await r diff --git a/packages/server/graphql/private/mutations/upgradeToTeamTier.ts b/packages/server/graphql/private/mutations/upgradeToTeamTier.ts index 38efacb4caf..e7fd466a0ad 100644 --- a/packages/server/graphql/private/mutations/upgradeToTeamTier.ts +++ b/packages/server/graphql/private/mutations/upgradeToTeamTier.ts @@ -28,8 +28,10 @@ const upgradeToTeamTier: MutationResolvers['upgradeToTeamTier'] = async ( const userId = getUserId(authToken) const manager = getStripeManager() const invoice = await manager.retrieveInvoice(invoiceId) - const customerId = invoice.customer as string - const customer = await manager.retrieveCustomer(customerId) + const stripeId = invoice.customer as string + const stripeSubscriptionId = invoice.subscription as string + const customer = await manager.retrieveCustomer(stripeId) + if (customer.deleted) { return standardError(new Error('Customer has been deleted'), {userId}) } @@ -51,24 +53,7 @@ const upgradeToTeamTier: MutationResolvers['upgradeToTeamTier'] = async ( dataLoader.get('users').loadNonNull(viewerId) ]) - const { - stripeId, - tier, - activeDomain, - name: orgName, - stripeSubscriptionId, - trialStartDate - } = organization - - if (!stripeId) { - return standardError(new Error('Organization does not have a stripe id'), { - userId: viewerId - }) - } - - if (!stripeSubscriptionId) { - return standardError(new Error('Organization does not have a subscription'), {userId: viewerId}) - } + const {tier, activeDomain, name: orgName, trialStartDate} = organization if (tier === 'enterprise') { return standardError(new Error("Can not change an org's plan from enterprise to team"), { @@ -93,7 +78,9 @@ const upgradeToTeamTier: MutationResolvers['upgradeToTeamTier'] = async ( scheduledLockAt: null, lockedAt: null, updatedAt: now, - trialStartDate: null + trialStartDate: null, + stripeId, + stripeSubscriptionId }) }).run(), pg @@ -134,10 +121,6 @@ const upgradeToTeamTier: MutationResolvers['upgradeToTeamTier'] = async ( const data = {orgId, teamIds, meetingIds} publish(SubscriptionChannel.ORGANIZATION, orgId, 'UpgradeToTeamTierSuccess', data, subOptions) - teamIds.forEach((teamId) => { - const teamData = {orgId, teamIds: [teamId]} - publish(SubscriptionChannel.TEAM, teamId, 'UpgradeToTeamTierSuccess', teamData, subOptions) - }) return data } diff --git a/packages/server/graphql/public/mutations/createStripeSubscription.ts b/packages/server/graphql/public/mutations/createStripeSubscription.ts index 02ceb15c616..671078946aa 100644 --- a/packages/server/graphql/public/mutations/createStripeSubscription.ts +++ b/packages/server/graphql/public/mutations/createStripeSubscription.ts @@ -1,7 +1,6 @@ import Stripe from 'stripe' import getRethink from '../../../database/rethinkDriver' import {getUserId} from '../../../utils/authorization' -import {fromEpochSeconds} from '../../../utils/epochTime' import standardError from '../../../utils/standardError' import {getStripeManager} from '../../../utils/stripe' import {MutationResolvers} from '../resolverTypes' @@ -12,7 +11,6 @@ const createStripeSubscription: MutationResolvers['createStripeSubscription'] = {authToken, dataLoader} ) => { const viewerId = getUserId(authToken) - const now = new Date() const r = await getRethink() const [viewer, organization, orgUsersCount, organizationUser] = await Promise.all([ @@ -38,37 +36,24 @@ const createStripeSubscription: MutationResolvers['createStripeSubscription'] = return standardError(new Error('Organization already has a subscription'), {userId: viewerId}) } const {email} = viewer - const customer = stripeId - ? await manager.retrieveCustomer(stripeId) - : await manager.createCustomer(orgId, email) - const {id: customerId} = customer - const res = await manager.attachPaymentToCustomer(customerId, paymentMethodId) - if (res instanceof Error) return standardError(res, {userId: viewerId}) - // wait until the payment is attached to the customer before updating the default payment method - await manager.updateDefaultPaymentMethod(customerId, paymentMethodId) + let customer: Stripe.Response + if (stripeId) { + customer = await manager.retrieveCustomer(stripeId) + const {id: customerId} = customer + const res = await manager.attachPaymentToCustomer(customerId, paymentMethodId) + if (res instanceof Error) return standardError(res, {userId: viewerId}) + // cannot updateDefaultPaymentMethod until it is attached to the customer + await manager.updateDefaultPaymentMethod(customerId, paymentMethodId) + } else { + customer = await manager.createCustomer(orgId, email, paymentMethodId) + } + const subscription = await manager.createTeamSubscription(customer.id, orgId, orgUsersCount) const latestInvoice = subscription.latest_invoice as Stripe.Invoice const paymentIntent = latestInvoice.payment_intent as Stripe.PaymentIntent const clientSecret = paymentIntent.client_secret - const subscriptionFields = { - periodEnd: fromEpochSeconds(subscription.current_period_end), - periodStart: fromEpochSeconds(subscription.current_period_start), - stripeSubscriptionId: subscription.id - } - - await r({ - updatedOrg: r - .table('Organization') - .get(orgId) - .update({ - ...subscriptionFields, - stripeId: customer.id, - updatedAt: now - }) - }).run() - const data = {stripeSubscriptionClientSecret: clientSecret} return data } diff --git a/packages/server/utils/defaultTier.ts b/packages/server/utils/defaultTier.ts index dcce379dd6a..793bf254dc1 100644 --- a/packages/server/utils/defaultTier.ts +++ b/packages/server/utils/defaultTier.ts @@ -1 +1 @@ -export const defaultTier = process.env.IS_ENTERPRISE ? 'enterprise' : 'starter' +export const defaultTier = process.env.IS_ENTERPRISE === 'true' ? 'enterprise' : 'starter' diff --git a/packages/server/utils/stripe/StripeManager.ts b/packages/server/utils/stripe/StripeManager.ts index 5a2c74e665d..346d5456776 100644 --- a/packages/server/utils/stripe/StripeManager.ts +++ b/packages/server/utils/stripe/StripeManager.ts @@ -90,10 +90,21 @@ export default class StripeManager { } } - async createCustomer(orgId: string, email: string, source?: string) { + async createCustomer( + orgId: string, + email: string, + 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 } From 55b2dfb425f89135a39bb38b02b5bc955ea2e5a0 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 1 Jul 2024 14:37:56 -0700 Subject: [PATCH 299/529] fix: remove Organization.teams field from gql (#9918) Signed-off-by: Matt Krick --- packages/client/mutations/fragments/UpgradeToTeamTierFrag.ts | 2 +- packages/server/graphql/public/typeDefs/Organization.graphql | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/client/mutations/fragments/UpgradeToTeamTierFrag.ts b/packages/client/mutations/fragments/UpgradeToTeamTierFrag.ts index d3d93ce6e8c..1cb28acf99a 100644 --- a/packages/client/mutations/fragments/UpgradeToTeamTierFrag.ts +++ b/packages/client/mutations/fragments/UpgradeToTeamTierFrag.ts @@ -16,7 +16,7 @@ graphql` periodStart updatedAt lockedAt - teams { + viewerTeams { isPaid tier } diff --git a/packages/server/graphql/public/typeDefs/Organization.graphql b/packages/server/graphql/public/typeDefs/Organization.graphql index eda5e8f299a..139dc3118e0 100644 --- a/packages/server/graphql/public/typeDefs/Organization.graphql +++ b/packages/server/graphql/public/typeDefs/Organization.graphql @@ -52,11 +52,6 @@ type Organization { """ picture: URL - """ - all the teams the viewer is on in the organization - """ - teams: [Team!]! - tier: TierEnum! billingTier: TierEnum! From c67a6a87efc35fcb8f41e388d451b1a784a045e2 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 1 Jul 2024 16:10:02 -0700 Subject: [PATCH 300/529] fix: bugs during upgrade/downgrade (#9919) Signed-off-by: Matt Krick --- .../components/OrgBilling/BillingForm.tsx | 17 +++++-- .../OrgBilling/OrgPlansAndBilling.tsx | 11 +++-- .../components/OrgBilling/PaymentDetails.tsx | 12 +++-- packages/client/mutations/AddOrgMutation.ts | 1 + .../mutations/DowngradeToStarterMutation.ts | 1 + .../fragments/CompleteOrganizationFrag.ts | 1 + .../fragments/UpgradeToTeamTierFrag.ts | 3 ++ packages/client/package.json | 2 +- yarn.lock | 45 +++++-------------- 9 files changed, 40 insertions(+), 53 deletions(-) diff --git a/packages/client/modules/userDashboard/components/OrgBilling/BillingForm.tsx b/packages/client/modules/userDashboard/components/OrgBilling/BillingForm.tsx index eb8404d8fdf..aeb45f9a9e4 100644 --- a/packages/client/modules/userDashboard/components/OrgBilling/BillingForm.tsx +++ b/packages/client/modules/userDashboard/components/OrgBilling/BillingForm.tsx @@ -6,8 +6,12 @@ import { useElements, useStripe } from '@stripe/react-stripe-js' -import {StripeElementChangeEvent} from '@stripe/stripe-js' -import React, {useState} from 'react' +import { + StripeCardNumberElement, + StripeCardNumberElementOptions, + StripeElementChangeEvent +} from '@stripe/stripe-js' +import React, {MutableRefObject, useState} from 'react' import {commitLocalUpdate} from 'relay-runtime' import {CreateStripeSubscriptionMutation$data} from '../../../../__generated__/CreateStripeSubscriptionMutation.graphql' import Ellipsis from '../../../../components/Ellipsis/Ellipsis' @@ -48,7 +52,8 @@ const ErrorMsg = styled(StyledError)({ textTransform: 'none' }) -const CARD_ELEMENT_OPTIONS = { +const CARD_ELEMENT_OPTIONS: StripeCardNumberElementOptions = { + disableLink: true, style: { base: { color: PALETTE.SLATE_800, @@ -64,10 +69,11 @@ const CARD_ELEMENT_OPTIONS = { type Props = { orgId: string + cardNumberRef: MutableRefObject } const BillingForm = (props: Props) => { - const {orgId} = props + const {cardNumberRef, orgId} = props const stripe = useStripe() const elements = useElements() const [isLoading, setIsLoading] = useState(false) @@ -191,6 +197,9 @@ const BillingForm = (props: Props) => {
{ + cardNumberRef.current = e + }} className='focus:ring-indigo-500 focus:border-indigo-500 block w-full border-b border-slate-400 bg-slate-200 px-4 py-3 shadow-sm outline-none sm:text-sm' options={CARD_ELEMENT_OPTIONS} onChange={handleChange('CardNumber')} diff --git a/packages/client/modules/userDashboard/components/OrgBilling/OrgPlansAndBilling.tsx b/packages/client/modules/userDashboard/components/OrgBilling/OrgPlansAndBilling.tsx index bb00f1fecae..834b06f7447 100644 --- a/packages/client/modules/userDashboard/components/OrgBilling/OrgPlansAndBilling.tsx +++ b/packages/client/modules/userDashboard/components/OrgBilling/OrgPlansAndBilling.tsx @@ -1,5 +1,6 @@ +import {StripeCardNumberElement} from '@stripe/stripe-js' import graphql from 'babel-plugin-relay/macro' -import React, {Suspense, useState} from 'react' +import React, {Suspense, useRef, useState} from 'react' import {PreloadedQuery, useFragment, usePreloadedQuery, useRefetchableFragment} from 'react-relay' import {OrgPlansAndBillingQuery} from '../../../../__generated__/OrgPlansAndBillingQuery.graphql' import {OrgPlansAndBillingRefetchQuery} from '../../../../__generated__/OrgPlansAndBillingRefetchQuery.graphql' @@ -57,9 +58,10 @@ const OrgPlansAndBilling = (props: Props) => { ) const [hasSelectedTeamPlan, setHasSelectedTeamPlan] = useState(false) const {billingTier, isBillingLeader} = organization - + const cardNumberRef = useRef(null) const handleSelectTeamPlan = () => { setHasSelectedTeamPlan(true) + cardNumberRef.current?.focus() } if (billingTier === 'starter') { @@ -72,10 +74,7 @@ const OrgPlansAndBilling = (props: Props) => { handleSelectTeamPlan={handleSelectTeamPlan} hasSelectedTeamPlan={hasSelectedTeamPlan} /> - +
diff --git a/packages/client/modules/userDashboard/components/OrgBilling/PaymentDetails.tsx b/packages/client/modules/userDashboard/components/OrgBilling/PaymentDetails.tsx index 643649acff4..11250aa1069 100644 --- a/packages/client/modules/userDashboard/components/OrgBilling/PaymentDetails.tsx +++ b/packages/client/modules/userDashboard/components/OrgBilling/PaymentDetails.tsx @@ -1,14 +1,13 @@ import styled from '@emotion/styled' import {Divider} from '@mui/material' import {Elements} from '@stripe/react-stripe-js' -import {loadStripe} from '@stripe/stripe-js' +import {StripeCardNumberElement, loadStripe} from '@stripe/stripe-js' import graphql from 'babel-plugin-relay/macro' -import React, {useRef} from 'react' +import React, {MutableRefObject, useRef} from 'react' import {useFragment} from 'react-relay' import {PaymentDetails_organization$key} from '../../../../__generated__/PaymentDetails_organization.graphql' import Panel from '../../../../components/Panel/Panel' import Row from '../../../../components/Row/Row' -import useScrollIntoView from '../../../../hooks/useScrollIntoVIew' import {PALETTE} from '../../../../styles/paletteV3' import {ElementWidth} from '../../../../types/constEnums' import {MONTHLY_PRICE} from '../../../../utils/constants' @@ -96,12 +95,12 @@ const ActiveUserBlock = styled('div')({ const stripePromise = loadStripe(window.__ACTION__.stripe) type Props = { + cardNumberRef: MutableRefObject organizationRef: PaymentDetails_organization$key - hasSelectedTeamPlan: boolean } const PaymentDetails = (props: Props) => { - const {organizationRef, hasSelectedTeamPlan} = props + const {cardNumberRef, organizationRef} = props const organization = useFragment( graphql` @@ -118,7 +117,6 @@ const PaymentDetails = (props: Props) => { const {activeUserCount} = orgUserCount const price = activeUserCount * MONTHLY_PRICE const ref = useRef(null) - useScrollIntoView(ref, hasSelectedTeamPlan) return ( @@ -127,7 +125,7 @@ const PaymentDetails = (props: Props) => { {'Credit Card Details'} - + diff --git a/packages/client/mutations/AddOrgMutation.ts b/packages/client/mutations/AddOrgMutation.ts index 2b19332a808..d36710fae37 100644 --- a/packages/client/mutations/AddOrgMutation.ts +++ b/packages/client/mutations/AddOrgMutation.ts @@ -26,6 +26,7 @@ graphql` } picture tier + billingTier } team { id diff --git a/packages/client/mutations/DowngradeToStarterMutation.ts b/packages/client/mutations/DowngradeToStarterMutation.ts index 10e19e8ce7f..759b9b4ca05 100644 --- a/packages/client/mutations/DowngradeToStarterMutation.ts +++ b/packages/client/mutations/DowngradeToStarterMutation.ts @@ -7,6 +7,7 @@ graphql` fragment DowngradeToStarterMutation_organization on DowngradeToStarterPayload { organization { tier + billingTier } } ` diff --git a/packages/client/mutations/fragments/CompleteOrganizationFrag.ts b/packages/client/mutations/fragments/CompleteOrganizationFrag.ts index 240721884c8..80f3c7396ea 100644 --- a/packages/client/mutations/fragments/CompleteOrganizationFrag.ts +++ b/packages/client/mutations/fragments/CompleteOrganizationFrag.ts @@ -23,5 +23,6 @@ graphql` periodStart periodEnd tier + billingTier } ` diff --git a/packages/client/mutations/fragments/UpgradeToTeamTierFrag.ts b/packages/client/mutations/fragments/UpgradeToTeamTierFrag.ts index 1cb28acf99a..e3ff4d0a59c 100644 --- a/packages/client/mutations/fragments/UpgradeToTeamTierFrag.ts +++ b/packages/client/mutations/fragments/UpgradeToTeamTierFrag.ts @@ -12,6 +12,9 @@ graphql` tier } tier + billingTier + isBillingLeader + isOrgAdmin periodEnd periodStart updatedAt diff --git a/packages/client/package.json b/packages/client/package.json index b53839335aa..70f58d832d9 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -84,7 +84,7 @@ "@radix-ui/react-tooltip": "^1.0.7", "@sentry/browser": "^5.8.0", "@stripe/react-stripe-js": "^1.16.5", - "@stripe/stripe-js": "^1.47.0", + "@stripe/stripe-js": "^4.0.0", "@tiptap/extension-character-count": "^2.0.4", "@tiptap/extension-link": "^2.0.4", "@tiptap/extension-mention": "^2.0.4", diff --git a/yarn.lock b/yarn.lock index 8d7b138ae27..774e599c991 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7216,10 +7216,10 @@ dependencies: prop-types "^15.7.2" -"@stripe/stripe-js@^1.47.0": - version "1.47.0" - resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-1.47.0.tgz#48626a2e43302330aa826ce498a2d9761db4053d" - integrity sha512-jKSClqEIKS2MbPCXlSsseDSZyJ3dVrfUrYMz5LBY1o9iS2tfKbpTZACt8r2g+xyQozI+uHr76pVTyFsmBKA4Mg== +"@stripe/stripe-js@^4.0.0": + version "4.0.0" + resolved "https://registry.yarnpkg.com/@stripe/stripe-js/-/stripe-js-4.0.0.tgz#57f258cdd595bba0b8a4f5dc94fc58bfa0a324a6" + integrity sha512-R5zewuzTVPGn4dXkavbgDk8vSILkT5hRlzga10p6JzngR17qKi1fgc27kl58TmaVvgBZGngTRNH2j9kYdvfPGA== "@sucrase/webpack-loader@^2.0.0": version "2.0.0" @@ -9941,9 +9941,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001580, caniuse-lite@~1.0.0: - version "1.0.30001623" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001623.tgz#e982099dcb229bb6ab35f5aebe2f8d79ccf6e8a8" - integrity sha512-X/XhAVKlpIxWPpgRTnlgZssJrF0m6YtRA0QDWgsBNT12uZM6LPRydR7ip405Y3t1LamD8cP2TZFEDZFBf5ApcA== + version "1.0.30001639" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001639.tgz#972b3a6adeacdd8f46af5fc7f771e9639f6c1521" + integrity sha512-eFHflNTBIlFwP2AIKaYuBQN/apnUoKNhBdza8ZnW/h2di4LCZ4xFqYlxUxo+LQ76KFI1PGcC1QDxMbxTZpSCAg== capital-case@^1.0.4: version "1.0.4" @@ -20357,7 +20357,7 @@ string-similarity@^3.0.0: resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-3.0.0.tgz#07b0bc69fae200ad88ceef4983878d03793847c7" integrity sha512-7kS7LyTp56OqOI2BDWQNVnLX/rCxIQn+/5M0op1WV6P8Xx6TZNdajpuqQdiJ7Xx+p1C5CsWMvdiBp9ApMhxzEQ== -"string-width-cjs@npm:string-width@^4.2.0": +"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -20375,15 +20375,6 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" -"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - string-width@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.0.tgz#5ab00980cfb29f43e736b113a120a73a0fb569d3" @@ -20460,7 +20451,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1": +"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -20474,13 +20465,6 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -22328,7 +22312,7 @@ workbox-window@6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.5.4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -22346,15 +22330,6 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From ae577e28a791350f20dbd29779511f75f439224c Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 2 Jul 2024 09:08:02 +0200 Subject: [PATCH 301/529] fix: Allow to start recurrence for existing Standups (#9909) --- .../public/mutations/updateRecurrenceSettings.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts index bbcc52b08ad..d3ce01392c1 100644 --- a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts +++ b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts @@ -139,16 +139,17 @@ const updateRecurrenceSettings: MutationResolvers['updateRecurrenceSettings'] = if (!meeting) { return standardError(new Error('Meeting not found'), {userId: viewerId}) } - const {teamId, meetingSeriesId} = meeting - if (!meetingSeriesId) { - return standardError(new Error('Meeting is not recurring'), {userId: viewerId}) - } + const {teamId, meetingType, meetingSeriesId} = meeting if (!isTeamMember(authToken, teamId)) { return standardError(new Error('Team not found'), {userId: viewerId}) } - if (meeting.meetingSeriesId) { - const meetingSeries = await dataLoader.get('meetingSeries').loadNonNull(meeting.meetingSeriesId) + if (meetingType !== 'teamPrompt' && meetingType !== 'retrospective') { + return standardError(new Error('Recurring meeting type is not implemented'), {userId: viewerId}) + } + + if (meetingSeriesId) { + const meetingSeries = await dataLoader.get('meetingSeries').loadNonNull(meetingSeriesId) const {gcalSeriesId, teamId, facilitatorId, recurrenceRule} = meetingSeries if (!recurrenceSettings.rrule) { From d17345ff90bc88cba673fbd3a9efb76848556449 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 2 Jul 2024 09:08:22 +0200 Subject: [PATCH 302/529] chore: Fix debug output when retrying after Cloudflare error (#9912) --- packages/server/fileStorage/S3FileStoreManager.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/fileStorage/S3FileStoreManager.ts b/packages/server/fileStorage/S3FileStoreManager.ts index f68cde04464..9746ce38cb6 100644 --- a/packages/server/fileStorage/S3FileStoreManager.ts +++ b/packages/server/fileStorage/S3FileStoreManager.ts @@ -14,7 +14,7 @@ class CloudflareRetry extends StandardRetryStrategy { const status = errorInfo.error?.$response?.statusCode if (status && status >= 520 && status < 530) { const date = errorInfo.error?.$response?.headers?.date - console.log('Cloudflare error', { + console.log('Retrying after Cloudflare error', { status, date: date && new Date(date).toISOString(), path: errorInfo.error?.$response?.body?.req?.path From 28a500b8eb256924c1f8194bee4f7b6275f3fd10 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 2 Jul 2024 09:29:48 +0100 Subject: [PATCH 303/529] chore(release): release v7.38.0 (#9905) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 23 +++++++++++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 35 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6bdf9ff88da..ba8fdca4366 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.37.8" + ".": "7.38.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 79484a06a85..3b4868aa5c7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,29 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.38.0](https://github.com/ParabolInc/parabol/compare/v7.37.8...v7.38.0) (2024-07-02) + + +### Added + +* Add search template mutation ([#9802](https://github.com/ParabolInc/parabol/issues/9802)) ([486f670](https://github.com/ParabolInc/parabol/commit/486f6703a997c827ffcd157c13e3fa1321e977ed)) + + +### Fixed + +* Allow to start recurrence for existing Standups ([#9909](https://github.com/ParabolInc/parabol/issues/9909)) ([ae577e2](https://github.com/ParabolInc/parabol/commit/ae577e28a791350f20dbd29779511f75f439224c)) +* Avoid adding embedding jobs without metadata id ([#9881](https://github.com/ParabolInc/parabol/issues/9881)) ([4e2fec1](https://github.com/ParabolInc/parabol/commit/4e2fec1ea0e1b9eb442a799b0d3a2d66ef40772b)) +* bugs during upgrade/downgrade ([#9919](https://github.com/ParabolInc/parabol/issues/9919)) ([c67a6a8](https://github.com/ParabolInc/parabol/commit/c67a6a87efc35fcb8f41e388d451b1a784a045e2)) +* remove ai from summary url if no ai env var ([#9895](https://github.com/ParabolInc/parabol/issues/9895)) ([4413142](https://github.com/ParabolInc/parabol/commit/4413142e55c94388e1cfe78503cc420d2945c334)) +* remove Organization.teams field from gql ([#9918](https://github.com/ParabolInc/parabol/issues/9918)) ([55b2dfb](https://github.com/ParabolInc/parabol/commit/55b2dfb425f89135a39bb38b02b5bc955ea2e5a0)) +* speed up team upgrade ([#9902](https://github.com/ParabolInc/parabol/issues/9902)) ([d91f649](https://github.com/ParabolInc/parabol/commit/d91f649918ed471bc37332abdefcc6e07d737e6d)) + + +### Changed + +* Fix debug output when retrying after Cloudflare error ([#9912](https://github.com/ParabolInc/parabol/issues/9912)) ([d17345f](https://github.com/ParabolInc/parabol/commit/d17345ff90bc88cba673fbd3a9efb76848556449)) +* remove contact us message for team users that want to downgrade ([#9903](https://github.com/ParabolInc/parabol/issues/9903)) ([7e90ac2](https://github.com/ParabolInc/parabol/commit/7e90ac2d1ba2144271779982272160c7c1b0df33)) + ## [7.37.8](https://github.com/ParabolInc/parabol/compare/v7.37.7...v7.37.8) (2024-06-27) diff --git a/package.json b/package.json index 8d0e796e8f0..96a4276a340 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.37.8", + "version": "7.38.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index a88696f59fd..2b1996dac6f 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.37.8", + "version": "7.38.0", "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.37.8" + "parabol-server": "7.38.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 70f58d832d9..3ed2625617a 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.37.8", + "version": "7.38.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index ccb4172b3cd..394998ad4b6 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.37.8", + "version": "7.38.0", "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 1aaf90bc810..e81c37e3e06 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.37.8", + "version": "7.38.0", "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.37.8", - "parabol-server": "7.37.8", + "parabol-client": "7.38.0", + "parabol-server": "7.38.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 16caa3e64cd..919495c659a 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.37.8", + "version": "7.38.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 04b87f3a2a6..8c4d82c61cd 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.37.8", + "version": "7.38.0", "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.37.8", + "parabol-client": "7.38.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 04bfa6c69c07be8a190542db4a5fb907e43d67ad Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 2 Jul 2024 14:24:37 +0200 Subject: [PATCH 304/529] chore: Show only available integrations (#9908) --- .env.example | 2 + .../client/components/AddToGitHubMenuItem.tsx | 1 + .../client/components/AddToJiraMenuItem.tsx | 1 + 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 + packages/client/types/modules.d.ts | 2 + .../client/utils/AtlassianClientManager.ts | 2 + packages/client/utils/GitHubClientManager.ts | 1 + packages/client/utils/SlackClientManager.ts | 1 + .../toolboxSrc/applyEnvVarsToClientAssets.ts | 2 + scripts/webpack/dev.client.config.js | 2 + 17 files changed, 71 insertions(+), 11 deletions(-) diff --git a/.env.example b/.env.example index 71aed091304..9e8e1932cd0 100644 --- a/.env.example +++ b/.env.example @@ -123,6 +123,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/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 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 ( <> { 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/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, From 3f2ca482aef98cf07a7f27b6a3872c9505735334 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 2 Jul 2024 16:49:14 +0200 Subject: [PATCH 305/529] fix: Allow starting recurring meetings without GCal (#9920) --- .../components/ActivityLibrary/ScheduleMeetingButton.tsx | 7 +------ packages/client/components/ScheduleDialog.tsx | 5 ++--- 2 files changed, 3 insertions(+), 9 deletions(-) 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/ScheduleDialog.tsx b/packages/client/components/ScheduleDialog.tsx index b3c6d5f6724..8f26e708164 100644 --- a/packages/client/components/ScheduleDialog.tsx +++ b/packages/client/components/ScheduleDialog.tsx @@ -139,12 +139,11 @@ export const ScheduleDialog = (props: Props) => { } } + 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}
Date: Tue, 2 Jul 2024 17:07:24 +0200 Subject: [PATCH 306/529] chore: Read Gitlab server URL from env for prime integrations (#9910) --- .env.example | 1 + scripts/toolboxSrc/primeIntegrations.ts | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.env.example b/.env.example index 9e8e1932cd0..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='' 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) From 1dce6366ae968718dfa72c44553201a016863213 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 2 Jul 2024 12:51:49 -0700 Subject: [PATCH 307/529] fix: connectionContext always available (#9923) Signed-off-by: Matt Krick --- packages/server/socketHandlers/handleOpen.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) 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}) From 068f91e33e0d3c160c67f52f8008a177eb5c326d Mon Sep 17 00:00:00 2001 From: Rafael Romero Date: Wed, 3 Jul 2024 11:55:31 +0100 Subject: [PATCH 308/529] chore(gitignore): ignore anything on the backups folder --- .gitignore | 1 + 1 file changed, 1 insertion(+) 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/ From 4663e9ea28f36dcf10bfe21347912865d22a8872 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 3 Jul 2024 08:04:25 -0700 Subject: [PATCH 309/529] fix: handle failed 3DS payments (#9924) Signed-off-by: Matt Krick --- .../components/OrgBilling/BillingForm.tsx | 33 +++++++------------ .../server/billing/stripeWebhookHandler.ts | 13 +------- .../mutations/helpers/oldUpgradeToTeamTier.ts | 2 +- .../mutations/draftEnterpriseInvoice.ts | 1 + ...ription.ts => stripeCreateSubscription.ts} | 14 ++++++-- .../graphql/private/typeDefs/_legacy.graphql | 4 +-- .../mutations/createStripeSubscription.ts | 4 ++- packages/server/utils/stripe/StripeManager.ts | 30 +++++++++-------- 8 files changed, 48 insertions(+), 53 deletions(-) rename packages/server/graphql/private/mutations/{stripeUpdateSubscription.ts => stripeCreateSubscription.ts} (66%) diff --git a/packages/client/modules/userDashboard/components/OrgBilling/BillingForm.tsx b/packages/client/modules/userDashboard/components/OrgBilling/BillingForm.tsx index aeb45f9a9e4..e86bb080242 100644 --- a/packages/client/modules/userDashboard/components/OrgBilling/BillingForm.tsx +++ b/packages/client/modules/userDashboard/components/OrgBilling/BillingForm.tsx @@ -76,10 +76,8 @@ const BillingForm = (props: Props) => { 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/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/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/private/mutations/draftEnterpriseInvoice.ts b/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts index aa0ea60f306..258c70bdeaf 100644 --- a/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts +++ b/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts @@ -101,6 +101,7 @@ 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 r.table('Organization').get(orgId).update({stripeId: customer.id}).run() customerId = customer.id } else { diff --git a/packages/server/graphql/private/mutations/stripeUpdateSubscription.ts b/packages/server/graphql/private/mutations/stripeCreateSubscription.ts similarity index 66% rename from packages/server/graphql/private/mutations/stripeUpdateSubscription.ts rename to packages/server/graphql/private/mutations/stripeCreateSubscription.ts index 12831a9fab4..55d0a872078 100644 --- a/packages/server/graphql/private/mutations/stripeUpdateSubscription.ts +++ b/packages/server/graphql/private/mutations/stripeCreateSubscription.ts @@ -1,9 +1,10 @@ +import Stripe from 'stripe' import getRethink from '../../../database/rethinkDriver' 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 +29,15 @@ 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 r .table('Organization') .get(orgId) @@ -39,4 +49,4 @@ const stripeUpdateSubscription: MutationResolvers['stripeUpdateSubscription'] = return true } -export default stripeUpdateSubscription +export default stripeCreateSubscription 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/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/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( From 6bb5fb2c2cfc0ba77679633acd2a21ac04fcbfd3 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 3 Jul 2024 18:20:44 -0700 Subject: [PATCH 310/529] chore(rethinkdb): Organization: Phase 1 (#9883) Signed-off-by: Matt Krick --- .../indexing/orgIdsWithFeatureFlag.ts | 15 ----- .../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 ++++++++++------ .../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/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 | 20 +++++++ .../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 ++ .../mutations/stripeCreateSubscription.ts | 9 +++ .../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 +++++++++----- .../mutations/acceptRequestToJoinDomain.ts | 9 ++- .../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 +- .../server/postgres/helpers/toCreditCard.ts | 6 ++ .../helpers/toGoogleAnalyzedEntity.ts | 6 ++ .../1719435764047_Organization-phase1.ts | 55 +++++++++++++++++++ .../safeArchiveEmptyStarterOrganization.ts | 8 ++- .../isRequestToJoinDomainAllowed.test.ts | 7 ++- scripts/toolboxSrc/setIsEnterprise.ts | 11 ++-- 49 files changed, 417 insertions(+), 156 deletions(-) delete mode 100644 packages/embedder/indexing/orgIdsWithFeatureFlag.ts 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/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/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/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/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 258c70bdeaf..ab2da48431c 100644 --- a/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts +++ b/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts @@ -102,6 +102,11 @@ const draftEnterpriseInvoice: MutationResolvers['draftEnterpriseInvoice'] = asyn // 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 { @@ -116,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/stripeCreateSubscription.ts b/packages/server/graphql/private/mutations/stripeCreateSubscription.ts index 55d0a872078..4acbd075ce5 100644 --- a/packages/server/graphql/private/mutations/stripeCreateSubscription.ts +++ b/packages/server/graphql/private/mutations/stripeCreateSubscription.ts @@ -1,5 +1,6 @@ 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' @@ -38,6 +39,14 @@ const stripeCreateSubscription: MutationResolvers['stripeCreateSubscription'] = 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) 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/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/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/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/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/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) From 7971e5c0125c30ae6b7a9c891bfda75e3d6e35e4 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 3 Jul 2024 18:25:19 -0700 Subject: [PATCH 311/529] chore(release): release v7.38.1 (#9922) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 17 +++++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 29 insertions(+), 12 deletions(-) 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/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/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/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", From 5baad4c9843a0189f40decfbcbd8ea7810599ea1 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 3 Jul 2024 18:35:03 -0700 Subject: [PATCH 312/529] chore(rethinkdb): Organization: Phase 2 (#9931) Signed-off-by: Matt Krick --- .../mutations/checkRethinkPgEquality.ts | 55 ++++---- .../1720026588542_Organization-phase2.ts | 121 ++++++++++++++++++ .../postgres/utils/rethinkEqualityFns.ts | 26 +++- 3 files changed, 176 insertions(+), 26 deletions(-) create mode 100644 packages/server/postgres/migrations/1720026588542_Organization-phase2.ts diff --git a/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts b/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts index 954411c9f50..a01373429eb 100644 --- a/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts +++ b/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts @@ -4,12 +4,13 @@ import getKysely from '../../../postgres/getKysely' import {checkRowCount, checkTableEq} from '../../../postgres/utils/checkEqBase' import { compareDateAlmostEqual, - compareOptionalPlaintextContent, - compareRValOptionalPluckedArray, + compareRValOptionalPluckedObject, + compareRValStringAsNumber, compareRValUndefinedAsEmptyArray, + compareRValUndefinedAsFalse, compareRValUndefinedAsNull, compareRValUndefinedAsNullAndTruncateRVal, - compareRealNumber, + compareRValUndefinedAsZero, defaultEqFn } from '../../../postgres/utils/rethinkEqualityFns' import {MutationResolvers} from '../resolverTypes' @@ -36,11 +37,11 @@ const checkRethinkPgEquality: MutationResolvers['checkRethinkPgEquality'] = asyn ) => { const r = await getRethink() - if (tableName === 'RetroReflection') { + if (tableName === 'Organization') { const rowCountResult = await checkRowCount(tableName) const rethinkQuery = (updatedAt: Date, id: string | number) => { return r - .table('RetroReflection' as any) + .table('Organization' as any) .between([updatedAt, id], [r.maxval, r.maxval], { index: 'updatedAtId', leftBound: 'open', @@ -50,12 +51,9 @@ const checkRethinkPgEquality: MutationResolvers['checkRethinkPgEquality'] = asyn } const pgQuery = async (ids: string[]) => { return getKysely() - .selectFrom('RetroReflection') + .selectFrom('Organization') .selectAll() - .select(({fn}) => [ - fn('to_json', ['entities']).as('entities'), - fn('to_json', ['reactjis']).as('reactjis') - ]) + .select(({fn}) => [fn('to_json', ['creditCard']).as('creditCard')]) .where('id', 'in', ids) .execute() } @@ -64,23 +62,30 @@ const checkRethinkPgEquality: MutationResolvers['checkRethinkPgEquality'] = asyn pgQuery, { id: defaultEqFn, + activeDomain: compareRValUndefinedAsNullAndTruncateRVal(100), + isActiveDomainTouched: compareRValUndefinedAsFalse, + creditCard: compareRValOptionalPluckedObject({ + brand: compareRValUndefinedAsNull, + expiry: compareRValUndefinedAsNull, + last4: compareRValStringAsNumber + }), createdAt: defaultEqFn, + name: compareRValUndefinedAsNullAndTruncateRVal(100), + payLaterClickCount: compareRValUndefinedAsZero, + periodEnd: compareRValUndefinedAsNull, + periodStart: compareRValUndefinedAsNull, + picture: compareRValUndefinedAsNull, + showConversionModal: compareRValUndefinedAsFalse, + stripeId: compareRValUndefinedAsNull, + stripeSubscriptionId: compareRValUndefinedAsNull, + upcomingInvoiceEmailSentAt: compareRValUndefinedAsNull, + tier: defaultEqFn, + tierLimitExceededAt: compareRValUndefinedAsNull, + trialStartDate: compareRValUndefinedAsNull, + scheduledLockAt: compareRValUndefinedAsNull, + lockedAt: compareRValUndefinedAsNull, updatedAt: compareDateAlmostEqual, - isActive: defaultEqFn, - meetingId: defaultEqFn, - promptId: defaultEqFn, - creatorId: compareRValUndefinedAsNull, - sortOrder: defaultEqFn, - reflectionGroupId: defaultEqFn, - content: compareRValUndefinedAsNullAndTruncateRVal(2000, 0.19), - plaintextContent: compareOptionalPlaintextContent, - entities: compareRValOptionalPluckedArray({ - name: defaultEqFn, - salience: compareRealNumber, - lemma: compareRValUndefinedAsNull - }), - reactjis: compareRValUndefinedAsEmptyArray, - sentimentScore: compareRValUndefinedAsNull + featureFlags: compareRValUndefinedAsEmptyArray }, maxErrors ) diff --git a/packages/server/postgres/migrations/1720026588542_Organization-phase2.ts b/packages/server/postgres/migrations/1720026588542_Organization-phase2.ts new file mode 100644 index 00000000000..78776890b41 --- /dev/null +++ b/packages/server/postgres/migrations/1720026588542_Organization-phase2.ts @@ -0,0 +1,121 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +const toCreditCard = (creditCard: any) => { + if (!creditCard) return null + return sql`(select json_populate_record(null::"CreditCard", ${JSON.stringify(creditCard)}))` +} + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + try { + console.log('Adding index') + await r + .table('Organization') + .indexCreate('updatedAtId', (row: any) => [row('updatedAt'), row('id')]) + .run() + await r.table('Organization').indexWait().run() + } catch { + // index already exists + } + console.log('Adding index complete') + const MAX_PG_PARAMS = 65545 + const PG_COLS = [ + 'id', + 'activeDomain', + 'isActiveDomainTouched', + 'creditCard', + 'createdAt', + 'name', + 'payLaterClickCount', + 'periodEnd', + 'periodStart', + 'picture', + 'showConversionModal', + 'stripeId', + 'stripeSubscriptionId', + 'upcomingInvoiceEmailSentAt', + 'tier', + 'tierLimitExceededAt', + 'trialStartDate', + 'scheduledLockAt', + 'lockedAt', + 'updatedAt', + 'featureFlags' + ] as const + type Organization = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + + let curUpdatedAt = r.minval + let curId = r.minval + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, curUpdatedAt, curId) + const rawRowsToInsert = (await r + .table('Organization') + .between([curUpdatedAt, curId], [r.maxval, r.maxval], { + index: 'updatedAtId', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'updatedAtId'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as Organization[] + + const rowsToInsert = rawRowsToInsert.map((row) => ({ + ...row, + activeDomain: row.activeDomain?.slice(0, 100) ?? null, + name: row.name.slice(0, 100), + creditCard: toCreditCard(row.creditCard) + })) + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curUpdatedAt = lastRow.updatedAt + curId = lastRow.id + try { + await pg + .insertInto('Organization') + .values(rowsToInsert) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + await Promise.all( + rowsToInsert.map(async (row) => { + try { + await pg + .insertInto('Organization') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + console.log(e, row) + } + }) + ) + } + } +} + +export async function down() { + await connectRethinkDB() + try { + await r.table('Organization').indexDrop('updatedAtId').run() + } catch { + // index already dropped + } + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await pg.deleteFrom('Organization').execute() +} diff --git a/packages/server/postgres/utils/rethinkEqualityFns.ts b/packages/server/postgres/utils/rethinkEqualityFns.ts index 8a7282be5f4..3d63e8381df 100644 --- a/packages/server/postgres/utils/rethinkEqualityFns.ts +++ b/packages/server/postgres/utils/rethinkEqualityFns.ts @@ -4,6 +4,7 @@ import stringSimilarity from 'string-similarity' export const defaultEqFn = (a: unknown, b: unknown) => { if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime() if (Array.isArray(a) && Array.isArray(b)) return JSON.stringify(a) === JSON.stringify(b) + if (typeof a === 'object' && typeof b === 'object') return JSON.stringify(a) === JSON.stringify(b) return a === b } export const compareDateAlmostEqual = (rVal: unknown, pgVal: unknown) => { @@ -30,11 +31,33 @@ export const compareRValUndefinedAsFalse = (rVal: unknown, pgVal: unknown) => { return normalizedRVal === pgVal } +export const compareRValUndefinedAsZero = (rVal: unknown, pgVal: unknown) => { + const normalizedRVal = rVal === undefined ? 0 : rVal + return normalizedRVal === pgVal +} + export const compareRValUndefinedAsEmptyArray = (rVal: unknown, pgVal: unknown) => { const normalizedRVal = rVal === undefined ? [] : rVal return defaultEqFn(normalizedRVal, pgVal) } +export const compareRValStringAsNumber = (rVal: unknown, pgVal: unknown) => { + const normalizedRVal = Number(rVal) + return defaultEqFn(normalizedRVal, pgVal) +} + +export const compareRValOptionalPluckedObject = + (pluckFields: Record) => (rVal: unknown, pgVal: unknown) => { + if (!rVal && !pgVal) return true + const rValObj = rVal || {} + const pgValItem = pgVal || {} + return Object.keys(pluckFields).every((prop) => { + const eqFn = pluckFields[prop]! + const rValItemProp = rValObj[prop as keyof typeof rValObj] + const pgValItemProp = pgValItem[prop as keyof typeof pgValItem] + return eqFn(rValItemProp, pgValItemProp) + }) + } export const compareRValOptionalPluckedArray = (pluckFields: Record) => (rVal: unknown, pgVal: unknown) => { const rValArray = Array.isArray(rVal) ? rVal : [] @@ -56,7 +79,8 @@ export const compareRValOptionalPluckedArray = } export const compareRValUndefinedAsNullAndTruncateRVal = - (length: number, similarity?: number) => (rVal: unknown, pgVal: unknown) => { + (length: number, similarity = 1) => + (rVal: unknown, pgVal: unknown) => { const truncatedRVal = typeof rVal === 'string' ? rVal.slice(0, length) : rVal const normalizedRVal = truncatedRVal === undefined ? null : truncatedRVal if ( From 0a60ff9b59b33d65ac532d80799f3e7425ee5e54 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Thu, 4 Jul 2024 12:33:40 +0200 Subject: [PATCH 313/529] fix: Read embedder URL from env (#9936) --- packages/server/graphql/public/types/User.ts | 21 +++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/packages/server/graphql/public/types/User.ts b/packages/server/graphql/public/types/User.ts index 7947deed713..df9e22b8f17 100644 --- a/packages/server/graphql/public/types/User.ts +++ b/packages/server/graphql/public/types/User.ts @@ -28,6 +28,20 @@ import {UserResolvers} from '../resolverTypes' declare const __PRODUCTION__: string +const MODEL = 'Embeddings_ember_1' +const EMBED_URL = (() => { + try { + const availableModels = + process.env.AI_EMBEDDING_MODELS && JSON.parse(process.env.AI_EMBEDDING_MODELS) + return availableModels.find( + ({model}: {model?: string}) => model?.split(':')[1] === 'llmrails/ember-v1' + )?.url + } catch { + return undefined + } +})() +const SIMILARITY_THRESHOLD = 0.5 + const User: UserResolvers = { activity: async (_source, {activityId}, {dataLoader}) => { const activity = await dataLoader.get('meetingTemplates').load(activityId) @@ -186,7 +200,7 @@ const User: UserResolvers = { return connectionFromTemplateArray(allActivities, first, after) }, templateSearch: async ({id: userId}, {search}, {authToken, dataLoader}) => { - if (!search) return [] + if (!search || !EMBED_URL) return [] const viewerId = getUserId(authToken) const user = await dataLoader.get('users').loadNonNull(userId) const teamIds = @@ -203,16 +217,13 @@ const User: UserResolvers = { // all team ids which could have accessible templates const allTeamIds = ['aGhostTeam', ...allOrgTeams.map(({id}) => id)] - const response = await fetch('http://localhost:3040/embed', { + const response = await fetch(EMBED_URL, { method: 'POST', body: JSON.stringify({inputs: search}), headers: {'Content-Type': 'application/json'} }) const data = await response.json() - const MODEL = 'Embeddings_ember_1' - const SIMILARITY_THRESHOLD = 0.5 - const pg = getKysely() const similarEmbeddings = await pg .with('Model', (qc) => From 9081e38f2f44c87941abf35e97b30942f6c9ccd7 Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Thu, 4 Jul 2024 19:48:32 +0100 Subject: [PATCH 314/529] chore(client): when a release happens, links to the specific tag version (#9937) --- packages/client/hooks/useServiceWorkerUpdater.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/hooks/useServiceWorkerUpdater.ts b/packages/client/hooks/useServiceWorkerUpdater.ts index 5b393821088..cf21d4751e7 100644 --- a/packages/client/hooks/useServiceWorkerUpdater.ts +++ b/packages/client/hooks/useServiceWorkerUpdater.ts @@ -21,7 +21,7 @@ const useServiceWorkerUpdater = () => { action: { label: `See what's changed`, callback: () => { - const url = 'https://github.com/ParabolInc/parabol/releases' + const url = `https://github.com/ParabolInc/parabol/releases/tag/v${__APP_VERSION__}` window.open(url, '_blank', 'noopener')?.focus() } } From ef6e62629a9eb1b9b4aec75b83ca004cf02919fc Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Mon, 8 Jul 2024 11:44:14 +0100 Subject: [PATCH 315/529] chore(postgres): Postgres upgraded to 15.7 and pgvector to 0.7.0 (#9941) --- .github/workflows/build.yml | 2 +- .github/workflows/test.yml | 2 +- docker/images/postgres/Dockerfile | 6 +++--- docker/stacks/single-tenant-host/docker-compose.yml | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4da61b03eac..d16c22e623f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: id-token: "write" services: postgres: - image: pgvector/pgvector:0.6.2-pg15 + image: pgvector/pgvector:0.7.0-pg15 # This env variables must be the same in the file PARABOL_BUILD_ENV_PATH env: POSTGRES_PASSWORD: "temppassword" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 171e080964f..1c09d7111d6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: id-token: "write" services: postgres: - image: pgvector/pgvector:0.6.2-pg15 + image: pgvector/pgvector:0.7.0-pg15 # This env variables must be the same in the file PARABOL_BUILD_ENV_PATH env: POSTGRES_PASSWORD: "temppassword" diff --git a/docker/images/postgres/Dockerfile b/docker/images/postgres/Dockerfile index 3ce8d5d5185..5ef9848889a 100644 --- a/docker/images/postgres/Dockerfile +++ b/docker/images/postgres/Dockerfile @@ -1,8 +1,8 @@ -FROM postgres:15.4 -ARG PGVECTOR_VERSION=v0.6.1 +FROM postgres:15.7 +ARG PGVECTOR_VERSION=v0.7.0 ARG PSQL_MAJOR_VERSION=15 -ADD extensions /extensions +COPY extensions /extensions RUN apt-get update && apt-get install -y \ build-essential \ diff --git a/docker/stacks/single-tenant-host/docker-compose.yml b/docker/stacks/single-tenant-host/docker-compose.yml index 49a918e985d..93d0ebf8ac1 100644 --- a/docker/stacks/single-tenant-host/docker-compose.yml +++ b/docker/stacks/single-tenant-host/docker-compose.yml @@ -17,7 +17,7 @@ services: postgres: container_name: postgres profiles: ["databases"] - image: pgvector/pgvector:0.6.2-pg15 + image: pgvector/pgvector:0.7.0-pg15 restart: always env_file: .env environment: From 46770a048bfd4cd21c0e1cab89abdc01f8a79af3 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Mon, 8 Jul 2024 11:53:55 +0100 Subject: [PATCH 316/529] chore(release): release v7.38.2 (#9935) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 14 ++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 26 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index aa061992a46..e86a0483340 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.38.1" + ".": "7.38.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 63e36d1a0d8..366de6d9689 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.38.2](https://github.com/ParabolInc/parabol/compare/v7.38.1...v7.38.2) (2024-07-08) + + +### Fixed + +* Read embedder URL from env ([#9936](https://github.com/ParabolInc/parabol/issues/9936)) ([0a60ff9](https://github.com/ParabolInc/parabol/commit/0a60ff9b59b33d65ac532d80799f3e7425ee5e54)) + + +### Changed + +* **client:** when a release happens, links to the specific tag version ([#9937](https://github.com/ParabolInc/parabol/issues/9937)) ([9081e38](https://github.com/ParabolInc/parabol/commit/9081e38f2f44c87941abf35e97b30942f6c9ccd7)) +* **postgres:** Postgres upgraded to 15.7 and pgvector to 0.7.0 ([#9941](https://github.com/ParabolInc/parabol/issues/9941)) ([ef6e626](https://github.com/ParabolInc/parabol/commit/ef6e62629a9eb1b9b4aec75b83ca004cf02919fc)) +* **rethinkdb:** Organization: Phase 2 ([#9931](https://github.com/ParabolInc/parabol/issues/9931)) ([5baad4c](https://github.com/ParabolInc/parabol/commit/5baad4c9843a0189f40decfbcbd8ea7810599ea1)) + ## [7.38.1](https://github.com/ParabolInc/parabol/compare/v7.38.0...v7.38.1) (2024-07-04) diff --git a/package.json b/package.json index 24fd810e27b..a146f79baa4 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.1", + "version": "7.38.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 58c4e103b81..77014d2c977 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.38.1", + "version": "7.38.2", "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.1" + "parabol-server": "7.38.2" } } diff --git a/packages/client/package.json b/packages/client/package.json index d65de7ee356..1d1568631b4 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.1", + "version": "7.38.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 0ec59146240..ddee4edcb19 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.38.1", + "version": "7.38.2", "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 c3f2dd4dc89..7956b82562d 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.38.1", + "version": "7.38.2", "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.1", - "parabol-server": "7.38.1", + "parabol-client": "7.38.2", + "parabol-server": "7.38.2", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 3ae4007d8e2..b99877d08d6 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.1", + "version": "7.38.2", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 8f51b4b15fd..06fb050e2e1 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.1", + "version": "7.38.2", "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.1", + "parabol-client": "7.38.2", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 151b0298013837c912bd2c58226519196d800a94 Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Tue, 9 Jul 2024 20:30:00 +0100 Subject: [PATCH 317/529] chore(rethinkdb): phase 4 of RetroReflection, RetroReflectionGroup and TimelineEvent (#9943) --- .../1720517256158_TimelineEvent-phase4.ts | 15 +++++++++++++++ .../1720517631974_RetroReflectionGroup-phase4.ts | 15 +++++++++++++++ .../1720518455750_RetroReflection-phase4.ts | 15 +++++++++++++++ 3 files changed, 45 insertions(+) create mode 100644 packages/server/postgres/migrations/1720517256158_TimelineEvent-phase4.ts create mode 100644 packages/server/postgres/migrations/1720517631974_RetroReflectionGroup-phase4.ts create mode 100644 packages/server/postgres/migrations/1720518455750_RetroReflection-phase4.ts diff --git a/packages/server/postgres/migrations/1720517256158_TimelineEvent-phase4.ts b/packages/server/postgres/migrations/1720517256158_TimelineEvent-phase4.ts new file mode 100644 index 00000000000..a63a8c23ca1 --- /dev/null +++ b/packages/server/postgres/migrations/1720517256158_TimelineEvent-phase4.ts @@ -0,0 +1,15 @@ +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' + +export async function up() { + await connectRethinkDB() + try { + await r.tableDrop('TimelineEvent').run() + } catch (error) { + // table already dropped + } +} + +export async function down() { + // No migration down +} diff --git a/packages/server/postgres/migrations/1720517631974_RetroReflectionGroup-phase4.ts b/packages/server/postgres/migrations/1720517631974_RetroReflectionGroup-phase4.ts new file mode 100644 index 00000000000..20b56b3c712 --- /dev/null +++ b/packages/server/postgres/migrations/1720517631974_RetroReflectionGroup-phase4.ts @@ -0,0 +1,15 @@ +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' + +export async function up() { + await connectRethinkDB() + try { + await r.tableDrop('RetroReflectionGroup').run() + } catch (error) { + // table already dropped + } +} + +export async function down() { + // No migration down +} diff --git a/packages/server/postgres/migrations/1720518455750_RetroReflection-phase4.ts b/packages/server/postgres/migrations/1720518455750_RetroReflection-phase4.ts new file mode 100644 index 00000000000..120661226c6 --- /dev/null +++ b/packages/server/postgres/migrations/1720518455750_RetroReflection-phase4.ts @@ -0,0 +1,15 @@ +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' + +export async function up() { + await connectRethinkDB() + try { + await r.tableDrop('RetroReflection').run() + } catch (error) { + // table already dropped + } +} + +export async function down() { + // No migration down +} From 3cdf5d40c6bd8ba6872eadeb05685b2d6111fd0e Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 10:51:23 +0100 Subject: [PATCH 318/529] chore(release): release v7.38.3 (#9946) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e86a0483340..093037ef878 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.38.2" + ".": "7.38.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 366de6d9689..4fe7fac4075 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.38.3](https://github.com/ParabolInc/parabol/compare/v7.38.2...v7.38.3) (2024-07-09) + + +### Changed + +* **rethinkdb:** phase 4 of RetroReflection, RetroReflectionGroup and TimelineEvent ([#9943](https://github.com/ParabolInc/parabol/issues/9943)) ([151b029](https://github.com/ParabolInc/parabol/commit/151b0298013837c912bd2c58226519196d800a94)) + ## [7.38.2](https://github.com/ParabolInc/parabol/compare/v7.38.1...v7.38.2) (2024-07-08) diff --git a/package.json b/package.json index a146f79baa4..c9afd47f822 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.2", + "version": "7.38.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 77014d2c977..38550d8eb9b 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.38.2", + "version": "7.38.3", "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.2" + "parabol-server": "7.38.3" } } diff --git a/packages/client/package.json b/packages/client/package.json index 1d1568631b4..d224e55adfb 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.2", + "version": "7.38.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index ddee4edcb19..5a1782285e2 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.38.2", + "version": "7.38.3", "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 7956b82562d..332e2b9c756 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.38.2", + "version": "7.38.3", "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.2", - "parabol-server": "7.38.2", + "parabol-client": "7.38.3", + "parabol-server": "7.38.3", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index b99877d08d6..f227dc1fe13 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.2", + "version": "7.38.3", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 06fb050e2e1..3ff338d2374 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.2", + "version": "7.38.3", "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.2", + "parabol-client": "7.38.3", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 70084f86b1832dc087b0bf7eb279253b61dacf01 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 10 Jul 2024 11:06:00 -0700 Subject: [PATCH 319/529] chore(rethinkdb): Organization: Phase 3 (#9933) Signed-off-by: Matt Krick --- codegen.json | 9 +- .../email/components/UpcomingInvoiceEmail.tsx | 68 --- packages/server/__tests__/common.ts | 24 + packages/server/__tests__/globalSetup.ts | 8 +- .../server/billing/helpers/adjustUserCount.ts | 14 +- .../server/billing/helpers/fetchAllLines.ts | 2 +- .../server/billing/helpers/generateInvoice.ts | 5 +- .../helpers/generateUpcomingInvoice.ts | 2 +- .../handleEnterpriseOrgQuantityChanges.ts | 6 +- .../helpers/handleTeamOrgQuantityChanges.ts | 4 +- .../server/billing/helpers/teamLimitsCheck.ts | 29 +- .../billing/helpers/terminateSubscription.ts | 47 +- .../helpers/updateSubscriptionQuantity.ts | 7 +- packages/server/database/rethinkDriver.ts | 5 - packages/server/database/types/Invoice.ts | 4 +- .../types/NotificationPaymentRejected.ts | 4 +- .../types/NotificationTeamsLimitExceeded.ts | 4 +- .../types/NotificationTeamsLimitReminder.ts | 4 +- .../database/types/processTeamsLimitsJob.ts | 15 +- .../__tests__/isOrgVerified.test.ts | 130 ++--- .../server/dataloader/customLoaderMakers.ts | 77 +-- .../dataloader/foreignKeyLoaderMakers.ts | 13 +- .../dataloader/primaryKeyLoaderMakers.ts | 78 ++- .../rethinkForeignKeyLoaderMakers.ts | 8 - .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../email/UpcomingInvoiceEmailTemplate.tsx | 37 -- packages/server/graphql/mutations/addTeam.ts | 4 +- .../graphql/mutations/archiveOrganization.ts | 2 +- .../graphql/mutations/downgradeToStarter.ts | 2 +- .../mutations/helpers/canAccessAISummary.ts | 6 +- .../graphql/mutations/helpers/createNewOrg.ts | 5 +- .../mutations/helpers/createTeamAndLeader.ts | 2 +- .../endMeeting/sendNewMeetingSummary.ts | 2 +- .../mutations/helpers/generateGroups.ts | 2 +- .../mutations/helpers/hideConversionModal.ts | 9 +- .../mutations/helpers/inviteToTeamHelper.ts | 2 +- .../mutations/helpers/isStartMeetingLocked.ts | 2 +- .../helpers/notifications/MSTeamsNotifier.ts | 4 +- .../notifications/MattermostNotifier.ts | 16 +- .../NotificationIntegrationHelper.ts | 14 +- .../helpers/notifications/SlackNotifier.ts | 12 +- .../mutations/helpers/oldUpgradeToTeamTier.ts | 35 +- .../mutations/helpers/removeFromOrg.ts | 2 +- .../helpers/resolveDowngradeToStarter.ts | 14 +- .../helpers/safeCreateRetrospective.ts | 2 +- .../server/graphql/mutations/moveTeamToOrg.ts | 2 +- .../graphql/mutations/oldUpgradeToTeamTier.ts | 4 +- packages/server/graphql/mutations/payLater.ts | 8 - .../private/mutations/backupOrganization.ts | 3 - .../private/mutations/changeEmailDomain.ts | 5 - .../mutations/draftEnterpriseInvoice.ts | 20 +- .../graphql/private/mutations/endTrial.ts | 15 +- .../private/mutations/flagConversionModal.ts | 10 - .../private/mutations/processRecurrence.ts | 2 +- .../mutations/sendUpcomingInvoiceEmails.ts | 153 ----- .../mutations/setOrganizationDomain.ts | 11 +- .../graphql/private/mutations/startTrial.ts | 17 +- .../mutations/stripeCreateSubscription.ts | 10 - .../mutations/stripeDeleteSubscription.ts | 15 +- .../private/mutations/stripeInvoicePaid.ts | 20 +- .../private/mutations/stripeSucceedPayment.ts | 20 +- .../mutations/stripeUpdateCreditCard.ts | 3 - .../private/mutations/updateOrgFeatureFlag.ts | 23 +- .../private/mutations/upgradeToTeamTier.ts | 18 +- .../graphql/private/queries/suProOrgInfo.ts | 31 +- .../types/DraftEnterpriseInvoicePayload.ts | 2 +- .../graphql/private/types/EndTrialSuccess.ts | 9 +- .../types/FlagConversionModalPayload.ts | 2 +- .../private/types/StartTrialSuccess.ts | 9 +- .../mutations/acceptRequestToJoinDomain.ts | 2 +- .../mutations/createStripeSubscription.ts | 2 +- .../public/mutations/setMeetingSettings.ts | 2 +- .../public/mutations/updateCreditCard.ts | 20 +- .../graphql/public/mutations/updateOrg.ts | 9 - .../public/mutations/uploadOrgImage.ts | 12 - .../AddApprovedOrganizationDomainsSuccess.ts | 2 +- .../public/types/AddPokerTemplateSuccess.ts | 4 +- .../graphql/public/types/DomainJoinRequest.ts | 2 +- .../server/graphql/public/types/NewMeeting.ts | 2 +- .../public/types/NotifyPaymentRejected.ts | 2 +- .../public/types/NotifyPromoteToOrgLeader.ts | 2 +- .../graphql/public/types/Organization.ts | 5 + ...emoveApprovedOrganizationDomainsSuccess.ts | 2 +- packages/server/graphql/public/types/SAML.ts | 2 +- .../public/types/SetOrgUserRoleSuccess.ts | 2 +- .../public/types/StripeFailPaymentPayload.ts | 2 +- packages/server/graphql/public/types/Team.ts | 4 + .../public/types/UpdateCreditCardSuccess.ts | 2 +- .../graphql/public/types/UpdateOrgPayload.ts | 2 +- .../public/types/UpgradeToTeamTierSuccess.ts | 2 +- packages/server/graphql/public/types/User.ts | 2 +- .../queries/helpers/countTiersForUserId.ts | 4 - .../queries/helpers/makeUpcomingInvoice.ts | 4 +- packages/server/graphql/queries/invoices.ts | 4 +- packages/server/graphql/types/Organization.ts | 4 +- packages/server/graphql/types/Team.ts | 2 +- packages/server/graphql/types/User.ts | 14 +- .../graphql/types/helpers/isMeetingLocked.ts | 2 +- packages/server/postgres/getKysely.ts | 10 +- packages/server/postgres/getPg.ts | 9 +- .../queries/src/updateUserTiersQuery.sql | 9 - .../postgres/queries/updateUserTiers.ts | 11 - .../safeMutations/acceptTeamInvitation.ts | 10 +- .../safeArchiveEmptyStarterOrganization.ts | 2 +- .../isRequestToJoinDomainAllowed.test.ts | 551 ++++++------------ .../utils/isRequestToJoinDomainAllowed.ts | 108 ++-- packages/server/utils/setTierForOrgUsers.ts | 24 +- .../server/utils/setUserTierForUserIds.ts | 100 ++-- scripts/toolboxSrc/setIsEnterprise.ts | 22 +- 109 files changed, 752 insertions(+), 1378 deletions(-) delete mode 100644 packages/client/modules/email/components/UpcomingInvoiceEmail.tsx delete mode 100644 packages/server/email/UpcomingInvoiceEmailTemplate.tsx delete mode 100644 packages/server/graphql/private/mutations/sendUpcomingInvoiceEmails.ts delete mode 100644 packages/server/postgres/queries/src/updateUserTiersQuery.sql delete mode 100644 packages/server/postgres/queries/updateUserTiers.ts diff --git a/codegen.json b/codegen.json index fa006c77adb..cfa97845070 100644 --- a/codegen.json +++ b/codegen.json @@ -21,7 +21,7 @@ "LoginsPayload": "./types/LoginsPayload#LoginsPayloadSource", "MeetingTemplate": "../../database/types/MeetingTemplate#default as IMeetingTemplate", "NewMeeting": "../../postgres/types/Meeting#AnyMeeting", - "Organization": "../../database/types/Organization#default as Organization", + "Organization": "../public/types/Organization#OrganizationSource", "PingableServices": "./types/PingableServices#PingableServicesSource", "ProcessRecurrenceSuccess": "./types/ProcessRecurrenceSuccess#ProcessRecurrenceSuccessSource", "RemoveAuthIdentitySuccess": "./types/RemoveAuthIdentitySuccess#RemoveAuthIdentitySuccessSource", @@ -30,7 +30,7 @@ "SignupsPayload": "./types/SignupsPayload#SignupsPayloadSource", "StartTrialSuccess": "./types/StartTrialSuccess#StartTrialSuccessSource", "StripeFailPaymentPayload": "./mutations/stripeFailPayment#StripeFailPaymentPayloadSource", - "Team": "../../postgres/queries/getTeamsByIds#Team", + "Team": "../public/types/Team#TeamSource", "UpdateOrgFeatureFlagSuccess": "./types/UpdateOrgFeatureFlagSuccess#UpdateOrgFeatureFlagSuccessSource", "UpgradeToTeamTierSuccess": "./mutations/upgradeToTeamTier#UpgradeToTeamTierSuccessSource", "User": "../../postgres/types/IUser#default as IUser", @@ -91,10 +91,11 @@ "NotifyResponseReplied": "../../database/types/NotifyResponseReplied#default as NotifyResponseRepliedDB", "NotifyTaskInvolves": "../../database/types/NotificationTaskInvolves#default", "NotifyTeamArchived": "../../database/types/NotificationTeamArchived#default", - "Organization": "../../database/types/Organization#default as Organization", + "Organization": "./types/Organization#OrganizationSource", "OrganizationUser": "../../database/types/OrganizationUser#default as OrganizationUser", "PokerMeeting": "../../database/types/MeetingPoker#default as MeetingPoker", "PokerMeetingMember": "../../database/types/MeetingPokerMeetingMember#default as PokerMeetingMemberDB", + "PokerTemplate": "../../database/types/PokerTemplate#default as PokerTemplateDB", "RRule": "rrule#RRule", "Reactable": "../../database/types/Reactable#Reactable", "Reactji": "../types/Reactji#ReactjiSource", @@ -120,7 +121,7 @@ "StartTeamPromptSuccess": "./types/StartTeamPromptSuccess#StartTeamPromptSuccessSource", "StripeFailPaymentPayload": "./types/StripeFailPaymentPayload#StripeFailPaymentPayloadSource", "Task": "../../database/types/Task#default", - "Team": "../../postgres/queries/getTeamsByIds#Team", + "Team": "./types/Team#TeamSource", "TeamHealthPhase": "./types/TeamHealthPhase#TeamHealthPhaseSource", "TeamHealthStage": "./types/TeamHealthStage#TeamHealthStageSource", "TeamInvitation": "../../database/types/TeamInvitation#default", diff --git a/packages/client/modules/email/components/UpcomingInvoiceEmail.tsx b/packages/client/modules/email/components/UpcomingInvoiceEmail.tsx deleted file mode 100644 index d285f9af4d3..00000000000 --- a/packages/client/modules/email/components/UpcomingInvoiceEmail.tsx +++ /dev/null @@ -1,68 +0,0 @@ -import React from 'react' -import {EMAIL_CORS_OPTIONS} from '../../../types/cors' -import {emailCopyStyle, emailLinkStyle, emailProductTeamSignature} from '../styles' -import EmailBlock from './EmailBlock/EmailBlock' -import EmailFooter from './EmailFooter/EmailFooter' -import EmptySpace from './EmptySpace/EmptySpace' -import Header from './Header/Header' -import Layout from './Layout/Layout' - -const innerMaxWidth = 480 - -const listItemStyle = { - ...emailCopyStyle, - margin: 0 -} - -export interface UpcomingInvoiceEmailProps { - appOrigin: string - memberUrl: string - periodEndStr: string - newUsers: {email: string; name: string}[] -} - -const UpcomingInvoiceEmail = (props: UpcomingInvoiceEmailProps) => { - const {appOrigin, periodEndStr, newUsers, memberUrl} = props - return ( - - -
-

{'Hello, '}

-

- {`Your teams have added the following users to your organization for the billing cycle ending on ${periodEndStr}.`} -

-
    - {newUsers.map((newUser) => ( -
  • - {`${newUser.name}`} - {' ('} - {`${newUser.email}`} - {')'} -
  • - ))} -
-

- {'If any of these users were added by mistake, simply remove them under: '} - - {'Organization Settings'} - -

-

- {'Get in touch if we can help in any way,'} -
- {emailProductTeamSignature} -
- - {'love@parabol.co'} - -

- - - - - - - ) -} - -export default UpcomingInvoiceEmail diff --git a/packages/server/__tests__/common.ts b/packages/server/__tests__/common.ts index 93c134b229b..71e00b8a71a 100644 --- a/packages/server/__tests__/common.ts +++ b/packages/server/__tests__/common.ts @@ -1,8 +1,10 @@ import base64url from 'base64url' import crypto from 'crypto' import faker from 'faker' +import {sql} from 'kysely' import getRethink from '../database/rethinkDriver' import ServerAuthToken from '../database/types/ServerAuthToken' +import getKysely from '../postgres/getKysely' import encodeAuthToken from '../utils/encodeAuthToken' const HOST = process.env.GRAPHQL_HOST || 'localhost:3000' @@ -201,3 +203,25 @@ export const getUserTeams = async (userId: string) => { }) return user.data.user.teams as [{id: string}, ...{id: string}[]] } + +export const createPGTables = async (...tables: string[]) => { + const pg = getKysely() + await Promise.all( + tables.map(async (table) => { + return sql` + CREATE TABLE IF NOT EXISTS ${sql.table(table)} (like "public".${sql.table(table)} including ALL)`.execute( + pg + ) + }) + ) + await truncatePGTables(...tables) +} + +export const truncatePGTables = async (...tables: string[]) => { + const pg = getKysely() + await Promise.all( + tables.map(async (table) => { + return sql`TRUNCATE TABLE ${sql.table(table)} CASCADE`.execute(pg) + }) + ) +} diff --git a/packages/server/__tests__/globalSetup.ts b/packages/server/__tests__/globalSetup.ts index 69df2a89fdd..6cecd76a0f3 100644 --- a/packages/server/__tests__/globalSetup.ts +++ b/packages/server/__tests__/globalSetup.ts @@ -1,18 +1,12 @@ import '../../../scripts/webpack/utils/dotenv' import getRethink from '../database/rethinkDriver' -import getKysely from '../postgres/getKysely' async function setup() { const r = await getRethink() - const pg = getKysely() // The IP address is always localhost // so the safety checks will eventually fail if run too much - await Promise.all([ - pg.deleteFrom('FailedAuthRequest').execute(), - r.table('PasswordResetRequest').delete().run(), - pg.deleteFrom('SAMLDomain').where('domain', '=', 'example.com').execute() - ]) + await Promise.all([r.table('PasswordResetRequest').delete().run()]) } export default setup diff --git a/packages/server/billing/helpers/adjustUserCount.ts b/packages/server/billing/helpers/adjustUserCount.ts index 649f568f161..6fd57f11267 100644 --- a/packages/server/billing/helpers/adjustUserCount.ts +++ b/packages/server/billing/helpers/adjustUserCount.ts @@ -22,8 +22,7 @@ const maybeUpdateOrganizationActiveDomain = async ( newUserEmail: string, dataLoader: DataLoaderWorker ) => { - const r = await getRethink() - const organization = await dataLoader.get('organizations').load(orgId) + const organization = await dataLoader.get('organizations').loadNonNull(orgId) const {isActiveDomainTouched, activeDomain} = organization // don't modify if the domain was set manually if (isActiveDomainTouched) return @@ -41,16 +40,7 @@ const maybeUpdateOrganizationActiveDomain = async ( if (!domain || domain === activeDomain) return 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() - ]) + await pg.updateTable('Organization').set({activeDomain: domain}).where('id', '=', orgId).execute() } const changePause = (inactive: boolean) => async (_orgIds: string[], user: IUser) => { diff --git a/packages/server/billing/helpers/fetchAllLines.ts b/packages/server/billing/helpers/fetchAllLines.ts index fc97e0b3b66..7f8c30d0e4b 100644 --- a/packages/server/billing/helpers/fetchAllLines.ts +++ b/packages/server/billing/helpers/fetchAllLines.ts @@ -1,7 +1,7 @@ import Stripe from 'stripe' import {getStripeManager} from '../../utils/stripe' -export default async function fetchAllLines(invoiceId: string, customerId?: string) { +export default async function fetchAllLines(invoiceId: string, customerId?: string | null) { const stripeLineItems = [] as Stripe.InvoiceLineItem[] const options = {limit: 100} as Stripe.InvoiceLineItemListParams & {customer: string} // used for upcoming invoices diff --git a/packages/server/billing/helpers/generateInvoice.ts b/packages/server/billing/helpers/generateInvoice.ts index 1b75d4a6537..cebf0d6c88f 100644 --- a/packages/server/billing/helpers/generateInvoice.ts +++ b/packages/server/billing/helpers/generateInvoice.ts @@ -354,7 +354,7 @@ export default async function generateInvoice( ? fromEpochSeconds(invoice.status_transitions.paid_at) : undefined const [organization, billingLeaderIds] = await Promise.all([ - dataLoader.get('organizations').load(orgId), + dataLoader.get('organizations').loadNonNull(orgId), r .table('OrganizationUser') .getAll(orgId, {index: 'orgId'}) @@ -378,6 +378,7 @@ export default async function generateInvoice( })) || null + const {creditCard} = organization const dbInvoice = new Invoice({ id: invoiceId, amountDue: invoice.amount_due, @@ -385,7 +386,7 @@ export default async function generateInvoice( coupon, total: invoice.total, billingLeaderEmails, - creditCard: organization.creditCard, + creditCard: creditCard ? {...creditCard, last4: String(creditCard.last4)} : undefined, endAt: fromEpochSeconds(invoice.period_end), invoiceDate: fromEpochSeconds(invoice.due_date!), lines: invoiceLineItems, diff --git a/packages/server/billing/helpers/generateUpcomingInvoice.ts b/packages/server/billing/helpers/generateUpcomingInvoice.ts index 7980c3871df..ddc4b1b3a12 100644 --- a/packages/server/billing/helpers/generateUpcomingInvoice.ts +++ b/packages/server/billing/helpers/generateUpcomingInvoice.ts @@ -6,7 +6,7 @@ import generateInvoice from './generateInvoice' const generateUpcomingInvoice = async (orgId: string, dataLoader: DataLoaderWorker) => { const invoiceId = getUpcomingInvoiceId(orgId) - const organization = await dataLoader.get('organizations').load(orgId) + const organization = await dataLoader.get('organizations').loadNonNull(orgId) const {stripeId} = organization const manager = getStripeManager() const [stripeLineItems, upcomingInvoice] = await Promise.all([ diff --git a/packages/server/billing/helpers/handleEnterpriseOrgQuantityChanges.ts b/packages/server/billing/helpers/handleEnterpriseOrgQuantityChanges.ts index ad62e1fadd4..215ac9396ef 100644 --- a/packages/server/billing/helpers/handleEnterpriseOrgQuantityChanges.ts +++ b/packages/server/billing/helpers/handleEnterpriseOrgQuantityChanges.ts @@ -1,12 +1,12 @@ import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' -import Organization from '../../database/types/Organization' import {DataLoaderWorker} from '../../graphql/graphql' +import {OrganizationSource} from '../../graphql/public/types/Organization' import {analytics} from '../../utils/analytics/analytics' import {getStripeManager} from '../../utils/stripe' const sendEnterpriseOverageEvent = async ( - organization: Organization, + organization: OrganizationSource, dataLoader: DataLoaderWorker ) => { const r = await getRethink() @@ -41,7 +41,7 @@ const sendEnterpriseOverageEvent = async ( } const handleEnterpriseOrgQuantityChanges = async ( - paidOrgs: Organization[], + paidOrgs: OrganizationSource[], dataLoader: DataLoaderWorker ) => { const enterpriseOrgs = paidOrgs.filter((org) => org.tier === 'enterprise') diff --git a/packages/server/billing/helpers/handleTeamOrgQuantityChanges.ts b/packages/server/billing/helpers/handleTeamOrgQuantityChanges.ts index 4c3040e8117..2ae1f729a64 100644 --- a/packages/server/billing/helpers/handleTeamOrgQuantityChanges.ts +++ b/packages/server/billing/helpers/handleTeamOrgQuantityChanges.ts @@ -1,7 +1,7 @@ -import Organization from '../../database/types/Organization' +import {OrganizationSource} from '../../graphql/public/types/Organization' import updateSubscriptionQuantity from './updateSubscriptionQuantity' -const handleTeamOrgQuantityChanges = async (paidOrgs: Organization[]) => { +const handleTeamOrgQuantityChanges = async (paidOrgs: OrganizationSource[]) => { const teamOrgs = paidOrgs.filter((org) => org.tier === 'team') if (teamOrgs.length === 0) return diff --git a/packages/server/billing/helpers/teamLimitsCheck.ts b/packages/server/billing/helpers/teamLimitsCheck.ts index fff842326db..6652c8ea6c7 100644 --- a/packages/server/billing/helpers/teamLimitsCheck.ts +++ b/packages/server/billing/helpers/teamLimitsCheck.ts @@ -5,10 +5,10 @@ import {Threshold} from 'parabol-client/types/constEnums' import {sql} from 'kysely' import {r} from 'rethinkdb-ts' import NotificationTeamsLimitExceeded from '../../database/types/NotificationTeamsLimitExceeded' -import Organization from '../../database/types/Organization' import scheduleTeamLimitsJobs from '../../database/types/scheduleTeamLimitsJobs' import {DataLoaderWorker} from '../../graphql/graphql' import publishNotification from '../../graphql/public/mutations/helpers/publishNotification' +import {OrganizationSource} from '../../graphql/public/types/Organization' import getActiveTeamCountByTeamIds from '../../graphql/public/types/helpers/getActiveTeamCountByTeamIds' import {getFeatureTier} from '../../graphql/types/helpers/getFeatureTier' import {domainHasActiveDeals} from '../../hubSpot/hubSpotApi' @@ -35,7 +35,7 @@ const enableUsageStats = async (userIds: string[], orgId: string) => { } const sendWebsiteNotifications = async ( - organization: Organization, + organization: OrganizationSource, userIds: string[], dataLoader: DataLoaderWorker ) => { @@ -72,7 +72,7 @@ const isLimitExceeded = async (orgId: string) => { // Warning: the function might be expensive export const maybeRemoveRestrictions = async (orgId: string, dataLoader: DataLoaderWorker) => { - const organization = await dataLoader.get('organizations').load(orgId) + const organization = await dataLoader.get('organizations').loadNonNull(orgId) if (!organization.tierLimitExceededAt) { return @@ -87,16 +87,6 @@ export const maybeRemoveRestrictions = async (orgId: string, dataLoader: DataLoa .set({tierLimitExceededAt: null, scheduledLockAt: null, lockedAt: null}) .where('id', '=', orgId) .execute(), - r - .table('Organization') - .get(orgId) - .update({ - tierLimitExceededAt: null, - scheduledLockAt: null, - lockedAt: null, - updatedAt: new Date() - }) - .run(), r .table('OrganizationUser') .getAll(r.args(billingLeadersIds), {index: 'userId'}) @@ -111,7 +101,7 @@ export const maybeRemoveRestrictions = async (orgId: string, dataLoader: DataLoa // Warning: the function might be expensive export const checkTeamsLimit = async (orgId: string, dataLoader: DataLoaderWorker) => { - const organization = await dataLoader.get('organizations').load(orgId) + const organization = await dataLoader.get('organizations').loadNonNull(orgId) const {tierLimitExceededAt, tier, trialStartDate, featureFlags, name: orgName} = organization if (!featureFlags?.includes('teamsLimit')) return @@ -144,16 +134,7 @@ export const checkTeamsLimit = async (orgId: string, dataLoader: DataLoaderWorke scheduledLockAt }) .where('id', '=', orgId) - .execute(), - r - .table('Organization') - .get(orgId) - .update({ - tierLimitExceededAt: now, - scheduledLockAt, - updatedAt: now - }) - .run() + .execute() ]) dataLoader.get('organizations').clear(orgId) diff --git a/packages/server/billing/helpers/terminateSubscription.ts b/packages/server/billing/helpers/terminateSubscription.ts index 1850a614828..200e451fbb5 100644 --- a/packages/server/billing/helpers/terminateSubscription.ts +++ b/packages/server/billing/helpers/terminateSubscription.ts @@ -1,45 +1,24 @@ -import getRethink from '../../database/rethinkDriver' -import Organization from '../../database/types/Organization' +import {sql} from 'kysely' 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 [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 = await pg + .with('OldOrg', (qc) => + qc.selectFrom('Organization').select('stripeSubscriptionId').where('id', '=', orgId) + ) + .updateTable('Organization') + .set({periodEnd: sql`CURRENT_TIMESTAMP`, stripeSubscriptionId: null}) + .where('id', '=', orgId) + .returning((qc) => + qc.selectFrom('OldOrg').select('stripeSubscriptionId').as('stripeSubscriptionId') + ) + .executeTakeFirstOrThrow() 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/helpers/updateSubscriptionQuantity.ts b/packages/server/billing/helpers/updateSubscriptionQuantity.ts index 25917b13e58..23fcb80095f 100644 --- a/packages/server/billing/helpers/updateSubscriptionQuantity.ts +++ b/packages/server/billing/helpers/updateSubscriptionQuantity.ts @@ -1,4 +1,5 @@ import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import insertStripeQuantityMismatchLogging from '../../postgres/queries/insertStripeQuantityMismatchLogging' import RedisLockQueue from '../../utils/RedisLockQueue' import sendToSentry from '../../utils/sendToSentry' @@ -12,7 +13,11 @@ const updateSubscriptionQuantity = async (orgId: string, logMismatch?: boolean) const r = await getRethink() const manager = getStripeManager() - const org = await r.table('Organization').get(orgId).run() + const org = await getKysely() + .selectFrom('Organization') + .selectAll() + .where('id', '=', orgId) + .executeTakeFirst() if (!org) throw new Error(`org not found for invoice`) const {stripeSubscriptionId, tier} = org diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index d88791af467..3eb79efcc06 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -1,5 +1,4 @@ import {MasterPool, r} from 'rethinkdb-ts' -import Organization from '../database/types/Organization' import SlackAuth from '../database/types/SlackAuth' import SlackNotification from '../database/types/SlackNotification' import TeamInvitation from '../database/types/TeamInvitation' @@ -113,10 +112,6 @@ export type RethinkSchema = { | NotificationMentioned index: 'userId' } - Organization: { - type: Organization - index: 'tier' | 'activeDomain' - } OrganizationUser: { type: OrganizationUser index: 'orgId' | 'userId' diff --git a/packages/server/database/types/Invoice.ts b/packages/server/database/types/Invoice.ts index 81fa0bff0e3..ee34077a5de 100644 --- a/packages/server/database/types/Invoice.ts +++ b/packages/server/database/types/Invoice.ts @@ -13,7 +13,7 @@ interface Input { coupon?: Coupon | null total: number billingLeaderEmails: string[] - creditCard?: CreditCard + creditCard?: CreditCard | null endAt: Date invoiceDate: Date lines: InvoiceLineItem[] @@ -36,7 +36,7 @@ export default class Invoice { coupon?: Coupon | null total: number billingLeaderEmails: string[] - creditCard?: CreditCard + creditCard?: CreditCard | null endAt: Date invoiceDate: Date lines: InvoiceLineItem[] diff --git a/packages/server/database/types/NotificationPaymentRejected.ts b/packages/server/database/types/NotificationPaymentRejected.ts index c6cb6cda233..53a779c47d3 100644 --- a/packages/server/database/types/NotificationPaymentRejected.ts +++ b/packages/server/database/types/NotificationPaymentRejected.ts @@ -2,7 +2,7 @@ import Notification from './Notification' interface Input { orgId: string - last4: string + last4: string | number brand: string userId: string } @@ -17,7 +17,7 @@ export default class NotificationPaymentRejected extends Notification { const {orgId, last4, brand, userId} = input super({userId, type: 'PAYMENT_REJECTED'}) this.orgId = orgId - this.last4 = last4 + this.last4 = String(last4) this.brand = brand } } diff --git a/packages/server/database/types/NotificationTeamsLimitExceeded.ts b/packages/server/database/types/NotificationTeamsLimitExceeded.ts index d67ff7a4ad6..8a070846a9c 100644 --- a/packages/server/database/types/NotificationTeamsLimitExceeded.ts +++ b/packages/server/database/types/NotificationTeamsLimitExceeded.ts @@ -3,7 +3,7 @@ import Notification from './Notification' interface Input { orgId: string orgName: string - orgPicture?: string + orgPicture?: string | null userId: string } @@ -11,7 +11,7 @@ export default class NotificationTeamsLimitExceeded extends Notification { readonly type = 'TEAMS_LIMIT_EXCEEDED' orgId: string orgName: string - orgPicture?: string + orgPicture?: string | null constructor(input: Input) { const {userId, orgId, orgName, orgPicture} = input super({userId, type: 'TEAMS_LIMIT_EXCEEDED'}) diff --git a/packages/server/database/types/NotificationTeamsLimitReminder.ts b/packages/server/database/types/NotificationTeamsLimitReminder.ts index 720b47c04d7..a03c33edb9c 100644 --- a/packages/server/database/types/NotificationTeamsLimitReminder.ts +++ b/packages/server/database/types/NotificationTeamsLimitReminder.ts @@ -3,7 +3,7 @@ import Notification from './Notification' interface Input { orgId: string orgName: string - orgPicture?: string + orgPicture?: string | null userId: string scheduledLockAt: Date } @@ -12,7 +12,7 @@ export default class NotificationTeamsLimitReminder extends Notification { readonly type = 'TEAMS_LIMIT_REMINDER' orgId: string orgName: string - orgPicture?: string + orgPicture?: string | null scheduledLockAt: Date constructor(input: Input) { const {userId, orgId, orgName, orgPicture, scheduledLockAt} = input diff --git a/packages/server/database/types/processTeamsLimitsJob.ts b/packages/server/database/types/processTeamsLimitsJob.ts index 562a9698028..6dfb2b8e8df 100644 --- a/packages/server/database/types/processTeamsLimitsJob.ts +++ b/packages/server/database/types/processTeamsLimitsJob.ts @@ -10,7 +10,7 @@ import ScheduledTeamLimitsJob from './ScheduledTeamLimitsJob' const processTeamsLimitsJob = async (job: ScheduledTeamLimitsJob, dataLoader: DataLoaderWorker) => { const {orgId, type} = job const [organization, orgUsers] = await Promise.all([ - dataLoader.get('organizations').load(orgId), + dataLoader.get('organizations').loadNonNull(orgId), dataLoader.get('organizationUsersByOrgId').load(orgId) ]) const {name: orgName, picture: orgPicture, scheduledLockAt, lockedAt} = organization @@ -28,14 +28,11 @@ const processTeamsLimitsJob = async (job: ScheduledTeamLimitsJob, dataLoader: Da if (type === 'LOCK_ORGANIZATION') { const now = new Date() - await Promise.all([ - getKysely() - .updateTable('Organization') - .set({lockedAt: now}) - .where('id', '=', 'orgId') - .execute(), - r.table('Organization').get(orgId).update({lockedAt: now}).run() - ]) + await getKysely() + .updateTable('Organization') + .set({lockedAt: now}) + .where('id', '=', 'orgId') + .execute() organization.lockedAt = lockedAt } else if (type === 'WARN_ORGANIZATION') { const notificationsToInsert = billingLeadersIds.map((userId) => { diff --git a/packages/server/dataloader/__tests__/isOrgVerified.test.ts b/packages/server/dataloader/__tests__/isOrgVerified.test.ts index 58247c515ab..96a455b9767 100644 --- a/packages/server/dataloader/__tests__/isOrgVerified.test.ts +++ b/packages/server/dataloader/__tests__/isOrgVerified.test.ts @@ -1,14 +1,16 @@ /* eslint-env jest */ +import {Insertable} from 'kysely' import {r} from 'rethinkdb-ts' +import {createPGTables, truncatePGTables} from '../../__tests__/common' import getRethinkConfig from '../../database/getRethinkConfig' import getRethink from '../../database/rethinkDriver' import OrganizationUser from '../../database/types/OrganizationUser' import generateUID from '../../generateUID' -import {DataLoaderWorker} from '../../graphql/graphql' +import getKysely from '../../postgres/getKysely' +import {User} from '../../postgres/pg' import getRedis from '../../utils/getRedis' import isUserVerified from '../../utils/isUserVerified' import RootDataLoader from '../RootDataLoader' -import {isOrgVerified} from '../customLoaderMakers' jest.mock('../../database/rethinkDriver') jest.mock('../../utils/isUserVerified') @@ -28,6 +30,11 @@ const testConfig = { db: TEST_DB } +type TestUser = Insertable +const addUsers = async (users: TestUser[]) => { + getKysely().insertInto('User').values(users).execute() +} + const createTables = async (...tables: string[]) => { for (const tableName of tables) { const structure = await r @@ -50,35 +57,16 @@ type TestOrganizationUser = Partial< } > -const userLoader = { - load: jest.fn(), - loadMany: jest.fn() -} -const isCompanyDomainLoader = { - load: jest.fn(), - loadMany: jest.fn() -} -isCompanyDomainLoader.load.mockReturnValue(true) - -const dataLoader = { - get: jest.fn((loader) => { - const loaders = { - users: userLoader, - isCompanyDomain: isCompanyDomainLoader - } - return loaders[loader as keyof typeof loaders] - }) -} as any as DataLoaderWorker - const addOrg = async ( activeDomain: string | null, members: TestOrganizationUser[], featureFlags?: string[] ) => { - const orgId = activeDomain + const orgId = activeDomain! const org = { id: orgId, activeDomain, + name: 'baddadan', featureFlags: featureFlags ?? [] } @@ -92,37 +80,32 @@ const addOrg = async ( removedAt: member.removedAt ?? null })) - await r.table('Organization').insert(org).run() + const pg = getKysely() + await pg.insertInto('Organization').values(org).execute() await r.table('OrganizationUser').insert(orgUsers).run() - const users = orgUsers.map(({userId, domain}) => ({ - id: userId, - domain: domain ?? activeDomain - })) - - userLoader.load.mockImplementation((userId) => users.find((u) => u.id === userId)) - userLoader.loadMany.mockImplementation((userIds) => userIds.map(userLoader.load)) - return orgId } -const isOrgVerifiedLoader = isOrgVerified(dataLoader as any as RootDataLoader) - beforeAll(async () => { await r.connectPool(testConfig) + const pg = getKysely() + try { await r.dbDrop(TEST_DB).run() } catch (e) { //ignore } + await pg.schema.createSchema(TEST_DB).ifNotExists().execute() + await r.dbCreate(TEST_DB).run() - await createTables('Organization', 'OrganizationUser') + await createPGTables('Organization', 'User', 'SAML', 'SAMLDomain') + await createTables('OrganizationUser') }) afterEach(async () => { - await r.table('Organization').delete().run() + await truncatePGTables('Organization', 'User') await r.table('OrganizationUser').delete().run() - isOrgVerifiedLoader.clearAll() }) afterAll(async () => { @@ -138,30 +121,37 @@ test('Founder is billing lead', async () => { userId: 'user1' } ]) - - const isVerified = await isOrgVerifiedLoader.load('parabol.co') + await addUsers([ + { + id: 'user1', + email: 'user1@parabol.co', + picture: '', + preferredName: '', + identities: [{isEmailVerified: true}] + } + ]) + const dataLoader = new RootDataLoader() + const isVerified = await dataLoader.get('isOrgVerified').load('parabol.co') expect(isVerified).toBe(true) }) -test('Inactive founder is ignored', async () => { - await addOrg('parabol.co', [ +test('Non-founder billing lead is checked', async () => { + await addUsers([ { - joinedAt: new Date('2023-09-06'), - role: 'BILLING_LEADER', - userId: 'founder1', - inactive: true + id: 'founder1', + email: 'user1@parabol.co', + picture: '', + preferredName: '', + identities: [{isEmailVerified: true}] }, { - joinedAt: new Date('2023-09-12'), - userId: 'member1' + id: 'billing1', + email: 'billing1@parabol.co', + picture: '', + preferredName: '', + identities: [{isEmailVerified: true}] } ]) - - const isVerified = await isOrgVerifiedLoader.load('parabol.co') - expect(isVerified).toBe(false) -}) - -test('Non-founder billing lead is checked', async () => { await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), @@ -180,34 +170,29 @@ test('Non-founder billing lead is checked', async () => { } ]) - const isVerified = await isOrgVerifiedLoader.load('parabol.co') - expect(isVerified).toBe(true) -}) - -test('Founder is checked even when not billing lead', async () => { - await addOrg('parabol.co', [ - { - joinedAt: new Date('2023-09-06'), - userId: 'user1' - }, - { - joinedAt: new Date('2023-09-12'), - userId: 'user2' - } - ]) - - const isVerified = await isOrgVerifiedLoader.load('parabol.co') + const dataLoader = new RootDataLoader() + const isVerified = await dataLoader.get('isOrgVerified').load('parabol.co') expect(isVerified).toBe(true) }) test('Empty org does not throw', async () => { await addOrg('parabol.co', []) - const isVerified = await isOrgVerifiedLoader.load('parabol.co') + const dataLoader = new RootDataLoader() + const isVerified = await dataLoader.get('isOrgVerified').load('parabol.co') expect(isVerified).toBe(false) }) test('Orgs with verified emails from different domains do not qualify', async () => { + await addUsers([ + { + id: 'founder1', + email: 'user1@not-parabol.co', + picture: '', + preferredName: '', + identities: [{isEmailVerified: true}] + } + ]) await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), @@ -216,6 +201,7 @@ test('Orgs with verified emails from different domains do not qualify', async () } as any ]) - const isVerified = await isOrgVerifiedLoader.load('parabol.co') + const dataLoader = new RootDataLoader() + const isVerified = await dataLoader.get('isOrgVerified').load('parabol.co') expect(isVerified).toBe(false) }) diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 1743d3959d9..46a03715026 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -6,13 +6,13 @@ import getRethink, {RethinkSchema} from '../database/rethinkDriver' import {RDatum} from '../database/stricterR' import MeetingSettingsTeamPrompt from '../database/types/MeetingSettingsTeamPrompt' import MeetingTemplate from '../database/types/MeetingTemplate' -import Organization from '../database/types/Organization' import OrganizationUser from '../database/types/OrganizationUser' import {Reactable, ReactableEnum} from '../database/types/Reactable' import Task, {TaskStatusEnum} from '../database/types/Task' import getFileStoreManager from '../fileStorage/getFileStoreManager' import isValid from '../graphql/isValid' import {SAMLSource} from '../graphql/public/types/SAML' +import {TeamSource} from '../graphql/public/types/Team' import getKysely from '../postgres/getKysely' import {TeamMeetingTemplate} from '../postgres/pg.d' import {IGetLatestTaskEstimatesQueryResult} from '../postgres/queries/generated/getLatestTaskEstimatesQuery' @@ -29,7 +29,6 @@ import getLatestTaskEstimates from '../postgres/queries/getLatestTaskEstimates' import getMeetingTaskEstimates, { MeetingTaskEstimatesResult } from '../postgres/queries/getMeetingTaskEstimates' -import {Team} from '../postgres/queries/getTeamsByIds' import {AnyMeeting, MeetingTypeEnum} from '../postgres/types/Meeting' import {Logger} from '../utils/Logger' import getRedis from '../utils/getRedis' @@ -38,6 +37,7 @@ import NullableDataLoader from './NullableDataLoader' import RootDataLoader from './RootDataLoader' import normalizeArrayResults from './normalizeArrayResults' import normalizeResults from './normalizeResults' +import {selectTeams} from './primaryKeyLoaderMakers' export interface MeetingSettingsKey { teamId: string @@ -736,57 +736,29 @@ export const samlByOrgId = (parent: RootDataLoader) => { ) } -type OrgWithFounderAndLeads = Organization & { - founder: OrganizationUser | null - billingLeads: OrganizationUser[] -} - // Check if the org has a founder or billing lead with a verified email and their email domain is the same as the org domain export const isOrgVerified = (parent: RootDataLoader) => { return new DataLoader( async (orgIds) => { - const r = await getRethink() - const orgs: OrgWithFounderAndLeads[] = await r - .table('Organization') - .getAll(r.args(orgIds)) - .merge((org: RDatum) => ({ - members: r - .table('OrganizationUser') - .getAll(org('id'), {index: 'orgId'}) - .orderBy('joinedAt') - .coerceTo('array') - })) - .merge((org: RDatum) => ({ - founder: org('members').nth(0).default(null), - billingLeads: org('members') - .filter({inactive: false}) - .filter((row: RDatum) => r.expr(['BILLING_LEADER', 'ORG_ADMIN']).contains(row('role'))) - })) - .run() - - const userIds = orgs - .flatMap((org) => [ - org.founder ? org.founder.userId : null, - ...org.billingLeads.map((lead) => lead.userId) - ]) - .filter((id): id is string => Boolean(id)) - - const users = (await parent.get('users').loadMany(userIds)).filter(isValid) - - return orgIds.map((orgId) => { - const isValid = orgs.some((org) => { - if (org.id !== orgId) return false - const checkEmailDomain = (userId: string) => { - const user = users.find((user) => user.id === userId) - if (!user) return false - return isUserVerified(user) && user.domain === org.activeDomain - } - return [org.founder, ...org.billingLeads].some( - (orgUser) => orgUser && !orgUser.inactive && checkEmailDomain(orgUser.userId) - ) + const orgUsersRes = await parent.get('organizationUsersByOrgId').loadMany(orgIds) + const orgUsersWithRole = orgUsersRes + .filter(isValid) + .flat() + .filter(({role}) => role && ['BILLING_LEADER', 'ORG_ADMIN'].includes(role)) + const orgUsersUserIds = orgUsersWithRole.map((orgUser) => orgUser.userId) + const usersRes = await parent.get('users').loadMany(orgUsersUserIds) + const verifiedUsers = usersRes.filter(isValid).filter(isUserVerified) + const verifiedOrgUsers = orgUsersWithRole.filter((orgUser) => + verifiedUsers.some((user) => user.id === orgUser.userId) + ) + return await Promise.all( + orgIds.map(async (orgId) => { + const isUserVerified = verifiedOrgUsers.some((orgUser) => orgUser.orgId === orgId) + if (isUserVerified) return true + const isOrgSAML = await parent.get('samlByOrgId').load(orgId) + return !!isOrgSAML }) - return isValid - }) + ) }, { ...parent.dataLoaderOptions @@ -795,23 +767,20 @@ export const isOrgVerified = (parent: RootDataLoader) => { } export const autoJoinTeamsByOrgId = (parent: RootDataLoader) => { - return new DataLoader( + return new DataLoader( async (orgIds) => { const verificationResults = await parent.get('isOrgVerified').loadMany(orgIds) const verifiedOrgIds = orgIds.filter((_, index) => verificationResults[index]) - const pg = getKysely() - const teams = verifiedOrgIds.length === 0 ? [] - : ((await pg - .selectFrom('Team') + : await selectTeams() .where('orgId', 'in', verifiedOrgIds) .where('autoJoin', '=', true) .where('isArchived', '!=', true) .selectAll() - .execute()) as unknown as Team[]) + .execute() return orgIds.map((orgId) => teams.filter((team) => team.orgId === orgId)) }, diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 9bfccba0e94..543647daedb 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -1,10 +1,9 @@ import getKysely from '../postgres/getKysely' -import getTeamsByOrgIds from '../postgres/queries/getTeamsByOrgIds' import {foreignKeyLoaderMaker} from './foreignKeyLoaderMaker' -import {selectRetroReflections} from './primaryKeyLoaderMakers' +import {selectOrganizations, selectRetroReflections, selectTeams} from './primaryKeyLoaderMakers' export const teamsByOrgIds = foreignKeyLoaderMaker('teams', 'orgId', (orgIds) => - getTeamsByOrgIds(orgIds, {isArchived: false}) + selectTeams().where('orgId', 'in', orgIds).where('isArchived', '=', false).execute() ) export const discussionsByMeetingId = foreignKeyLoaderMaker( @@ -74,3 +73,11 @@ export const timelineEventsByMeetingId = foreignKeyLoaderMaker( .execute() } ) + +export const organizationsByActiveDomain = foreignKeyLoaderMaker( + 'organizations', + 'activeDomain', + async (activeDomains) => { + return selectOrganizations().where('activeDomain', 'in', activeDomains).execute() + } +) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index dcb6eb8ac0a..dd10a3f0d75 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -4,14 +4,54 @@ import {getDomainJoinRequestsByIds} from '../postgres/queries/getDomainJoinReque import getMeetingSeriesByIds from '../postgres/queries/getMeetingSeriesByIds' import getMeetingTemplatesByIds from '../postgres/queries/getMeetingTemplatesByIds' import {getTeamPromptResponsesByIds} from '../postgres/queries/getTeamPromptResponsesByIds' -import getTeamsByIds from '../postgres/queries/getTeamsByIds' import getTemplateRefsByIds from '../postgres/queries/getTemplateRefsByIds' import getTemplateScaleRefsByIds from '../postgres/queries/getTemplateScaleRefsByIds' import {getUsersByIds} from '../postgres/queries/getUsersByIds' import {primaryKeyLoaderMaker} from './primaryKeyLoaderMaker' export const users = primaryKeyLoaderMaker(getUsersByIds) -export const teams = primaryKeyLoaderMaker(getTeamsByIds) + +export const selectTeams = () => + getKysely() + .selectFrom('Team') + .select([ + 'autoJoin', + 'createdAt', + 'createdBy', + 'id', + 'insightsUpdatedAt', + 'isArchived', + 'isOnboardTeam', + 'isPaid', + 'kudosEmojiUnicode', + 'lastMeetingType', + 'lockMessageHTML', + 'meetingEngagement', + 'mostUsedEmojis', + 'name', + 'orgId', + 'qualAIMeetingsCount', + 'tier', + 'topRetroTemplates', + 'trialStartDate', + 'updatedAt' + ]) + .select(({fn}) => [ + fn< + { + dimensionName: string + cloudId: string + projectKey: string + issueKey: string + fieldName: string + fieldType: string + fieldId: string + }[] + >('to_json', ['jiraDimensionFields']).as('jiraDimensionFields') + ]) +export const teams = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectTeams().where('id', 'in', ids).execute() +}) export const discussions = primaryKeyLoaderMaker(getDiscussionsByIds) export const templateRefs = primaryKeyLoaderMaker(getTemplateRefsByIds) export const templateScaleRefs = primaryKeyLoaderMaker(getTemplateScaleRefsByIds) @@ -57,3 +97,37 @@ export const retroReflections = primaryKeyLoaderMaker((ids: readonly string[]) = export const timelineEvents = primaryKeyLoaderMaker((ids: readonly string[]) => { return getKysely().selectFrom('TimelineEvent').selectAll().where('id', 'in', ids).execute() }) + +export const selectOrganizations = () => + getKysely() + .selectFrom('Organization') + .select([ + 'id', + 'activeDomain', + 'isActiveDomainTouched', + 'createdAt', + 'name', + 'payLaterClickCount', + 'periodEnd', + 'periodStart', + 'picture', + 'showConversionModal', + 'stripeId', + 'stripeSubscriptionId', + 'upcomingInvoiceEmailSentAt', + 'tier', + 'tierLimitExceededAt', + 'trialStartDate', + 'scheduledLockAt', + 'lockedAt', + 'updatedAt', + 'featureFlags' + ]) + .select(({fn}) => [ + fn<{brand: string; expiry: string; last4: number} | null>('to_json', ['creditCard']).as( + 'creditCard' + ) + ]) +export const organizations = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectOrganizations().where('id', 'in', ids).execute() +}) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index 461c58dd606..4385bb32fd9 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -116,14 +116,6 @@ export const meetingMembersByUserId = new RethinkForeignKeyLoaderMaker( } ) -export const organizationsByActiveDomain = new RethinkForeignKeyLoaderMaker( - 'organizations', - 'activeDomain', - async (activeDomains) => { - const r = await getRethink() - return r.table('Organization').getAll(r.args(activeDomains), {index: 'activeDomain'}).run() - } -) export const organizationUsersByOrgId = new RethinkForeignKeyLoaderMaker( 'organizationUsers', 'orgId', diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index d5dbd237079..78a198aa9b9 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -13,7 +13,6 @@ export const meetingMembers = new RethinkPrimaryKeyLoaderMaker('MeetingMember') export const newMeetings = new RethinkPrimaryKeyLoaderMaker('NewMeeting') export const newFeatures = new RethinkPrimaryKeyLoaderMaker('NewFeature') export const notifications = new RethinkPrimaryKeyLoaderMaker('Notification') -export const organizations = new RethinkPrimaryKeyLoaderMaker('Organization') export const organizationUsers = new RethinkPrimaryKeyLoaderMaker('OrganizationUser') export const templateScales = new RethinkPrimaryKeyLoaderMaker('TemplateScale') export const slackAuths = new RethinkPrimaryKeyLoaderMaker('SlackAuth') diff --git a/packages/server/email/UpcomingInvoiceEmailTemplate.tsx b/packages/server/email/UpcomingInvoiceEmailTemplate.tsx deleted file mode 100644 index 9e9f765fc2a..00000000000 --- a/packages/server/email/UpcomingInvoiceEmailTemplate.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import Oy from 'oy-vey' -import UpcomingInvoiceEmail, { - UpcomingInvoiceEmailProps -} from 'parabol-client/modules/email/components/UpcomingInvoiceEmail' -import {headCSS} from 'parabol-client/modules/email/styles' -import React from 'react' - -const subject = 'Your monthly summary' - -export const makeBody = (props: UpcomingInvoiceEmailProps) => { - const {periodEndStr, newUsers, memberUrl} = props - const newUserBullets = newUsers.reduce( - (str, newUser) => str + `* ${newUser.name} (${newUser.email})\n`, - '' - ) - return ` -Hello, - -Your teams have added the following users to your organization for the billing cycle ending on ${periodEndStr}: -${newUserBullets} - -If any of these users were added by mistake, simply remove them under Organization Settings: ${memberUrl} - -Your friends, -The Parabol Product Team -` -} - -export default (props: UpcomingInvoiceEmailProps) => ({ - subject, - body: makeBody(props), - html: Oy.renderTemplate(, { - headCSS, - title: subject, - previewText: subject - }) -}) diff --git a/packages/server/graphql/mutations/addTeam.ts b/packages/server/graphql/mutations/addTeam.ts index 3b9fb4f5bdb..2663a74cf26 100644 --- a/packages/server/graphql/mutations/addTeam.ts +++ b/packages/server/graphql/mutations/addTeam.ts @@ -55,7 +55,7 @@ export default { // VALIDATION const [orgTeams, organization, viewer] = await Promise.all([ getTeamsByOrgIds([orgId], {isArchived: false}), - dataLoader.get('organizations').load(orgId), + dataLoader.get('organizations').loadNonNull(orgId), dataLoader.get('users').loadNonNull(viewerId) ]) const orgTeamNames = orgTeams.map((team) => team.name) @@ -74,7 +74,7 @@ export default { return standardError(new Error('Failed input validation'), {userId: viewerId}) } if (orgTeams.length >= Threshold.MAX_FREE_TEAMS) { - const organization = await dataLoader.get('organizations').load(orgId) + const organization = await dataLoader.get('organizations').loadNonNull(orgId) if (getFeatureTier(organization) === 'starter') { return standardError(new Error('Max free teams reached'), {userId: viewerId}) } diff --git a/packages/server/graphql/mutations/archiveOrganization.ts b/packages/server/graphql/mutations/archiveOrganization.ts index 474d9d45fc4..9c998e49810 100644 --- a/packages/server/graphql/mutations/archiveOrganization.ts +++ b/packages/server/graphql/mutations/archiveOrganization.ts @@ -41,7 +41,7 @@ export default { } const [organization, viewer] = await Promise.all([ - dataLoader.get('organizations').load(orgId), + dataLoader.get('organizations').loadNonNull(orgId), dataLoader.get('users').loadNonNull(viewerId) ]) const {tier} = organization diff --git a/packages/server/graphql/mutations/downgradeToStarter.ts b/packages/server/graphql/mutations/downgradeToStarter.ts index bbed44fd08c..dc91b9f6a60 100644 --- a/packages/server/graphql/mutations/downgradeToStarter.ts +++ b/packages/server/graphql/mutations/downgradeToStarter.ts @@ -54,7 +54,7 @@ export default { return standardError(new Error('Other tool name is too long'), {userId: viewerId}) } - const {stripeSubscriptionId, tier} = await dataLoader.get('organizations').load(orgId) + const {stripeSubscriptionId, tier} = await dataLoader.get('organizations').loadNonNull(orgId) dataLoader.get('organizations').clear(orgId) if (tier === 'starter') { diff --git a/packages/server/graphql/mutations/helpers/canAccessAISummary.ts b/packages/server/graphql/mutations/helpers/canAccessAISummary.ts index a3fdb939d46..4ba56c793fc 100644 --- a/packages/server/graphql/mutations/helpers/canAccessAISummary.ts +++ b/packages/server/graphql/mutations/helpers/canAccessAISummary.ts @@ -1,17 +1,17 @@ import {Threshold} from 'parabol-client/types/constEnums' -import {Team} from '../../../postgres/queries/getTeamsByIds' import {DataLoaderWorker} from '../../graphql' +import {TeamSource} from '../../public/types/Team' import {getFeatureTier} from '../../types/helpers/getFeatureTier' const canAccessAISummary = async ( - team: Team, + team: TeamSource, featureFlags: string[], dataLoader: DataLoaderWorker, meetingType: 'standup' | 'retrospective' ) => { if (featureFlags.includes('noAISummary') || !team) return false const {qualAIMeetingsCount, orgId} = team - const organization = await dataLoader.get('organizations').load(orgId) + const organization = await dataLoader.get('organizations').loadNonNull(orgId) if (organization.featureFlags?.includes('noAISummary')) return false if (meetingType === 'standup') { if (!organization.featureFlags?.includes('standupAISummary')) return false diff --git a/packages/server/graphql/mutations/helpers/createNewOrg.ts b/packages/server/graphql/mutations/helpers/createNewOrg.ts index b5fa95ea827..bbfeec1f9d1 100644 --- a/packages/server/graphql/mutations/helpers/createNewOrg.ts +++ b/packages/server/graphql/mutations/helpers/createNewOrg.ts @@ -33,8 +33,5 @@ export default async function createNewOrg( .insertInto('Organization') .values({...org, creditCard: null}) .execute() - return r({ - org: r.table('Organization').insert(org), - organizationUser: r.table('OrganizationUser').insert(orgUser) - }).run() + await r.table('OrganizationUser').insert(orgUser).run() } diff --git a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts index 1019dd0b789..7452113d85d 100644 --- a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts +++ b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts @@ -26,7 +26,7 @@ export default async function createTeamAndLeader( const r = await getRethink() const {id: userId} = user const {id: teamId, orgId} = newTeam - const organization = await dataLoader.get('organizations').load(orgId) + const organization = await dataLoader.get('organizations').loadNonNull(orgId) const {tier, trialStartDate} = organization const verifiedTeam = new Team({...newTeam, createdBy: userId, tier, trialStartDate}) const meetingSettings = [ diff --git a/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts b/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts index 804b50a4849..1b22cefe1b4 100644 --- a/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts +++ b/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts @@ -24,7 +24,7 @@ export default async function sendNewMeetingSummary( const [content, users, organization] = await Promise.all([ newMeetingSummaryEmailCreator({meetingId, context}), dataLoader.get('users').loadMany(userIds), - dataLoader.get('organizations').load(orgId) + dataLoader.get('organizations').loadNonNull(orgId) ]) const {tier, name: orgName} = organization const emailAddresses = users diff --git a/packages/server/graphql/mutations/helpers/generateGroups.ts b/packages/server/graphql/mutations/helpers/generateGroups.ts index a7e80d060a3..d08a1922f8f 100644 --- a/packages/server/graphql/mutations/helpers/generateGroups.ts +++ b/packages/server/graphql/mutations/helpers/generateGroups.ts @@ -16,7 +16,7 @@ const generateGroups = async ( if (reflections.length === 0) return const {meetingId} = reflections[0]! const team = await dataLoader.get('teams').loadNonNull(teamId) - const organization = await dataLoader.get('organizations').load(team.orgId) + const organization = await dataLoader.get('organizations').loadNonNull(team.orgId) const {featureFlags} = organization const hasSuggestGroupsFlag = featureFlags?.includes('suggestGroups') if (!hasSuggestGroupsFlag) return diff --git a/packages/server/graphql/mutations/helpers/hideConversionModal.ts b/packages/server/graphql/mutations/helpers/hideConversionModal.ts index 3e186459c44..df578c85a2c 100644 --- a/packages/server/graphql/mutations/helpers/hideConversionModal.ts +++ b/packages/server/graphql/mutations/helpers/hideConversionModal.ts @@ -4,7 +4,7 @@ import errorFilter from '../../errorFilter' import {DataLoaderWorker} from '../../graphql' const hideConversionModal = async (orgId: string, dataLoader: DataLoaderWorker) => { - const organization = await dataLoader.get('organizations').load(orgId) + const organization = await dataLoader.get('organizations').loadNonNull(orgId) const {showConversionModal} = organization if (showConversionModal) { const r = await getRethink() @@ -13,13 +13,6 @@ const hideConversionModal = async (orgId: string, dataLoader: DataLoaderWorker) .set({showConversionModal: false}) .where('id', '=', orgId) .execute() - await r - .table('Organization') - .get(orgId) - .update({ - showConversionModal: false - }) - .run() organization.showConversionModal = false const teams = await dataLoader.get('teamsByOrgIds').load(orgId) const teamIds = teams.map(({id}) => id) diff --git a/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts b/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts index 423e3d980c2..916faae658a 100644 --- a/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts +++ b/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts @@ -87,7 +87,7 @@ const inviteToTeamHelper = async ( } const {name: teamName, createdAt, isOnboardTeam, orgId} = team - const organization = await dataLoader.get('organizations').load(orgId) + const organization = await dataLoader.get('organizations').loadNonNull(orgId) const {tier, name: orgName} = organization const uniqueInvitees = Array.from(new Set(validInvitees)) // filter out emails already on team diff --git a/packages/server/graphql/mutations/helpers/isStartMeetingLocked.ts b/packages/server/graphql/mutations/helpers/isStartMeetingLocked.ts index e7b55bf3dd4..75e2d2f3c84 100644 --- a/packages/server/graphql/mutations/helpers/isStartMeetingLocked.ts +++ b/packages/server/graphql/mutations/helpers/isStartMeetingLocked.ts @@ -3,7 +3,7 @@ import {DataLoaderWorker} from '../../graphql' const isStartMeetingLocked = async (teamId: string, dataLoader: DataLoaderWorker) => { const team = await dataLoader.get('teams').loadNonNull(teamId) - const organization = await dataLoader.get('organizations').load(team.orgId) + const organization = await dataLoader.get('organizations').loadNonNull(team.orgId) const {lockedAt: organizationLockedAt, name: organizationName} = organization const {isPaid, lockMessageHTML} = team diff --git a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts index 23159529393..7ca953ab19c 100644 --- a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts @@ -6,13 +6,13 @@ import appOrigin from '../../../../appOrigin' import Meeting from '../../../../database/types/Meeting' import {SlackNotificationEventEnum as EventEnum} from '../../../../database/types/SlackNotification' import {IntegrationProviderMSTeams} from '../../../../postgres/queries/getIntegrationProvidersByIds' -import {Team} from '../../../../postgres/queries/getTeamsByIds' import IUser from '../../../../postgres/types/IUser' import {MeetingTypeEnum} from '../../../../postgres/types/Meeting' import MSTeamsServerManager from '../../../../utils/MSTeamsServerManager' import {analytics} from '../../../../utils/analytics/analytics' import sendToSentry from '../../../../utils/sendToSentry' import {DataLoaderWorker} from '../../../graphql' +import {TeamSource} from '../../../public/types/Team' import {NotificationIntegrationHelper} from './NotificationIntegrationHelper' import {createNotifier} from './Notifier' import getSummaryText from './getSummaryText' @@ -359,7 +359,7 @@ function GenerateACMeetingTitle(meetingTitle: string) { return titleTextBlock } -function GenerateACMeetingAndTeamsDetails(team: Team, meeting: Meeting) { +function GenerateACMeetingAndTeamsDetails(team: TeamSource, meeting: Meeting) { const meetingDetailColumnSet = new AdaptiveCards.ColumnSet() const teamDetailColumn = new AdaptiveCards.Column() teamDetailColumn.width = 'stretch' diff --git a/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts index 3eadfa37b7d..9598dac6f1b 100644 --- a/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts @@ -7,7 +7,6 @@ import appOrigin from '../../../../appOrigin' import Meeting from '../../../../database/types/Meeting' import {SlackNotificationEventEnum as EventEnum} from '../../../../database/types/SlackNotification' import {IntegrationProviderMattermost} from '../../../../postgres/queries/getIntegrationProvidersByIds' -import {Team} from '../../../../postgres/queries/getTeamsByIds' import IUser from '../../../../postgres/types/IUser' import {MeetingTypeEnum} from '../../../../postgres/types/Meeting' import MattermostServerManager from '../../../../utils/MattermostServerManager' @@ -15,6 +14,7 @@ import {analytics} from '../../../../utils/analytics/analytics' import {toEpochSeconds} from '../../../../utils/epochTime' import sendToSentry from '../../../../utils/sendToSentry' import {DataLoaderWorker} from '../../../graphql' +import {TeamSource} from '../../../public/types/Team' import {NotificationIntegrationHelper} from './NotificationIntegrationHelper' import {createNotifier} from './Notifier' import getSummaryText from './getSummaryText' @@ -92,7 +92,7 @@ const makeEndMeetingButtons = (meeting: Meeting) => { type MattermostNotificationAuth = IntegrationProviderMattermost & {userId: string} const makeTeamPromptStartMeetingNotification = ( - team: Team, + team: TeamSource, meeting: Meeting, meetingUrl: string ) => { @@ -118,7 +118,11 @@ const makeTeamPromptStartMeetingNotification = ( ] } -const makeGenericStartMeetingNotification = (team: Team, meeting: Meeting, meetingUrl: string) => { +const makeGenericStartMeetingNotification = ( + team: TeamSource, + meeting: Meeting, + meetingUrl: string +) => { return [ makeFieldsAttachment( [ @@ -148,7 +152,11 @@ const makeGenericStartMeetingNotification = (team: Team, meeting: Meeting, meeti const makeStartMeetingNotificationLookup: Record< MeetingTypeEnum, - (team: Team, meeting: Meeting, meetingUrl: string) => ReturnType[] + ( + team: TeamSource, + meeting: Meeting, + meetingUrl: string + ) => ReturnType[] > = { teamPrompt: makeTeamPromptStartMeetingNotification, action: makeGenericStartMeetingNotification, diff --git a/packages/server/graphql/mutations/helpers/notifications/NotificationIntegrationHelper.ts b/packages/server/graphql/mutations/helpers/notifications/NotificationIntegrationHelper.ts index 153f0b4d645..7dd24c4c78c 100644 --- a/packages/server/graphql/mutations/helpers/notifications/NotificationIntegrationHelper.ts +++ b/packages/server/graphql/mutations/helpers/notifications/NotificationIntegrationHelper.ts @@ -1,7 +1,7 @@ import Meeting from '../../../../database/types/Meeting' import {TeamPromptResponse} from '../../../../postgres/queries/getTeamPromptResponsesByIds' -import {Team} from '../../../../postgres/queries/getTeamsByIds' import User from '../../../../postgres/types/IUser' +import {TeamSource} from '../../../public/types/Team' export type NotifyResponse = | 'success' @@ -12,25 +12,25 @@ export type NotifyResponse = } export type NotificationIntegration = { - startMeeting(meeting: Meeting, team: Team, user: User): Promise - updateMeeting?(meeting: Meeting, team: Team, user: User): Promise + startMeeting(meeting: Meeting, team: TeamSource, user: User): Promise + updateMeeting?(meeting: Meeting, team: TeamSource, user: User): Promise endMeeting( meeting: Meeting, - team: Team, + team: TeamSource, user: User, standupResponses: {user: User; response: TeamPromptResponse}[] | null ): Promise startTimeLimit( scheduledEndTime: Date, meeting: Meeting, - team: Team, + team: TeamSource, user: User ): Promise - endTimeLimit(meeting: Meeting, team: Team, user: User): Promise + endTimeLimit(meeting: Meeting, team: TeamSource, user: User): Promise integrationUpdated(user: User): Promise standupResponseSubmitted( meeting: Meeting, - team: Team, + team: TeamSource, user: User, response: TeamPromptResponse ): Promise diff --git a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts index ff9e282f1a5..37ff8e356cb 100644 --- a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts @@ -12,7 +12,6 @@ import {SlackNotificationEvent} from '../../../../database/types/SlackNotificati import {SlackNotificationAuth} from '../../../../dataloader/integrationAuthLoaders' import {TeamPromptResponse} from '../../../../postgres/queries/getTeamPromptResponsesByIds' import {getTeamPromptResponsesByMeetingId} from '../../../../postgres/queries/getTeamPromptResponsesByMeetingIds' -import {Team} from '../../../../postgres/queries/getTeamsByIds' import User from '../../../../postgres/types/IUser' import {AnyMeeting, MeetingTypeEnum} from '../../../../postgres/types/Meeting' import SlackServerManager from '../../../../utils/SlackServerManager' @@ -21,6 +20,7 @@ import {toEpochSeconds} from '../../../../utils/epochTime' import sendToSentry from '../../../../utils/sendToSentry' import {convertToMarkdown} from '../../../../utils/tiptap/convertToMarkdown' import {DataLoaderWorker} from '../../../graphql' +import {TeamSource} from '../../../public/types/Team' import {NotificationIntegrationHelper} from './NotificationIntegrationHelper' import {createNotifier} from './Notifier' import getSummaryText from './getSummaryText' @@ -137,12 +137,12 @@ const makeEndMeetingButtons = (meeting: Meeting) => { } } -const createTeamSectionContent = (team: Team) => `*Team:*\n${team.name}` +const createTeamSectionContent = (team: TeamSource) => `*Team:*\n${team.name}` const createMeetingSectionContent = (meeting: Meeting) => `*Meeting:*\n${meeting.name}` const makeTeamPromptStartMeetingNotification = ( - team: Team, + team: TeamSource, meeting: Meeting, meetingUrl: string ): SlackNotification => { @@ -157,7 +157,7 @@ const makeTeamPromptStartMeetingNotification = ( } const makeGenericStartMeetingNotification = ( - team: Team, + team: TeamSource, meeting: Meeting, meetingUrl: string ): SlackNotification => { @@ -173,7 +173,7 @@ const makeGenericStartMeetingNotification = ( const makeStartMeetingNotificationLookup: Record< MeetingTypeEnum, - (team: Team, meeting: Meeting, meetingUrl: string) => SlackNotification + (team: TeamSource, meeting: Meeting, meetingUrl: string) => SlackNotification > = { teamPrompt: makeTeamPromptStartMeetingNotification, action: makeGenericStartMeetingNotification, @@ -184,7 +184,7 @@ const makeStartMeetingNotificationLookup: Record< const addStandupResponsesToThread = async ( res: PostMessageResponse, standupResponses: Array<{user: User; response: TeamPromptResponse}> | null, - team: Team, + team: TeamSource, user: User, meeting: Meeting, notificationChannel: NotificationChannel diff --git a/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts b/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts index 5e998500973..24c2ed3f6bc 100644 --- a/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts +++ b/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts @@ -1,6 +1,7 @@ import removeTeamsLimitObjects from '../../../billing/helpers/removeTeamsLimitObjects' import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' +import {toCreditCard} from '../../../postgres/helpers/toCreditCard' import {fromEpochSeconds} from '../../../utils/epochTime' import setTierForOrgUsers from '../../../utils/setTierForOrgUsers' import setUserTierForOrgId from '../../../utils/setUserTierForOrgId' @@ -18,7 +19,7 @@ const oldUpgradeToTeamTier = async ( const pg = getKysely() const now = new Date() - const organization = await r.table('Organization').get(orgId).run() + const organization = await dataLoader.get('organizations').load(orgId) if (!organization) throw new Error('Bad orgId') const {stripeId, stripeSubscriptionId} = organization @@ -44,22 +45,22 @@ const oldUpgradeToTeamTier = async ( } } - await r({ - updatedOrg: r - .table('Organization') - .get(orgId) - .update({ - ...subscriptionFields, - creditCard: await getCCFromCustomer(customer), - tier: 'team', - stripeId: customer.id, - tierLimitExceededAt: null, - scheduledLockAt: null, - lockedAt: null, - updatedAt: now, - trialStartDate: null - }) - }).run() + const creditCard = await getCCFromCustomer(customer) + await getKysely() + .updateTable('Organization') + .set({ + ...subscriptionFields, + creditCard: toCreditCard(creditCard), + tier: 'team', + stripeId: customer.id, + tierLimitExceededAt: null, + scheduledLockAt: null, + lockedAt: null, + updatedAt: now, + trialStartDate: null + }) + .where('id', '=', orgId) + .execute() // If subscription already exists and has open invoices, try to process them if (stripeSubscriptionId) { diff --git a/packages/server/graphql/mutations/helpers/removeFromOrg.ts b/packages/server/graphql/mutations/helpers/removeFromOrg.ts index ac4fb4ccca5..af706293f0b 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 dataLoader.get('organizations').load(orgId) + const organization = await dataLoader.get('organizations').loadNonNull(orgId) // if no other billing leader, promote the oldest // if team tier & no other member, downgrade to starter const otherBillingLeaders = await r diff --git a/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts b/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts index 803c767e1e7..8e44110824c 100644 --- a/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts +++ b/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts @@ -1,4 +1,3 @@ -import getRethink from '../../../database/rethinkDriver' import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' import getKysely from '../../../postgres/getKysely' import updateTeamByOrgId from '../../../postgres/queries/updateTeamByOrgId' @@ -19,7 +18,6 @@ const resolveDowngradeToStarter = async ( ) => { const now = new Date() const manager = getStripeManager() - const r = await getRethink() const pg = getKysely() try { await manager.deleteSubscription(stripeSubscriptionId) @@ -28,7 +26,7 @@ const resolveDowngradeToStarter = async ( } const [org] = await Promise.all([ - dataLoader.get('organizations').load(orgId), + dataLoader.get('organizations').loadNonNull(orgId), pg .updateTable('Organization') .set({ @@ -43,14 +41,6 @@ const resolveDowngradeToStarter = async ( .set({metadata: null, lastUpdatedBy: user.id}) .where('orgId', '=', orgId) .execute(), - r({ - orgUpdate: r.table('Organization').get(orgId).update({ - tier: 'starter', - periodEnd: now, - stripeSubscriptionId: null, - updatedAt: now - }) - }).run(), updateTeamByOrgId( { tier: 'starter', @@ -63,7 +53,7 @@ const resolveDowngradeToStarter = async ( await Promise.all([setUserTierForOrgId(orgId), setTierForOrgUsers(orgId)]) analytics.organizationDowngraded(user, { orgId, - domain: org.activeDomain, + domain: org.activeDomain || undefined, orgName: org.name, oldTier: 'team', newTier: 'starter', diff --git a/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts b/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts index c803811cc56..221557c3b09 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 dataLoader.get('organizations').load(team.orgId) + const organization = await dataLoader.get('organizations').loadNonNull(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 bef33207dd1..7e3ea1ad40d 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([ - dataLoader.get('organizations').load(orgId), + dataLoader.get('organizations').loadNonNull(orgId), getTeamsByIds([teamId]), pg .selectFrom('Team') diff --git a/packages/server/graphql/mutations/oldUpgradeToTeamTier.ts b/packages/server/graphql/mutations/oldUpgradeToTeamTier.ts index f8d28d78b62..ddcd8d27035 100644 --- a/packages/server/graphql/mutations/oldUpgradeToTeamTier.ts +++ b/packages/server/graphql/mutations/oldUpgradeToTeamTier.ts @@ -42,7 +42,7 @@ export default { stripeSubscriptionId: startingSubId, name: orgName, activeDomain: domain - } = await r.table('Organization').get(orgId).run() + } = await dataLoader.get('organizations').loadNonNull(orgId) if (startingSubId) { return standardError(new Error('Already an organization on the team tier'), { @@ -76,7 +76,7 @@ export default { const teamIds = teams.map(({id}) => id) analytics.organizationUpgraded(viewer, { orgId, - domain, + domain: domain || undefined, orgName, oldTier: 'starter', newTier: 'team' diff --git a/packages/server/graphql/mutations/payLater.ts b/packages/server/graphql/mutations/payLater.ts index 49a4b6ae794..18ec12d6af9 100644 --- a/packages/server/graphql/mutations/payLater.ts +++ b/packages/server/graphql/mutations/payLater.ts @@ -1,7 +1,6 @@ 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' @@ -57,13 +56,6 @@ export default { })) .where('id', '=', orgId) .execute() - await r - .table('Organization') - .get(orgId) - .update((row: RValue) => ({ - payLaterClickCount: row('payLaterClickCount').default(0).add(1) - })) - .run() await r .table('NewMeeting') .get(meetingId) diff --git a/packages/server/graphql/private/mutations/backupOrganization.ts b/packages/server/graphql/private/mutations/backupOrganization.ts index cfc321db1ad..c347258c54b 100644 --- a/packages/server/graphql/private/mutations/backupOrganization.ts +++ b/packages/server/graphql/private/mutations/backupOrganization.ts @@ -191,9 +191,6 @@ const backupOrganization: MutationResolvers['backupOrganization'] = async (_sour newMeeting: (r.table('NewMeeting').getAll(r.args(teamIds), {index: 'teamId'}) as any) .coerceTo('array') .do((items: RValue) => r.db(DESTINATION).table('NewMeeting').insert(items)), - organization: (r.table('Organization').getAll(r.args(orgIds)) as any) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('Organization').insert(items)), organizationUser: (r.table('OrganizationUser').getAll(r.args(orgIds), {index: 'orgId'}) as any) .coerceTo('array') .do((items: RValue) => r.db(DESTINATION).table('OrganizationUser').insert(items)), diff --git a/packages/server/graphql/private/mutations/changeEmailDomain.ts b/packages/server/graphql/private/mutations/changeEmailDomain.ts index 351bbe85af4..85228837367 100644 --- a/packages/server/graphql/private/mutations/changeEmailDomain.ts +++ b/packages/server/graphql/private/mutations/changeEmailDomain.ts @@ -60,11 +60,6 @@ const changeEmailDomain: MutationResolvers['changeEmailDomain'] = async ( .set({activeDomain: normalizedNewDomain}) .where('activeDomain', '=', normalizedOldDomain) .execute(), - r - .table('Organization') - .filter((row: RDatum) => row('activeDomain').eq(normalizedOldDomain)) - .update({activeDomain: normalizedNewDomain}) - .run(), r .table('TeamMember') .filter((row: RDatum) => row('email').match(`@${normalizedOldDomain}$`)) diff --git a/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts b/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts index ab2da48431c..a3f1dfe13f6 100644 --- a/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts +++ b/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts @@ -59,7 +59,6 @@ const draftEnterpriseInvoice: MutationResolvers['draftEnterpriseInvoice'] = asyn {orgId, quantity, email, apEmail, plan}, {dataLoader} ) => { - const r = await getRethink() const pg = getKysely() const now = new Date() @@ -107,7 +106,6 @@ const draftEnterpriseInvoice: MutationResolvers['draftEnterpriseInvoice'] = asyn .set({stripeId: customer.id}) .where('id', '=', orgId) .execute() - await r.table('Organization').get(orgId).update({stripeId: customer.id}).run() customerId = customer.id } else { customerId = stripeId @@ -136,22 +134,6 @@ const draftEnterpriseInvoice: MutationResolvers['draftEnterpriseInvoice'] = asyn }) .where('id', '=', orgId) .execute(), - r({ - updatedOrg: r - .table('Organization') - .get(orgId) - .update({ - 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 - }) - }).run(), pg .updateTable('Team') .set({ @@ -171,7 +153,7 @@ const draftEnterpriseInvoice: MutationResolvers['draftEnterpriseInvoice'] = asyn ]) analytics.organizationUpgraded(user, { orgId, - domain: org.activeDomain, + domain: org.activeDomain || undefined, orgName: org.name, isTrial: !!org.trialStartDate, oldTier: 'starter', diff --git a/packages/server/graphql/private/mutations/endTrial.ts b/packages/server/graphql/private/mutations/endTrial.ts index 890ffdb0015..eb600c5806d 100644 --- a/packages/server/graphql/private/mutations/endTrial.ts +++ b/packages/server/graphql/private/mutations/endTrial.ts @@ -1,4 +1,3 @@ -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import setTierForOrgUsers from '../../../utils/setTierForOrgUsers' import setUserTierForOrgId from '../../../utils/setUserTierForOrgId' @@ -6,12 +5,12 @@ import standardError from '../../../utils/standardError' import {MutationResolvers} from '../resolverTypes' const endTrial: MutationResolvers['endTrial'] = async (_source, {orgId}, {dataLoader}) => { - const now = new Date() - const r = await getRethink() const pg = getKysely() const organization = await dataLoader.get('organizations').load(orgId) - + if (!organization) { + return {error: {message: 'Organization not found'}} + } // VALIDATION if (!organization.trialStartDate) { return standardError(new Error('No trial active for org')) @@ -20,12 +19,6 @@ 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, - updatedAt: now - }) - }).run(), pg.updateTable('Team').set({trialStartDate: null}).where('orgId', '=', orgId).execute() ]) @@ -34,7 +27,7 @@ const endTrial: MutationResolvers['endTrial'] = async (_source, {orgId}, {dataLo await Promise.all([setUserTierForOrgId(orgId), setTierForOrgUsers(orgId)]) - return {organization, trialStartDate: initialTrialStartDate} + return {orgId, trialStartDate: initialTrialStartDate} } export default endTrial diff --git a/packages/server/graphql/private/mutations/flagConversionModal.ts b/packages/server/graphql/private/mutations/flagConversionModal.ts index 4ba7bd4b4f1..0ca11d6139f 100644 --- a/packages/server/graphql/private/mutations/flagConversionModal.ts +++ b/packages/server/graphql/private/mutations/flagConversionModal.ts @@ -1,4 +1,3 @@ -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {MutationResolvers} from '../resolverTypes' @@ -7,8 +6,6 @@ const flagConversionModal: MutationResolvers['flagConversionModal'] = async ( {active, orgId}, {dataLoader} ) => { - const r = await getRethink() - // VALIDATION const organization = await dataLoader.get('organizations').load(orgId) if (!organization) { @@ -22,13 +19,6 @@ const flagConversionModal: MutationResolvers['flagConversionModal'] = async ( .set({showConversionModal: active}) .where('id', '=', orgId) .execute() - await r - .table('Organization') - .get(orgId) - .update({ - showConversionModal: active - }) - .run() return {orgId} } diff --git a/packages/server/graphql/private/mutations/processRecurrence.ts b/packages/server/graphql/private/mutations/processRecurrence.ts index 57d1e8578d5..22bdd3b5815 100644 --- a/packages/server/graphql/private/mutations/processRecurrence.ts +++ b/packages/server/graphql/private/mutations/processRecurrence.ts @@ -165,7 +165,7 @@ const processRecurrence: MutationResolvers['processRecurrence'] = async (_source } const [seriesOrg, lastMeeting] = await Promise.all([ - dataLoader.get('organizations').load(seriesTeam.orgId), + dataLoader.get('organizations').loadNonNull(seriesTeam.orgId), dataLoader.get('lastMeetingByMeetingSeriesId').load(meetingSeries.id) ]) diff --git a/packages/server/graphql/private/mutations/sendUpcomingInvoiceEmails.ts b/packages/server/graphql/private/mutations/sendUpcomingInvoiceEmails.ts deleted file mode 100644 index d49383b0486..00000000000 --- a/packages/server/graphql/private/mutations/sendUpcomingInvoiceEmails.ts +++ /dev/null @@ -1,153 +0,0 @@ -import {UpcomingInvoiceEmailProps} from 'parabol-client/modules/email/components/UpcomingInvoiceEmail' -import makeAppURL from 'parabol-client/utils/makeAppURL' -import {months} from 'parabol-client/utils/makeDateString' -import {isNotNull} from 'parabol-client/utils/predicates' -import {Threshold} from '../../../../client/types/constEnums' -import appOrigin from '../../../appOrigin' -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' - -interface Details extends UpcomingInvoiceEmailProps { - emails: string[] -} - -interface Organization { - id: string - periodEnd: Date - billingLeaderIds: string[] - newUserIds: string[] -} - -const makePeriodEndStr = (periodEnd: Date) => { - const date = new Date(periodEnd) - const month = date.getMonth() - const day = date.getDate() - const monthStr = months[month] - return `${monthStr} ${day}` -} - -const getEmailDetails = (organizations: Organization[], userMap: Map) => { - const details = [] as Details[] - organizations.forEach((organization) => { - const {id: orgId, billingLeaderIds, periodEnd} = organization - const newUsers = organization.newUserIds - .map((id) => { - const newUser = userMap.get(id) - return ( - newUser && { - email: newUser.email, - name: newUser.preferredName - } - ) - }) - .filter((newUser) => newUser !== undefined) as {name: string; email: string}[] - details.push({ - appOrigin, - emails: billingLeaderIds - .map((id) => userMap.get(id)?.email) - .filter((email) => email !== undefined) as string[], - periodEndStr: makePeriodEndStr(periodEnd), - memberUrl: makeAppURL(appOrigin, `me/organizations/${orgId}/members`), - newUsers - }) - }) - return details -} - -const sendUpcomingInvoiceEmails: MutationResolvers['sendUpcomingInvoiceEmails'] = async ( - _source, - _args, - {dataLoader} -) => { - const r = await getRethink() - const now = new Date() - const periodEndThresh = new Date(Date.now() + Threshold.UPCOMING_INVOICE_EMAIL_WARNING) - const lastSentThresh = new Date(Date.now() - Threshold.UPCOMING_INVOICE_EMAIL_WARNING) - - const organizations = (await r - .table('Organization') - .getAll('team', {index: 'tier'}) - .filter((organization: RValue) => - r.and( - organization('periodEnd').le(periodEndThresh).default(false), - organization('upcomingInvoiceEmailSentAt').le(lastSentThresh).default(true) - ) - ) - .coerceTo('array') - .merge((organization: RValue) => ({ - newUserIds: r - .table('OrganizationUser') - .getAll(organization('id'), {index: 'orgId'}) - .filter((organizationUser: RValue) => organizationUser('newUserUntil').ge(now)) - .filter({removedAt: null, role: null})('userId') - .coerceTo('array') - })) - .filter((organization: RValue) => organization('newUserIds').count().ge(1)) - .merge((organization: RValue) => ({ - billingLeaderIds: r - .table('OrganizationUser') - .getAll(organization('id'), {index: 'orgId'}) - .filter({removedAt: null}) - .filter((row: RDatum) => r.expr(['BILLING_LEADER', 'ORG_ADMIN']).contains(row('role')))( - 'userId' - ) - .coerceTo('array') - })) - .coerceTo('array') - .run()) as Organization[] - - if (organizations.length === 0) return [] - - // collect all users to reduce roundtrips to db and do the merging when formatting the data - const allUserIds = organizations.reduce( - (prev, cur) => prev.concat(cur.billingLeaderIds, cur.newUserIds), - [] as string[] - ) - const allUsers = await Promise.all( - allUserIds.map((userId) => dataLoader.get('users').load(userId)) - ) - const allUserMap = allUsers.filter(isNotNull).reduce((prev, cur) => { - prev.set(cur.id, cur) - return prev - }, new Map()) - - const details = getEmailDetails(organizations, allUserMap) - await Promise.all( - details.map((detail) => { - const {emails, ...props} = detail - const {subject, body, html} = UpcomingInvoiceEmailTemplate(props) - return Promise.all( - emails.map((to) => { - return getMailManager().sendEmail({ - to, - subject, - body, - html, - tags: ['type:upcomingInvoice'] - }) - }) - ) - }) - ) - 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)) - .update({ - upcomingInvoiceEmailSentAt: now - }) - .run() - return details.map(({emails}) => emails.join(',')) -} - -export default sendUpcomingInvoiceEmails diff --git a/packages/server/graphql/private/mutations/setOrganizationDomain.ts b/packages/server/graphql/private/mutations/setOrganizationDomain.ts index b374ed7ce35..52b649b4bd8 100644 --- a/packages/server/graphql/private/mutations/setOrganizationDomain.ts +++ b/packages/server/graphql/private/mutations/setOrganizationDomain.ts @@ -1,4 +1,3 @@ -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {MutationResolvers} from '../resolverTypes' @@ -7,7 +6,6 @@ const setOrganizationDomain: MutationResolvers['setOrganizationDomain'] = async {orgId, domain}, {dataLoader} ) => { - const r = await getRethink() // VALIDATION const organization = await dataLoader.get('organizations').load(orgId) dataLoader.get('organizations').clear(orgId) @@ -21,14 +19,7 @@ const setOrganizationDomain: MutationResolvers['setOrganizationDomain'] = async .set({activeDomain: domain, isActiveDomainTouched: true}) .where('id', '=', orgId) .execute() - await r - .table('Organization') - .get(orgId) - .update({ - activeDomain: domain, - isActiveDomainTouched: true - }) - .run() + return true } diff --git a/packages/server/graphql/private/mutations/startTrial.ts b/packages/server/graphql/private/mutations/startTrial.ts index cff3800cb7b..2b32eddd1b8 100644 --- a/packages/server/graphql/private/mutations/startTrial.ts +++ b/packages/server/graphql/private/mutations/startTrial.ts @@ -1,5 +1,4 @@ import removeTeamsLimitObjects from '../../../billing/helpers/removeTeamsLimitObjects' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import setTierForOrgUsers from '../../../utils/setTierForOrgUsers' import setUserTierForOrgId from '../../../utils/setUserTierForOrgId' @@ -8,11 +7,12 @@ import hideConversionModal from '../../mutations/helpers/hideConversionModal' import {MutationResolvers} from '../resolverTypes' const startTrial: MutationResolvers['startTrial'] = async (_source, {orgId}, {dataLoader}) => { - const r = await getRethink() const pg = getKysely() const now = new Date() const organization = await dataLoader.get('organizations').load(orgId) - + if (!organization) { + return {error: {message: 'Organization not found'}} + } // VALIDATION if (organization.tier !== 'starter') { return standardError(new Error('Cannot start trial for organization on paid tier')) @@ -30,15 +30,6 @@ const startTrial: MutationResolvers['startTrial'] = async (_source, {orgId}, {da .set({trialStartDate: now, tierLimitExceededAt: null, scheduledLockAt: null, lockedAt: null}) .where('id', '=', orgId) .execute(), - r({ - updatedOrg: r.table('Organization').get(orgId).update({ - trialStartDate: now, - tierLimitExceededAt: null, - scheduledLockAt: null, - lockedAt: null, - updatedAt: now - }) - }).run(), pg.updateTable('Team').set({trialStartDate: now}).where('orgId', '=', orgId).execute(), removeTeamsLimitObjects(orgId, dataLoader) ]) @@ -48,7 +39,7 @@ const startTrial: MutationResolvers['startTrial'] = async (_source, {orgId}, {da await hideConversionModal(orgId, dataLoader) - return {organization} + return {orgId} } export default startTrial diff --git a/packages/server/graphql/private/mutations/stripeCreateSubscription.ts b/packages/server/graphql/private/mutations/stripeCreateSubscription.ts index 4acbd075ce5..2397b8d3ee2 100644 --- a/packages/server/graphql/private/mutations/stripeCreateSubscription.ts +++ b/packages/server/graphql/private/mutations/stripeCreateSubscription.ts @@ -1,5 +1,4 @@ import Stripe from 'stripe' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {isSuperUser} from '../../../utils/authorization' import {getStripeManager} from '../../../utils/stripe' @@ -10,7 +9,6 @@ const stripeCreateSubscription: MutationResolvers['stripeCreateSubscription'] = {customerId, subscriptionId}, {authToken} ) => { - const r = await getRethink() // AUTH if (!isSuperUser(authToken)) { throw new Error('Don’t be rude.') @@ -47,14 +45,6 @@ const stripeCreateSubscription: MutationResolvers['stripeCreateSubscription'] = .where('id', '=', orgId) .execute() - await r - .table('Organization') - .get(orgId) - .update({ - stripeSubscriptionId: subscriptionId - }) - .run() - return true } diff --git a/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts b/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts index 3b8d2dd1f7f..9a00d98d386 100644 --- a/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts +++ b/packages/server/graphql/private/mutations/stripeDeleteSubscription.ts @@ -1,5 +1,3 @@ -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' @@ -10,7 +8,6 @@ const stripeDeleteSubscription: MutationResolvers['stripeDeleteSubscription'] = {customerId, subscriptionId}, {authToken, dataLoader} ) => { - const r = await getRethink() // AUTH if (!isSuperUser(authToken)) { throw new Error('Don’t be rude.') @@ -29,7 +26,10 @@ const stripeDeleteSubscription: MutationResolvers['stripeDeleteSubscription'] = if (!orgId) { throw new Error(`orgId not found on metadata for customer ${customerId}`) } - const org: Organization = await dataLoader.get('organizations').load(orgId) + const org = await dataLoader.get('organizations').load(orgId) + if (!org) { + throw new Error(`Organization not found for orgId ${orgId}`) + } const {stripeSubscriptionId} = org if (!stripeSubscriptionId) return false @@ -42,13 +42,6 @@ const stripeDeleteSubscription: MutationResolvers['stripeDeleteSubscription'] = .set({stripeSubscriptionId: null}) .where('id', '=', orgId) .execute() - await r - .table('Organization') - .get(orgId) - .update({ - stripeSubscriptionId: r.literal() - }) - .run() return true } diff --git a/packages/server/graphql/private/mutations/stripeInvoicePaid.ts b/packages/server/graphql/private/mutations/stripeInvoicePaid.ts index 527dd1ec1fa..3d533b39b40 100644 --- a/packages/server/graphql/private/mutations/stripeInvoicePaid.ts +++ b/packages/server/graphql/private/mutations/stripeInvoicePaid.ts @@ -51,16 +51,18 @@ const stripeInvoicePaid: MutationResolvers['stripeInvoicePaid'] = async ( } await Promise.all([ r({ - invoice: r.table('Invoice').get(invoiceId).update({ - creditCard, - paidAt: now, - status: 'PAID' - }), - org: r - .table('Organization') - .get(orgId) + invoice: r + .table('Invoice') + .get(invoiceId) .update({ - stripeSubscriptionId: invoice.subscription as string + creditCard: creditCard + ? { + ...creditCard, + last4: String(creditCard) + } + : undefined, + paidAt: now, + status: 'PAID' }) }).run(), updateTeamByOrgId(teamUpdates, orgId) diff --git a/packages/server/graphql/private/mutations/stripeSucceedPayment.ts b/packages/server/graphql/private/mutations/stripeSucceedPayment.ts index 03a62264dae..76c93a60856 100644 --- a/packages/server/graphql/private/mutations/stripeSucceedPayment.ts +++ b/packages/server/graphql/private/mutations/stripeSucceedPayment.ts @@ -51,16 +51,18 @@ const stripeSucceedPayment: MutationResolvers['stripeSucceedPayment'] = async ( } await Promise.all([ r({ - invoice: r.table('Invoice').get(invoiceId).update({ - creditCard, - paidAt: now, - status: 'PAID' - }), - org: r - .table('Organization') - .get(orgId) + invoice: r + .table('Invoice') + .get(invoiceId) .update({ - stripeSubscriptionId: invoice.subscription as string + creditCard: creditCard + ? { + ...creditCard, + last4: String(creditCard.last4) + } + : undefined, + paidAt: now, + status: 'PAID' }) }).run(), updateTeamByOrgId(teamUpdates, orgId) diff --git a/packages/server/graphql/private/mutations/stripeUpdateCreditCard.ts b/packages/server/graphql/private/mutations/stripeUpdateCreditCard.ts index 2772c922b30..90427111fa3 100644 --- a/packages/server/graphql/private/mutations/stripeUpdateCreditCard.ts +++ b/packages/server/graphql/private/mutations/stripeUpdateCreditCard.ts @@ -1,4 +1,3 @@ -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {toCreditCard} from '../../../postgres/helpers/toCreditCard' import {isSuperUser} from '../../../utils/authorization' @@ -15,7 +14,6 @@ const stripeUpdateCreditCard: MutationResolvers['stripeUpdateCreditCard'] = asyn if (!isSuperUser(authToken)) { throw new Error('Don’t be rude.') } - const r = await getRethink() const manager = getStripeManager() const customer = await manager.retrieveCustomer(customerId) if (customer.deleted) { @@ -33,7 +31,6 @@ const stripeUpdateCreditCard: MutationResolvers['stripeUpdateCreditCard'] = asyn .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 733d03ae10b..dbd12ba0c41 100644 --- a/packages/server/graphql/private/mutations/updateOrgFeatureFlag.ts +++ b/packages/server/graphql/private/mutations/updateOrgFeatureFlag.ts @@ -1,6 +1,4 @@ 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' @@ -10,7 +8,6 @@ const updateOrgFeatureFlag: MutationResolvers['updateOrgFeatureFlag'] = async ( {orgIds, flag, addFlag}, {dataLoader} ) => { - const r = await getRethink() const existingOrgs = (await dataLoader.get('organizations').loadMany(orgIds)).filter(isValid) const existingIds = existingOrgs.map(({id}) => id) @@ -21,7 +18,7 @@ const updateOrgFeatureFlag: MutationResolvers['updateOrgFeatureFlag'] = async ( } // RESOLUTION - await getKysely() + const updatedOrgIds = await getKysely() .updateTable('Organization') .$if(addFlag, (db) => db.set({featureFlags: sql`arr_append_uniq("featureFlags",${flag})`})) .$if(!addFlag, (db) => @@ -32,24 +29,8 @@ const updateOrgFeatureFlag: MutationResolvers['updateOrgFeatureFlag'] = async ( .where('id', 'in', orgIds) .returning('id') .execute() - const updatedOrgIds = (await r - .table('Organization') - .getAll(r.args(orgIds)) - .update( - (row: RValue) => ({ - featureFlags: r.branch( - addFlag, - row('featureFlags').default([]).setInsert(flag), - row('featureFlags') - .default([]) - .filter((featureFlag: RValue) => featureFlag.ne(flag)) - ) - }), - {returnChanges: true} - )('changes')('new_val')('id') - .run()) as string[] - return {updatedOrgIds} + return {updatedOrgIds: updatedOrgIds.map(({id}) => id)} } export default updateOrgFeatureFlag diff --git a/packages/server/graphql/private/mutations/upgradeToTeamTier.ts b/packages/server/graphql/private/mutations/upgradeToTeamTier.ts index aef8652f492..e0e04d94b5a 100644 --- a/packages/server/graphql/private/mutations/upgradeToTeamTier.ts +++ b/packages/server/graphql/private/mutations/upgradeToTeamTier.ts @@ -45,12 +45,11 @@ const upgradeToTeamTier: MutationResolvers['upgradeToTeamTier'] = async ( const pg = getKysely() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} - const now = new Date() // AUTH const viewerId = getUserId(authToken) const [organization, viewer] = await Promise.all([ - dataLoader.get('organizations').load(orgId), + dataLoader.get('organizations').loadNonNull(orgId), dataLoader.get('users').loadNonNull(viewerId) ]) @@ -83,19 +82,6 @@ const upgradeToTeamTier: MutationResolvers['upgradeToTeamTier'] = async ( }) .where('id', '=', orgId) .execute(), - r({ - 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') .set({ @@ -125,7 +111,7 @@ const upgradeToTeamTier: MutationResolvers['upgradeToTeamTier'] = async ( const teamIds = teams.map(({id}) => id) analytics.organizationUpgraded(viewer, { orgId, - domain: activeDomain, + domain: activeDomain || undefined, isTrial: !!trialStartDate, orgName, oldTier: 'starter', diff --git a/packages/server/graphql/private/queries/suProOrgInfo.ts b/packages/server/graphql/private/queries/suProOrgInfo.ts index 387ceadc429..870c5237419 100644 --- a/packages/server/graphql/private/queries/suProOrgInfo.ts +++ b/packages/server/graphql/private/queries/suProOrgInfo.ts @@ -1,22 +1,27 @@ import getRethink from '../../../database/rethinkDriver' -import {RValue} from '../../../database/stricterR' +import {RDatum} from '../../../database/stricterR' +import {selectOrganizations} from '../../../dataloader/primaryKeyLoaderMakers' import {QueryResolvers} from '../resolverTypes' const suProOrgInfo: QueryResolvers['suProOrgInfo'] = async (_source, {includeInactive}) => { const r = await getRethink() - return r - .table('Organization') - .getAll('team', {index: 'tier'}) - .merge((organization: RValue) => ({ - users: r - .table('OrganizationUser') - .getAll(organization('id'), {index: 'orgId'}) - .filter({removedAt: null}) - .filter((user: RValue) => r.branch(includeInactive, true, user('inactive').not())) - .count() - })) - .filter((org: RValue) => r.branch(includeInactive, true, org('users').ge(1))) + const proOrgs = await selectOrganizations().where('tier', '=', 'team').execute() + if (includeInactive) return proOrgs + + const proOrgIds = proOrgs.map(({id}) => id) + const activeOrgIds = await ( + r + .table('OrganizationUser') + .getAll(r.args(proOrgIds), {index: 'orgId'}) + .filter({removedAt: null, inactive: false}) + .group('orgId') as RDatum + ) + .count() + .ungroup() + .filter((row: RDatum) => row('reduction').ge(1))('group') .run() + + return proOrgs.filter((org) => activeOrgIds.includes(org.id)) } export default suProOrgInfo diff --git a/packages/server/graphql/private/types/DraftEnterpriseInvoicePayload.ts b/packages/server/graphql/private/types/DraftEnterpriseInvoicePayload.ts index a1aa209e552..1341dbaf9ae 100644 --- a/packages/server/graphql/private/types/DraftEnterpriseInvoicePayload.ts +++ b/packages/server/graphql/private/types/DraftEnterpriseInvoicePayload.ts @@ -8,7 +8,7 @@ export type DraftEnterpriseInvoicePayloadSource = const DraftEnterpriseInvoicePayload: DraftEnterpriseInvoicePayloadResolvers = { organization: (source, _args, {dataLoader}) => { - return 'orgId' in source ? dataLoader.get('organizations').load(source.orgId) : null + return 'orgId' in source ? dataLoader.get('organizations').loadNonNull(source.orgId) : null } } diff --git a/packages/server/graphql/private/types/EndTrialSuccess.ts b/packages/server/graphql/private/types/EndTrialSuccess.ts index 80c645bbfab..4ecfe4bad97 100644 --- a/packages/server/graphql/private/types/EndTrialSuccess.ts +++ b/packages/server/graphql/private/types/EndTrialSuccess.ts @@ -1,11 +1,14 @@ -import Organization from '../../../database/types/Organization' import {EndTrialSuccessResolvers} from '../resolverTypes' export type EndTrialSuccessSource = { - organization: Organization + orgId: string trialStartDate: Date } -const EndTrialSuccess: EndTrialSuccessResolvers = {} +const EndTrialSuccess: EndTrialSuccessResolvers = { + organization: async ({orgId}, _args, {dataLoader}) => { + return dataLoader.get('organizations').loadNonNull(orgId) + } +} export default EndTrialSuccess diff --git a/packages/server/graphql/private/types/FlagConversionModalPayload.ts b/packages/server/graphql/private/types/FlagConversionModalPayload.ts index 7f7e66195fd..9356d163ee1 100644 --- a/packages/server/graphql/private/types/FlagConversionModalPayload.ts +++ b/packages/server/graphql/private/types/FlagConversionModalPayload.ts @@ -8,7 +8,7 @@ export type FlagConversionModalPayloadSource = const FlagConversionModalPayload: FlagConversionModalPayloadResolvers = { org: (source, _args, {dataLoader}) => { - return 'orgId' in source ? dataLoader.get('organizations').load(source.orgId) : null + return 'orgId' in source ? dataLoader.get('organizations').loadNonNull(source.orgId) : null } } diff --git a/packages/server/graphql/private/types/StartTrialSuccess.ts b/packages/server/graphql/private/types/StartTrialSuccess.ts index b166c9164d2..bbe467bb343 100644 --- a/packages/server/graphql/private/types/StartTrialSuccess.ts +++ b/packages/server/graphql/private/types/StartTrialSuccess.ts @@ -1,10 +1,13 @@ -import Organization from '../../../database/types/Organization' import {StartTrialSuccessResolvers} from '../resolverTypes' export type StartTrialSuccessSource = { - organization: Organization + orgId: string } -const StartTrialSuccess: StartTrialSuccessResolvers = {} +const StartTrialSuccess: StartTrialSuccessResolvers = { + organization: async ({orgId}, _args, {dataLoader}) => { + return dataLoader.get('organizations').loadNonNull(orgId) + } +} export default StartTrialSuccess diff --git a/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts b/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts index 324c4404ecd..9c7cddc6117 100644 --- a/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts +++ b/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts @@ -57,7 +57,7 @@ const acceptRequestToJoinDomain: MutationResolvers['acceptRequestToJoinDomain'] // Provided request domain should match team's organizations activeDomain const leadTeams = await getTeamsByIds(validTeamMembers.map((teamMember) => teamMember.teamId)) const teamOrgs = await Promise.all( - leadTeams.map((t) => dataLoader.get('organizations').load(t.orgId)) + leadTeams.map((t) => dataLoader.get('organizations').loadNonNull(t.orgId)) ) const validOrgIds = teamOrgs.filter((org) => org.activeDomain === domain).map(({id}) => id) diff --git a/packages/server/graphql/public/mutations/createStripeSubscription.ts b/packages/server/graphql/public/mutations/createStripeSubscription.ts index bebad7a017c..47fb36d4dba 100644 --- a/packages/server/graphql/public/mutations/createStripeSubscription.ts +++ b/packages/server/graphql/public/mutations/createStripeSubscription.ts @@ -15,7 +15,7 @@ const createStripeSubscription: MutationResolvers['createStripeSubscription'] = const [viewer, organization, orgUsersCount, organizationUser] = await Promise.all([ dataLoader.get('users').loadNonNull(viewerId), - dataLoader.get('organizations').load(orgId), + dataLoader.get('organizations').loadNonNull(orgId), r .table('OrganizationUser') .getAll(orgId, {index: 'orgId'}) diff --git a/packages/server/graphql/public/mutations/setMeetingSettings.ts b/packages/server/graphql/public/mutations/setMeetingSettings.ts index 2bbaa9693e4..0b0cc44a899 100644 --- a/packages/server/graphql/public/mutations/setMeetingSettings.ts +++ b/packages/server/graphql/public/mutations/setMeetingSettings.ts @@ -30,7 +30,7 @@ const setMeetingSettings: MutationResolvers['setMeetingSettings'] = async ( dataLoader.get('teams').loadNonNull(teamId), dataLoader.get('users').loadNonNull(viewerId) ]) - const organization = await dataLoader.get('organizations').load(team.orgId) + const organization = await dataLoader.get('organizations').loadNonNull(team.orgId) const {featureFlags} = organization const hasTranscriptFlag = featureFlags?.includes('zoomTranscription') diff --git a/packages/server/graphql/public/mutations/updateCreditCard.ts b/packages/server/graphql/public/mutations/updateCreditCard.ts index 984c9758e17..d5bcade0c82 100644 --- a/packages/server/graphql/public/mutations/updateCreditCard.ts +++ b/packages/server/graphql/public/mutations/updateCreditCard.ts @@ -1,7 +1,6 @@ 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' @@ -21,8 +20,6 @@ const updateCreditCard: MutationResolvers['updateCreditCard'] = async ( ) => { const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} - const now = new Date() - const r = await getRethink() // AUTH const viewerId = getUserId(authToken) @@ -31,7 +28,9 @@ const updateCreditCard: MutationResolvers['updateCreditCard'] = async ( } // RESOLUTION - const organization = await dataLoader.get('organizations').load(orgId) + const organization = await dataLoader.get('organizations').loadNonNull(orgId) + if (!organization) return {error: {message: 'Organization not found'}} + const {stripeId, stripeSubscriptionId} = organization if (!stripeId || !stripeSubscriptionId) { return standardError(new Error('Organization is not subscribed to a plan'), {userId: viewerId}) @@ -70,17 +69,6 @@ const updateCreditCard: MutationResolvers['updateCreditCard'] = async ( }) .where('id', '=', orgId) .execute(), - r({ - updatedOrg: r.table('Organization').get(orgId).update({ - creditCard, - tier: 'team', - stripeId: customer.id, - tierLimitExceededAt: null, - scheduledLockAt: null, - lockedAt: null, - updatedAt: now - }) - }).run(), updateTeamByOrgId( { isPaid: true, @@ -89,7 +77,7 @@ const updateCreditCard: MutationResolvers['updateCreditCard'] = async ( orgId ) ]) - organization.creditCard = creditCard + dataLoader.get('organizations').clear(orgId) // If there are unpaid open invoices, try to process them const openInvoices = (await manager.listSubscriptionOpenInvoices(stripeSubscriptionId)).data diff --git a/packages/server/graphql/public/mutations/updateOrg.ts b/packages/server/graphql/public/mutations/updateOrg.ts index 3aedaf7d82c..fb641c5c4bf 100644 --- a/packages/server/graphql/public/mutations/updateOrg.ts +++ b/packages/server/graphql/public/mutations/updateOrg.ts @@ -1,5 +1,4 @@ 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' @@ -11,8 +10,6 @@ const updateOrg: MutationResolvers['updateOrg'] = async ( {updatedOrg}, {authToken, dataLoader, socketId: mutatorId} ) => { - const r = await getRethink() - const now = new Date() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -37,17 +34,11 @@ const updateOrg: MutationResolvers['updateOrg'] = async ( } // RESOLUTION - const dbUpdate = { - id: orgId, - 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} publish(SubscriptionChannel.ORGANIZATION, orgId, 'UpdateOrgPayload', data, subOptions) diff --git a/packages/server/graphql/public/mutations/uploadOrgImage.ts b/packages/server/graphql/public/mutations/uploadOrgImage.ts index e03a52f2ce7..f728a4805da 100644 --- a/packages/server/graphql/public/mutations/uploadOrgImage.ts +++ b/packages/server/graphql/public/mutations/uploadOrgImage.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' import getFileStoreManager from '../../../fileStorage/getFileStoreManager' import normalizeAvatarUpload from '../../../fileStorage/normalizeAvatarUpload' import validateAvatarUpload from '../../../fileStorage/validateAvatarUpload' @@ -14,8 +13,6 @@ const uploadOrgImage: MutationResolvers['uploadOrgImage'] = async ( {file, orgId}, {authToken, dataLoader, socketId: mutatorId} ) => { - const r = await getRethink() - const now = new Date() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -39,15 +36,6 @@ const uploadOrgImage: MutationResolvers['uploadOrgImage'] = async ( .set({picture: publicLocation}) .where('id', '=', orgId) .execute() - await r - .table('Organization') - .get(orgId) - .update({ - id: orgId, - picture: publicLocation, - updatedAt: now - }) - .run() const data = {orgId} publish(SubscriptionChannel.ORGANIZATION, orgId, 'UpdateOrgPayload', data, subOptions) diff --git a/packages/server/graphql/public/types/AddApprovedOrganizationDomainsSuccess.ts b/packages/server/graphql/public/types/AddApprovedOrganizationDomainsSuccess.ts index 8ab3e35500a..b3adbe5fc56 100644 --- a/packages/server/graphql/public/types/AddApprovedOrganizationDomainsSuccess.ts +++ b/packages/server/graphql/public/types/AddApprovedOrganizationDomainsSuccess.ts @@ -6,7 +6,7 @@ export type AddApprovedOrganizationDomainsSuccessSource = { const AddApprovedOrganizationDomainsSuccess: AddApprovedOrganizationDomainsSuccessResolvers = { organization: async ({orgId}, _args, {dataLoader}) => { - return dataLoader.get('organizations').load(orgId) + return dataLoader.get('organizations').loadNonNull(orgId) } } diff --git a/packages/server/graphql/public/types/AddPokerTemplateSuccess.ts b/packages/server/graphql/public/types/AddPokerTemplateSuccess.ts index f6d2b3d9146..b881e454d1a 100644 --- a/packages/server/graphql/public/types/AddPokerTemplateSuccess.ts +++ b/packages/server/graphql/public/types/AddPokerTemplateSuccess.ts @@ -1,5 +1,5 @@ import {getUserId} from '../../../utils/authorization' -import {AddPokerTemplateSuccessResolvers, PokerTemplate} from '../resolverTypes' +import {AddPokerTemplateSuccessResolvers} from '../resolverTypes' export type AddPokerTemplateSuccessSource = { templateId: string @@ -7,7 +7,7 @@ export type AddPokerTemplateSuccessSource = { const AddPokerTemplateSuccess: AddPokerTemplateSuccessResolvers = { pokerTemplate: async ({templateId}, _args, {dataLoader}) => { - return (await dataLoader.get('meetingTemplates').load(templateId)) as PokerTemplate + return await dataLoader.get('meetingTemplates').loadNonNull(templateId) }, user: async (_src, _args, {authToken, dataLoader}) => { const viewerId = getUserId(authToken) diff --git a/packages/server/graphql/public/types/DomainJoinRequest.ts b/packages/server/graphql/public/types/DomainJoinRequest.ts index 0b030ce8d79..772526f3cbd 100644 --- a/packages/server/graphql/public/types/DomainJoinRequest.ts +++ b/packages/server/graphql/public/types/DomainJoinRequest.ts @@ -32,7 +32,7 @@ const DomainJoinRequest: DomainJoinRequestResolvers = { const leadTeamIds = leadTeamMembers.map((teamMember) => teamMember.teamId) const leadTeams = (await dataLoader.get('teams').loadMany(leadTeamIds)).filter(isValid) const teamOrgs = await Promise.all( - leadTeams.map((t) => dataLoader.get('organizations').load(t.orgId)) + leadTeams.map((t) => dataLoader.get('organizations').loadNonNull(t.orgId)) ) const validOrgIds = teamOrgs.filter((org) => org.activeDomain === domain).map(({id}) => id) diff --git a/packages/server/graphql/public/types/NewMeeting.ts b/packages/server/graphql/public/types/NewMeeting.ts index 24452df666e..d4e8f36c565 100644 --- a/packages/server/graphql/public/types/NewMeeting.ts +++ b/packages/server/graphql/public/types/NewMeeting.ts @@ -48,7 +48,7 @@ const NewMeeting: NewMeetingResolvers = { organization: async ({teamId}, _args, {dataLoader}) => { const team = await dataLoader.get('teams').loadNonNull(teamId) const {orgId} = team - return dataLoader.get('organizations').load(orgId) + return dataLoader.get('organizations').loadNonNull(orgId) }, phases: async ({phases, id: meetingId, teamId, endedAt}, _args, {authToken, dataLoader}) => { const viewerId = getUserId(authToken) diff --git a/packages/server/graphql/public/types/NotifyPaymentRejected.ts b/packages/server/graphql/public/types/NotifyPaymentRejected.ts index 179167fa37c..19cdd7f5b43 100644 --- a/packages/server/graphql/public/types/NotifyPaymentRejected.ts +++ b/packages/server/graphql/public/types/NotifyPaymentRejected.ts @@ -3,7 +3,7 @@ import {NotifyPaymentRejectedResolvers} from '../resolverTypes' const NotifyPaymentRejected: NotifyPaymentRejectedResolvers = { __isTypeOf: ({type}) => type === 'PAYMENT_REJECTED', organization: async ({orgId}, _args, {dataLoader}) => { - return dataLoader.get('organizations').load(orgId) + return dataLoader.get('organizations').loadNonNull(orgId) } } diff --git a/packages/server/graphql/public/types/NotifyPromoteToOrgLeader.ts b/packages/server/graphql/public/types/NotifyPromoteToOrgLeader.ts index bd481628efe..3fe01a16c1d 100644 --- a/packages/server/graphql/public/types/NotifyPromoteToOrgLeader.ts +++ b/packages/server/graphql/public/types/NotifyPromoteToOrgLeader.ts @@ -3,7 +3,7 @@ import {NotifyPromoteToOrgLeaderResolvers} from '../resolverTypes' const NotifyPromoteToOrgLeader: NotifyPromoteToOrgLeaderResolvers = { __isTypeOf: ({type}) => type === 'PROMOTE_TO_BILLING_LEADER', organization: async ({orgId}, _args, {dataLoader}) => { - return dataLoader.get('organizations').load(orgId) + return dataLoader.get('organizations').loadNonNull(orgId) } } diff --git a/packages/server/graphql/public/types/Organization.ts b/packages/server/graphql/public/types/Organization.ts index bb50fa30a33..15b84bd3907 100644 --- a/packages/server/graphql/public/types/Organization.ts +++ b/packages/server/graphql/public/types/Organization.ts @@ -1,7 +1,12 @@ +import {ExtractTypeFromQueryBuilderSelect} from '../../../../client/types/generics' +import {selectOrganizations} from '../../../dataloader/primaryKeyLoaderMakers' import {isSuperUser} from '../../../utils/authorization' import {getFeatureTier} from '../../types/helpers/getFeatureTier' import {OrganizationResolvers} from '../resolverTypes' +export interface OrganizationSource + extends ExtractTypeFromQueryBuilderSelect {} + const Organization: OrganizationResolvers = { approvedDomains: async ({id: orgId}, _args, {dataLoader}) => { return dataLoader.get('organizationApprovedDomainsByOrgId').load(orgId) diff --git a/packages/server/graphql/public/types/RemoveApprovedOrganizationDomainsSuccess.ts b/packages/server/graphql/public/types/RemoveApprovedOrganizationDomainsSuccess.ts index e1efaba3de2..16f94d37863 100644 --- a/packages/server/graphql/public/types/RemoveApprovedOrganizationDomainsSuccess.ts +++ b/packages/server/graphql/public/types/RemoveApprovedOrganizationDomainsSuccess.ts @@ -7,7 +7,7 @@ export type RemoveApprovedOrganizationDomainsSuccessSource = { const RemoveApprovedOrganizationDomainsSuccess: RemoveApprovedOrganizationDomainsSuccessResolvers = { organization: async ({orgId}, _args, {dataLoader}) => { - return dataLoader.get('organizations').load(orgId) + return dataLoader.get('organizations').loadNonNull(orgId) } } diff --git a/packages/server/graphql/public/types/SAML.ts b/packages/server/graphql/public/types/SAML.ts index 93db425c722..24312a433d4 100644 --- a/packages/server/graphql/public/types/SAML.ts +++ b/packages/server/graphql/public/types/SAML.ts @@ -17,7 +17,7 @@ const SAML: SamlResolvers = { }, organization: async ({orgId}, _args, {dataLoader}) => { if (!orgId) return null - return dataLoader.get('organizations').load(orgId) + return dataLoader.get('organizations').loadNonNull(orgId) } } diff --git a/packages/server/graphql/public/types/SetOrgUserRoleSuccess.ts b/packages/server/graphql/public/types/SetOrgUserRoleSuccess.ts index 53169f52620..9d70f951f12 100644 --- a/packages/server/graphql/public/types/SetOrgUserRoleSuccess.ts +++ b/packages/server/graphql/public/types/SetOrgUserRoleSuccess.ts @@ -10,7 +10,7 @@ export type SetOrgUserRoleSuccessSource = { const SetOrgUserRoleSuccess: SetOrgUserRoleSuccessResolvers = { organization: async ({orgId}, _args, {dataLoader}) => { - return dataLoader.get('organizations').load(orgId) + return dataLoader.get('organizations').loadNonNull(orgId) }, updatedOrgMember: async ({organizationUserId}, _args, {dataLoader}) => { return dataLoader.get('organizationUsers').load(organizationUserId) diff --git a/packages/server/graphql/public/types/StripeFailPaymentPayload.ts b/packages/server/graphql/public/types/StripeFailPaymentPayload.ts index 55543c86818..e04fa093e86 100644 --- a/packages/server/graphql/public/types/StripeFailPaymentPayload.ts +++ b/packages/server/graphql/public/types/StripeFailPaymentPayload.ts @@ -8,7 +8,7 @@ export type StripeFailPaymentPayloadSource = { const StripeFailPaymentPayload: StripeFailPaymentPayloadResolvers = { organization: ({orgId}, _args, {dataLoader}) => { - return dataLoader.get('organizations').load(orgId) + return dataLoader.get('organizations').loadNonNull(orgId) }, notification: async ({notificationId}, _args, {dataLoader}) => { const notification = await dataLoader.get('notifications').load(notificationId) diff --git a/packages/server/graphql/public/types/Team.ts b/packages/server/graphql/public/types/Team.ts index cfb3d4122f4..4c13a4adc0f 100644 --- a/packages/server/graphql/public/types/Team.ts +++ b/packages/server/graphql/public/types/Team.ts @@ -1,9 +1,13 @@ import TeamInsightsId from 'parabol-client/shared/gqlIds/TeamInsightsId' +import {ExtractTypeFromQueryBuilderSelect} from '../../../../client/types/generics' import toTeamMemberId from '../../../../client/utils/relay/toTeamMemberId' +import {selectTeams} from '../../../dataloader/primaryKeyLoaderMakers' import {getUserId, isTeamMember} from '../../../utils/authorization' import {getFeatureTier} from '../../types/helpers/getFeatureTier' import {TeamResolvers} from '../resolverTypes' +export interface TeamSource extends ExtractTypeFromQueryBuilderSelect {} + const Team: TeamResolvers = { insights: async ( {id, orgId, mostUsedEmojis, meetingEngagement, topRetroTemplates}, diff --git a/packages/server/graphql/public/types/UpdateCreditCardSuccess.ts b/packages/server/graphql/public/types/UpdateCreditCardSuccess.ts index b9a40ba01d8..463ffba04dd 100644 --- a/packages/server/graphql/public/types/UpdateCreditCardSuccess.ts +++ b/packages/server/graphql/public/types/UpdateCreditCardSuccess.ts @@ -8,7 +8,7 @@ export type UpdateCreditCardSuccessSource = { const UpdateCreditCardSuccess: UpdateCreditCardSuccessResolvers = { organization: async ({orgId}, _args, {dataLoader}) => { - return dataLoader.get('organizations').load(orgId) + return dataLoader.get('organizations').loadNonNull(orgId) }, teamsUpdated: async ({teamIds}, _args, {dataLoader}) => { const teams = await dataLoader.get('teams').loadMany(teamIds) diff --git a/packages/server/graphql/public/types/UpdateOrgPayload.ts b/packages/server/graphql/public/types/UpdateOrgPayload.ts index e5554f2a74c..6343a50f0ce 100644 --- a/packages/server/graphql/public/types/UpdateOrgPayload.ts +++ b/packages/server/graphql/public/types/UpdateOrgPayload.ts @@ -10,7 +10,7 @@ const UpdateOrgPayload: UpdateOrgPayloadResolvers = { organization: async (source, _args, {dataLoader}) => { if ('error' in source) return null const {orgId} = source - return dataLoader.get('organizations').load(orgId) + return dataLoader.get('organizations').loadNonNull(orgId) } } diff --git a/packages/server/graphql/public/types/UpgradeToTeamTierSuccess.ts b/packages/server/graphql/public/types/UpgradeToTeamTierSuccess.ts index fc985cb0939..038fd98f4ef 100644 --- a/packages/server/graphql/public/types/UpgradeToTeamTierSuccess.ts +++ b/packages/server/graphql/public/types/UpgradeToTeamTierSuccess.ts @@ -9,7 +9,7 @@ export type UpgradeToTeamTierSuccessSource = { const UpgradeToTeamTierSuccess: UpgradeToTeamTierSuccessResolvers = { organization: async ({orgId}, _args, {dataLoader}) => { - return dataLoader.get('organizations').load(orgId) + return dataLoader.get('organizations').loadNonNull(orgId) }, teams: async ({teamIds}, _args, {dataLoader}) => { const teams = await dataLoader.get('teams').loadMany(teamIds) diff --git a/packages/server/graphql/public/types/User.ts b/packages/server/graphql/public/types/User.ts index df9e22b8f17..183ef7cee31 100644 --- a/packages/server/graphql/public/types/User.ts +++ b/packages/server/graphql/public/types/User.ts @@ -86,7 +86,7 @@ const User: UserResolvers = { .map(({orgId}) => orgId) const organizations = await Promise.all( - orgIds.map((orgId) => dataLoader.get('organizations').load(orgId)) + orgIds.map((orgId) => dataLoader.get('organizations').loadNonNull(orgId)) ) const approvedDomains = organizations.map(({activeDomain}) => activeDomain).filter(isNotNull) return [...new Set(approvedDomains)].map((id) => ({id})) diff --git a/packages/server/graphql/queries/helpers/countTiersForUserId.ts b/packages/server/graphql/queries/helpers/countTiersForUserId.ts index cd031ccf2e5..4e5aa7eee19 100644 --- a/packages/server/graphql/queries/helpers/countTiersForUserId.ts +++ b/packages/server/graphql/queries/helpers/countTiersForUserId.ts @@ -1,5 +1,4 @@ import getRethink from '../../../database/rethinkDriver' -import {RValue} from '../../../database/stricterR' import OrganizationUser from '../../../database/types/OrganizationUser' // breaking this out into its own helper so it can be used directly to @@ -11,9 +10,6 @@ const countTiersForUserId = async (userId: string) => { .table('OrganizationUser') .getAll(userId, {index: 'userId'}) .filter({inactive: false, removedAt: null}) - .merge((organizationUser: RValue) => ({ - tier: r.table('Organization').get(organizationUser('orgId'))('tier').default('starter') - })) .run()) as OrganizationUser[] const tierStarterCount = organizationUsers.filter( (organizationUser) => organizationUser.tier === 'starter' diff --git a/packages/server/graphql/queries/helpers/makeUpcomingInvoice.ts b/packages/server/graphql/queries/helpers/makeUpcomingInvoice.ts index 13b53503b98..0ad3a1755f7 100644 --- a/packages/server/graphql/queries/helpers/makeUpcomingInvoice.ts +++ b/packages/server/graphql/queries/helpers/makeUpcomingInvoice.ts @@ -1,14 +1,14 @@ import dayjs from 'dayjs' import Stripe from 'stripe' import Invoice from '../../../database/types/Invoice' -import Organization from '../../../database/types/Organization' import {fromEpochSeconds} from '../../../utils/epochTime' import getUpcomingInvoiceId from '../../../utils/getUpcomingInvoiceId' import {getStripeManager} from '../../../utils/stripe' import StripeManager from '../../../utils/stripe/StripeManager' +import {OrganizationSource} from '../../public/types/Organization' export default async function makeUpcomingInvoice( - org: Organization, + org: OrganizationSource, quantity: number, stripeId?: string | null ): Promise { diff --git a/packages/server/graphql/queries/invoices.ts b/packages/server/graphql/queries/invoices.ts index c9e043efd8c..82cab7a8849 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 dataLoader.get('organizations').load(orgId) + const {stripeId} = await dataLoader.get('organizations').loadNonNull(orgId) const dbAfter = after ? new Date(after) : r.maxval const [tooManyInvoices, orgUserCount] = await Promise.all([ r @@ -64,7 +64,7 @@ export default { .count() .run() ]) - const org = await dataLoader.get('organizations').load(orgId) + const org = await dataLoader.get('organizations').loadNonNull(orgId) const upcomingInvoice = after ? undefined : await makeUpcomingInvoice(org, orgUserCount, stripeId) diff --git a/packages/server/graphql/types/Organization.ts b/packages/server/graphql/types/Organization.ts index f729b625b85..452c765de79 100644 --- a/packages/server/graphql/types/Organization.ts +++ b/packages/server/graphql/types/Organization.ts @@ -83,7 +83,7 @@ const Organization: GraphQLObjectType = new GraphQLObjectType = new GraphQLObjectType { const [allTeamsOnOrg, organization] = await Promise.all([ dataLoader.get('teamsByOrgIds').load(orgId), - dataLoader.get('organizations').load(orgId) + dataLoader.get('organizations').loadNonNull(orgId) ]) const hasPublicTeamsFlag = !!organization.featureFlags?.includes('publicTeams') if (!isSuperUser(authToken) || !hasPublicTeamsFlag) return [] diff --git a/packages/server/graphql/types/Team.ts b/packages/server/graphql/types/Team.ts index 7d737ad98b1..f1c2ec71be7 100644 --- a/packages/server/graphql/types/Team.ts +++ b/packages/server/graphql/types/Team.ts @@ -259,7 +259,7 @@ const Team: GraphQLObjectType = new GraphQLObjectType({ _args: unknown, {authToken, dataLoader}: GQLContext ) => { - const organization = await dataLoader.get('organizations').load(orgId) + const organization = await dataLoader.get('organizations').loadNonNull(orgId) // TODO this is bad, we should probably just put the perms on each field in the org if (!isTeamMember(authToken, teamId)) { return { diff --git a/packages/server/graphql/types/User.ts b/packages/server/graphql/types/User.ts index 78b6b2380dd..0054e59fa36 100644 --- a/packages/server/graphql/types/User.ts +++ b/packages/server/graphql/types/User.ts @@ -16,7 +16,6 @@ import { } from '../../../client/utils/constants' import groupReflections from '../../../client/utils/smartGroup/groupReflections' import MeetingMemberType from '../../database/types/MeetingMember' -import OrganizationType from '../../database/types/Organization' import OrganizationUserType from '../../database/types/OrganizationUser' import SuggestedActionType from '../../database/types/SuggestedAction' import getKysely from '../../postgres/getKysely' @@ -24,7 +23,6 @@ import {getUserId, isSuperUser, isTeamMember} from '../../utils/authorization' import getMonthlyStreak from '../../utils/getMonthlyStreak' import getRedis from '../../utils/getRedis' import standardError from '../../utils/standardError' -import errorFilter from '../errorFilter' import {DataLoaderWorker, GQLContext} from '../graphql' import isValid from '../isValid' import invoices from '../queries/invoices' @@ -405,11 +403,11 @@ const User: GraphQLObjectType = new GraphQLObjectType orgId) + const orgIds = organizationUsers.map(({orgId}) => orgId) const organizations = (await dataLoader.get('organizations').loadMany(orgIds)).filter( - errorFilter + isValid ) - organizations.sort((a: OrganizationType, b: OrganizationType) => (a.name > b.name ? 1 : -1)) + organizations.sort((a, b) => (a.name > b.name ? 1 : -1)) const viewerId = getUserId(authToken) if (viewerId === userId || isSuperUser(authToken)) { return organizations @@ -417,10 +415,8 @@ const User: GraphQLObjectType = new GraphQLObjectType orgId) - return organizations.filter((organization: OrganizationType) => - viewerOrgIds.includes(organization.id) - ) + const viewerOrgIds = viewerOrganizationUsers.map(({orgId}) => orgId) + return organizations.filter((organization) => viewerOrgIds.includes(organization.id)) } }, overLimitCopy: { diff --git a/packages/server/graphql/types/helpers/isMeetingLocked.ts b/packages/server/graphql/types/helpers/isMeetingLocked.ts index 9ef490b93ec..e455a26b02e 100644 --- a/packages/server/graphql/types/helpers/isMeetingLocked.ts +++ b/packages/server/graphql/types/helpers/isMeetingLocked.ts @@ -30,7 +30,7 @@ const isMeetingLocked = async ( // Archived teams are not updated with the current tier, just check the organization if (isArchived) { - const organization = await dataLoader.get('organizations').load(orgId) + const organization = await dataLoader.get('organizations').loadNonNull(orgId) if (getFeatureTier(organization) !== 'starter') { return false } diff --git a/packages/server/postgres/getKysely.ts b/packages/server/postgres/getKysely.ts index 0a3256081d3..c7c02a59d16 100644 --- a/packages/server/postgres/getKysely.ts +++ b/packages/server/postgres/getKysely.ts @@ -4,9 +4,9 @@ import {DB} from './pg.d' let kysely: Kysely | undefined -const makeKysely = () => { - const nextPg = getPg() - nextPg.on('poolChange' as any, makeKysely) +const makeKysely = (schema?: string) => { + const nextPg = getPg(schema) + nextPg.on('poolChange' as any, () => makeKysely(schema)) return new Kysely({ dialect: new PostgresDialect({ pool: nextPg @@ -20,9 +20,9 @@ const makeKysely = () => { }) } -const getKysely = () => { +const getKysely = (schema?: string) => { if (!kysely) { - kysely = makeKysely() + kysely = makeKysely(schema) } return kysely } diff --git a/packages/server/postgres/getPg.ts b/packages/server/postgres/getPg.ts index 2edc5ff4729..0e82891eeb7 100644 --- a/packages/server/postgres/getPg.ts +++ b/packages/server/postgres/getPg.ts @@ -22,10 +22,17 @@ const graceFullyReconnect = async () => { } let pool: Pool | undefined -const getPg = () => { +const getPg = (schema?: string) => { if (!pool) { pool = new Pool(config) pool.on('error', graceFullyReconnect) + if (schema) { + pool.on('connect', (client) => { + // passing the search_path as a connection option does not work + // That strategy requires explicitly stating the schema in each query + client.query(`SET search_path TO "${schema}"`) + }) + } } return pool } diff --git a/packages/server/postgres/queries/src/updateUserTiersQuery.sql b/packages/server/postgres/queries/src/updateUserTiersQuery.sql deleted file mode 100644 index 4f8bef44487..00000000000 --- a/packages/server/postgres/queries/src/updateUserTiersQuery.sql +++ /dev/null @@ -1,9 +0,0 @@ -/* - @name updateUserTiersQuery - @param users -> ((tier, trialStartDate, id)...) -*/ -UPDATE "User" AS u SET - "tier" = c."tier"::"TierEnum", - "trialStartDate" = c."trialStartDate"::TIMESTAMP -FROM (VALUES :users) AS c("tier", "trialStartDate", "id") -WHERE c."id" = u."id"; diff --git a/packages/server/postgres/queries/updateUserTiers.ts b/packages/server/postgres/queries/updateUserTiers.ts deleted file mode 100644 index ec9961dad57..00000000000 --- a/packages/server/postgres/queries/updateUserTiers.ts +++ /dev/null @@ -1,11 +0,0 @@ -import getPg from '../getPg' -import catchAndLog from '../utils/catchAndLog' -import {IUpdateUserTiersQueryParams, updateUserTiersQuery} from './generated/updateUserTiersQuery' - -const updateUserTiers = async ({users}: IUpdateUserTiersQueryParams) => { - if (users.length) { - await catchAndLog(() => updateUserTiersQuery.run({users}, getPg())) - } -} - -export default updateUserTiers diff --git a/packages/server/safeMutations/acceptTeamInvitation.ts b/packages/server/safeMutations/acceptTeamInvitation.ts index be5ec868c6d..13c055771c9 100644 --- a/packages/server/safeMutations/acceptTeamInvitation.ts +++ b/packages/server/safeMutations/acceptTeamInvitation.ts @@ -4,14 +4,14 @@ import getRethink from '../database/rethinkDriver' import SuggestedActionCreateNewTeam from '../database/types/SuggestedActionCreateNewTeam' import generateUID from '../generateUID' import {DataLoaderWorker} from '../graphql/graphql' -import {Team} from '../postgres/queries/getTeamsByIds' +import {TeamSource} from '../graphql/public/types/Team' import getNewTeamLeadUserId from '../safeQueries/getNewTeamLeadUserId' import {Logger} from '../utils/Logger' import setUserTierForUserIds from '../utils/setUserTierForUserIds' import addTeamIdToTMS from './addTeamIdToTMS' import insertNewTeamMember from './insertNewTeamMember' -const handleFirstAcceptedInvitation = async (team: Team): Promise => { +const handleFirstAcceptedInvitation = async (team: TeamSource): Promise => { const r = await getRethink() const now = new Date() const {id: teamId, isOnboardTeam} = team @@ -46,7 +46,11 @@ const handleFirstAcceptedInvitation = async (team: Team): Promise return newTeamLeadUserId } -const acceptTeamInvitation = async (team: Team, userId: string, dataLoader: DataLoaderWorker) => { +const acceptTeamInvitation = async ( + team: TeamSource, + userId: string, + dataLoader: DataLoaderWorker +) => { const r = await getRethink() const now = new Date() const {id: teamId, orgId} = team diff --git a/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts b/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts index e53f082ffce..86205cdf3f8 100644 --- a/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts +++ b/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts @@ -15,7 +15,7 @@ const safeArchiveEmptyStarterOrganization = async ( const teamCountRemainingOnOldOrg = orgTeams.length if (teamCountRemainingOnOldOrg > 0) return - const org = await dataLoader.get('organizations').load(orgId) + const org = await dataLoader.get('organizations').loadNonNull(orgId) if (org.tier !== 'starter') return await r diff --git a/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts b/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts index 1a152cfa456..73df49f1454 100644 --- a/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts +++ b/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts @@ -1,14 +1,18 @@ /* eslint-env jest */ +import {Insertable} from 'kysely' import {r} from 'rethinkdb-ts' +import {createPGTables, truncatePGTables} from '../../__tests__/common' import getRethinkConfig from '../../database/getRethinkConfig' import getRethink from '../../database/rethinkDriver' import {TierEnum} from '../../database/types/Invoice' import OrganizationUser from '../../database/types/OrganizationUser' +import RootDataLoader from '../../dataloader/RootDataLoader' import generateUID from '../../generateUID' -import {DataLoaderWorker} from '../../graphql/graphql' import getKysely from '../../postgres/getKysely' +import {User} from '../../postgres/pg' import getRedis from '../getRedis' import {getEligibleOrgIdsByDomain} from '../isRequestToJoinDomainAllowed' + jest.mock('../../database/rethinkDriver') jest.mocked(getRethink).mockImplementation(() => { @@ -43,6 +47,10 @@ type TestOrganizationUser = Partial< Pick > +type TestUser = Insertable +const addUsers = async (users: TestUser[]) => { + getKysely().insertInto('User').values(users).execute() +} const addOrg = async ( activeDomain: string | null, members: TestOrganizationUser[], @@ -67,46 +75,26 @@ const addOrg = async ( 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 } -const userLoader = { - load: jest.fn(), - loadMany: jest.fn() -} -userLoader.loadMany.mockReturnValue([]) - -const isCompanyDomainLoader = { - load: jest.fn(), - loadMany: jest.fn() -} -isCompanyDomainLoader.load.mockReturnValue(true) - -const dataLoader = { - get: jest.fn((loader) => { - const loaders = { - users: userLoader, - isCompanyDomain: isCompanyDomainLoader - } - return loaders[loader as keyof typeof loaders] - }) -} as any as DataLoaderWorker - beforeAll(async () => { await r.connectPool(testConfig) + const pg = getKysely(TEST_DB) try { await r.dbDrop(TEST_DB).run() } catch (e) { //ignore } + await pg.schema.createSchema(TEST_DB).ifNotExists().execute() await r.dbCreate(TEST_DB).run() - await createTables('Organization', 'OrganizationUser') + await createPGTables('Organization', 'User', 'FreemailDomain', 'SAML', 'SAMLDomain') + await createTables('OrganizationUser') }) afterEach(async () => { - await r.table('Organization').delete().run() + await truncatePGTables('Organization', 'User') await r.table('OrganizationUser').delete().run() }) @@ -115,254 +103,81 @@ afterAll(async () => { getRedis().quit() }) -test('Founder is billing lead', async () => { - await addOrg('parabol.co', [ - { - joinedAt: new Date('2023-09-06'), - role: 'BILLING_LEADER', - userId: 'user1' - }, - { - joinedAt: new Date('2023-09-12'), - userId: 'user2' - } - ]) - - await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(userLoader.loadMany).toHaveBeenCalledTimes(1) - expect(userLoader.loadMany).toHaveBeenCalledWith(['user1']) -}) - -test('Org with noPromptToJoinOrg feature flag is ignored', async () => { - await addOrg( - 'parabol.co', - [ - { - joinedAt: new Date('2023-09-06'), - role: 'BILLING_LEADER', - userId: 'user1' - }, - { - joinedAt: new Date('2023-09-12'), - userId: 'user2' - } - ], - {featureFlags: ['noPromptToJoinOrg']} - ) - - await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(userLoader.loadMany).toHaveBeenCalledTimes(0) -}) - -test('Inactive founder is ignored', async () => { - await addOrg('parabol.co', [ - { - joinedAt: new Date('2023-09-06'), - role: 'BILLING_LEADER', - userId: 'founder1', - inactive: true - }, - { - joinedAt: new Date('2023-09-12'), - userId: 'member1' - }, - { - joinedAt: new Date('2023-09-12'), - userId: 'member2' - } - ]) - - await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - // implementation detail, important is only that no user was loaded - expect(userLoader.loadMany).toHaveBeenCalledTimes(1) - expect(userLoader.loadMany).toHaveBeenCalledWith([]) -}) - -test('Non-founder billing lead is checked', async () => { +test('Only the biggest org with verified emails qualify', async () => { await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), - role: 'BILLING_LEADER', userId: 'founder1', - inactive: true - }, - { - joinedAt: new Date('2023-09-12'), - role: 'BILLING_LEADER', - userId: 'billing1' + role: 'BILLING_LEADER' }, { - joinedAt: new Date('2023-09-12'), + joinedAt: new Date('2023-09-07'), userId: 'member1' } ]) - - await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(userLoader.loadMany).toHaveBeenCalledTimes(1) - expect(userLoader.loadMany).toHaveBeenCalledWith(['billing1']) -}) - -test('Founder is checked even when not billing lead', async () => { - await addOrg('parabol.co', [ - { - joinedAt: new Date('2023-09-06'), - userId: 'user1' - }, - { - joinedAt: new Date('2023-09-12'), - userId: 'user2' - } - ]) - - await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(userLoader.loadMany).toHaveBeenCalledTimes(1) - expect(userLoader.loadMany).toHaveBeenCalledWith(['user1']) -}) - -test('All matching orgs are checked', async () => { - await addOrg('parabol.co', [ + const biggerOrg = await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), - userId: 'founder1' + userId: 'founder2', + role: 'BILLING_LEADER' }, { joinedAt: new Date('2023-09-07'), - userId: 'member1' - } - ]) - await addOrg('parabol.co', [ - { - joinedAt: new Date('2023-09-12'), - userId: 'founder2' - }, - { - joinedAt: new Date('2023-09-13'), userId: 'member2' - } - ]) - - await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - // implementation detail, important is only that both users were loaded - expect(userLoader.loadMany).toHaveBeenCalledTimes(2) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder1']) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder2']) -}) - -test('Empty org does not throw', async () => { - await addOrg('parabol.co', []) - - await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(userLoader.loadMany).toHaveBeenCalledTimes(0) -}) - -test('No org does not throw', async () => { - await getEligibleOrgIdsByDomain('example.com', 'newUser', dataLoader) - expect(userLoader.loadMany).toHaveBeenCalledTimes(0) -}) - -test('1 person orgs are ignored', async () => { - await addOrg('parabol.co', [ - { - joinedAt: new Date('2023-09-06'), - role: 'BILLING_LEADER', - userId: 'founder1' - } - ]) - - await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(userLoader.loadMany).toHaveBeenCalledTimes(0) -}) - -test('Org matching the user are ignored', async () => { - await addOrg('parabol.co', [ - { - joinedAt: new Date('2023-09-06'), - userId: 'user1' }, { - joinedAt: new Date('2023-09-06'), - userId: 'newUser' + joinedAt: new Date('2023-09-07'), + userId: 'member3' } ]) - - await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(userLoader.loadMany).toHaveBeenCalledTimes(0) -}) - -test('Only the biggest org with verified emails qualify', async () => { await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), - userId: 'founder1' + userId: 'founder3', + role: 'BILLING_LEADER' }, { joinedAt: new Date('2023-09-07'), - userId: 'member1' + userId: 'member3' } ]) - const biggerOrg = await addOrg('parabol.co', [ + addUsers([ { - joinedAt: new Date('2023-09-06'), - userId: 'founder2' - }, - { - joinedAt: new Date('2023-09-07'), - userId: 'member2' + id: 'founder1', + email: 'user1@parabol.co', + picture: '', + preferredName: 'user1', + identities: [ + { + isEmailVerified: true + } + ] }, { - joinedAt: new Date('2023-09-07'), - userId: 'member3' - } - ]) - await addOrg('parabol.co', [ - { - joinedAt: new Date('2023-09-06'), - userId: 'founder3' + id: 'founder2', + email: 'user2@parabol.co', + picture: '', + preferredName: 'user2', + identities: [ + { + isEmailVerified: true + } + ] }, { - joinedAt: new Date('2023-09-07'), - userId: 'member3' + id: 'founder3', + email: 'user3@parabol.co', + picture: '', + preferredName: 'user3', + identities: [ + { + isEmailVerified: false + } + ] } ]) - - userLoader.loadMany.mockImplementation((userIds) => { - const users = { - founder1: { - email: 'user1@parabol.co', - identities: [ - { - isEmailVerified: true - } - ] - }, - founder2: { - email: 'user2@parabol.co', - identities: [ - { - isEmailVerified: true - } - ] - }, - founder3: { - email: 'user3@parabol.co', - identities: [ - { - isEmailVerified: false - } - ] - } - } - return userIds.map((id: keyof typeof users) => ({ - id, - ...users[id] - })) - }) - + const dataLoader = new RootDataLoader() const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(userLoader.loadMany).toHaveBeenCalledTimes(3) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder1']) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder2']) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder3']) expect(orgIds).toIncludeSameMembers([biggerOrg]) }) @@ -370,7 +185,8 @@ test('All the biggest orgs with verified emails qualify', async () => { const org1 = await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), - userId: 'founder1' + userId: 'founder1', + role: 'BILLING_LEADER' }, { joinedAt: new Date('2023-09-07'), @@ -380,7 +196,8 @@ test('All the biggest orgs with verified emails qualify', async () => { const org2 = await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), - userId: 'founder2' + userId: 'founder2', + role: 'BILLING_LEADER' }, { joinedAt: new Date('2023-09-07'), @@ -390,52 +207,52 @@ test('All the biggest orgs with verified emails qualify', async () => { await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), - userId: 'founder3' + userId: 'founder3', + role: 'BILLING_LEADER' }, { joinedAt: new Date('2023-09-07'), userId: 'member3' } ]) - - userLoader.loadMany.mockImplementation((userIds) => { - const users = { - founder1: { - email: 'user1@parabol.co', - identities: [ - { - isEmailVerified: true - } - ] - }, - founder2: { - email: 'user2@parabol.co', - identities: [ - { - isEmailVerified: true - } - ] - }, - founder3: { - email: 'user3@parabol.co', - identities: [ - { - isEmailVerified: false - } - ] - } + await addUsers([ + { + id: 'founder1', + email: 'user1@parabol.co', + picture: '', + preferredName: 'user1', + identities: [ + { + isEmailVerified: true + } + ] + }, + { + id: 'founder2', + email: 'user2@parabol.co', + picture: '', + preferredName: 'user2', + identities: [ + { + isEmailVerified: true + } + ] + }, + { + id: 'founder3', + email: 'user3@parabol.co', + picture: '', + preferredName: 'user3', + identities: [ + { + isEmailVerified: false + } + ] } - return userIds.map((id: keyof typeof users) => ({ - id, - ...users[id] - })) - }) + ]) + const dataLoader = new RootDataLoader() const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(userLoader.loadMany).toHaveBeenCalledTimes(3) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder1']) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder2']) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder3']) expect(orgIds).toIncludeSameMembers([org1, org2]) }) @@ -445,7 +262,8 @@ test('Team trumps starter tier with more users org', async () => { [ { joinedAt: new Date('2023-09-06'), - userId: 'founder1' + userId: 'founder1', + role: 'BILLING_LEADER' }, { joinedAt: new Date('2023-09-07'), @@ -457,7 +275,8 @@ test('Team trumps starter tier with more users org', async () => { await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), - userId: 'founder2' + userId: 'founder2', + role: 'BILLING_LEADER' }, { joinedAt: new Date('2023-09-07'), @@ -471,7 +290,8 @@ test('Team trumps starter tier with more users org', async () => { await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), - userId: 'founder3' + userId: 'founder3', + role: 'BILLING_LEADER' }, { joinedAt: new Date('2023-09-07'), @@ -479,44 +299,43 @@ test('Team trumps starter tier with more users org', async () => { } ]) - userLoader.loadMany.mockImplementation((userIds) => { - const users = { - founder1: { - email: 'user1@parabol.co', - identities: [ - { - isEmailVerified: true - } - ] - }, - founder2: { - email: 'user2@parabol.co', - identities: [ - { - isEmailVerified: true - } - ] - }, - founder3: { - email: 'user3@parabol.co', - identities: [ - { - isEmailVerified: false - } - ] - } + await addUsers([ + { + id: 'founder1', + email: 'user1@parabol.co', + picture: '', + preferredName: 'user1', + identities: [ + { + isEmailVerified: true + } + ] + }, + { + id: 'founder2', + email: 'user2@parabol.co', + picture: '', + preferredName: 'user2', + identities: [ + { + isEmailVerified: true + } + ] + }, + { + id: 'founder3', + email: 'user3@parabol.co', + picture: '', + preferredName: 'user3', + identities: [ + { + isEmailVerified: false + } + ] } - return userIds.map((id: keyof typeof users) => ({ - id, - ...users[id] - })) - }) - + ]) + const dataLoader = new RootDataLoader() const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(userLoader.loadMany).toHaveBeenCalledTimes(3) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder1']) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder2']) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder3']) expect(orgIds).toIncludeSameMembers([teamOrg]) }) @@ -526,7 +345,8 @@ test('Enterprise trumps team tier with more users org', async () => { [ { joinedAt: new Date('2023-09-06'), - userId: 'founder1' + userId: 'founder1', + role: 'BILLING_LEADER' }, { joinedAt: new Date('2023-09-07'), @@ -540,7 +360,8 @@ test('Enterprise trumps team tier with more users org', async () => { [ { joinedAt: new Date('2023-09-06'), - userId: 'founder2' + userId: 'founder2', + role: 'BILLING_LEADER' }, { joinedAt: new Date('2023-09-07'), @@ -556,7 +377,8 @@ test('Enterprise trumps team tier with more users org', async () => { await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), - userId: 'founder3' + userId: 'founder3', + role: 'BILLING_LEADER' }, { joinedAt: new Date('2023-09-07'), @@ -564,44 +386,43 @@ test('Enterprise trumps team tier with more users org', async () => { } ]) - userLoader.loadMany.mockImplementation((userIds) => { - const users = { - founder1: { - email: 'user1@parabol.co', - identities: [ - { - isEmailVerified: true - } - ] - }, - founder2: { - email: 'user2@parabol.co', - identities: [ - { - isEmailVerified: true - } - ] - }, - founder3: { - email: 'user3@parabol.co', - identities: [ - { - isEmailVerified: false - } - ] - } + await addUsers([ + { + id: 'founder1', + email: 'user1@parabol.co', + picture: '', + preferredName: 'user1', + identities: [ + { + isEmailVerified: true + } + ] + }, + { + id: 'founder2', + email: 'user2@parabol.co', + picture: '', + preferredName: 'user2', + identities: [ + { + isEmailVerified: true + } + ] + }, + { + id: 'founder3', + email: 'user3@parabol.co', + picture: '', + preferredName: 'user3', + identities: [ + { + isEmailVerified: false + } + ] } - return userIds.map((id: keyof typeof users) => ({ - id, - ...users[id] - })) - }) - + ]) + const dataLoader = new RootDataLoader() const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(userLoader.loadMany).toHaveBeenCalledTimes(3) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder1']) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder2']) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder3']) expect(orgIds).toIncludeSameMembers([enterpriseOrg]) }) @@ -617,10 +438,12 @@ test('Orgs with verified emails from different domains do not qualify', async () } ]) - userLoader.loadMany.mockReturnValue([ + await addUsers([ { id: 'founder1', email: 'user1@parabol.fun', + picture: '', + preferredName: 'user1', identities: [ { isEmailVerified: true @@ -629,9 +452,8 @@ test('Orgs with verified emails from different domains do not qualify', async () } ]) + const dataLoader = new RootDataLoader() const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(userLoader.loadMany).toHaveBeenCalledTimes(1) - expect(userLoader.loadMany).toHaveBeenCalledWith(['founder1']) expect(orgIds).toIncludeSameMembers([]) }) @@ -654,10 +476,12 @@ test('Orgs with at least 1 verified billing lead with correct email qualify', as } ]) - userLoader.loadMany.mockReturnValue([ + await addUsers([ { id: 'user1', email: 'user1@parabol.fun', + preferredName: '', + picture: '', identities: [ { isEmailVerified: true @@ -667,6 +491,8 @@ test('Orgs with at least 1 verified billing lead with correct email qualify', as { id: 'user2', email: 'user2@parabol.fun', + preferredName: '', + picture: '', identities: [ { isEmailVerified: true @@ -676,6 +502,8 @@ test('Orgs with at least 1 verified billing lead with correct email qualify', as { id: 'user3', email: 'user3@parabol.co', + preferredName: '', + picture: '', identities: [ { isEmailVerified: true @@ -684,8 +512,7 @@ test('Orgs with at least 1 verified billing lead with correct email qualify', as } ]) + const dataLoader = new RootDataLoader() const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(userLoader.loadMany).toHaveBeenCalledTimes(1) - expect(userLoader.loadMany).toHaveBeenCalledWith(['user1', 'user2', 'user3']) expect(orgIds).toIncludeSameMembers([org1]) }) diff --git a/packages/server/utils/isRequestToJoinDomainAllowed.ts b/packages/server/utils/isRequestToJoinDomainAllowed.ts index 7fef1c6cbe1..e04cf5de76d 100644 --- a/packages/server/utils/isRequestToJoinDomainAllowed.ts +++ b/packages/server/utils/isRequestToJoinDomainAllowed.ts @@ -1,95 +1,63 @@ -import getRethink from '../database/rethinkDriver' -import {RDatum} from '../database/stricterR' -import Organization from '../database/types/Organization' -import TeamMember from '../database/types/TeamMember' import User from '../database/types/User' +import {DataLoaderInstance} from '../dataloader/RootDataLoader' import {DataLoaderWorker} from '../graphql/graphql' -import isValid from '../graphql/isValid' import isUserVerified from './isUserVerified' export const getEligibleOrgIdsByDomain = async ( activeDomain: string, userId: string, - dataLoader: DataLoaderWorker + dataLoader: DataLoaderInstance ) => { const isCompanyDomain = await dataLoader.get('isCompanyDomain').load(activeDomain) if (!isCompanyDomain) { return [] } - const r = await getRethink() + const orgs = await dataLoader.get('organizationsByActiveDomain').load(activeDomain) + if (orgs.length === 0) return [] - const orgs = await r - .table('Organization') - .getAll(activeDomain, {index: 'activeDomain'}) - .filter((org: RDatum) => org('featureFlags').default([]).contains('noPromptToJoinOrg').not()) - .merge((org: RDatum) => ({ - members: r - .table('OrganizationUser') - .getAll(org('id'), {index: 'orgId'}) - .orderBy('joinedAt') - .coerceTo('array') - })) - .merge((org: RDatum) => ({ - founder: org('members').nth(0).default(null), - billingLeads: org('members') - .filter({inactive: false, removedAt: null}) - .filter((row: RDatum) => r.expr(['BILLING_LEADER', 'ORG_ADMIN']).contains(row('role'))), - activeMembers: org('members').filter({inactive: false, removedAt: null}).count() - })) - .filter((org: RDatum) => - org('activeMembers').gt(1).and(org('members').filter({userId}).isEmpty()) - ) - .run() + const viewerOrgUsers = await dataLoader.get('organizationUsersByUserId').load(userId) + const viewerOrgIds = viewerOrgUsers.map(({orgId}) => orgId) + const newOrgs = orgs.filter((org) => !viewerOrgIds.includes(org.id)) + if (newOrgs.length === 0) return [] - type OrgWithActiveMembers = Organization & {activeMembers: number} - const eligibleOrgs = (await Promise.all( - orgs.map(async (org) => { - const {founder} = org - const importantMembers = org.billingLeads.slice() as TeamMember[] - if ( - !founder.inactive && - !founder.removedAt && - founder.role !== 'BILLING_LEADER' && - founder.role !== 'ORG_ADMIN' - ) { - importantMembers.push(founder) - } + const verifiedOrgMask = await Promise.all( + newOrgs.map(({id}) => dataLoader.get('isOrgVerified').load(id)) + ) + const verifiedOrgs = newOrgs.filter((_, idx) => verifiedOrgMask[idx]) + const verifiedOrgUsers = await Promise.all( + verifiedOrgs.map((org) => dataLoader.get('organizationUsersByOrgId').load(org.id)) + ) - const users = ( - await dataLoader.get('users').loadMany(importantMembers.map(({userId}) => userId)) - ).filter(isValid) - if ( - !users.some((user) => user.email.split('@')[1] === activeDomain && isUserVerified(user)) - ) { - return null - } - return org - }) - )) as OrgWithActiveMembers[] + const verifiedOrgsWithActiveUserCount = verifiedOrgs.map((org, idx) => ({ + ...org, + activeMembers: verifiedOrgUsers[idx]?.filter((org) => !org.inactive).length ?? 0 + })) - const highestTierOrgs = eligibleOrgs.filter(isValid).reduce((acc, org) => { - if (acc.length === 0) { - return [org] - } - const highestTier = acc[0]!.tier - if (org.tier === highestTier) { - return [...acc, org] - } - if (org.tier === 'enterprise') { - return [org] - } - if (highestTier === 'starter' && org.tier === 'team') { - return [org] - } - return acc - }, [] as OrgWithActiveMembers[]) + const highestTierOrgs = verifiedOrgsWithActiveUserCount.reduce( + (acc, org) => { + if (acc.length === 0) { + return [org] + } + const highestTier = acc[0]!.tier + if (org.tier === highestTier) { + return [...acc, org] + } + if (org.tier === 'enterprise') { + return [org] + } + if (highestTier === 'starter' && org.tier === 'team') { + return [org] + } + return acc + }, + [] as typeof verifiedOrgsWithActiveUserCount + ) const biggestSize = highestTierOrgs.reduce( (acc, org) => (org.activeMembers > acc ? org.activeMembers : acc), 0 ) - return highestTierOrgs .filter(({activeMembers}) => activeMembers === biggestSize) .map(({id}) => id) diff --git a/packages/server/utils/setTierForOrgUsers.ts b/packages/server/utils/setTierForOrgUsers.ts index 3477e29b2ad..0983faae06d 100644 --- a/packages/server/utils/setTierForOrgUsers.ts +++ b/packages/server/utils/setTierForOrgUsers.ts @@ -8,25 +8,25 @@ * will be created. */ import getRethink from '../database/rethinkDriver' -import {TierEnum} from '../database/types/Invoice' +import getKysely from '../postgres/getKysely' const setTierForOrgUsers = async (orgId: string) => { const r = await getRethink() + const organization = await getKysely() + .selectFrom('Organization') + .select(['trialStartDate', 'tier']) + .where('id', '=', orgId) + .executeTakeFirstOrThrow() + const {tier, trialStartDate} = organization + await r .table('OrganizationUser') .getAll(orgId, {index: 'orgId'}) .filter({removedAt: null}) - .update( - { - tier: r.table('Organization').get(orgId).getField('tier') as unknown as TierEnum, - trialStartDate: r - .table('Organization') - .get(orgId) - .getField('trialStartDate') - .default(null) as unknown as Date | null - }, - {nonAtomic: true} - ) + .update({ + tier, + trialStartDate + }) .run() } diff --git a/packages/server/utils/setUserTierForUserIds.ts b/packages/server/utils/setUserTierForUserIds.ts index 880f2f23996..6b22fff26a7 100644 --- a/packages/server/utils/setUserTierForUserIds.ts +++ b/packages/server/utils/setUserTierForUserIds.ts @@ -1,58 +1,68 @@ import getRethink from '../database/rethinkDriver' -import {RDatum} from '../database/stricterR' -import OrganizationUser from '../database/types/OrganizationUser' -import {TierEnum} from '../postgres/queries/generated/updateUserQuery' -import {getUsersByIds} from '../postgres/queries/getUsersByIds' -import updateUserTiers from '../postgres/queries/updateUserTiers' +import isValid from '../graphql/isValid' +import getKysely from '../postgres/getKysely' import {analytics} from './analytics/analytics' +// MK: this is crazy spaghetti & needs to go away. See https://github.com/ParabolInc/parabol/issues/9932 + // This doesn't actually read any tier/trial fields on the 'OrganizationUser' object - these fields // come directly from 'Organization' instead. As a result, this can be run in parallel with // 'setTierForOrgUsers'. -const setUserTierForUserIds = async (userIds: string[]) => { + +const setUserTierForUserId = async (userId: string) => { const r = await getRethink() - const userTiers = (await r + const pg = getKysely() + + const orgUsers = await r .table('OrganizationUser') - .getAll(r.args(userIds), {index: 'userId'}) + .getAll(userId, {index: 'userId'}) .filter({removedAt: null}) - .merge((orgUser: RDatum) => ({ - tier: r.table('Organization').get(orgUser('orgId'))('tier').default('starter'), - trialStartDate: r.table('Organization').get(orgUser('orgId'))('trialStartDate').default(null) - })) - .group('userId') - .ungroup() - .map((row) => ({ - id: row('group'), - tier: r.branch( - row('reduction')('tier').contains('enterprise'), - 'enterprise', - row('reduction')('tier').contains('team'), - 'team', - 'starter' - ), - trialStartDate: r.max(row('reduction')('trialStartDate')) - })) - .run()) as {id: string; tier: TierEnum; trialStartDate: string | null}[] - - const userUpdates = userIds.map((userId) => { - const userTier = userTiers.find((userTier) => userTier.id === userId) - return { - id: userId, - tier: userTier ? userTier.tier : 'starter', - trialStartDate: userTier ? userTier.trialStartDate : null - } - }) - await updateUserTiers({users: userUpdates}) - - const users = await getUsersByIds(userIds) - users.forEach((user) => { - user && - analytics.identify({ - userId: user.id, - email: user.email, - highestTier: user.tier - }) + .run() + + const orgIds = orgUsers.map((orgUser) => orgUser.orgId) + if (orgIds.length === 0) return + + const organizations = await pg + .selectFrom('Organization') + .select(['trialStartDate', 'tier']) + .where('id', 'in', orgIds) + .execute() + + const allTiers = organizations.map((org) => org.tier) + const allTrialStartDates = organizations + .map((org) => org.trialStartDate?.getTime()) + .filter(isValid) + const maxTrialStartDate = Math.max(...allTrialStartDates) + const trialStartDate = maxTrialStartDate > 0 ? new Date(maxTrialStartDate) : null + const highestTier = allTiers.includes('enterprise') + ? 'enterprise' + : allTiers.includes('team') + ? 'team' + : 'starter' + + await pg + .updateTable('User') + .set({ + tier: highestTier, + trialStartDate + }) + .where('id', '=', userId) + .execute() + const user = await pg + .selectFrom('User') + .select('email') + .where('id', '=', userId) + .executeTakeFirstOrThrow() + + analytics.identify({ + userId, + email: user.email, + highestTier }) } +const setUserTierForUserIds = async (userIds: string[]) => { + return await Promise.all(userIds.map(setUserTierForUserId)) +} + export default setUserTierForUserIds diff --git a/scripts/toolboxSrc/setIsEnterprise.ts b/scripts/toolboxSrc/setIsEnterprise.ts index 4fe84a5242b..53e9ebf370b 100644 --- a/scripts/toolboxSrc/setIsEnterprise.ts +++ b/scripts/toolboxSrc/setIsEnterprise.ts @@ -16,21 +16,13 @@ export default async function setIsEnterprise() { '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) - .update({ - tier: 'enterprise' - }) - .run() - - console.log(`Updated ${result.replaced} rows in ${table} table in RethinkDB.`) - return result - }) + await r + .table('OrganizationUser') + .update({ + tier: 'enterprise' + }) + .run() const pg = getPg() @@ -50,7 +42,7 @@ export default async function setIsEnterprise() { const pgPromises = [updateUserPromise, updateTeamPromise] - await Promise.all([...rethinkPromises, ...pgPromises]) + await Promise.all(pgPromises) console.log('Finished updating tiers.') From fa69000950b930ac2444ea57bf530457e86ceab3 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 10 Jul 2024 11:53:47 -0700 Subject: [PATCH 320/529] chore(release): release v7.38.4 (#9950) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 093037ef878..c4a1821f12c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.38.3" + ".": "7.38.4" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4fe7fac4075..2601de361fb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.38.4](https://github.com/ParabolInc/parabol/compare/v7.38.3...v7.38.4) (2024-07-10) + + +### Changed + +* **rethinkdb:** Organization: Phase 3 ([#9933](https://github.com/ParabolInc/parabol/issues/9933)) ([70084f8](https://github.com/ParabolInc/parabol/commit/70084f86b1832dc087b0bf7eb279253b61dacf01)) + ## [7.38.3](https://github.com/ParabolInc/parabol/compare/v7.38.2...v7.38.3) (2024-07-09) diff --git a/package.json b/package.json index c9afd47f822..909851fd1e5 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.3", + "version": "7.38.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 38550d8eb9b..3c5a4b33f83 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.38.3", + "version": "7.38.4", "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.3" + "parabol-server": "7.38.4" } } diff --git a/packages/client/package.json b/packages/client/package.json index d224e55adfb..20667bb99ce 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.3", + "version": "7.38.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 5a1782285e2..fb3a3e9c785 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.38.3", + "version": "7.38.4", "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 332e2b9c756..0cd6dd3a29e 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.38.3", + "version": "7.38.4", "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.3", - "parabol-server": "7.38.3", + "parabol-client": "7.38.4", + "parabol-server": "7.38.4", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index f227dc1fe13..5ac66baa82d 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.3", + "version": "7.38.4", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 3ff338d2374..4cf59fd1831 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.3", + "version": "7.38.4", "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.3", + "parabol-client": "7.38.4", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From b4a91292cf941895d4766fbea545436101689a3c Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Thu, 11 Jul 2024 16:29:50 +0200 Subject: [PATCH 321/529] fix: Missing email summary for retros (#9960) --- packages/client/utils/AtlassianClientManager.ts | 2 +- packages/client/utils/GitHubClientManager.ts | 2 +- packages/client/utils/SlackClientManager.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/client/utils/AtlassianClientManager.ts b/packages/client/utils/AtlassianClientManager.ts index 00817341852..6aeee46aa2f 100644 --- a/packages/client/utils/AtlassianClientManager.ts +++ b/packages/client/utils/AtlassianClientManager.ts @@ -8,7 +8,7 @@ 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 isAvailable = typeof window !== 'undefined' && !!window.__ACTION__.atlassian static openOAuth( atmosphere: Atmosphere, diff --git a/packages/client/utils/GitHubClientManager.ts b/packages/client/utils/GitHubClientManager.ts index e5c90cf15d8..bc8eb6e87fd 100644 --- a/packages/client/utils/GitHubClientManager.ts +++ b/packages/client/utils/GitHubClientManager.ts @@ -8,7 +8,7 @@ class GitHubClientManager { static SCOPE = Providers.GITHUB_SCOPE fetch = window.fetch.bind(window) - static isAvailable = !!window.__ACTION__.github + static isAvailable = typeof window !== 'undefined' && !!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 e3584f1a198..0799377ee25 100644 --- a/packages/client/utils/SlackClientManager.ts +++ b/packages/client/utils/SlackClientManager.ts @@ -6,7 +6,7 @@ import SlackManager from './SlackManager' class SlackClientManager extends SlackManager { fetch = window.fetch.bind(window) - static isAvailable = !!window.__ACTION__.slack + static isAvailable = typeof window !== 'undefined' && !!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) From 50b9ef15126b06629c651fe60b1029ca204a604e Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 16:35:35 +0200 Subject: [PATCH 322/529] chore(release): release v7.38.5 (#9961) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c4a1821f12c..e5c7a133eca 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.38.4" + ".": "7.38.5" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 2601de361fb..7ad70d64140 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.38.5](https://github.com/ParabolInc/parabol/compare/v7.38.4...v7.38.5) (2024-07-11) + + +### Fixed + +* Missing email summary for retros ([#9960](https://github.com/ParabolInc/parabol/issues/9960)) ([b4a9129](https://github.com/ParabolInc/parabol/commit/b4a91292cf941895d4766fbea545436101689a3c)) + ## [7.38.4](https://github.com/ParabolInc/parabol/compare/v7.38.3...v7.38.4) (2024-07-10) diff --git a/package.json b/package.json index 909851fd1e5..b0b66797517 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.4", + "version": "7.38.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 3c5a4b33f83..74d8aa3ee68 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.38.4", + "version": "7.38.5", "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.4" + "parabol-server": "7.38.5" } } diff --git a/packages/client/package.json b/packages/client/package.json index 20667bb99ce..99c8ace3832 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.4", + "version": "7.38.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index fb3a3e9c785..f4b8b3cef20 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.38.4", + "version": "7.38.5", "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 0cd6dd3a29e..2e5b561e375 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.38.4", + "version": "7.38.5", "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.4", - "parabol-server": "7.38.4", + "parabol-client": "7.38.5", + "parabol-server": "7.38.5", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 5ac66baa82d..56587aeafa6 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.4", + "version": "7.38.5", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 4cf59fd1831..8b505f448b2 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.4", + "version": "7.38.5", "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.4", + "parabol-client": "7.38.5", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From f63c16e8fc17e1f7a34cc300667bafc993c85500 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 11 Jul 2024 08:26:25 -0700 Subject: [PATCH 323/529] chore(rethinkdb): OrganizationUser: Phase 1 (#9952) Signed-off-by: Matt Krick --- .../components/BillingLeaderActionMenu.tsx | 15 +- .../components/OrgUserRow/OrgMemberRow.tsx | 9 +- .../server/billing/helpers/adjustUserCount.ts | 39 ++-- .../server/billing/helpers/generateInvoice.ts | 14 +- .../handleEnterpriseOrgQuantityChanges.ts | 24 +-- .../server/billing/helpers/teamLimitsCheck.ts | 14 ++ .../helpers/updateSubscriptionQuantity.ts | 21 +- .../server/database/types/OrganizationUser.ts | 20 +- .../__tests__/isOrgVerified.test.ts | 51 +++-- packages/server/graphql/graphql.ts | 1 + .../graphql/mutations/archiveOrganization.ts | 8 + .../graphql/mutations/helpers/createNewOrg.ts | 5 +- .../mutations/helpers/oldUpgradeToTeamTier.ts | 11 +- .../mutations/helpers/removeFromOrg.ts | 39 ++-- .../server/graphql/mutations/moveTeamToOrg.ts | 60 ++---- .../graphql/mutations/oldUpgradeToTeamTier.ts | 8 + .../private/mutations/autopauseUsers.ts | 14 +- .../private/mutations/backupOrganization.ts | 3 - .../private/mutations/connectSocket.ts | 18 +- .../mutations/draftEnterpriseInvoice.ts | 8 + .../private/mutations/stripeFailPayment.ts | 15 +- .../private/mutations/toggleAllowInsights.ts | 10 + .../private/mutations/upgradeToTeamTier.ts | 7 + .../private/queries/suCountTiersForUser.ts | 8 +- .../graphql/private/queries/suOrgCount.ts | 20 ++ .../graphql/private/queries/suProOrgInfo.ts | 16 +- .../mutations/acceptRequestToJoinDomain.ts | 8 +- .../mutations/createStripeSubscription.ts | 16 +- .../public/mutations/setOrgUserRole.ts | 57 +++-- .../subscriptions/organizationSubscription.ts | 36 ++-- .../graphql/public/typeDefs/_legacy.graphql | 5 - .../queries/helpers/countTiersForUserId.ts | 13 +- packages/server/graphql/queries/invoices.ts | 14 +- .../server/graphql/types/OrganizationUser.ts | 5 - .../1720556055134_OrganizationUser-phase1.ts | 53 +++++ .../safeArchiveEmptyStarterOrganization.ts | 10 +- .../isRequestToJoinDomainAllowed.test.ts | 202 ++++++++++-------- packages/server/utils/authorization.ts | 26 +-- .../server/utils/getActiveDomainForOrgId.ts | 15 +- packages/server/utils/setTierForOrgUsers.ts | 8 +- packages/server/utils/setUserTierForOrgId.ts | 9 + .../server/utils/setUserTierForUserIds.ts | 8 +- scripts/toolboxSrc/setIsEnterprise.ts | 44 +--- 43 files changed, 543 insertions(+), 444 deletions(-) create mode 100644 packages/server/postgres/migrations/1720556055134_OrganizationUser-phase1.ts diff --git a/packages/client/components/BillingLeaderActionMenu.tsx b/packages/client/components/BillingLeaderActionMenu.tsx index 9f6e1e7604c..79811629b26 100644 --- a/packages/client/components/BillingLeaderActionMenu.tsx +++ b/packages/client/components/BillingLeaderActionMenu.tsx @@ -36,7 +36,6 @@ const BillingLeaderActionMenu = (props: Props) => { graphql` fragment BillingLeaderActionMenu_organization on Organization { id - billingTier } `, organizationRef @@ -45,7 +44,6 @@ const BillingLeaderActionMenu = (props: Props) => { graphql` fragment BillingLeaderActionMenu_organizationUser on OrganizationUser { role - newUserUntil user { id } @@ -54,9 +52,9 @@ const BillingLeaderActionMenu = (props: Props) => { organizationUserRef ) const atmosphere = useAtmosphere() - const {id: orgId, billingTier} = organization + const {id: orgId} = organization const {viewerId} = atmosphere - const {newUserUntil, role, user} = organizationUser + const {role, user} = organizationUser const isBillingLeader = role === 'BILLING_LEADER' const {id: userId} = user @@ -82,14 +80,7 @@ const BillingLeaderActionMenu = (props: Props) => { )} {viewerId !== userId && ( - new Date() - ? 'Refund and Remove' - : 'Remove from Organization' - } - onClick={toggleRemove} - /> + )} diff --git a/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx b/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx index 7087976fa84..d6b0862a091 100644 --- a/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx +++ b/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx @@ -21,7 +21,6 @@ import RowInfoHeader from '../../../../components/Row/RowInfoHeader' import RowInfoHeading from '../../../../components/Row/RowInfoHeading' import RowInfoLink from '../../../../components/Row/RowInfoLink' import BaseTag from '../../../../components/Tag/BaseTag' -import EmphasisTag from '../../../../components/Tag/EmphasisTag' import InactiveTag from '../../../../components/Tag/InactiveTag' import RoleTag from '../../../../components/Tag/RoleTag' import {MenuPosition} from '../../../../hooks/useCoords' @@ -126,7 +125,6 @@ interface UserInfoProps { isBillingLeader: boolean isOrgAdmin: boolean inactive: boolean | null - newUserUntil: string } const UserInfo: React.FC = ({ @@ -134,8 +132,7 @@ const UserInfo: React.FC = ({ email, isBillingLeader, isOrgAdmin, - inactive, - newUserUntil + inactive }) => ( @@ -143,7 +140,6 @@ const UserInfo: React.FC = ({ {isBillingLeader && Billing Leader} {isOrgAdmin && Org Admin} {inactive && !isBillingLeader && !isOrgAdmin && Inactive} - {new Date(newUserUntil) > new Date() && New} {email} @@ -258,7 +254,6 @@ const OrgMemberRow = (props: Props) => { preferredName } role - newUserUntil ...BillingLeaderActionMenu_organizationUser ...OrgAdminActionMenu_organizationUser } @@ -269,7 +264,6 @@ const OrgMemberRow = (props: Props) => { const {isViewerBillingLeader, isViewerOrgAdmin} = organization const { - newUserUntil, user: {email, inactive, picture, preferredName}, role } = organizationUser @@ -291,7 +285,6 @@ const OrgMemberRow = (props: Props) => { isBillingLeader={isBillingLeader} isOrgAdmin={isOrgAdmin} inactive={inactive} - newUserUntil={newUserUntil} /> async (_orgIds: string[], user: IUser email, isActive: !inactive }) - return Promise.all([ + await Promise.all([ updateUser( { inactive }, userId ), + getKysely() + .updateTable('OrganizationUser') + .set({inactive}) + .where('userId', '=', userId) + .where('removedAt', 'is', null) + .execute(), r .table('OrganizationUser') .getAll(userId, {index: 'userId'}) @@ -73,13 +80,9 @@ const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWork const r = await getRethink() const [rawOrganizations, organizationUsers] = await Promise.all([ dataLoader.get('organizations').loadMany(orgIds), - r - .table('OrganizationUser') - .getAll(userId, {index: 'userId'}) - .orderBy(r.desc('newUserUntil')) - .coerceTo('array') - .run() + dataLoader.get('organizationUsersByUserId').load(userId) ]) + dataLoader.get('organizationUsersByUserId').clear(userId) const organizations = rawOrganizations.filter(isValid) const docs = orgIds.map((orgId) => { const oldOrganizationUser = organizationUsers.find( @@ -87,19 +90,18 @@ const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWork ) const organization = organizations.find((organization) => organization.id === orgId)! // continue the grace period from before, if any OR set to the end of the invoice OR (if it is a free account) no grace period - const newUserUntil = - (oldOrganizationUser && oldOrganizationUser.newUserUntil) || - organization.periodEnd || - new Date() return new OrganizationUser({ id: oldOrganizationUser?.id, orgId, userId, - newUserUntil, tier: organization.tier }) }) - + await getKysely() + .insertInto('OrganizationUser') + .values(docs) + .onConflict((oc) => oc.doNothing()) + .execute() await r.table('OrganizationUser').insert(docs, {conflict: 'replace'}).run() await Promise.all( orgIds.map((orgId) => { @@ -111,7 +113,13 @@ const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWork const deleteUser = async (orgIds: string[], user: IUser) => { const r = await getRethink() orgIds.forEach((orgId) => analytics.userRemovedFromOrg(user, orgId)) - return r + await getKysely() + .updateTable('OrganizationUser') + .set({removedAt: sql`CURRENT_TIMESTAMP`}) + .where('userId', '=', user.id) + .where('orgId', 'in', orgIds) + .execute() + await r .table('OrganizationUser') .getAll(user.id, {index: 'userId'}) .filter((row: RDatum) => r.expr(orgIds).contains(row('orgId'))) @@ -152,7 +160,6 @@ export default async function adjustUserCount( const dbAction = dbActionTypeLookup[type] await dbAction(orgIds, user, dataLoader) - const auditEventType = auditEventTypeLookup[type] await insertOrgUserAudit(orgIds, userId, auditEventType) diff --git a/packages/server/billing/helpers/generateInvoice.ts b/packages/server/billing/helpers/generateInvoice.ts index cebf0d6c88f..b9cc7abb80b 100644 --- a/packages/server/billing/helpers/generateInvoice.ts +++ b/packages/server/billing/helpers/generateInvoice.ts @@ -1,7 +1,6 @@ import {InvoiceItemType} from 'parabol-client/types/constEnums' import Stripe from 'stripe' import getRethink from '../../database/rethinkDriver' -import {RDatum} from '../../database/stricterR' import Coupon from '../../database/types/Coupon' import Invoice, {InvoiceStatusEnum} from '../../database/types/Invoice' import {InvoiceLineItemEnum} from '../../database/types/InvoiceLineItem' @@ -353,16 +352,13 @@ export default async function generateInvoice( invoice.status === 'paid' && invoice.status_transitions.paid_at ? fromEpochSeconds(invoice.status_transitions.paid_at) : undefined - const [organization, billingLeaderIds] = await Promise.all([ + const [organization, orgUsers] = await Promise.all([ dataLoader.get('organizations').loadNonNull(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') - .run() as any as string[] + dataLoader.get('organizationUsersByOrgId').load(orgId) ]) + const billingLeaderIds = orgUsers + .filter(({role}) => role && ['BILLING_LEADER', 'ORG_ADMIN'].includes(role)) + .map(({userId}) => userId) const billingLeaders = (await dataLoader.get('users').loadMany(billingLeaderIds)).filter(isValid) const billingLeaderEmails = billingLeaders.map((user) => user.email) diff --git a/packages/server/billing/helpers/handleEnterpriseOrgQuantityChanges.ts b/packages/server/billing/helpers/handleEnterpriseOrgQuantityChanges.ts index 215ac9396ef..e9cb414b876 100644 --- a/packages/server/billing/helpers/handleEnterpriseOrgQuantityChanges.ts +++ b/packages/server/billing/helpers/handleEnterpriseOrgQuantityChanges.ts @@ -1,5 +1,3 @@ -import getRethink from '../../database/rethinkDriver' -import {RDatum} from '../../database/stricterR' import {DataLoaderWorker} from '../../graphql/graphql' import {OrganizationSource} from '../../graphql/public/types/Organization' import {analytics} from '../../utils/analytics/analytics' @@ -9,31 +7,23 @@ const sendEnterpriseOverageEvent = async ( organization: OrganizationSource, dataLoader: DataLoaderWorker ) => { - const r = await getRethink() const manager = getStripeManager() const {id: orgId, stripeSubscriptionId} = organization if (!stripeSubscriptionId) return - const [orgUserCount, subscriptionItem] = await Promise.all([ - r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({removedAt: null, inactive: false}) - .count() - .run(), + const [orgUsers, subscriptionItem] = await Promise.all([ + dataLoader.get('organizationUsersByOrgId').load(orgId), manager.getSubscriptionItem(stripeSubscriptionId) ]) + const activeOrgUsers = orgUsers.filter(({inactive}) => !inactive) + const orgUserCount = activeOrgUsers.length if (!subscriptionItem) return const quantity = subscriptionItem.quantity if (!quantity) return if (orgUserCount > quantity) { - const billingLeaderOrgUser = await r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({removedAt: null}) - .filter((row: RDatum) => r.expr(['BILLING_LEADER', 'ORG_ADMIN']).contains(row('role'))) - .nth(0) - .run() + const billingLeaderOrgUser = orgUsers.find( + ({role}) => role && ['BILLING_LEADER', 'ORG_ADMIN'].includes(role) + )! const {id: userId} = billingLeaderOrgUser const user = await dataLoader.get('users').loadNonNull(userId) analytics.enterpriseOverUserLimit(user, orgId) diff --git a/packages/server/billing/helpers/teamLimitsCheck.ts b/packages/server/billing/helpers/teamLimitsCheck.ts index 6652c8ea6c7..9d2b8d52944 100644 --- a/packages/server/billing/helpers/teamLimitsCheck.ts +++ b/packages/server/billing/helpers/teamLimitsCheck.ts @@ -21,6 +21,13 @@ import sendTeamsLimitEmail from './sendTeamsLimitEmail' const enableUsageStats = async (userIds: string[], orgId: string) => { const pg = getKysely() + await pg + .updateTable('OrganizationUser') + .set({suggestedTier: 'team'}) + .where('orgId', '=', orgId) + .where('userId', 'in', userIds) + .where('removedAt', 'is', null) + .execute() await r .table('OrganizationUser') .getAll(r.args(userIds), {index: 'userId'}) @@ -87,6 +94,13 @@ export const maybeRemoveRestrictions = async (orgId: string, dataLoader: DataLoa .set({tierLimitExceededAt: null, scheduledLockAt: null, lockedAt: null}) .where('id', '=', orgId) .execute(), + pg + .updateTable('OrganizationUser') + .set({suggestedTier: 'starter'}) + .where('orgId', '=', orgId) + .where('userId', 'in', billingLeadersIds) + .where('removedAt', 'is', null) + .execute(), r .table('OrganizationUser') .getAll(r.args(billingLeadersIds), {index: 'userId'}) diff --git a/packages/server/billing/helpers/updateSubscriptionQuantity.ts b/packages/server/billing/helpers/updateSubscriptionQuantity.ts index 23fcb80095f..b6b2634eb39 100644 --- a/packages/server/billing/helpers/updateSubscriptionQuantity.ts +++ b/packages/server/billing/helpers/updateSubscriptionQuantity.ts @@ -11,9 +11,10 @@ import {getStripeManager} from '../../utils/stripe' */ const updateSubscriptionQuantity = async (orgId: string, logMismatch?: boolean) => { const r = await getRethink() + const pg = getKysely() const manager = getStripeManager() - const org = await getKysely() + const org = await pg .selectFrom('Organization') .selectAll() .where('id', '=', orgId) @@ -34,15 +35,29 @@ const updateSubscriptionQuantity = async (orgId: string, logMismatch?: boolean) return } - const [orgUserCount, teamSubscription] = await Promise.all([ + const [orgUserCountRes, orgUserCount, teamSubscription] = await Promise.all([ + pg + .selectFrom('OrganizationUser') + .select(({fn}) => fn.count('id').as('count')) + .where('orgId', '=', orgId) + .where('removedAt', 'is', null) + .where('inactive', '=', false) + .executeTakeFirstOrThrow(), r .table('OrganizationUser') .getAll(orgId, {index: 'orgId'}) .filter({removedAt: null, inactive: false}) .count() .run(), - await manager.getSubscriptionItem(stripeSubscriptionId) + manager.getSubscriptionItem(stripeSubscriptionId) ]) + if (orgUserCountRes.count !== orgUserCount) { + sendToSentry(new Error('OrganizationUser count mismatch'), { + tags: { + orgId + } + }) + } if ( teamSubscription && teamSubscription.quantity !== undefined && diff --git a/packages/server/database/types/OrganizationUser.ts b/packages/server/database/types/OrganizationUser.ts index 0d58058d67d..927242b2dc6 100644 --- a/packages/server/database/types/OrganizationUser.ts +++ b/packages/server/database/types/OrganizationUser.ts @@ -5,7 +5,6 @@ export type OrgUserRole = 'BILLING_LEADER' | 'ORG_ADMIN' interface Input { orgId: string userId: string - newUserUntil?: Date id?: string inactive?: boolean joinedAt?: Date @@ -20,36 +19,23 @@ export default class OrganizationUser { suggestedTier: TierEnum | null inactive: boolean joinedAt: Date - newUserUntil: Date orgId: string removedAt: Date | null role: OrgUserRole | null userId: string - tier: TierEnum | null + tier: TierEnum trialStartDate?: Date | null constructor(input: Input) { - const { - suggestedTier, - userId, - id, - removedAt, - inactive, - orgId, - joinedAt, - newUserUntil, - role, - tier - } = input + const {suggestedTier, userId, id, removedAt, inactive, orgId, joinedAt, role, tier} = input this.id = id || generateUID() this.suggestedTier = suggestedTier || null this.inactive = inactive || false this.joinedAt = joinedAt || new Date() - this.newUserUntil = newUserUntil || new Date() this.orgId = orgId this.removedAt = removedAt || null this.role = role || null this.userId = userId - this.tier = tier || null + this.tier = tier || 'starter' } } diff --git a/packages/server/dataloader/__tests__/isOrgVerified.test.ts b/packages/server/dataloader/__tests__/isOrgVerified.test.ts index 96a455b9767..6fa0c0c1b0e 100644 --- a/packages/server/dataloader/__tests__/isOrgVerified.test.ts +++ b/packages/server/dataloader/__tests__/isOrgVerified.test.ts @@ -32,7 +32,7 @@ const testConfig = { type TestUser = Insertable const addUsers = async (users: TestUser[]) => { - getKysely().insertInto('User').values(users).execute() + return getKysely().insertInto('User').values(users).execute() } const createTables = async (...tables: string[]) => { @@ -52,9 +52,7 @@ const createTables = async (...tables: string[]) => { } type TestOrganizationUser = Partial< - Pick & { - domain: string - } + Pick > const addOrg = async ( @@ -77,19 +75,28 @@ const addOrg = async ( ...member, inactive: member.inactive ?? false, role: member.role ?? null, - removedAt: member.removedAt ?? null + removedAt: member.removedAt ?? null, + tier: 'starter' as const })) const pg = getKysely() - await pg.insertInto('Organization').values(org).execute() - await r.table('OrganizationUser').insert(orgUsers).run() + if (orgUsers.length > 0) { + await pg + .with('Org', (qc) => qc.insertInto('Organization').values(org)) + .insertInto('OrganizationUser') + .values(orgUsers) + .execute() + } else { + await pg.insertInto('Organization').values(org).execute() + } + await r.table('OrganizationUser').insert(orgUsers).run() return orgId } beforeAll(async () => { await r.connectPool(testConfig) - const pg = getKysely() + const pg = getKysely(TEST_DB) try { await r.dbDrop(TEST_DB).run() @@ -99,7 +106,7 @@ beforeAll(async () => { await pg.schema.createSchema(TEST_DB).ifNotExists().execute() await r.dbCreate(TEST_DB).run() - await createPGTables('Organization', 'User', 'SAML', 'SAMLDomain') + await createPGTables('Organization', 'User', 'SAML', 'SAMLDomain', 'OrganizationUser') await createTables('OrganizationUser') }) @@ -110,17 +117,11 @@ afterEach(async () => { afterAll(async () => { await r.getPoolMaster()?.drain() + await getKysely().destroy() getRedis().quit() }) test('Founder is billing lead', async () => { - await addOrg('parabol.co', [ - { - joinedAt: new Date('2023-09-06'), - role: 'BILLING_LEADER', - userId: 'user1' - } - ]) await addUsers([ { id: 'user1', @@ -130,6 +131,14 @@ test('Founder is billing lead', async () => { identities: [{isEmailVerified: true}] } ]) + await addOrg('parabol.co', [ + { + joinedAt: new Date('2023-09-06'), + role: 'BILLING_LEADER', + userId: 'user1' + } + ]) + const dataLoader = new RootDataLoader() const isVerified = await dataLoader.get('isOrgVerified').load('parabol.co') expect(isVerified).toBe(true) @@ -150,6 +159,13 @@ test('Non-founder billing lead is checked', async () => { picture: '', preferredName: '', identities: [{isEmailVerified: true}] + }, + { + id: 'member1', + email: 'member1@parabol.co', + picture: '', + preferredName: '', + identities: [{isEmailVerified: true}] } ]) await addOrg('parabol.co', [ @@ -196,8 +212,7 @@ test('Orgs with verified emails from different domains do not qualify', async () await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), - userId: 'founder1', - domain: 'not-parabol.co' + userId: 'founder1' } as any ]) diff --git a/packages/server/graphql/graphql.ts b/packages/server/graphql/graphql.ts index ebf6702751b..cd69285748f 100644 --- a/packages/server/graphql/graphql.ts +++ b/packages/server/graphql/graphql.ts @@ -15,6 +15,7 @@ export interface GQLContext { dataLoader: DataLoaderWorker } +export type SubscriptionContext = Omit export interface InternalContext { dataLoader: DataLoaderWorker authToken: AuthToken diff --git a/packages/server/graphql/mutations/archiveOrganization.ts b/packages/server/graphql/mutations/archiveOrganization.ts index 9c998e49810..cbe6942d08b 100644 --- a/packages/server/graphql/mutations/archiveOrganization.ts +++ b/packages/server/graphql/mutations/archiveOrganization.ts @@ -1,9 +1,11 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' +import {sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import removeTeamsLimitObjects from '../../billing/helpers/removeTeamsLimitObjects' import getRethink from '../../database/rethinkDriver' import Team from '../../database/types/Team' import User from '../../database/types/User' +import getKysely from '../../postgres/getKysely' import IUser from '../../postgres/types/IUser' import safeArchiveTeam from '../../safeMutations/safeArchiveTeam' import {analytics} from '../../utils/analytics/analytics' @@ -81,6 +83,12 @@ export default { const uniqueUserIds = Array.from(new Set(allUserIds)) await Promise.all([ + getKysely() + .updateTable('OrganizationUser') + .set({removedAt: sql`CURRENT_TIMESTAMP`}) + .where('orgId', '=', orgId) + .where('removedAt', 'is', null) + .execute(), r .table('OrganizationUser') .getAll(orgId, {index: 'orgId'}) diff --git a/packages/server/graphql/mutations/helpers/createNewOrg.ts b/packages/server/graphql/mutations/helpers/createNewOrg.ts index bbfeec1f9d1..d4b069caee2 100644 --- a/packages/server/graphql/mutations/helpers/createNewOrg.ts +++ b/packages/server/graphql/mutations/helpers/createNewOrg.ts @@ -30,8 +30,9 @@ export default async function createNewOrg( }) await insertOrgUserAudit([orgId], leaderUserId, 'added') await getKysely() - .insertInto('Organization') - .values({...org, creditCard: null}) + .with('Org', (qc) => qc.insertInto('Organization').values({...org, creditCard: null})) + .insertInto('OrganizationUser') + .values(orgUser) .execute() await r.table('OrganizationUser').insert(orgUser).run() } diff --git a/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts b/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts index 24c2ed3f6bc..7e6cfc21ebb 100644 --- a/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts +++ b/packages/server/graphql/mutations/helpers/oldUpgradeToTeamTier.ts @@ -1,5 +1,4 @@ import removeTeamsLimitObjects from '../../../billing/helpers/removeTeamsLimitObjects' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {toCreditCard} from '../../../postgres/helpers/toCreditCard' import {fromEpochSeconds} from '../../../utils/epochTime' @@ -15,7 +14,6 @@ const oldUpgradeToTeamTier = async ( email: string, dataLoader: DataLoaderWorker ) => { - const r = await getRethink() const pg = getKysely() const now = new Date() @@ -23,12 +21,9 @@ const oldUpgradeToTeamTier = async ( if (!organization) throw new Error('Bad orgId') const {stripeId, stripeSubscriptionId} = organization - const quantity = await r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({removedAt: null, inactive: false}) - .count() - .run() + const orgUsers = await dataLoader.get('organizationUsersByOrgId').load(orgId) + const activeOrgUsers = orgUsers.filter(({inactive}) => !inactive) + const quantity = activeOrgUsers.length const manager = getStripeManager() const customer = stripeId diff --git a/packages/server/graphql/mutations/helpers/removeFromOrg.ts b/packages/server/graphql/mutations/helpers/removeFromOrg.ts index af706293f0b..dc6f2ad9934 100644 --- a/packages/server/graphql/mutations/helpers/removeFromOrg.ts +++ b/packages/server/graphql/mutations/helpers/removeFromOrg.ts @@ -1,8 +1,9 @@ +import {sql} from 'kysely' import {InvoiceItemType} from 'parabol-client/types/constEnums' import adjustUserCount from '../../../billing/helpers/adjustUserCount' import getRethink from '../../../database/rethinkDriver' -import {RDatum} from '../../../database/stricterR' import OrganizationUser from '../../../database/types/OrganizationUser' +import getKysely from '../../../postgres/getKysely' import getTeamsByOrgIds from '../../../postgres/queries/getTeamsByOrgIds' import {Logger} from '../../../utils/Logger' import setUserTierForUserIds from '../../../utils/setUserTierForUserIds' @@ -17,6 +18,7 @@ const removeFromOrg = async ( dataLoader: DataLoaderWorker ) => { const r = await getRethink() + const pg = getKysely() const now = new Date() const orgTeams = await getTeamsByOrgIds([orgId]) const teamIds = orgTeams.map((team) => team.id) @@ -42,7 +44,15 @@ const removeFromOrg = async ( return arr }, []) - const [organizationUser, user] = await Promise.all([ + const [_pgOrgUser, organizationUser, user] = await Promise.all([ + pg + .updateTable('OrganizationUser') + .set({removedAt: sql`CURRENT_TIMESTAMP`}) + .where('userId', '=', userId) + .where('orgId', '=', orgId) + .where('removedAt', 'is', null) + .returning('role') + .executeTakeFirstOrThrow(), r .table('OrganizationUser') .getAll(userId, {index: 'userId'}) @@ -60,22 +70,19 @@ const removeFromOrg = async ( const organization = await dataLoader.get('organizations').loadNonNull(orgId) // if no other billing leader, promote the oldest // if team tier & no other member, downgrade to starter - const otherBillingLeaders = await r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({removedAt: null}) - .filter((row: RDatum) => r.expr(['BILLING_LEADER', 'ORG_ADMIN']).contains(row('role'))) - .run() + const allOrgUsers = await dataLoader.get('organizationUsersByOrgId').load(orgId) + const otherBillingLeaders = allOrgUsers.filter( + ({role}) => role && ['BILLING_LEADER', 'ORG_ADMIN'].includes(role) + ) if (otherBillingLeaders.length === 0) { - const nextInLine = await r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({removedAt: null}) - .orderBy('joinedAt') - .nth(0) - .default(null) - .run() + const orgUsersByJoinAt = allOrgUsers.sort((a, b) => (a.joinedAt < b.joinedAt ? -1 : 1)) + const nextInLine = orgUsersByJoinAt[0] if (nextInLine) { + await pg + .updateTable('OrganizationUser') + .set({role: 'BILLING_LEADER'}) + .where('id', '=', nextInLine.id) + .execute() await r .table('OrganizationUser') .get(nextInLine.id) diff --git a/packages/server/graphql/mutations/moveTeamToOrg.ts b/packages/server/graphql/mutations/moveTeamToOrg.ts index 7e3ea1ad40d..972a7a81043 100644 --- a/packages/server/graphql/mutations/moveTeamToOrg.ts +++ b/packages/server/graphql/mutations/moveTeamToOrg.ts @@ -3,7 +3,6 @@ import {InvoiceItemType} from 'parabol-client/types/constEnums' import adjustUserCount from '../../billing/helpers/adjustUserCount' import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' -import Notification from '../../database/types/Notification' import getKysely from '../../postgres/getKysely' import getTeamsByIds from '../../postgres/queries/getTeamsByIds' import updateMeetingTemplateOrgId from '../../postgres/queries/updateMeetingTemplateOrgId' @@ -50,13 +49,11 @@ const moveToOrg = async ( if (!userId) { return standardError(new Error('No userId provided')) } - const newOrganizationUser = await r - .table('OrganizationUser') - .getAll(userId, {index: 'userId'}) - .filter({orgId, removedAt: null}) - .nth(0) - .default(null) - .run() + const [newOrganizationUser, oldOrganizationUser] = await Promise.all([ + dataLoader.get('organizationUsersByUserIdOrgId').load({orgId, userId}), + dataLoader.get('organizationUsersByUserIdOrgId').load({orgId: currentOrgId, userId}) + ]) + if (!newOrganizationUser) { return standardError(new Error('Not on organization'), {userId}) } @@ -65,14 +62,9 @@ const moveToOrg = async ( if (!isBillingLeaderForOrg) { return standardError(new Error('Not organization leader'), {userId}) } - const oldOrganizationUser = await r - .table('OrganizationUser') - .getAll(userId, {index: 'userId'}) - .filter({orgId: currentOrgId, removedAt: null}) - .nth(0) - .run() + const isBillingLeaderForTeam = - oldOrganizationUser.role === 'BILLING_LEADER' || oldOrganizationUser.role === 'ORG_ADMIN' + oldOrganizationUser?.role === 'BILLING_LEADER' || oldOrganizationUser?.role === 'ORG_ADMIN' if (!isBillingLeaderForTeam) { return standardError(new Error('Not organization leader'), {userId}) } @@ -90,31 +82,25 @@ const moveToOrg = async ( trialStartDate: org.trialStartDate, updatedAt: new Date() } - const [rethinkResult] = await Promise.all([ - r({ - notifications: r - .table('Notification') - .filter({teamId}) - .filter((notification: RDatum) => notification('orgId').default(null).ne(null)) - .update({orgId}) as unknown as Notification[], - newToOrgUserIds: r - .table('TeamMember') - .getAll(teamId, {index: 'teamId'}) - .filter({isNotRemoved: true}) - .filter((teamMember: RDatum) => { - return r - .table('OrganizationUser') - .getAll(teamMember('userId'), {index: 'userId'}) - .filter({orgId, removedAt: null}) - .count() - .eq(0) - })('userId') - .coerceTo('array') as unknown as string[] - }).run(), + const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) + const teamMemberUserIds = teamMembers.map(({userId}) => userId) + const orgUserKeys = teamMemberUserIds.map((userId) => ({userId, orgId})) + const existingOrgUsers = ( + await dataLoader.get('organizationUsersByUserIdOrgId').loadMany(orgUserKeys) + ).filter(isValid) + const newToOrgUserIds = teamMemberUserIds.filter( + (userId) => !existingOrgUsers.find((orgUser) => orgUser.userId === userId) + ) + await Promise.all([ + r + .table('Notification') + .filter({teamId}) + .filter((notification: RDatum) => notification('orgId').default(null).ne(null)) + .update({orgId}) + .run(), updateMeetingTemplateOrgId(currentOrgId, orgId), updateTeamByTeamId(updates, teamId) ]) - const {newToOrgUserIds} = rethinkResult // if no teams remain on the org, remove it await safeArchiveEmptyStarterOrganization(currentOrgId, dataLoader) diff --git a/packages/server/graphql/mutations/oldUpgradeToTeamTier.ts b/packages/server/graphql/mutations/oldUpgradeToTeamTier.ts index ddcd8d27035..7bf2e98adec 100644 --- a/packages/server/graphql/mutations/oldUpgradeToTeamTier.ts +++ b/packages/server/graphql/mutations/oldUpgradeToTeamTier.ts @@ -1,6 +1,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {analytics} from '../../utils/analytics/analytics' import {getUserId} from '../../utils/authorization' import publish from '../../utils/publish' @@ -65,6 +66,13 @@ export default { const activeMeetings = await hideConversionModal(orgId, dataLoader) const meetingIds = activeMeetings.map(({id}) => id) + await getKysely() + .updateTable('OrganizationUser') + .set({role: 'BILLING_LEADER'}) + .where('userId', '=', viewerId) + .where('orgId', '=', orgId) + .where('removedAt', 'is', null) + .execute() await r .table('OrganizationUser') .getAll(viewerId, {index: 'userId'}) diff --git a/packages/server/graphql/private/mutations/autopauseUsers.ts b/packages/server/graphql/private/mutations/autopauseUsers.ts index 3dbbc4742f4..741f96be9af 100644 --- a/packages/server/graphql/private/mutations/autopauseUsers.ts +++ b/packages/server/graphql/private/mutations/autopauseUsers.ts @@ -1,6 +1,7 @@ import {InvoiceItemType, Threshold} from 'parabol-client/types/constEnums' import adjustUserCount from '../../../billing/helpers/adjustUserCount' import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import getUserIdsToPause from '../../../postgres/queries/getUserIdsToPause' import {Logger} from '../../../utils/Logger' import {MutationResolvers} from '../resolverTypes' @@ -11,7 +12,7 @@ const autopauseUsers: MutationResolvers['autopauseUsers'] = async ( {dataLoader} ) => { const r = await getRethink() - + const pg = getKysely() // RESOLUTION const activeThresh = new Date(Date.now() - Threshold.AUTO_PAUSE) const userIdsToPause = await getUserIdsToPause(activeThresh) @@ -21,6 +22,17 @@ const autopauseUsers: MutationResolvers['autopauseUsers'] = async ( const skip = i * BATCH_SIZE const userIdBatch = userIdsToPause.slice(skip, skip + BATCH_SIZE) if (userIdBatch.length < 1) break + const pgResults = await pg + .selectFrom('OrganizationUser') + .select(({fn}) => ['userId', fn.agg('array_agg', ['orgId']).as('orgIds')]) + .where('userId', 'in', userIdBatch) + .where('removedAt', 'is', null) + .groupBy('userId') + .execute() + + // TEST in Phase 2! + console.log(pgResults) + const results = (await ( r .table('OrganizationUser') diff --git a/packages/server/graphql/private/mutations/backupOrganization.ts b/packages/server/graphql/private/mutations/backupOrganization.ts index c347258c54b..d05c2e55189 100644 --- a/packages/server/graphql/private/mutations/backupOrganization.ts +++ b/packages/server/graphql/private/mutations/backupOrganization.ts @@ -191,9 +191,6 @@ const backupOrganization: MutationResolvers['backupOrganization'] = async (_sour newMeeting: (r.table('NewMeeting').getAll(r.args(teamIds), {index: 'teamId'}) as any) .coerceTo('array') .do((items: RValue) => r.db(DESTINATION).table('NewMeeting').insert(items)), - organizationUser: (r.table('OrganizationUser').getAll(r.args(orgIds), {index: 'orgId'}) as any) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('OrganizationUser').insert(items)), reflectPrompt: (r.table('ReflectPrompt').getAll(r.args(teamIds), {index: 'teamId'}) as any) .coerceTo('array') .do((items: RValue) => r.db(DESTINATION).table('ReflectPrompt').insert(items)), diff --git a/packages/server/graphql/private/mutations/connectSocket.ts b/packages/server/graphql/private/mutations/connectSocket.ts index 002b6d67001..6b69ebcb515 100644 --- a/packages/server/graphql/private/mutations/connectSocket.ts +++ b/packages/server/graphql/private/mutations/connectSocket.ts @@ -1,6 +1,5 @@ import {InvoiceItemType, SubscriptionChannel} from 'parabol-client/types/constEnums' import adjustUserCount from '../../../billing/helpers/adjustUserCount' -import getRethink from '../../../database/rethinkDriver' import updateUser from '../../../postgres/queries/updateUser' import {Logger} from '../../../utils/Logger' import {analytics} from '../../../utils/analytics/analytics' @@ -8,6 +7,7 @@ import {getUserId} from '../../../utils/authorization' import getListeningUserIds, {RedisCommand} from '../../../utils/getListeningUserIds' import getRedis from '../../../utils/getRedis' import publish from '../../../utils/publish' +import {DataLoaderWorker} from '../../graphql' import {MutationResolvers} from '../resolverTypes' export interface UserPresence { @@ -16,12 +16,18 @@ export interface UserPresence { socketId: string } +const handleInactive = async (userId: string, dataLoader: DataLoaderWorker) => { + const orgUsers = await dataLoader.get('organizationUsersByUserId').load(userId) + const orgIds = orgUsers.map(({orgId}) => orgId) + await adjustUserCount(userId, orgIds, InvoiceItemType.UNPAUSE_USER, dataLoader).catch(Logger.log) + // TODO: re-identify +} + const connectSocket: MutationResolvers['connectSocket'] = async ( _source, {socketInstanceId}, {authToken, dataLoader, socketId} ) => { - const r = await getRethink() const redis = getRedis() const now = new Date() @@ -40,13 +46,7 @@ const connectSocket: MutationResolvers['connectSocket'] = async ( // no need to wait for this, it's just for billing if (inactive) { - const orgIds = await r - .table('OrganizationUser') - .getAll(userId, {index: 'userId'}) - .filter({removedAt: null, inactive: true})('orgId') - .run() - adjustUserCount(userId, orgIds, InvoiceItemType.UNPAUSE_USER, dataLoader).catch(Logger.log) - // TODO: re-identify + handleInactive(userId, dataLoader) } const datesAreOnSameDay = now.toDateString() === lastSeenAt.toDateString() if (!datesAreOnSameDay) { diff --git a/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts b/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts index a3f1dfe13f6..e07c5fadcec 100644 --- a/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts +++ b/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts @@ -19,6 +19,7 @@ const getBillingLeaderUser = async ( dataLoader: DataLoaderWorker ) => { const r = await getRethink() + const pg = getKysely() if (email) { const user = await getUserByEmail(email) if (!user) { @@ -32,6 +33,13 @@ const getBillingLeaderUser = async ( throw new Error('Email not associated with a user on that org') } if (organizationUser.role !== 'ORG_ADMIN') { + await pg + .updateTable('OrganizationUser') + .set({role: 'BILLING_LEADER'}) + .where('userId', '=', userId) + .where('orgId', '=', orgId) + .where('removedAt', 'is', null) + .execute() await r .table('OrganizationUser') .getAll(userId, {index: 'userId'}) diff --git a/packages/server/graphql/private/mutations/stripeFailPayment.ts b/packages/server/graphql/private/mutations/stripeFailPayment.ts index 64495a4b3b9..023a6037369 100644 --- a/packages/server/graphql/private/mutations/stripeFailPayment.ts +++ b/packages/server/graphql/private/mutations/stripeFailPayment.ts @@ -2,7 +2,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import Stripe from 'stripe' import terminateSubscription from '../../../billing/helpers/terminateSubscription' import getRethink from '../../../database/rethinkDriver' -import {RDatum} from '../../../database/stricterR' import NotificationPaymentRejected from '../../../database/types/NotificationPaymentRejected' import {isSuperUser} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -76,15 +75,11 @@ const stripeFailPayment: MutationResolvers['stripeFailPayment'] = async ( // Not to handle this particular case in 23 hours, we do it now await terminateSubscription(orgId) } - - const billingLeaderUserIds = (await r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({removedAt: null}) - .filter((row: RDatum) => r.expr(['BILLING_LEADER', 'ORG_ADMIN']).contains(row('role')))( - 'userId' - ) - .run()) as string[] + const orgUsers = await dataLoader.get('organizationUsersByOrgId').load(orgId) + const billingLeaderOrgUsers = orgUsers.filter( + ({role}) => role && ['BILLING_LEADER', 'ORG_ADMIN'].includes(role) + ) + const billingLeaderUserIds = billingLeaderOrgUsers.map(({userId}) => userId) const {default_source} = customer diff --git a/packages/server/graphql/private/mutations/toggleAllowInsights.ts b/packages/server/graphql/private/mutations/toggleAllowInsights.ts index e10494235df..0d4a6d80cb4 100644 --- a/packages/server/graphql/private/mutations/toggleAllowInsights.ts +++ b/packages/server/graphql/private/mutations/toggleAllowInsights.ts @@ -1,4 +1,5 @@ import {r, RValue} from 'rethinkdb-ts' +import getKysely from '../../../postgres/getKysely' import {getUsersByEmails} from '../../../postgres/queries/getUsersByEmails' import {MutationResolvers} from '../resolverTypes' @@ -7,6 +8,7 @@ const toggleAllowInsights: MutationResolvers['toggleAllowInsights'] = async ( {suggestedTier, domain, emails}, {dataLoader} ) => { + const pg = getKysely() const organizations = await dataLoader.get('organizationsByActiveDomain').load(domain) if (organizations.length === 0) { return { @@ -24,6 +26,14 @@ const toggleAllowInsights: MutationResolvers['toggleAllowInsights'] = async ( const userIds = users.map(({id}) => id) const recordsReplaced = await Promise.all( userIds.map(async (userId) => { + await pg + .updateTable('OrganizationUser') + .set({suggestedTier}) + .where('userId', '=', userId) + .where('orgId', 'in', orgIds) + .where('removedAt', 'is', null) + .returning('id') + .execute() return r .table('OrganizationUser') .getAll(r.args(orgIds), {index: 'orgId'}) diff --git a/packages/server/graphql/private/mutations/upgradeToTeamTier.ts b/packages/server/graphql/private/mutations/upgradeToTeamTier.ts index e0e04d94b5a..e051a0ccb81 100644 --- a/packages/server/graphql/private/mutations/upgradeToTeamTier.ts +++ b/packages/server/graphql/private/mutations/upgradeToTeamTier.ts @@ -100,6 +100,13 @@ const upgradeToTeamTier: MutationResolvers['upgradeToTeamTier'] = async ( const activeMeetings = await hideConversionModal(orgId, dataLoader) const meetingIds = activeMeetings.map(({id}) => id) + await pg + .updateTable('OrganizationUser') + .set({role: 'BILLING_LEADER'}) + .where('userId', '=', viewerId) + .where('orgId', '=', orgId) + .where('removedAt', 'is', null) + .execute() await r .table('OrganizationUser') .getAll(viewerId, {index: 'userId'}) diff --git a/packages/server/graphql/private/queries/suCountTiersForUser.ts b/packages/server/graphql/private/queries/suCountTiersForUser.ts index 9040fda22a8..5796c09ffff 100644 --- a/packages/server/graphql/private/queries/suCountTiersForUser.ts +++ b/packages/server/graphql/private/queries/suCountTiersForUser.ts @@ -1,9 +1,13 @@ import countTiersForUserId from '../../queries/helpers/countTiersForUserId' import {QueryResolvers} from '../resolverTypes' -const suCountTiersForUser: QueryResolvers['suCountTiersForUser'] = async (_source, {userId}) => { +const suCountTiersForUser: QueryResolvers['suCountTiersForUser'] = async ( + _source, + {userId}, + {dataLoader} +) => { return { - ...(await countTiersForUserId(userId)), + ...(await countTiersForUserId(userId, dataLoader)), userId } } diff --git a/packages/server/graphql/private/queries/suOrgCount.ts b/packages/server/graphql/private/queries/suOrgCount.ts index f23c48c6850..b2dd6f6afc3 100644 --- a/packages/server/graphql/private/queries/suOrgCount.ts +++ b/packages/server/graphql/private/queries/suOrgCount.ts @@ -1,8 +1,28 @@ import getRethink from '../../../database/rethinkDriver' import {RValue} from '../../../database/stricterR' +import getKysely from '../../../postgres/getKysely' import {QueryResolvers} from '../resolverTypes' const suOrgCount: QueryResolvers['suOrgCount'] = async (_source, {minOrgSize, tier}) => { + const pg = getKysely() + const pgResults = await pg + .with('BigOrgs', (qb) => + qb + .selectFrom('OrganizationUser') + .select(({fn}) => fn.count('id').as('orgSize')) + .where('tier', '=', tier) + .where('inactive', '=', false) + .where('removedAt', 'is', null) + .groupBy('orgId') + .having(({eb, fn}) => eb(fn.count('id'), '>=', minOrgSize)) + ) + .selectFrom('BigOrgs') + .select(({fn}) => fn.count('orgSize').as('count')) + .executeTakeFirstOrThrow() + + // TEST in Phase 2! + console.log(pgResults) + const r = await getRethink() return ( r diff --git a/packages/server/graphql/private/queries/suProOrgInfo.ts b/packages/server/graphql/private/queries/suProOrgInfo.ts index 870c5237419..3e604f7a5bb 100644 --- a/packages/server/graphql/private/queries/suProOrgInfo.ts +++ b/packages/server/graphql/private/queries/suProOrgInfo.ts @@ -1,14 +1,28 @@ +import {sql} from 'kysely' import getRethink from '../../../database/rethinkDriver' import {RDatum} from '../../../database/stricterR' import {selectOrganizations} from '../../../dataloader/primaryKeyLoaderMakers' +import getKysely from '../../../postgres/getKysely' import {QueryResolvers} from '../resolverTypes' const suProOrgInfo: QueryResolvers['suProOrgInfo'] = async (_source, {includeInactive}) => { const r = await getRethink() + const pg = getKysely() const proOrgs = await selectOrganizations().where('tier', '=', 'team').execute() if (includeInactive) return proOrgs const proOrgIds = proOrgs.map(({id}) => id) + const pgResults = await pg + .selectFrom('OrganizationUser') + .select(({fn}) => fn.count('id').as('orgSize')) + // use ANY to support case where proOrgIds is empty array. Please use `in` after RethinkDB is gone + .where('orgId', '=', sql`ANY(${proOrgIds})`) + .where('inactive', '=', false) + .where('removedAt', 'is', null) + .groupBy('orgId') + .having(({eb, fn}) => eb(fn.count('id'), '>=', 1)) + .execute() + const activeOrgIds = await ( r .table('OrganizationUser') @@ -20,7 +34,7 @@ const suProOrgInfo: QueryResolvers['suProOrgInfo'] = async (_source, {includeIna .ungroup() .filter((row: RDatum) => row('reduction').ge(1))('group') .run() - + console.log({pgResults, activeOrgIds}) return proOrgs.filter((org) => activeOrgIds.includes(org.id)) } diff --git a/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts b/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts index 9c7cddc6117..29014431ff3 100644 --- a/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts +++ b/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts @@ -91,13 +91,7 @@ const acceptRequestToJoinDomain: MutationResolvers['acceptRequestToJoinDomain'] for (const validTeam of validTeams) { const {id: teamId, orgId} = validTeam const [organizationUser] = await Promise.all([ - r - .table('OrganizationUser') - .getAll(userId, {index: 'userId'}) - .filter({orgId, removedAt: null}) - .nth(0) - .default(null) - .run(), + dataLoader.get('organizationUsersByUserIdOrgId').load({orgId, userId}), insertNewTeamMember(user, teamId), addTeamIdToTMS(userId, teamId) ]) diff --git a/packages/server/graphql/public/mutations/createStripeSubscription.ts b/packages/server/graphql/public/mutations/createStripeSubscription.ts index 47fb36d4dba..6b65428c4b2 100644 --- a/packages/server/graphql/public/mutations/createStripeSubscription.ts +++ b/packages/server/graphql/public/mutations/createStripeSubscription.ts @@ -1,5 +1,4 @@ import Stripe from 'stripe' -import getRethink from '../../../database/rethinkDriver' import {getUserId} from '../../../utils/authorization' import standardError from '../../../utils/standardError' import {getStripeManager} from '../../../utils/stripe' @@ -11,20 +10,15 @@ const createStripeSubscription: MutationResolvers['createStripeSubscription'] = {authToken, dataLoader} ) => { const viewerId = getUserId(authToken) - const r = await getRethink() - const [viewer, organization, orgUsersCount, organizationUser] = await Promise.all([ + const [viewer, organization, orgUsers] = await Promise.all([ dataLoader.get('users').loadNonNull(viewerId), dataLoader.get('organizations').loadNonNull(orgId), - r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({removedAt: null, inactive: false}) - .count() - .run(), - dataLoader.get('organizationUsersByUserIdOrgId').load({orgId, userId: viewerId}) + dataLoader.get('organizationUsersByOrgId').load(orgId) ]) - + const activeOrgUsers = orgUsers.filter(({inactive}) => !inactive) + const orgUsersCount = activeOrgUsers.length + const organizationUser = orgUsers.find(({userId}) => userId === viewerId) if (!organizationUser) return standardError(new Error('Unable to create subscription'), { userId: viewerId diff --git a/packages/server/graphql/public/mutations/setOrgUserRole.ts b/packages/server/graphql/public/mutations/setOrgUserRole.ts index 5b6d2bb4fc6..d00522221fe 100644 --- a/packages/server/graphql/public/mutations/setOrgUserRole.ts +++ b/packages/server/graphql/public/mutations/setOrgUserRole.ts @@ -1,7 +1,7 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../../database/rethinkDriver' -import {RDatum} from '../../../database/stricterR' import NotificationPromoteToBillingLeader from '../../../database/types/NotificationPromoteToBillingLeader' +import getKysely from '../../../postgres/getKysely' import {analytics} from '../../../utils/analytics/analytics' import {getUserId, isSuperUser, isUserBillingLeader} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -22,6 +22,7 @@ const setOrgUserRole: MutationResolvers['setOrgUserRole'] = async ( {authToken, dataLoader, socketId: mutatorId} ) => { const r = await getRethink() + const pg = getKysely() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -39,24 +40,12 @@ const setOrgUserRole: MutationResolvers['setOrgUserRole'] = async ( return standardError(new Error('Invalid role to set'), {userId: viewerId}) } - const [organizationUser, viewer, viewerOrgUser] = await Promise.all([ - r - .table('OrganizationUser') - .getAll(userId, {index: 'userId'}) - .filter({orgId, removedAt: null}) - .nth(0) - .default(null) - .run(), - dataLoader.get('users').loadNonNull(viewerId), - r - .table('OrganizationUser') - .getAll(viewerId, {index: 'userId'}) - .filter({orgId, removedAt: null}) - .nth(0) - .default(null) - .run() + const [orgUsers, viewer] = await Promise.all([ + dataLoader.get('organizationUsersByOrgId').load(orgId), + dataLoader.get('users').loadNonNull(viewerId) ]) - + const organizationUser = orgUsers.find((orgUser) => orgUser.userId === userId) + const viewerOrgUser = orgUsers.find((orgUser) => orgUser.userId === viewerId) if (!organizationUser) { return standardError(new Error('Cannot find org user'), { userId: viewerId @@ -74,17 +63,14 @@ const setOrgUserRole: MutationResolvers['setOrgUserRole'] = async ( } } - // if someone is leaving, make sure there is someone else to take their place - if (userId === viewerId) { - const leaderCount = await r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({removedAt: null}) - .filter((row: RDatum) => r.expr(['BILLING_LEADER', 'ORG_ADMIN']).contains(row('role'))) - .count() - .run() - if (leaderCount === 1 && !roleToSet) { - return standardError(new Error('You’re the last leader, you can’t give that up'), { + // if removing a role, make sure someone else has elevated permissions + if (!roleToSet) { + const leaders = orgUsers.filter( + ({role}) => role && ['BILLING_LEADER', 'ORG_ADMIN'].includes(role) + ) + const leaderCount = leaders.length + if (leaderCount === 1) { + return standardError(new Error('Cannot remove permissions of the last leader'), { userId: viewerId }) } @@ -99,8 +85,17 @@ const setOrgUserRole: MutationResolvers['setOrgUserRole'] = async ( notificationIdsAdded: [] } } - await r.table('OrganizationUser').get(organizationUserId).update({role: roleToSet}).run() - + await pg + .updateTable('OrganizationUser') + .set({role: roleToSet || null}) + .where('id', '=', organizationUserId) + .execute() + await r + .table('OrganizationUser') + .get(organizationUserId) + .update({role: roleToSet || null}) + .run() + organizationUser.role = roleToSet || null if (roleToSet !== 'ORG_ADMIN') { const modificationType = roleToSet === 'BILLING_LEADER' ? 'add' : 'remove' analytics.billingLeaderModified(viewer, userId, orgId, modificationType) diff --git a/packages/server/graphql/public/subscriptions/organizationSubscription.ts b/packages/server/graphql/public/subscriptions/organizationSubscription.ts index 03bd9a377fd..3c6cff708e3 100644 --- a/packages/server/graphql/public/subscriptions/organizationSubscription.ts +++ b/packages/server/graphql/public/subscriptions/organizationSubscription.ts @@ -2,25 +2,27 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../../database/rethinkDriver' import {getUserId} from '../../../utils/authorization' import getPubSub from '../../../utils/getPubSub' +import {SubscriptionContext} from '../../graphql' import {SubscriptionResolvers} from '../resolverTypes' -const organizationSubscription: SubscriptionResolvers['organizationSubscription'] = { - subscribe: async (_source, _args, {authToken}) => { - // AUTH - const viewerId = getUserId(authToken) - const r = await getRethink() - const organizationUsers = await r - .table('OrganizationUser') - .getAll(viewerId, {index: 'userId'}) - .filter({removedAt: null}) - .run() - const orgIds = organizationUsers.map(({orgId}) => orgId) +const organizationSubscription: SubscriptionResolvers['organizationSubscription'] = + { + subscribe: async (_source, _args, {authToken}) => { + // AUTH + const viewerId = getUserId(authToken) + const r = await getRethink() + const organizationUsers = await r + .table('OrganizationUser') + .getAll(viewerId, {index: 'userId'}) + .filter({removedAt: null}) + .run() + const orgIds = organizationUsers.map(({orgId}) => orgId) - // RESOLUTION - const channelNames = orgIds - .concat(viewerId) - .map((id) => `${SubscriptionChannel.ORGANIZATION}.${id}`) - return getPubSub().subscribe(channelNames) + // RESOLUTION + const channelNames = orgIds + .concat(viewerId) + .map((id) => `${SubscriptionChannel.ORGANIZATION}.${id}`) + return getPubSub().subscribe(channelNames) + } } -} export default organizationSubscription diff --git a/packages/server/graphql/public/typeDefs/_legacy.graphql b/packages/server/graphql/public/typeDefs/_legacy.graphql index 4b4a247f5b1..04f64657889 100644 --- a/packages/server/graphql/public/typeDefs/_legacy.graphql +++ b/packages/server/graphql/public/typeDefs/_legacy.graphql @@ -1994,11 +1994,6 @@ type OrganizationUser { """ joinedAt: DateTime! - """ - The last moment a billing leader can remove the user from the org & receive a refund. Set to the subscription periodEnd - """ - newUserUntil: DateTime! - """ FK """ diff --git a/packages/server/graphql/queries/helpers/countTiersForUserId.ts b/packages/server/graphql/queries/helpers/countTiersForUserId.ts index 4e5aa7eee19..9a16bcd3da0 100644 --- a/packages/server/graphql/queries/helpers/countTiersForUserId.ts +++ b/packages/server/graphql/queries/helpers/countTiersForUserId.ts @@ -1,16 +1,11 @@ -import getRethink from '../../../database/rethinkDriver' -import OrganizationUser from '../../../database/types/OrganizationUser' +import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' // breaking this out into its own helper so it can be used directly to // populate segment traits -const countTiersForUserId = async (userId: string) => { - const r = await getRethink() - const organizationUsers = (await r - .table('OrganizationUser') - .getAll(userId, {index: 'userId'}) - .filter({inactive: false, removedAt: null}) - .run()) as OrganizationUser[] +const countTiersForUserId = async (userId: string, dataLoader: DataLoaderInstance) => { + const allOrgUsers = await dataLoader.get('organizationUsersByUserId').load(userId) + const organizationUsers = allOrgUsers.filter(({inactive}) => !inactive) const tierStarterCount = organizationUsers.filter( (organizationUser) => organizationUser.tier === 'starter' ).length diff --git a/packages/server/graphql/queries/invoices.ts b/packages/server/graphql/queries/invoices.ts index 82cab7a8849..23e6b7c029d 100644 --- a/packages/server/graphql/queries/invoices.ts +++ b/packages/server/graphql/queries/invoices.ts @@ -40,7 +40,7 @@ export default { // RESOLUTION const {stripeId} = await dataLoader.get('organizations').loadNonNull(orgId) const dbAfter = after ? new Date(after) : r.maxval - const [tooManyInvoices, orgUserCount] = await Promise.all([ + const [tooManyInvoices, orgUsers] = await Promise.all([ r .table('Invoice') .between([orgId, r.minval], [orgId, dbAfter], { @@ -54,16 +54,10 @@ export default { .orderBy(r.desc('startAt'), r.desc('createdAt')) .limit(first + 1) .run(), - r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({ - inactive: false, - removedAt: null - }) - .count() - .run() + dataLoader.get('organizationUsersByOrgId').load(orgId) ]) + const activeOrgUsers = orgUsers.filter(({inactive}) => !inactive) + const orgUserCount = activeOrgUsers.length const org = await dataLoader.get('organizations').loadNonNull(orgId) const upcomingInvoice = after ? undefined diff --git a/packages/server/graphql/types/OrganizationUser.ts b/packages/server/graphql/types/OrganizationUser.ts index 942a57530a3..ec583e27372 100644 --- a/packages/server/graphql/types/OrganizationUser.ts +++ b/packages/server/graphql/types/OrganizationUser.ts @@ -24,11 +24,6 @@ const OrganizationUser = new GraphQLObjectType({ type: new GraphQLNonNull(GraphQLISO8601Type), description: 'the datetime the user first joined the org' }, - newUserUntil: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: - 'The last moment a billing leader can remove the user from the org & receive a refund. Set to the subscription periodEnd' - }, orgId: { type: new GraphQLNonNull(GraphQLID), description: 'FK' diff --git a/packages/server/postgres/migrations/1720556055134_OrganizationUser-phase1.ts b/packages/server/postgres/migrations/1720556055134_OrganizationUser-phase1.ts new file mode 100644 index 00000000000..0d348909bff --- /dev/null +++ b/packages/server/postgres/migrations/1720556055134_OrganizationUser-phase1.ts @@ -0,0 +1,53 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'OrgUserRoleEnum') THEN + CREATE TYPE "OrgUserRoleEnum" AS ENUM ( + 'BILLING_LEADER', + 'ORG_ADMIN' + ); + END IF; + CREATE TABLE IF NOT EXISTS "OrganizationUser" ( + "id" VARCHAR(100) PRIMARY KEY, + "suggestedTier" "TierEnum", + "inactive" BOOLEAN NOT NULL DEFAULT FALSE, + "joinedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "orgId" VARCHAR(100) NOT NULL, + "removedAt" TIMESTAMP WITH TIME ZONE, + "role" "OrgUserRoleEnum", + "userId" VARCHAR(100) NOT NULL, + "tier" "TierEnum" NOT NULL, + "trialStartDate" TIMESTAMP WITH TIME ZONE, + CONSTRAINT "fk_userId" + FOREIGN KEY("userId") + REFERENCES "User"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_orgId" + FOREIGN KEY("orgId") + REFERENCES "Organization"("id") + ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS "idx_OrganizationUser_tier" ON "OrganizationUser"("tier") WHERE "removedAt" IS NULL AND "inactive" = FALSE; + CREATE INDEX IF NOT EXISTS "idx_OrganizationUser_orgId" ON "OrganizationUser"("orgId") WHERE "removedAt" IS NULL; + CREATE INDEX IF NOT EXISTS "idx_OrganizationUser_userId" ON "OrganizationUser"("userId") WHERE "removedAt" IS NULL; + + END $$; +`) + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE "OrganizationUser"; + DROP TYPE "OrgUserRoleEnum"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts b/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts index 86205cdf3f8..c5ddd92aec5 100644 --- a/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts +++ b/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts @@ -1,5 +1,7 @@ +import {sql} from 'kysely' import getRethink from '../database/rethinkDriver' import {DataLoaderInstance} from '../dataloader/RootDataLoader' +import getKysely from '../postgres/getKysely' import getTeamsByOrgIds from '../postgres/queries/getTeamsByOrgIds' // Only does something if the organization is empty & not paid @@ -10,6 +12,7 @@ const safeArchiveEmptyStarterOrganization = async ( dataLoader: DataLoaderInstance ) => { const r = await getRethink() + const pg = getKysely() const now = new Date() const orgTeams = await getTeamsByOrgIds([orgId]) const teamCountRemainingOnOldOrg = orgTeams.length @@ -17,7 +20,12 @@ const safeArchiveEmptyStarterOrganization = async ( if (teamCountRemainingOnOldOrg > 0) return const org = await dataLoader.get('organizations').loadNonNull(orgId) if (org.tier !== 'starter') return - + await pg + .updateTable('OrganizationUser') + .set({removedAt: sql`CURRENT_TIMESTAMP`}) + .where('orgId', '=', orgId) + .where('removedAt', 'is', null) + .execute() await r .table('OrganizationUser') .getAll(orgId, {index: 'orgId'}) diff --git a/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts b/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts index 73df49f1454..a17af53e9aa 100644 --- a/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts +++ b/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts @@ -45,11 +45,11 @@ const createTables = async (...tables: string[]) => { type TestOrganizationUser = Partial< Pick -> +> & {userId: string} type TestUser = Insertable const addUsers = async (users: TestUser[]) => { - getKysely().insertInto('User').values(users).execute() + return getKysely().insertInto('User').values(users).execute() } const addOrg = async ( activeDomain: string | null, @@ -72,9 +72,14 @@ const addOrg = async ( ...member, inactive: member.inactive ?? false, role: member.role ?? null, - removedAt: member.removedAt ?? null + removedAt: member.removedAt ?? null, + tier: 'starter' as const })) - await getKysely().insertInto('Organization').values(org).execute() + await getKysely() + .with('Org', (qc) => qc.insertInto('Organization').values(org)) + .insertInto('OrganizationUser') + .values(orgUsers) + .execute() await r.table('OrganizationUser').insert(orgUsers).run() return orgId } @@ -89,21 +94,82 @@ beforeAll(async () => { } await pg.schema.createSchema(TEST_DB).ifNotExists().execute() await r.dbCreate(TEST_DB).run() - await createPGTables('Organization', 'User', 'FreemailDomain', 'SAML', 'SAMLDomain') + await createPGTables( + 'Organization', + 'User', + 'FreemailDomain', + 'SAML', + 'SAMLDomain', + 'OrganizationUser' + ) await createTables('OrganizationUser') }) afterEach(async () => { - await truncatePGTables('Organization', 'User') + await truncatePGTables('Organization', 'User', 'OrganizationUser') await r.table('OrganizationUser').delete().run() }) afterAll(async () => { await r.getPoolMaster()?.drain() + await getKysely().destroy() getRedis().quit() }) test('Only the biggest org with verified emails qualify', async () => { + await addUsers([ + { + id: 'founder1', + email: 'user1@parabol.co', + picture: '', + preferredName: 'user1', + identities: [ + { + isEmailVerified: true + } + ] + }, + { + id: 'founder2', + email: 'user2@parabol.co', + picture: '', + preferredName: 'user2', + identities: [ + { + isEmailVerified: true + } + ] + }, + { + id: 'founder3', + email: 'user3@parabol.co', + picture: '', + preferredName: 'user3', + identities: [ + { + isEmailVerified: false + } + ] + }, + { + id: 'member1', + email: 'member1@parabol.co', + picture: '', + preferredName: '' + }, + { + id: 'member2', + email: 'member2@parabol.co', + picture: '', + preferredName: '' + }, + { + id: 'member3', + email: 'member3@parabol.co', + picture: '', + preferredName: '' + } + ]) await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), @@ -141,7 +207,13 @@ test('Only the biggest org with verified emails qualify', async () => { userId: 'member3' } ]) - addUsers([ + const dataLoader = new RootDataLoader() + const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) + expect(orgIds).toIncludeSameMembers([biggerOrg]) +}) + +test('All the biggest orgs with verified emails qualify', async () => { + await addUsers([ { id: 'founder1', email: 'user1@parabol.co', @@ -176,12 +248,6 @@ test('Only the biggest org with verified emails qualify', async () => { ] } ]) - const dataLoader = new RootDataLoader() - const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(orgIds).toIncludeSameMembers([biggerOrg]) -}) - -test('All the biggest orgs with verified emails qualify', async () => { const org1 = await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), @@ -215,6 +281,13 @@ test('All the biggest orgs with verified emails qualify', async () => { userId: 'member3' } ]) + + const dataLoader = new RootDataLoader() + const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) + expect(orgIds).toIncludeSameMembers([org1, org2]) +}) + +test('Team trumps starter tier with more users org', async () => { await addUsers([ { id: 'founder1', @@ -250,13 +323,6 @@ test('All the biggest orgs with verified emails qualify', async () => { ] } ]) - - const dataLoader = new RootDataLoader() - const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(orgIds).toIncludeSameMembers([org1, org2]) -}) - -test('Team trumps starter tier with more users org', async () => { const teamOrg = await addOrg( 'parabol.co', [ @@ -299,6 +365,12 @@ test('Team trumps starter tier with more users org', async () => { } ]) + const dataLoader = new RootDataLoader() + const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) + expect(orgIds).toIncludeSameMembers([teamOrg]) +}) + +test('Enterprise trumps team tier with more users org', async () => { await addUsers([ { id: 'founder1', @@ -334,12 +406,6 @@ test('Team trumps starter tier with more users org', async () => { ] } ]) - const dataLoader = new RootDataLoader() - const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(orgIds).toIncludeSameMembers([teamOrg]) -}) - -test('Enterprise trumps team tier with more users org', async () => { const enterpriseOrg = await addOrg( 'parabol.co', [ @@ -386,10 +452,16 @@ test('Enterprise trumps team tier with more users org', async () => { } ]) + const dataLoader = new RootDataLoader() + const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) + expect(orgIds).toIncludeSameMembers([enterpriseOrg]) +}) + +test('Orgs with verified emails from different domains do not qualify', async () => { await addUsers([ { id: 'founder1', - email: 'user1@parabol.co', + email: 'user1@parabol.fun', picture: '', preferredName: 'user1', identities: [ @@ -397,36 +469,9 @@ test('Enterprise trumps team tier with more users org', async () => { isEmailVerified: true } ] - }, - { - id: 'founder2', - email: 'user2@parabol.co', - picture: '', - preferredName: 'user2', - identities: [ - { - isEmailVerified: true - } - ] - }, - { - id: 'founder3', - email: 'user3@parabol.co', - picture: '', - preferredName: 'user3', - identities: [ - { - isEmailVerified: false - } - ] } ]) - const dataLoader = new RootDataLoader() - const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) - expect(orgIds).toIncludeSameMembers([enterpriseOrg]) -}) -test('Orgs with verified emails from different domains do not qualify', async () => { await addOrg('parabol.co', [ { joinedAt: new Date('2023-09-06'), @@ -438,44 +483,12 @@ test('Orgs with verified emails from different domains do not qualify', async () } ]) - await addUsers([ - { - id: 'founder1', - email: 'user1@parabol.fun', - picture: '', - preferredName: 'user1', - identities: [ - { - isEmailVerified: true - } - ] - } - ]) - const dataLoader = new RootDataLoader() const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) expect(orgIds).toIncludeSameMembers([]) }) test('Orgs with at least 1 verified billing lead with correct email qualify', async () => { - const org1 = await addOrg('parabol.co', [ - { - joinedAt: new Date('2023-09-06'), - userId: 'user1', - role: 'BILLING_LEADER' - }, - { - joinedAt: new Date('2023-09-07'), - userId: 'user2', - role: 'BILLING_LEADER' - }, - { - joinedAt: new Date('2023-09-08'), - userId: 'user3', - role: 'BILLING_LEADER' - } - ]) - await addUsers([ { id: 'user1', @@ -511,6 +524,23 @@ test('Orgs with at least 1 verified billing lead with correct email qualify', as ] } ]) + const org1 = await addOrg('parabol.co', [ + { + joinedAt: new Date('2023-09-06'), + userId: 'user1', + role: 'BILLING_LEADER' + }, + { + joinedAt: new Date('2023-09-07'), + userId: 'user2', + role: 'BILLING_LEADER' + }, + { + joinedAt: new Date('2023-09-08'), + userId: 'user3', + role: 'BILLING_LEADER' + } + ]) const dataLoader = new RootDataLoader() const orgIds = await getEligibleOrgIdsByDomain('parabol.co', 'newUser', dataLoader) diff --git a/packages/server/utils/authorization.ts b/packages/server/utils/authorization.ts index bcfb150ce19..b2c0f0b17f6 100644 --- a/packages/server/utils/authorization.ts +++ b/packages/server/utils/authorization.ts @@ -1,8 +1,7 @@ import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import getRethink from '../database/rethinkDriver' -import {RDatum} from '../database/stricterR' import AuthToken from '../database/types/AuthToken' -import OrganizationUser, {OrgUserRole} from '../database/types/OrganizationUser' +import {OrgUserRole} from '../database/types/OrganizationUser' import {DataLoaderWorker} from '../graphql/graphql' export const getUserId = (authToken: any) => { @@ -90,26 +89,3 @@ export const isUserInOrg = async (userId: string, orgId: string, dataLoader: Dat .load({userId, orgId}) return !!organizationUser } - -export const isOrgLeaderOfUser = async (authToken: AuthToken, userId: string) => { - const r = await getRethink() - const viewerId = getUserId(authToken) - const {viewerOrgIds, userOrgIds} = await r({ - viewerOrgIds: r - .table('OrganizationUser') - .getAll(viewerId, {index: 'userId'}) - .filter({removedAt: null}) - .filter((row: RDatum) => r.expr(['BILLING_LEADER', 'ORG_ADMIN']).contains(row('role')))( - 'orgId' - ) - .coerceTo('array') as any as OrganizationUser[], - userOrgIds: r - .table('OrganizationUser') - .getAll(userId, {index: 'userId'}) - .filter({removedAt: null})('orgId') - .coerceTo('array') as any as OrganizationUser[] - }).run() - const uniques = new Set(viewerOrgIds.concat(userOrgIds)) - const total = viewerOrgIds.length + userOrgIds.length - return uniques.size < total -} diff --git a/packages/server/utils/getActiveDomainForOrgId.ts b/packages/server/utils/getActiveDomainForOrgId.ts index 9f5c9f1b7ea..51b8a27a217 100644 --- a/packages/server/utils/getActiveDomainForOrgId.ts +++ b/packages/server/utils/getActiveDomainForOrgId.ts @@ -1,18 +1,13 @@ -import getRethink from '../database/rethinkDriver' +import {DataLoaderInstance} from '../dataloader/RootDataLoader' import getKysely from '../postgres/getKysely' - /** * Most used company domain for a given orgId */ -const getActiveDomainForOrgId = async (orgId: string) => { - const r = await getRethink() - const pg = getKysely() - const userIds = await r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({removedAt: null})('userId') - .run() +const getActiveDomainForOrgId = async (orgId: string, dataLoader: DataLoaderInstance) => { + const pg = getKysely() + const orgUsers = await dataLoader.get('organizationUsersByOrgId').load(orgId) + const userIds = orgUsers.map(({userId}) => userId) const activeDomain = await pg .selectFrom('User') diff --git a/packages/server/utils/setTierForOrgUsers.ts b/packages/server/utils/setTierForOrgUsers.ts index 0983faae06d..8131dd85f1b 100644 --- a/packages/server/utils/setTierForOrgUsers.ts +++ b/packages/server/utils/setTierForOrgUsers.ts @@ -12,13 +12,19 @@ import getKysely from '../postgres/getKysely' const setTierForOrgUsers = async (orgId: string) => { const r = await getRethink() + const pg = getKysely() const organization = await getKysely() .selectFrom('Organization') .select(['trialStartDate', 'tier']) .where('id', '=', orgId) .executeTakeFirstOrThrow() const {tier, trialStartDate} = organization - + await pg + .updateTable('OrganizationUser') + .set({tier, trialStartDate}) + .where('orgId', '=', orgId) + .where('removedAt', 'is', null) + .execute() await r .table('OrganizationUser') .getAll(orgId, {index: 'orgId'}) diff --git a/packages/server/utils/setUserTierForOrgId.ts b/packages/server/utils/setUserTierForOrgId.ts index 65d7470ed54..23e1480070a 100644 --- a/packages/server/utils/setUserTierForOrgId.ts +++ b/packages/server/utils/setUserTierForOrgId.ts @@ -1,8 +1,17 @@ import getRethink from '../database/rethinkDriver' +import getKysely from '../postgres/getKysely' import setUserTierForUserIds from './setUserTierForUserIds' const setUserTierForOrgId = async (orgId: string) => { const r = await getRethink() + const pg = getKysely() + const _userIds = await pg + .selectFrom('OrganizationUser') + .select('userId') + .where('orgId', '=', orgId) + .where('removedAt', 'is', null) + .execute() + console.log({_userIds}) const userIds = await r .table('OrganizationUser') .getAll(orgId, {index: 'orgId'}) diff --git a/packages/server/utils/setUserTierForUserIds.ts b/packages/server/utils/setUserTierForUserIds.ts index 6b22fff26a7..2e1c9dae552 100644 --- a/packages/server/utils/setUserTierForUserIds.ts +++ b/packages/server/utils/setUserTierForUserIds.ts @@ -12,7 +12,13 @@ import {analytics} from './analytics/analytics' const setUserTierForUserId = async (userId: string) => { const r = await getRethink() const pg = getKysely() - + const _orgUsers = await pg + .selectFrom('OrganizationUser') + .selectAll() + .where('userId', '=', userId) + .where('removedAt', 'is', null) + .execute() + console.log({_orgUsers}) const orgUsers = await r .table('OrganizationUser') .getAll(userId, {index: 'userId'}) diff --git a/scripts/toolboxSrc/setIsEnterprise.ts b/scripts/toolboxSrc/setIsEnterprise.ts index 53e9ebf370b..9fe23b5cfeb 100644 --- a/scripts/toolboxSrc/setIsEnterprise.ts +++ b/scripts/toolboxSrc/setIsEnterprise.ts @@ -1,6 +1,4 @@ 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() { @@ -10,43 +8,17 @@ export default async function setIsEnterprise() { ) } - const r = await getRethink() - - console.log( - 'Updating tier to "enterprise" for Organization and OrganizationUser tables in RethinkDB' - ) - - await getKysely().updateTable('Organization').set({tier: 'enterprise'}).execute() - await r - .table('OrganizationUser') - .update({ - tier: 'enterprise' - }) - .run() - - const pg = getPg() - - const updateUserPromise = pg - .query(`UPDATE "User" SET tier = 'enterprise' RETURNING id`) - .then((res) => { - console.log('Updated User tier to enterprise for:', res.rows.length, 'records in PostgreSQL.') - return res - }) - - const updateTeamPromise = pg - .query(`UPDATE "Team" SET tier = 'enterprise' RETURNING id`) - .then((res) => { - console.log('Updated Team tier to enterprise for:', res.rows.length, 'records in PostgreSQL.') - return res - }) - - const pgPromises = [updateUserPromise, updateTeamPromise] - - await Promise.all(pgPromises) + const pg = getKysely() + await Promise.all([ + pg.updateTable('Organization').set({tier: 'enterprise'}).execute(), + pg.updateTable('OrganizationUser').set({tier: 'enterprise'}).execute(), + pg.updateTable('User').set({tier: 'enterprise'}).execute(), + pg.updateTable('Team').set({tier: 'enterprise'}).execute() + ]) console.log('Finished updating tiers.') - await pg.end() + await pg.destroy() process.exit() } From 97bfc0fad786577534c6519ef4654d3e587e228f Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Thu, 11 Jul 2024 18:01:06 +0200 Subject: [PATCH 324/529] chore: Make meeting series naming consistent (#9928) --- .../ActivityDetailsSidebar.tsx | 27 +++------ .../ActivityLibrary/ScheduleMeetingButton.tsx | 18 ++---- .../Recurrence/EndRecurringMeetingModal.tsx | 2 +- .../Recurrence/RecurrenceSettings.tsx | 2 +- .../UpdateRecurrenceSettingsModal.tsx | 8 +-- packages/client/components/ScheduleDialog.tsx | 24 ++++---- .../mutations/StartRetrospectiveMutation.ts | 9 +-- .../mutations/StartTeamPromptMutation.ts | 9 +-- .../UpdateRecurrenceSettingsMutation.ts | 7 +-- .../__tests__/startRetrospective.test.ts | 58 ++++++++++++++++--- .../server/database/types/MeetingAction.ts | 4 +- .../server/database/types/MeetingPoker.ts | 4 +- .../server/dataloader/customLoaderMakers.ts | 24 ++++++++ .../mutations/helpers/createGcalEvent.ts | 13 +++-- .../helpers/safeCreateRetrospective.ts | 15 +---- .../graphql/mutations/startSprintPoker.ts | 22 ++++++- .../graphql/public/mutations/startCheckIn.ts | 12 +++- .../public/mutations/startRetrospective.ts | 31 +++++----- .../public/mutations/startTeamPrompt.ts | 26 ++++----- .../mutations/updateRecurrenceSettings.ts | 27 ++++----- .../typeDefs/CreateGcalEventInput.graphql | 22 +++++++ .../typeDefs/RecurrenceSettingsInput.graphql | 14 ----- .../graphql/public/typeDefs/_legacy.graphql | 4 ++ .../public/typeDefs/startCheckIn.graphql | 4 ++ .../typeDefs/startRetrospective.graphql | 35 ++--------- .../public/typeDefs/startTeamPrompt.graphql | 35 ++--------- .../typeDefs/updateRecurrenceSettings.graphql | 9 ++- .../public/types/CreateGcalEventInput.ts | 9 +-- 28 files changed, 244 insertions(+), 230 deletions(-) create mode 100644 packages/server/graphql/public/typeDefs/CreateGcalEventInput.graphql delete mode 100644 packages/server/graphql/public/typeDefs/RecurrenceSettingsInput.graphql diff --git a/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx b/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx index 3d688e42d93..65f2f386017 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetailsSidebar.tsx @@ -5,16 +5,14 @@ import clsx from 'clsx' import React, {useEffect, useRef, useState} from 'react' import {useFragment} from 'react-relay' import {useHistory} from 'react-router' +import {RRule} from 'rrule' import {ActivityDetailsSidebar_teams$key} from '~/__generated__/ActivityDetailsSidebar_teams.graphql' import {ActivityDetailsSidebar_template$key} from '~/__generated__/ActivityDetailsSidebar_template.graphql' import StartRetrospectiveMutation from '~/mutations/StartRetrospectiveMutation' import StartSprintPokerMutation from '~/mutations/StartSprintPokerMutation' import UpdateReflectTemplateScopeMutation from '~/mutations/UpdateReflectTemplateScopeMutation' import {MeetingTypeEnum} from '../../__generated__/ActivityDetailsQuery.graphql' -import { - CreateGcalEventInput, - RecurrenceSettingsInput -} from '../../__generated__/StartRetrospectiveMutation.graphql' +import {CreateGcalEventInput} from '../../__generated__/StartRetrospectiveMutation.graphql' import useAtmosphere from '../../hooks/useAtmosphere' import useMutationProps from '../../hooks/useMutationProps' import SelectTemplateMutation from '../../mutations/SelectTemplateMutation' @@ -116,10 +114,7 @@ const ActivityDetailsSidebar = (props: Props) => { const {onError, onCompleted, submitting, submitMutation, error} = mutationProps const history = useHistory() - const handleStartActivity = ( - gcalInput?: CreateGcalEventInput, - recurrenceSettings?: RecurrenceSettingsInput - ) => { + const handleStartActivity = (name?: string, rrule?: RRule, gcalInput?: CreateGcalEventInput) => { if (submitting) return submitMutation() if (type === 'teamPrompt') { @@ -127,12 +122,8 @@ const ActivityDetailsSidebar = (props: Props) => { atmosphere, { teamId: selectedTeam.id, - recurrenceSettings: recurrenceSettings - ? { - rrule: recurrenceSettings.rrule?.toString(), - name: recurrenceSettings.name - } - : undefined, + name, + rrule: rrule?.toString(), gcalInput }, {history, onError, onCompleted} @@ -155,12 +146,8 @@ const ActivityDetailsSidebar = (props: Props) => { atmosphere, { teamId: selectedTeam.id, - recurrenceSettings: recurrenceSettings - ? { - rrule: recurrenceSettings.rrule?.toString(), - name: recurrenceSettings.name - } - : undefined, + name, + rrule: rrule?.toString(), gcalInput }, {history, onError, onCompleted} diff --git a/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx b/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx index 3b66168331c..dda2754f057 100644 --- a/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx +++ b/packages/client/components/ActivityLibrary/ScheduleMeetingButton.tsx @@ -1,11 +1,9 @@ import graphql from 'babel-plugin-relay/macro' import React from 'react' import {useFragment} from 'react-relay' +import {RRule} from 'rrule' import {ScheduleMeetingButton_team$key} from '~/__generated__/ScheduleMeetingButton_team.graphql' -import { - CreateGcalEventInput, - RecurrenceSettingsInput -} from '../../__generated__/StartRetrospectiveMutation.graphql' +import {CreateGcalEventInput} from '../../__generated__/StartRetrospectiveMutation.graphql' import useModal from '../../hooks/useModal' import {MenuMutationProps} from '../../hooks/useMutationProps' import DialogContainer from '../DialogContainer' @@ -14,10 +12,7 @@ import SecondaryButton from '../SecondaryButton' type Props = { mutationProps: MenuMutationProps - handleStartActivity: ( - gcalInput?: CreateGcalEventInput, - recurrenceInput?: RecurrenceSettingsInput - ) => void + handleStartActivity: (name?: string, rrule?: RRule, gcalInput?: CreateGcalEventInput) => void teamRef: ScheduleMeetingButton_team$key placeholder: string withRecurrence?: boolean @@ -60,11 +55,8 @@ const ScheduleMeetingButton = (props: Props) => { const handleClick = () => { toggleModal() } - const onStartActivity = ( - gcalInput?: CreateGcalEventInput, - recurrenceInput?: RecurrenceSettingsInput - ) => { - handleStartActivity(gcalInput, recurrenceInput) + const onStartActivity = (name?: string, rrule?: RRule, gcalInput?: CreateGcalEventInput) => { + handleStartActivity(name, rrule, gcalInput) closeModal() } diff --git a/packages/client/components/Recurrence/EndRecurringMeetingModal.tsx b/packages/client/components/Recurrence/EndRecurringMeetingModal.tsx index 49c409083d9..2165e45b31f 100644 --- a/packages/client/components/Recurrence/EndRecurringMeetingModal.tsx +++ b/packages/client/components/Recurrence/EndRecurringMeetingModal.tsx @@ -86,7 +86,7 @@ export const EndRecurringMeetingModal = (props: Props) => { if (!isMeetingOnly) { UpdateRecurrenceSettingsMutation( atmosphere, - {meetingId, recurrenceSettings: {name: null, rrule: null}}, + {meetingId, name: null, rrule: null}, {onError, onCompleted} ) } else { diff --git a/packages/client/components/Recurrence/RecurrenceSettings.tsx b/packages/client/components/Recurrence/RecurrenceSettings.tsx index a1e039e913a..f5cca8d6f43 100644 --- a/packages/client/components/Recurrence/RecurrenceSettings.tsx +++ b/packages/client/components/Recurrence/RecurrenceSettings.tsx @@ -255,7 +255,7 @@ export const RecurrenceSettings = (props: Props) => { The next meeting in this series will be called{' '} - "{title} - {dayjs(recurrenceStartTime).format('MMM DD')}" + "{title} - {dayjs(new Date()).format('MMM DD')}" )} diff --git a/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx b/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx index da69cf7f869..63c17f230f6 100644 --- a/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx +++ b/packages/client/components/Recurrence/UpdateRecurrenceSettingsModal.tsx @@ -175,10 +175,8 @@ export const UpdateRecurrenceSettingsModal = (props: Props) => { atmosphere, { meetingId: meeting.id, - recurrenceSettings: { - rrule: rrule?.toString(), - name: title - } + rrule: rrule?.toString(), + name: title }, {onError, onCompleted: onRecurrenceSettingsUpdated} ) @@ -190,7 +188,7 @@ export const UpdateRecurrenceSettingsModal = (props: Props) => { UpdateRecurrenceSettingsMutation( atmosphere, - {meetingId: meeting.id, recurrenceSettings: {rrule: null}}, + {meetingId: meeting.id, rrule: null}, {onError, onCompleted: onRecurrenceSettingsUpdated} ) } diff --git a/packages/client/components/ScheduleDialog.tsx b/packages/client/components/ScheduleDialog.tsx index 8f26e708164..351f242064f 100644 --- a/packages/client/components/ScheduleDialog.tsx +++ b/packages/client/components/ScheduleDialog.tsx @@ -7,10 +7,7 @@ import React, {ChangeEvent, useState} from 'react' import {useFragment} from 'react-relay' import {RRule} from 'rrule' import {ScheduleDialog_team$key} from '~/__generated__/ScheduleDialog_team.graphql' -import { - CreateGcalEventInput, - RecurrenceSettingsInput -} from '../__generated__/StartRetrospectiveMutation.graphql' +import {CreateGcalEventInput} from '../__generated__/StartRetrospectiveMutation.graphql' import useAtmosphere from '../hooks/useAtmosphere' import useForm from '../hooks/useForm' import {MenuMutationProps} from '../hooks/useMutationProps' @@ -34,7 +31,7 @@ const validateTitle = (title: string) => new Legitity(title).trim().min(2, `C’mon, you call that a title?`) interface Props { - onStartActivity: (gcalInput?: CreateGcalEventInput, recurrence?: RecurrenceSettingsInput) => void + onStartActivity: (name?: string, rrule?: RRule, gcalInput?: CreateGcalEventInput) => void placeholder: string teamRef: ScheduleDialog_team$key onCancel: () => void @@ -101,16 +98,15 @@ export const ScheduleDialog = (props: Props) => { } const handleSubmit = () => { - const title = fields.title.value || placeholder - const titleRes = validateTitle(title) - if (titleRes.error) { - fields.title.setError(titleRes.error) + const name = fields.title.value || placeholder + const nameRes = validateTitle(name) + if (nameRes.error) { + fields.title.setError(nameRes.error) return } const gcalEventInput = addedInvite ? { - title, startTimestamp: gcalInput.start.unix(), endTimestamp: gcalInput.end.unix(), timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, @@ -118,7 +114,7 @@ export const ScheduleDialog = (props: Props) => { videoType: gcalInput.videoType ?? undefined } : undefined - props.onStartActivity(gcalEventInput, rrule ? {rrule} : undefined) + props.onStartActivity(name, rrule ?? undefined, gcalEventInput) } const onAddInvite = () => { @@ -201,7 +197,11 @@ export const ScheduleDialog = (props: Props) => { - + )} diff --git a/packages/client/mutations/StartRetrospectiveMutation.ts b/packages/client/mutations/StartRetrospectiveMutation.ts index ec3a120f8b9..5e60fc72048 100644 --- a/packages/client/mutations/StartRetrospectiveMutation.ts +++ b/packages/client/mutations/StartRetrospectiveMutation.ts @@ -25,14 +25,11 @@ graphql` const mutation = graphql` mutation StartRetrospectiveMutation( $teamId: ID! - $recurrenceSettings: RecurrenceSettingsInput + $name: String + $rrule: RRule $gcalInput: CreateGcalEventInput ) { - startRetrospective( - teamId: $teamId - recurrenceSettings: $recurrenceSettings - gcalInput: $gcalInput - ) { + startRetrospective(teamId: $teamId, name: $name, rrule: $rrule, gcalInput: $gcalInput) { ... on ErrorPayload { error { message diff --git a/packages/client/mutations/StartTeamPromptMutation.ts b/packages/client/mutations/StartTeamPromptMutation.ts index bc8821c3bd7..7320d2b7094 100644 --- a/packages/client/mutations/StartTeamPromptMutation.ts +++ b/packages/client/mutations/StartTeamPromptMutation.ts @@ -19,14 +19,11 @@ graphql` const mutation = graphql` mutation StartTeamPromptMutation( $teamId: ID! - $recurrenceSettings: RecurrenceSettingsInput + $name: String + $rrule: RRule $gcalInput: CreateGcalEventInput ) { - startTeamPrompt( - teamId: $teamId - recurrenceSettings: $recurrenceSettings - gcalInput: $gcalInput - ) { + startTeamPrompt(teamId: $teamId, name: $name, rrule: $rrule, gcalInput: $gcalInput) { ... on ErrorPayload { error { message diff --git a/packages/client/mutations/UpdateRecurrenceSettingsMutation.ts b/packages/client/mutations/UpdateRecurrenceSettingsMutation.ts index af0168c01ea..8646e886116 100644 --- a/packages/client/mutations/UpdateRecurrenceSettingsMutation.ts +++ b/packages/client/mutations/UpdateRecurrenceSettingsMutation.ts @@ -21,11 +21,8 @@ graphql` ` const mutation = graphql` - mutation UpdateRecurrenceSettingsMutation( - $meetingId: ID! - $recurrenceSettings: RecurrenceSettingsInput! - ) { - updateRecurrenceSettings(meetingId: $meetingId, recurrenceSettings: $recurrenceSettings) { + mutation UpdateRecurrenceSettingsMutation($meetingId: ID!, $name: String, $rrule: RRule) { + updateRecurrenceSettings(meetingId: $meetingId, name: $name, rrule: $rrule) { ... on ErrorPayload { error { message diff --git a/packages/server/__tests__/startRetrospective.test.ts b/packages/server/__tests__/startRetrospective.test.ts index 17deae321e5..2dc04eb1bd0 100644 --- a/packages/server/__tests__/startRetrospective.test.ts +++ b/packages/server/__tests__/startRetrospective.test.ts @@ -8,8 +8,8 @@ test('Retro is named Retro #1 by default', async () => { const newRetro = await sendPublic({ query: ` - mutation StartRetrospectiveMutation($teamId: ID!, $recurrenceSettings: RecurrenceSettingsInput, $gcalInput: CreateGcalEventInput) { - startRetrospective(teamId: $teamId, recurrenceSettings: $recurrenceSettings, gcalInput: $gcalInput) { + mutation StartRetrospectiveMutation($teamId: ID!, $name: String, $rrule: RRule, $gcalInput: CreateGcalEventInput) { + startRetrospective(teamId: $teamId, name: $name, rrule: $rrule, gcalInput: $gcalInput) { ... on ErrorPayload { error { message @@ -34,7 +34,49 @@ test('Retro is named Retro #1 by default', async () => { startRetrospective: { meeting: { id: expect.anything(), - name: 'Retro 1' + name: 'Retro #1' + } + } + } + }) +}) + +test('Single Retro can be named', async () => { + await getRethink() + const {userId, authToken} = await signUp() + const {id: teamId} = (await getUserTeams(userId))[0] + + const name = 'My Retro' + const newRetro = await sendPublic({ + query: ` + mutation StartRetrospectiveMutation($teamId: ID!, $name: String, $rrule: RRule, $gcalInput: CreateGcalEventInput) { + startRetrospective(teamId: $teamId, name: $name, rrule: $rrule, gcalInput: $gcalInput) { + ... on ErrorPayload { + error { + message + } + } + ... on StartRetrospectiveSuccess { + meeting { + id + name + } + } + } + } + `, + variables: { + teamId, + name + }, + authToken + }) + expect(newRetro).toMatchObject({ + data: { + startRetrospective: { + meeting: { + id: expect.anything(), + name } } } @@ -49,8 +91,8 @@ test('Recurring retro is named like RetroSeries Jan 1', async () => { const now = new Date() const newRetro = await sendPublic({ query: ` - mutation StartRetrospectiveMutation($teamId: ID!, $recurrenceSettings: RecurrenceSettingsInput, $gcalInput: CreateGcalEventInput) { - startRetrospective(teamId: $teamId, recurrenceSettings: $recurrenceSettings, gcalInput: $gcalInput) { + mutation StartRetrospectiveMutation($teamId: ID!, $name: String, $rrule: RRule, $gcalInput: CreateGcalEventInput) { + startRetrospective(teamId: $teamId, name: $name, rrule: $rrule, gcalInput: $gcalInput) { ... on ErrorPayload { error { message @@ -67,10 +109,8 @@ test('Recurring retro is named like RetroSeries Jan 1', async () => { `, variables: { teamId, - recurrenceSettings: { - rrule: 'DTSTART;TZID=Europe/Berlin:20240117T060000\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=WE', - name: 'RetroSeries' - } + rrule: 'DTSTART;TZID=Europe/Berlin:20240117T060000\nRRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=WE', + name: 'RetroSeries' }, authToken }) diff --git a/packages/server/database/types/MeetingAction.ts b/packages/server/database/types/MeetingAction.ts index bc0157cdd16..fee9b580b8d 100644 --- a/packages/server/database/types/MeetingAction.ts +++ b/packages/server/database/types/MeetingAction.ts @@ -9,7 +9,7 @@ interface Input { id?: string teamId: string meetingCount: number - name?: string + name: string phases: [CheckInMeetingPhase, ...CheckInMeetingPhase[]] facilitatorUserId: string } @@ -32,7 +32,7 @@ export default class MeetingAction extends Meeting { phases, facilitatorUserId, meetingType: 'action', - name: name ?? `Check-in #${meetingCount + 1}` + name }) } } diff --git a/packages/server/database/types/MeetingPoker.ts b/packages/server/database/types/MeetingPoker.ts index 94c87c2826a..5883fd6d8ac 100644 --- a/packages/server/database/types/MeetingPoker.ts +++ b/packages/server/database/types/MeetingPoker.ts @@ -8,7 +8,7 @@ interface Input { id: string teamId: string meetingCount: number - name?: string + name: string phases: [PokerPhase, ...PokerPhase[]] facilitatorUserId: string templateId: string @@ -35,7 +35,7 @@ export default class MeetingPoker extends Meeting { phases, facilitatorUserId, meetingType: 'poker', - name: name ?? `Sprint Poker #${meetingCount + 1}` + name }) this.templateId = templateId this.templateRefId = templateRefId diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 46a03715026..4de0beb1abb 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -858,3 +858,27 @@ export const fileStoreAsset = (parent: RootDataLoader) => { } ) } + +export const meetingCount = (parent: RootDataLoader) => { + return new DataLoader<{teamId: string; meetingType: MeetingTypeEnum}, number, string>( + async (keys) => { + const r = await getRethink() + const res = await Promise.all( + keys.map(async ({teamId, meetingType}) => { + return r + .table('NewMeeting') + .getAll(teamId, {index: 'teamId'}) + .filter({meetingType: meetingType as any}) + .count() + .default(0) + .run() + }) + ) + return res + }, + { + ...parent.dataLoaderOptions, + cacheKeyFn: (key) => `${key.teamId}:${key.meetingType}` + } + ) +} diff --git a/packages/server/graphql/mutations/helpers/createGcalEvent.ts b/packages/server/graphql/mutations/helpers/createGcalEvent.ts index 575fc9e4cdd..8aaa7efeddd 100644 --- a/packages/server/graphql/mutations/helpers/createGcalEvent.ts +++ b/packages/server/graphql/mutations/helpers/createGcalEvent.ts @@ -24,6 +24,7 @@ const convertRruleToGcal = (rrule: RRule | null | undefined) => { } type Input = { + name: string gcalInput?: CreateGcalEventInput | null meetingId: string viewerId: string @@ -35,12 +36,12 @@ type Input = { const createGcalEvent = async ( input: Input ): Promise<{gcalSeriesId?: string; error?: StandardMutationError}> => { - const {gcalInput, meetingId, viewerId, dataLoader, teamId, rrule} = input + const {name, gcalInput, meetingId, viewerId, dataLoader, teamId, rrule} = input if (!gcalInput) { return {} } - const {startTimestamp, endTimestamp, title, timeZone, invitees, videoType} = gcalInput + const {startTimestamp, endTimestamp, timeZone, invitees, videoType} = gcalInput const gcalAuth = await dataLoader.get('freshGcalAuth').load({teamId, userId: viewerId}) if (!gcalAuth) { @@ -78,7 +79,7 @@ const createGcalEvent = async ( const recurrence = convertRruleToGcal(rrule) const eventInput = { - summary: title, + summary: name, description, start: { dateTime: startDateTime, @@ -115,14 +116,14 @@ const createGcalEvent = async ( export type UpdateGcalSeriesInput = { gcalSeriesId: string - title?: string + name?: string rrule: RRule | null userId: string teamId: string dataLoader: DataLoaderWorker } export const updateGcalSeries = async (input: UpdateGcalSeriesInput) => { - const {gcalSeriesId, title, rrule, userId, teamId, dataLoader} = input + const {gcalSeriesId, name, rrule, userId, teamId, dataLoader} = input const gcalAuth = await dataLoader.get('freshGcalAuth').load({teamId, userId}) if (!gcalAuth) { @@ -146,7 +147,7 @@ export const updateGcalSeries = async (input: UpdateGcalSeriesInput) => { eventId: gcalSeriesId, requestBody: { recurrence, - summary: title + summary: name }, conferenceDataVersion: 1 }) diff --git a/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts b/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts index 221557c3b09..a756463e106 100644 --- a/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts @@ -1,4 +1,3 @@ -import getRethink from '../../../database/rethinkDriver' import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import generateUID from '../../../generateUID' import {MeetingTypeEnum} from '../../../postgres/types/Meeting' @@ -16,21 +15,14 @@ const safeCreateRetrospective = async ( videoMeetingURL?: string meetingSeriesId?: number scheduledEndTime?: Date - name?: string + name: string }, dataLoader: DataLoaderWorker ) => { - const r = await getRethink() const {teamId, facilitatorUserId, name} = meetingSettings const meetingType: MeetingTypeEnum = 'retrospective' const [meetingCount, team] = await Promise.all([ - r - .table('NewMeeting') - .getAll(teamId, {index: 'teamId'}) - .filter({meetingType}) - .count() - .default(0) - .run(), + dataLoader.get('meetingCount').load({teamId, meetingType}), dataLoader.get('teams').loadNonNull(teamId) ]) @@ -38,7 +30,6 @@ const safeCreateRetrospective = async ( const {showConversionModal} = organization const meetingId = generateUID() - const meetingName = name ?? `Retro ${meetingCount + 1}` const phases = await createNewMeetingPhases( facilitatorUserId, teamId, @@ -54,7 +45,7 @@ const safeCreateRetrospective = async ( phases, showConversionModal, ...meetingSettings, - name: meetingName + name }) } diff --git a/packages/server/graphql/mutations/startSprintPoker.ts b/packages/server/graphql/mutations/startSprintPoker.ts index 0c5388ba834..b25cc5364de 100644 --- a/packages/server/graphql/mutations/startSprintPoker.ts +++ b/packages/server/graphql/mutations/startSprintPoker.ts @@ -1,4 +1,4 @@ -import {GraphQLID, GraphQLNonNull} from 'graphql' +import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import toTeamMemberId from '../../../client/utils/relay/toTeamMemberId' import getRethink from '../../database/rethinkDriver' @@ -74,6 +74,10 @@ export default { type: new GraphQLNonNull(GraphQLID), description: 'The team starting the meeting' }, + name: { + type: GraphQLString, + description: 'The name of the meeting' + }, gcalInput: { type: CreateGcalEventInput, description: 'The gcal event to create. If not provided, no event will be created' @@ -81,7 +85,11 @@ export default { }, async resolve( _source: unknown, - {teamId, gcalInput}: {teamId: string; gcalInput?: CreateGcalEventInputType}, + { + teamId, + name, + gcalInput + }: {teamId: string; name: string | null | undefined; gcalInput?: CreateGcalEventInputType}, {authToken, socketId: mutatorId, dataLoader}: GQLContext ) { const r = await getRethink() @@ -128,6 +136,7 @@ export default { const meeting = new MeetingPoker({ id: meetingId, teamId, + name: name ?? `Sprint Poker #${meetingCount + 1}`, meetingCount, phases, facilitatorUserId: viewerId, @@ -175,7 +184,14 @@ export default { ]) IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting, template) - const {error} = await createGcalEvent({gcalInput, meetingId, teamId, viewerId, dataLoader}) + const {error} = await createGcalEvent({ + name: meeting.name, + gcalInput, + meetingId, + teamId, + viewerId, + dataLoader + }) const data = {teamId, meetingId: meetingId, hasGcalError: !!error?.message} publish(SubscriptionChannel.TEAM, teamId, 'StartSprintPokerSuccess', data, subOptions) return data diff --git a/packages/server/graphql/public/mutations/startCheckIn.ts b/packages/server/graphql/public/mutations/startCheckIn.ts index 9c5f10de62b..c411c3fcd9d 100644 --- a/packages/server/graphql/public/mutations/startCheckIn.ts +++ b/packages/server/graphql/public/mutations/startCheckIn.ts @@ -17,7 +17,7 @@ import {MutationResolvers} from '../resolverTypes' const startCheckIn: MutationResolvers['startCheckIn'] = async ( _source, - {teamId, gcalInput}, + {teamId, name, gcalInput}, context ) => { const r = await getRethink() @@ -59,6 +59,7 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( const meeting = new MeetingAction({ id: meetingId, teamId, + name: name ?? `Check-in #${meetingCount + 1}`, meetingCount, phases, facilitatorUserId: viewerId @@ -92,7 +93,14 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( ]) IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting) - const {error} = await createGcalEvent({gcalInput, teamId, meetingId, viewerId, dataLoader}) + const {error} = await createGcalEvent({ + name: meeting.name, + gcalInput, + teamId, + meetingId, + viewerId, + dataLoader + }) const data = {teamId, meetingId, hasGcalError: !!error?.message} publish(SubscriptionChannel.TEAM, teamId, 'StartCheckInSuccess', data, subOptions) return data diff --git a/packages/server/graphql/public/mutations/startRetrospective.ts b/packages/server/graphql/public/mutations/startRetrospective.ts index cc240ec1fb4..b4c7ba3ddad 100644 --- a/packages/server/graphql/public/mutations/startRetrospective.ts +++ b/packages/server/graphql/public/mutations/startRetrospective.ts @@ -20,7 +20,7 @@ import {startNewMeetingSeries} from './updateRecurrenceSettings' const startRetrospective: MutationResolvers['startRetrospective'] = async ( _source, - {teamId, recurrenceSettings, gcalInput}, + {teamId, name, rrule, gcalInput}, {authToken, socketId: mutatorId, dataLoader} ) => { const r = await getRethink() @@ -36,12 +36,14 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( if (unpaidError) return standardError(new Error(unpaidError), {userId: viewerId}) // RESOLUTION - const viewer = await dataLoader.get('users').loadNonNull(viewerId) - const meetingType: MeetingTypeEnum = 'retrospective' - const meetingSettings = (await dataLoader - .get('meetingSettingsByType') - .load({teamId, meetingType})) as MeetingSettingsRetrospective + const [viewer, meetingSettings, meetingCount] = await Promise.all([ + dataLoader.get('users').loadNonNull(viewerId), + dataLoader + .get('meetingSettingsByType') + .load({teamId, meetingType}) as Promise, + dataLoader.get('meetingCount').load({teamId, meetingType}) + ]) const { id: meetingSettingsId, @@ -52,9 +54,12 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( videoMeetingURL } = meetingSettings - const name = recurrenceSettings?.name - ? createMeetingSeriesTitle(recurrenceSettings.name, new Date(), 'UTC') - : undefined + const meetingName = !name + ? `Retro #${meetingCount + 1}` + : rrule + ? createMeetingSeriesTitle(name, new Date(), 'UTC') + : name + const meetingSeriesName = name || meetingName const meeting = await safeCreateRetrospective( { @@ -65,7 +70,7 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( disableAnonymity, templateId: selectedTemplateId, videoMeetingURL: videoMeetingURL ?? undefined, - name + name: meetingName }, dataLoader ) @@ -93,8 +98,7 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( lastMeetingType: meetingType } const [meetingSeries] = await Promise.all([ - recurrenceSettings?.rrule && - startNewMeetingSeries(meeting, recurrenceSettings.rrule, recurrenceSettings.name), + rrule && startNewMeetingSeries(meeting, rrule, meetingSeriesName), r .table('MeetingMember') .insert( @@ -120,11 +124,12 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting, template) const {error, gcalSeriesId} = await createGcalEvent({ + name: meetingSeriesName, gcalInput, meetingId, teamId, viewerId, - rrule: recurrenceSettings?.rrule, + rrule, dataLoader }) if (meetingSeries && gcalSeriesId) { diff --git a/packages/server/graphql/public/mutations/startTeamPrompt.ts b/packages/server/graphql/public/mutations/startTeamPrompt.ts index 75dcd21b975..393e802e1b7 100644 --- a/packages/server/graphql/public/mutations/startTeamPrompt.ts +++ b/packages/server/graphql/public/mutations/startTeamPrompt.ts @@ -18,7 +18,7 @@ const MEETING_START_DELAY_MS = 3000 const startTeamPrompt: MutationResolvers['startTeamPrompt'] = async ( _source, - {teamId, recurrenceSettings, gcalInput}, + {teamId, name, rrule, gcalInput}, {authToken, dataLoader, socketId: mutatorId} ) => { const r = await getRethink() @@ -46,11 +46,8 @@ const startTeamPrompt: MutationResolvers['startTeamPrompt'] = async ( } //TODO: use client timezone here (requires sending it from the client and passing it via gql context most likely) - const meetingName = createMeetingSeriesTitle( - recurrenceSettings?.name || 'Standup', - new Date(), - 'UTC' - ) + const meetingName = createMeetingSeriesTitle(name || 'Standup', new Date(), 'UTC') + const eventName = rrule ? name || 'Standup' : meetingName const meeting = await safeCreateTeamPrompt(meetingName, teamId, viewerId, r, dataLoader) await Promise.all([ @@ -64,19 +61,22 @@ const startTeamPrompt: MutationResolvers['startTeamPrompt'] = async ( ]) const {id: meetingId} = meeting - if (recurrenceSettings?.rrule) { - const meetingSeries = await startNewMeetingSeries( - meeting, - recurrenceSettings.rrule, - recurrenceSettings.name - ) + if (rrule) { + const meetingSeries = await startNewMeetingSeries(meeting, rrule, name) // meeting was modified if a new meeting series was created dataLoader.get('newMeetings').clear(meetingId) analytics.recurrenceStarted(viewer, meetingSeries) } IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting) - const {error} = await createGcalEvent({gcalInput, meetingId, teamId, viewerId, dataLoader}) + const {error} = await createGcalEvent({ + name: eventName, + gcalInput, + meetingId, + teamId, + viewerId, + dataLoader + }) const data = {teamId, meetingId: meetingId, hasGcalError: !!error?.message} publish(SubscriptionChannel.TEAM, teamId, 'StartTeamPromptSuccess', data, subOptions) return data diff --git a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts index d3ce01392c1..96f4f1b590d 100644 --- a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts +++ b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts @@ -124,7 +124,7 @@ const updateGCalRecurrenceRule = (oldRule: RRule, newRule: RRule | null | undefi const updateRecurrenceSettings: MutationResolvers['updateRecurrenceSettings'] = async ( _source, - {meetingId, recurrenceSettings}, + {meetingId, name, rrule}, {authToken, dataLoader, socketId: mutatorId} ) => { const viewerId = getUserId(authToken) @@ -152,46 +152,39 @@ const updateRecurrenceSettings: MutationResolvers['updateRecurrenceSettings'] = const meetingSeries = await dataLoader.get('meetingSeries').loadNonNull(meetingSeriesId) const {gcalSeriesId, teamId, facilitatorId, recurrenceRule} = meetingSeries - if (!recurrenceSettings.rrule) { + if (!rrule) { await stopMeetingSeries(meetingSeries) analytics.recurrenceStopped(viewer, meetingSeries) } else { - await updateMeetingSeries(meetingSeries, recurrenceSettings.rrule) + await updateMeetingSeries(meetingSeries, rrule) analytics.recurrenceStarted(viewer, meetingSeries) } if (gcalSeriesId) { - const rrule = updateGCalRecurrenceRule( - RRule.fromString(recurrenceRule), - recurrenceSettings.rrule - ) + const newRrule = updateGCalRecurrenceRule(RRule.fromString(recurrenceRule), rrule) await updateGcalSeries({ gcalSeriesId, - title: recurrenceSettings.name ?? undefined, - rrule, + name: name ?? undefined, + rrule: newRrule, teamId, userId: facilitatorId, dataLoader }) } - if (recurrenceSettings.name) { - await updateMeetingSeriesQuery({title: recurrenceSettings.name}, meetingSeries.id) + if (name) { + await updateMeetingSeriesQuery({title: name}, meetingSeries.id) } dataLoader.get('meetingSeries').clear(meetingSeries.id) } else { - if (!recurrenceSettings.rrule) { + if (!rrule) { return standardError( new Error('When meeting is not recurring, recurrence rule has to be provided'), {userId: viewerId} ) } - const newMeetingSeries = await startNewMeetingSeries( - meeting, - recurrenceSettings.rrule, - recurrenceSettings.name - ) + const newMeetingSeries = await startNewMeetingSeries(meeting, rrule, name) analytics.recurrenceStarted(viewer, newMeetingSeries) } diff --git a/packages/server/graphql/public/typeDefs/CreateGcalEventInput.graphql b/packages/server/graphql/public/typeDefs/CreateGcalEventInput.graphql new file mode 100644 index 00000000000..634041f5edd --- /dev/null +++ b/packages/server/graphql/public/typeDefs/CreateGcalEventInput.graphql @@ -0,0 +1,22 @@ +input CreateGcalEventInput { + """ + The start timestamp of the event + """ + startTimestamp: Int! + """ + The end timestamp of the event + """ + endTimestamp: Int! + """ + The timezone of the event + """ + timeZone: String! + """ + The type of video call to added to the gcal event. If not provided, no video call will be added + """ + videoType: GcalVideoTypeEnum + """ + The emails that will be invited to the gcal event. If not provided, the no one will be invited + """ + invitees: [Email!] +} diff --git a/packages/server/graphql/public/typeDefs/RecurrenceSettingsInput.graphql b/packages/server/graphql/public/typeDefs/RecurrenceSettingsInput.graphql deleted file mode 100644 index 04a67753120..00000000000 --- a/packages/server/graphql/public/typeDefs/RecurrenceSettingsInput.graphql +++ /dev/null @@ -1,14 +0,0 @@ -""" -Recurrence settings for a meeting series, used to create or update a meeting series -""" -input RecurrenceSettingsInput { - """ - The recurrence rule for the meeting series in RRULE format - """ - rrule: RRule - - """ - Meeting series name, by default "Standup" - """ - name: String -} diff --git a/packages/server/graphql/public/typeDefs/_legacy.graphql b/packages/server/graphql/public/typeDefs/_legacy.graphql index 04f64657889..d88cc2d86a6 100644 --- a/packages/server/graphql/public/typeDefs/_legacy.graphql +++ b/packages/server/graphql/public/typeDefs/_legacy.graphql @@ -5846,6 +5846,10 @@ type Mutation { """ teamId: ID! """ + The name of the meeting + """ + name: String + """ The gcal input if creating a gcal event """ gcalInput: CreateGcalEventInput diff --git a/packages/server/graphql/public/typeDefs/startCheckIn.graphql b/packages/server/graphql/public/typeDefs/startCheckIn.graphql index 35a8c0f4b69..ba6eb2e8cc9 100644 --- a/packages/server/graphql/public/typeDefs/startCheckIn.graphql +++ b/packages/server/graphql/public/typeDefs/startCheckIn.graphql @@ -20,6 +20,10 @@ extend type Mutation { """ teamId: ID! """ + The name of the meeting + """ + name: String + """ The gcal input if creating a gcal event """ gcalInput: CreateGcalEventInput diff --git a/packages/server/graphql/public/typeDefs/startRetrospective.graphql b/packages/server/graphql/public/typeDefs/startRetrospective.graphql index 91bb03261c2..8c60c60d8ef 100644 --- a/packages/server/graphql/public/typeDefs/startRetrospective.graphql +++ b/packages/server/graphql/public/typeDefs/startRetrospective.graphql @@ -8,9 +8,13 @@ extend type Mutation { """ teamId: ID! """ - The recurrence settings of the meeting + Name of the meeting or series """ - recurrenceSettings: RecurrenceSettingsInput + name: String + """ + The recurrence rule for the meeting series in RRULE format + """ + rrule: RRule """ The gcal input if creating a gcal event """ @@ -29,30 +33,3 @@ type StartRetrospectiveSuccess { team: Team! hasGcalError: Boolean } - -input CreateGcalEventInput { - """ - The title of the event - """ - title: String! - """ - The start timestamp of the event - """ - startTimestamp: Int! - """ - The end timestamp of the event - """ - endTimestamp: Int! - """ - The timezone of the event - """ - timeZone: String! - """ - The type of video call to added to the gcal event. If not provided, no video call will be added - """ - videoType: GcalVideoTypeEnum - """ - The emails that will be invited to the gcal event. If not provided, the no one will be invited - """ - invitees: [Email!] -} diff --git a/packages/server/graphql/public/typeDefs/startTeamPrompt.graphql b/packages/server/graphql/public/typeDefs/startTeamPrompt.graphql index 01bd98483b5..ea02edf4a2b 100644 --- a/packages/server/graphql/public/typeDefs/startTeamPrompt.graphql +++ b/packages/server/graphql/public/typeDefs/startTeamPrompt.graphql @@ -8,9 +8,13 @@ extend type Mutation { """ teamId: ID! """ - The recurrence settings of the meeting + Meeting or series name, by default "Standup" """ - recurrenceSettings: RecurrenceSettingsInput + name: String + """ + The recurrence rule for the meeting series in RRULE format + """ + rrule: RRule """ The gcal input if creating a gcal event. If not provided, no gcal event will be created """ @@ -37,30 +41,3 @@ type StartTeamPromptSuccess { """ hasGcalError: Boolean } - -input CreateGcalEventInput { - """ - The title of the event - """ - title: String! - """ - The start timestamp of the event - """ - startTimestamp: Int! - """ - The end timestamp of the event - """ - endTimestamp: Int! - """ - The timezone of the event - """ - timeZone: String! - """ - The type of video call to added to the gcal event. If not provided, no video call will be added - """ - videoType: GcalVideoTypeEnum - """ - The emails that will be invited to the gcal event. If not provided, the no one will be invited - """ - invitees: [Email!] -} diff --git a/packages/server/graphql/public/typeDefs/updateRecurrenceSettings.graphql b/packages/server/graphql/public/typeDefs/updateRecurrenceSettings.graphql index 1715cb3fa79..fad9895053d 100644 --- a/packages/server/graphql/public/typeDefs/updateRecurrenceSettings.graphql +++ b/packages/server/graphql/public/typeDefs/updateRecurrenceSettings.graphql @@ -12,9 +12,14 @@ extend type Mutation { meetingId: ID! """ - Updated recurrence settings + New meeting series name """ - recurrenceSettings: RecurrenceSettingsInput! + name: String + + """ + The recurrence rule for the meeting series in RRULE format + """ + rrule: RRule ): UpdateRecurrenceSettingsPayload! } diff --git a/packages/server/graphql/public/types/CreateGcalEventInput.ts b/packages/server/graphql/public/types/CreateGcalEventInput.ts index 49172372bd2..97a4e6ad98c 100644 --- a/packages/server/graphql/public/types/CreateGcalEventInput.ts +++ b/packages/server/graphql/public/types/CreateGcalEventInput.ts @@ -11,10 +11,6 @@ import GraphQLEmailType from '../../types/GraphQLEmailType' const CreateGcalEventInput = new GraphQLInputObjectType({ name: 'CreateGcalEventInput', fields: () => ({ - title: { - type: new GraphQLNonNull(GraphQLString), - description: 'The title of the meeting' - }, startTimestamp: { type: new GraphQLNonNull(GraphQLInt), description: 'The start dateTime of the meeting' @@ -40,14 +36,11 @@ const CreateGcalEventInput = new GraphQLInputObjectType({ }) }) -type GcalVideoTypeEnum = 'meet' | 'zoom' - export type CreateGcalEventInputType = { - title: string startTimestamp: number endTimestamp: number timeZone: string - videoType?: GcalVideoTypeEnum + videoType?: 'meet' | 'zoom' invitees?: string[] } From ac5c7fe21c803fd2ed14c510f7105f3cbc23847d Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 09:47:59 -0700 Subject: [PATCH 325/529] chore(release): release v7.38.6 (#9964) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e5c7a133eca..63ef2a21958 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.38.5" + ".": "7.38.6" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ad70d64140..eb6322d6c87 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.38.6](https://github.com/ParabolInc/parabol/compare/v7.38.5...v7.38.6) (2024-07-11) + + +### Changed + +* Make meeting series naming consistent ([#9928](https://github.com/ParabolInc/parabol/issues/9928)) ([97bfc0f](https://github.com/ParabolInc/parabol/commit/97bfc0fad786577534c6519ef4654d3e587e228f)) +* **rethinkdb:** OrganizationUser: Phase 1 ([#9952](https://github.com/ParabolInc/parabol/issues/9952)) ([f63c16e](https://github.com/ParabolInc/parabol/commit/f63c16e8fc17e1f7a34cc300667bafc993c85500)) + ## [7.38.5](https://github.com/ParabolInc/parabol/compare/v7.38.4...v7.38.5) (2024-07-11) diff --git a/package.json b/package.json index b0b66797517..3d10696faea 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.5", + "version": "7.38.6", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 74d8aa3ee68..00b5509152a 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.38.5", + "version": "7.38.6", "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.5" + "parabol-server": "7.38.6" } } diff --git a/packages/client/package.json b/packages/client/package.json index 99c8ace3832..6a867b594e1 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.5", + "version": "7.38.6", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index f4b8b3cef20..b98ec5aa480 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.38.5", + "version": "7.38.6", "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 2e5b561e375..426c501721e 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.38.5", + "version": "7.38.6", "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.5", - "parabol-server": "7.38.5", + "parabol-client": "7.38.6", + "parabol-server": "7.38.6", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 56587aeafa6..84d52b1f7ac 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.5", + "version": "7.38.6", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 8b505f448b2..bee996a21c4 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.5", + "version": "7.38.6", "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.5", + "parabol-client": "7.38.6", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 89d4c4faf3ee1152d7aa390792c713bcd150a416 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 11 Jul 2024 10:01:37 -0700 Subject: [PATCH 326/529] chore(rethinkdb): OrganizationUser: Phase 2 (#9953) Signed-off-by: Matt Krick --- .../mutations/checkRethinkPgEquality.ts | 62 +++------- .../1720640784862_OrganizationUser-phase2.ts | 110 ++++++++++++++++++ packages/server/postgres/utils/checkEqBase.ts | 4 +- 3 files changed, 128 insertions(+), 48 deletions(-) create mode 100644 packages/server/postgres/migrations/1720640784862_OrganizationUser-phase2.ts diff --git a/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts b/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts index a01373429eb..47c8215ed43 100644 --- a/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts +++ b/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts @@ -2,17 +2,7 @@ import getRethink from '../../../database/rethinkDriver' import getFileStoreManager from '../../../fileStorage/getFileStoreManager' import getKysely from '../../../postgres/getKysely' import {checkRowCount, checkTableEq} from '../../../postgres/utils/checkEqBase' -import { - compareDateAlmostEqual, - compareRValOptionalPluckedObject, - compareRValStringAsNumber, - compareRValUndefinedAsEmptyArray, - compareRValUndefinedAsFalse, - compareRValUndefinedAsNull, - compareRValUndefinedAsNullAndTruncateRVal, - compareRValUndefinedAsZero, - defaultEqFn -} from '../../../postgres/utils/rethinkEqualityFns' +import {compareRValUndefinedAsNull, defaultEqFn} from '../../../postgres/utils/rethinkEqualityFns' import {MutationResolvers} from '../resolverTypes' const handleResult = async ( @@ -37,55 +27,35 @@ const checkRethinkPgEquality: MutationResolvers['checkRethinkPgEquality'] = asyn ) => { const r = await getRethink() - if (tableName === 'Organization') { + if (tableName === 'OrganizationUser') { const rowCountResult = await checkRowCount(tableName) - const rethinkQuery = (updatedAt: Date, id: string | number) => { + const rethinkQuery = (joinedAt: Date, id: string | number) => { return r - .table('Organization' as any) - .between([updatedAt, id], [r.maxval, r.maxval], { - index: 'updatedAtId', + .table('OrganizationUser' as any) + .between([joinedAt, id], [r.maxval, r.maxval], { + index: 'joinedAtId', leftBound: 'open', rightBound: 'closed' }) - .orderBy({index: 'updatedAtId'}) as any + .orderBy({index: 'joinedAtId'}) as any } const pgQuery = async (ids: string[]) => { - return getKysely() - .selectFrom('Organization') - .selectAll() - .select(({fn}) => [fn('to_json', ['creditCard']).as('creditCard')]) - .where('id', 'in', ids) - .execute() + return getKysely().selectFrom('OrganizationUser').selectAll().where('id', 'in', ids).execute() } const errors = await checkTableEq( rethinkQuery, pgQuery, { id: defaultEqFn, - activeDomain: compareRValUndefinedAsNullAndTruncateRVal(100), - isActiveDomainTouched: compareRValUndefinedAsFalse, - creditCard: compareRValOptionalPluckedObject({ - brand: compareRValUndefinedAsNull, - expiry: compareRValUndefinedAsNull, - last4: compareRValStringAsNumber - }), - createdAt: defaultEqFn, - name: compareRValUndefinedAsNullAndTruncateRVal(100), - payLaterClickCount: compareRValUndefinedAsZero, - periodEnd: compareRValUndefinedAsNull, - periodStart: compareRValUndefinedAsNull, - picture: compareRValUndefinedAsNull, - showConversionModal: compareRValUndefinedAsFalse, - stripeId: compareRValUndefinedAsNull, - stripeSubscriptionId: compareRValUndefinedAsNull, - upcomingInvoiceEmailSentAt: compareRValUndefinedAsNull, + suggestedTier: compareRValUndefinedAsNull, + inactive: defaultEqFn, + joinedAt: defaultEqFn, + orgId: defaultEqFn, + removedAt: defaultEqFn, + role: compareRValUndefinedAsNull, + userId: defaultEqFn, tier: defaultEqFn, - tierLimitExceededAt: compareRValUndefinedAsNull, - trialStartDate: compareRValUndefinedAsNull, - scheduledLockAt: compareRValUndefinedAsNull, - lockedAt: compareRValUndefinedAsNull, - updatedAt: compareDateAlmostEqual, - featureFlags: compareRValUndefinedAsEmptyArray + trialStartDate: compareRValUndefinedAsNull }, maxErrors ) diff --git a/packages/server/postgres/migrations/1720640784862_OrganizationUser-phase2.ts b/packages/server/postgres/migrations/1720640784862_OrganizationUser-phase2.ts new file mode 100644 index 00000000000..ddeb041d9fc --- /dev/null +++ b/packages/server/postgres/migrations/1720640784862_OrganizationUser-phase2.ts @@ -0,0 +1,110 @@ +import {Kysely, PostgresDialect} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + try { + console.log('Adding index') + await r + .table('OrganizationUser') + .indexCreate('joinedAtId', (row: any) => [row('joinedAt'), row('id')]) + .run() + await r.table('OrganizationUser').indexWait().run() + } catch { + // index already exists + } + await r.table('OrganizationUser').get('aGhostOrganizationUser').update({tier: 'enterprise'}).run() + await console.log('Adding index complete') + const MAX_PG_PARAMS = 65545 + const PG_COLS = [ + 'id', + 'suggestedTier', + 'inactive', + 'joinedAt', + 'orgId', + 'removedAt', + 'role', + 'userId', + 'tier', + 'trialStartDate' + ] as const + type OrganizationUser = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + + let curjoinedAt = r.minval + let curId = r.minval + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, curjoinedAt, curId) + const rawRowsToInsert = (await r + .table('OrganizationUser') + .between([curjoinedAt, curId], [r.maxval, r.maxval], { + index: 'joinedAtId', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'joinedAtId'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as OrganizationUser[] + + const rowsToInsert = rawRowsToInsert.map((row) => { + const {newUserUntil, ...rest} = row as any + return { + ...rest + } + }) + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curjoinedAt = lastRow.joinedAt + curId = lastRow.id + try { + await pg + .insertInto('OrganizationUser') + .values(rowsToInsert) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + await Promise.all( + rowsToInsert.map(async (row) => { + try { + await pg + .insertInto('OrganizationUser') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_userId' || e.constraint === 'fk_orgId') { + console.log(`Skipping ${row.id} because it has no user/org`) + return + } + console.log(e, row) + } + }) + ) + } + } +} + +export async function down() { + await connectRethinkDB() + try { + await r.table('OrganizationUser').indexDrop('joinedAtId').run() + } catch { + // index already dropped + } + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await pg.deleteFrom('OrganizationUser').execute() +} diff --git a/packages/server/postgres/utils/checkEqBase.ts b/packages/server/postgres/utils/checkEqBase.ts index 175d20a57a5..bbe31117b3f 100644 --- a/packages/server/postgres/utils/checkEqBase.ts +++ b/packages/server/postgres/utils/checkEqBase.ts @@ -33,7 +33,7 @@ export const checkRowCount = async (tableName: string) => { } export async function checkTableEq( - rethinkQuery: (updatedAt: Date, id: string | number) => RSelection, + rethinkQuery: (joinedAt: Date, id: string | number) => RSelection, pgQuery: (ids: string[]) => Promise, equalityMap: Record boolean>, maxErrors: number | null | undefined @@ -51,7 +51,7 @@ export async function checkTableEq( .run()) as RethinkDoc[] if (rethinkRows.length === 0) break const lastRow = rethinkRows[rethinkRows.length - 1]! - curUpdatedDate = lastRow.updatedAt + curUpdatedDate = lastRow.joinedAt curId = lastRow.id const ids = rethinkRows.map((t) => t.id) const pgRows = (await pgQuery(ids)) ?? [] From 5aee8e492879c4b7bd344b72383b4a373c05a795 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2024 11:08:15 -0700 Subject: [PATCH 327/529] chore(release): release v7.38.7 (#9967) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 63ef2a21958..475369ad6f0 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.38.6" + ".": "7.38.7" } diff --git a/CHANGELOG.md b/CHANGELOG.md index eb6322d6c87..656a15d1a3b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.38.7](https://github.com/ParabolInc/parabol/compare/v7.38.6...v7.38.7) (2024-07-11) + + +### Changed + +* **rethinkdb:** OrganizationUser: Phase 2 ([#9953](https://github.com/ParabolInc/parabol/issues/9953)) ([89d4c4f](https://github.com/ParabolInc/parabol/commit/89d4c4faf3ee1152d7aa390792c713bcd150a416)) + ## [7.38.6](https://github.com/ParabolInc/parabol/compare/v7.38.5...v7.38.6) (2024-07-11) diff --git a/package.json b/package.json index 3d10696faea..7ce3c8a461e 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.6", + "version": "7.38.7", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 00b5509152a..5265fc7acec 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.38.6", + "version": "7.38.7", "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.6" + "parabol-server": "7.38.7" } } diff --git a/packages/client/package.json b/packages/client/package.json index 6a867b594e1..24c27b75d56 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.6", + "version": "7.38.7", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index b98ec5aa480..07bcac80f0d 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.38.6", + "version": "7.38.7", "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 426c501721e..c3907a446e7 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.38.6", + "version": "7.38.7", "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.6", - "parabol-server": "7.38.6", + "parabol-client": "7.38.7", + "parabol-server": "7.38.7", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 84d52b1f7ac..d518821a563 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.6", + "version": "7.38.7", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index bee996a21c4..40c02e32bd9 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.6", + "version": "7.38.7", "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.6", + "parabol-client": "7.38.7", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 0cff6dcb7308b851b14eeeba08fd6229a59a228c Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 15 Jul 2024 11:08:50 -0700 Subject: [PATCH 328/529] chore(rethinkdb): OrganizationUser: Phase 3 (#9965) Signed-off-by: Matt Krick --- codegen.json | 2 +- packages/server/__tests__/globalTeardown.ts | 6 +- .../server/billing/helpers/adjustUserCount.ts | 51 ++++---------- .../server/billing/helpers/teamLimitsCheck.ts | 12 ---- .../helpers/updateSubscriptionQuantity.ts | 18 +---- packages/server/database/rethinkDriver.ts | 5 -- .../server/database/types/OrganizationUser.ts | 41 ----------- .../__tests__/isOrgVerified.test.ts | 55 +-------------- .../server/dataloader/customLoaderMakers.ts | 69 ++++++++++--------- .../dataloader/foreignKeyLoaderMakers.ts | 26 +++++++ .../dataloader/primaryKeyLoaderMakers.ts | 4 ++ .../rethinkForeignKeyLoaderMakers.ts | 26 ------- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../graphql/mutations/archiveOrganization.ts | 11 --- .../graphql/mutations/helpers/createNewOrg.ts | 19 +++-- .../mutations/helpers/removeFromOrg.ts | 21 +----- .../graphql/mutations/oldUpgradeToTeamTier.ts | 8 --- .../private/mutations/autopauseUsers.ts | 16 +---- .../mutations/draftEnterpriseInvoice.ts | 8 --- .../private/mutations/hardDeleteUser.ts | 4 -- .../private/mutations/toggleAllowInsights.ts | 14 +--- .../private/mutations/upgradeToTeamTier.ts | 8 --- .../graphql/private/queries/suOrgCount.ts | 27 +------- .../graphql/private/queries/suProOrgInfo.ts | 23 ++----- .../public/mutations/setOrgUserRole.ts | 6 -- .../subscriptions/organizationSubscription.ts | 16 +++-- .../public/types/SetOrgUserRoleSuccess.ts | 2 +- packages/server/graphql/types/User.ts | 13 ++-- .../insertStripeQuantityMismatchLogging.ts | 2 +- packages/server/postgres/types/index.d.ts | 3 + .../safeArchiveEmptyStarterOrganization.ts | 9 --- .../utils/__tests__/RedisLockQueue.test.ts | 2 +- .../isRequestToJoinDomainAllowed.test.ts | 44 +----------- packages/server/utils/authorization.ts | 4 +- packages/server/utils/setTierForOrgUsers.ts | 11 --- packages/server/utils/setUserTierForOrgId.ts | 11 +-- .../server/utils/setUserTierForUserIds.ts | 10 +-- 37 files changed, 135 insertions(+), 473 deletions(-) delete mode 100644 packages/server/database/types/OrganizationUser.ts create mode 100644 packages/server/postgres/types/index.d.ts diff --git a/codegen.json b/codegen.json index cfa97845070..b4f07f4e750 100644 --- a/codegen.json +++ b/codegen.json @@ -92,7 +92,7 @@ "NotifyTaskInvolves": "../../database/types/NotificationTaskInvolves#default", "NotifyTeamArchived": "../../database/types/NotificationTeamArchived#default", "Organization": "./types/Organization#OrganizationSource", - "OrganizationUser": "../../database/types/OrganizationUser#default as OrganizationUser", + "OrganizationUser": "../../postgres/types/index#OrganizationUser", "PokerMeeting": "../../database/types/MeetingPoker#default as MeetingPoker", "PokerMeetingMember": "../../database/types/MeetingPokerMeetingMember#default as PokerMeetingMemberDB", "PokerTemplate": "../../database/types/PokerTemplate#default as PokerTemplateDB", diff --git a/packages/server/__tests__/globalTeardown.ts b/packages/server/__tests__/globalTeardown.ts index 6b98f0e346c..b5a3cd1633f 100644 --- a/packages/server/__tests__/globalTeardown.ts +++ b/packages/server/__tests__/globalTeardown.ts @@ -1,8 +1,12 @@ import getRethink from '../database/rethinkDriver' +import getKysely from '../postgres/getKysely' +import getRedis from '../utils/getRedis' async function teardown() { const r = await getRethink() - return r.getPoolMaster()?.drain() + await r.getPoolMaster()?.drain() + await getKysely().destroy() + await getRedis().quit() } export default teardown diff --git a/packages/server/billing/helpers/adjustUserCount.ts b/packages/server/billing/helpers/adjustUserCount.ts index 360a72df49d..dec7219d6a6 100644 --- a/packages/server/billing/helpers/adjustUserCount.ts +++ b/packages/server/billing/helpers/adjustUserCount.ts @@ -1,15 +1,12 @@ import {sql} from 'kysely' import {InvoiceItemType} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import {RDatum} from '../../database/stricterR' -import OrganizationUser from '../../database/types/OrganizationUser' +import generateUID from '../../generateUID' 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' -import updateUser from '../../postgres/queries/updateUser' import IUser from '../../postgres/types/IUser' import {Logger} from '../../utils/Logger' import {analytics} from '../../utils/analytics/analytics' @@ -45,7 +42,7 @@ const maybeUpdateOrganizationActiveDomain = async ( } const changePause = (inactive: boolean) => async (_orgIds: string[], user: IUser) => { - const r = await getRethink() + const pg = getKysely() const {id: userId, email} = user inactive ? analytics.accountPaused(user) : analytics.accountUnpaused(user) analytics.identify({ @@ -53,31 +50,17 @@ const changePause = (inactive: boolean) => async (_orgIds: string[], user: IUser email, isActive: !inactive }) - await Promise.all([ - updateUser( - { - inactive - }, - userId - ), - getKysely() - .updateTable('OrganizationUser') - .set({inactive}) - .where('userId', '=', userId) - .where('removedAt', 'is', null) - .execute(), - r - .table('OrganizationUser') - .getAll(userId, {index: 'userId'}) - .filter({removedAt: null}) - .update({inactive}) - .run() - ]) + await pg + .with('User', (qb) => qb.updateTable('User').set({inactive}).where('id', '=', userId)) + .updateTable('OrganizationUser') + .set({inactive}) + .where('userId', '=', userId) + .where('removedAt', 'is', null) + .execute() } const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWorker) => { const {id: userId} = user - const r = await getRethink() const [rawOrganizations, organizationUsers] = await Promise.all([ dataLoader.get('organizations').loadMany(orgIds), dataLoader.get('organizationUsersByUserId').load(userId) @@ -90,19 +73,18 @@ const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWork ) const organization = organizations.find((organization) => organization.id === orgId)! // continue the grace period from before, if any OR set to the end of the invoice OR (if it is a free account) no grace period - return new OrganizationUser({ - id: oldOrganizationUser?.id, + return { + id: oldOrganizationUser?.id || generateUID(), orgId, userId, tier: organization.tier - }) + } }) await getKysely() .insertInto('OrganizationUser') .values(docs) .onConflict((oc) => oc.doNothing()) .execute() - await r.table('OrganizationUser').insert(docs, {conflict: 'replace'}).run() await Promise.all( orgIds.map((orgId) => { return maybeUpdateOrganizationActiveDomain(orgId, user.email, dataLoader) @@ -111,7 +93,6 @@ const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWork } const deleteUser = async (orgIds: string[], user: IUser) => { - const r = await getRethink() orgIds.forEach((orgId) => analytics.userRemovedFromOrg(user, orgId)) await getKysely() .updateTable('OrganizationUser') @@ -119,14 +100,6 @@ const deleteUser = async (orgIds: string[], user: IUser) => { .where('userId', '=', user.id) .where('orgId', 'in', orgIds) .execute() - await r - .table('OrganizationUser') - .getAll(user.id, {index: 'userId'}) - .filter((row: RDatum) => r.expr(orgIds).contains(row('orgId'))) - .update({ - removedAt: new Date() - }) - .run() } const dbActionTypeLookup = { diff --git a/packages/server/billing/helpers/teamLimitsCheck.ts b/packages/server/billing/helpers/teamLimitsCheck.ts index 9d2b8d52944..849edb8420e 100644 --- a/packages/server/billing/helpers/teamLimitsCheck.ts +++ b/packages/server/billing/helpers/teamLimitsCheck.ts @@ -28,12 +28,6 @@ const enableUsageStats = async (userIds: string[], orgId: string) => { .where('userId', 'in', userIds) .where('removedAt', 'is', null) .execute() - await r - .table('OrganizationUser') - .getAll(r.args(userIds), {index: 'userId'}) - .filter({orgId}) - .update({suggestedTier: 'team'}) - .run() await pg .updateTable('User') .set({featureFlags: sql`arr_append_uniq("featureFlags", 'insights')`}) @@ -101,12 +95,6 @@ export const maybeRemoveRestrictions = async (orgId: string, dataLoader: DataLoa .where('userId', 'in', billingLeadersIds) .where('removedAt', 'is', null) .execute(), - r - .table('OrganizationUser') - .getAll(r.args(billingLeadersIds), {index: 'userId'}) - .filter({orgId}) - .update({suggestedTier: 'starter'}) - .run(), removeTeamsLimitObjects(orgId, dataLoader) ]) dataLoader.get('organizations').clear(orgId) diff --git a/packages/server/billing/helpers/updateSubscriptionQuantity.ts b/packages/server/billing/helpers/updateSubscriptionQuantity.ts index b6b2634eb39..79db4e1de5e 100644 --- a/packages/server/billing/helpers/updateSubscriptionQuantity.ts +++ b/packages/server/billing/helpers/updateSubscriptionQuantity.ts @@ -1,4 +1,3 @@ -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import insertStripeQuantityMismatchLogging from '../../postgres/queries/insertStripeQuantityMismatchLogging' import RedisLockQueue from '../../utils/RedisLockQueue' @@ -10,7 +9,6 @@ import {getStripeManager} from '../../utils/stripe' * @param logMismatch Pass true if a quantity mismatch should be logged */ const updateSubscriptionQuantity = async (orgId: string, logMismatch?: boolean) => { - const r = await getRethink() const pg = getKysely() const manager = getStripeManager() @@ -35,7 +33,7 @@ const updateSubscriptionQuantity = async (orgId: string, logMismatch?: boolean) return } - const [orgUserCountRes, orgUserCount, teamSubscription] = await Promise.all([ + const [orgUserCountRes, teamSubscription] = await Promise.all([ pg .selectFrom('OrganizationUser') .select(({fn}) => fn.count('id').as('count')) @@ -43,21 +41,9 @@ const updateSubscriptionQuantity = async (orgId: string, logMismatch?: boolean) .where('removedAt', 'is', null) .where('inactive', '=', false) .executeTakeFirstOrThrow(), - r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({removedAt: null, inactive: false}) - .count() - .run(), manager.getSubscriptionItem(stripeSubscriptionId) ]) - if (orgUserCountRes.count !== orgUserCount) { - sendToSentry(new Error('OrganizationUser count mismatch'), { - tags: { - orgId - } - }) - } + const {count: orgUserCount} = orgUserCountRes if ( teamSubscription && teamSubscription.quantity !== undefined && diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index 3eb79efcc06..fdada8558b9 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -25,7 +25,6 @@ import NotificationResponseReplied from './types/NotificationResponseReplied' import NotificationTaskInvolves from './types/NotificationTaskInvolves' import NotificationTeamArchived from './types/NotificationTeamArchived' import NotificationTeamInvitation from './types/NotificationTeamInvitation' -import OrganizationUser from './types/OrganizationUser' import PasswordResetRequest from './types/PasswordResetRequest' import PushInvitation from './types/PushInvitation' import RetrospectivePrompt from './types/RetrospectivePrompt' @@ -112,10 +111,6 @@ export type RethinkSchema = { | NotificationMentioned index: 'userId' } - OrganizationUser: { - type: OrganizationUser - index: 'orgId' | 'userId' - } PasswordResetRequest: { type: PasswordResetRequest index: 'email' | 'ip' | 'token' diff --git a/packages/server/database/types/OrganizationUser.ts b/packages/server/database/types/OrganizationUser.ts deleted file mode 100644 index 927242b2dc6..00000000000 --- a/packages/server/database/types/OrganizationUser.ts +++ /dev/null @@ -1,41 +0,0 @@ -import generateUID from '../../generateUID' -import {TierEnum} from './Invoice' - -export type OrgUserRole = 'BILLING_LEADER' | 'ORG_ADMIN' -interface Input { - orgId: string - userId: string - id?: string - inactive?: boolean - joinedAt?: Date - removedAt?: Date - role?: OrgUserRole - tier?: TierEnum - suggestedTier?: TierEnum -} - -export default class OrganizationUser { - id: string - suggestedTier: TierEnum | null - inactive: boolean - joinedAt: Date - orgId: string - removedAt: Date | null - role: OrgUserRole | null - userId: string - tier: TierEnum - trialStartDate?: Date | null - - constructor(input: Input) { - const {suggestedTier, userId, id, removedAt, inactive, orgId, joinedAt, role, tier} = input - this.id = id || generateUID() - this.suggestedTier = suggestedTier || null - this.inactive = inactive || false - this.joinedAt = joinedAt || new Date() - this.orgId = orgId - this.removedAt = removedAt || null - this.role = role || null - this.userId = userId - this.tier = tier || 'starter' - } -} diff --git a/packages/server/dataloader/__tests__/isOrgVerified.test.ts b/packages/server/dataloader/__tests__/isOrgVerified.test.ts index 6fa0c0c1b0e..ed37f906642 100644 --- a/packages/server/dataloader/__tests__/isOrgVerified.test.ts +++ b/packages/server/dataloader/__tests__/isOrgVerified.test.ts @@ -1,56 +1,19 @@ /* eslint-env jest */ import {Insertable} from 'kysely' -import {r} from 'rethinkdb-ts' import {createPGTables, truncatePGTables} from '../../__tests__/common' -import getRethinkConfig from '../../database/getRethinkConfig' -import getRethink from '../../database/rethinkDriver' -import OrganizationUser from '../../database/types/OrganizationUser' import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' import {User} from '../../postgres/pg' -import getRedis from '../../utils/getRedis' -import isUserVerified from '../../utils/isUserVerified' +import {OrganizationUser} from '../../postgres/types' import RootDataLoader from '../RootDataLoader' -jest.mock('../../database/rethinkDriver') -jest.mock('../../utils/isUserVerified') - -jest.mocked(getRethink).mockImplementation(() => { - return r as any -}) - -jest.mocked(isUserVerified).mockImplementation(() => { - return true -}) const TEST_DB = 'getVerifiedOrgIdsTest' -const config = getRethinkConfig() -const testConfig = { - ...config, - db: TEST_DB -} - type TestUser = Insertable const addUsers = async (users: TestUser[]) => { return getKysely().insertInto('User').values(users).execute() } -const createTables = async (...tables: string[]) => { - for (const tableName of tables) { - const structure = await r - .db('rethinkdb') - .table('table_config') - .filter({db: config.db, name: tableName}) - .run() - await r.tableCreate(tableName).run() - const {indexes} = structure[0] - for (const index of indexes) { - await r.table(tableName).indexCreate(index).run() - } - await r.table(tableName).indexWait().run() - } -} - type TestOrganizationUser = Partial< Pick > @@ -90,35 +53,21 @@ const addOrg = async ( await pg.insertInto('Organization').values(org).execute() } - await r.table('OrganizationUser').insert(orgUsers).run() return orgId } beforeAll(async () => { - await r.connectPool(testConfig) const pg = getKysely(TEST_DB) - - try { - await r.dbDrop(TEST_DB).run() - } catch (e) { - //ignore - } await pg.schema.createSchema(TEST_DB).ifNotExists().execute() - - await r.dbCreate(TEST_DB).run() await createPGTables('Organization', 'User', 'SAML', 'SAMLDomain', 'OrganizationUser') - await createTables('OrganizationUser') }) afterEach(async () => { - await truncatePGTables('Organization', 'User') - await r.table('OrganizationUser').delete().run() + await truncatePGTables('Organization', 'User', 'OrganizationUser') }) afterAll(async () => { - await r.getPoolMaster()?.drain() await getKysely().destroy() - getRedis().quit() }) test('Founder is billing lead', async () => { diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 4de0beb1abb..3b3f3fe0b23 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -6,11 +6,9 @@ import getRethink, {RethinkSchema} from '../database/rethinkDriver' import {RDatum} from '../database/stricterR' import MeetingSettingsTeamPrompt from '../database/types/MeetingSettingsTeamPrompt' import MeetingTemplate from '../database/types/MeetingTemplate' -import OrganizationUser from '../database/types/OrganizationUser' import {Reactable, ReactableEnum} from '../database/types/Reactable' import Task, {TaskStatusEnum} from '../database/types/Task' import getFileStoreManager from '../fileStorage/getFileStoreManager' -import isValid from '../graphql/isValid' import {SAMLSource} from '../graphql/public/types/SAML' import {TeamSource} from '../graphql/public/types/Team' import getKysely from '../postgres/getKysely' @@ -29,6 +27,7 @@ import getLatestTaskEstimates from '../postgres/queries/getLatestTaskEstimates' import getMeetingTaskEstimates, { MeetingTaskEstimatesResult } from '../postgres/queries/getMeetingTaskEstimates' +import {OrganizationUser} from '../postgres/types' import {AnyMeeting, MeetingTypeEnum} from '../postgres/types/Meeting' import {Logger} from '../utils/Logger' import getRedis from '../utils/getRedis' @@ -393,18 +392,20 @@ export const organizationApprovedDomains = (parent: RootDataLoader) => { export const organizationUsersByUserIdOrgId = (parent: RootDataLoader) => { return new DataLoader<{orgId: string; userId: string}, OrganizationUser | null, string>( async (keys) => { - const r = await getRethink() + const pg = getKysely() return Promise.all( - keys.map((key) => { + keys.map(async (key) => { const {userId, orgId} = key if (!userId || !orgId) return null - return r - .table('OrganizationUser') - .getAll(userId, {index: 'userId'}) - .filter({orgId, removedAt: null}) - .nth(0) - .default(null) - .run() + const res = await pg + .selectFrom('OrganizationUser') + .selectAll() + .where('userId', '=', userId) + .where('orgId', '=', orgId) + .where('removedAt', 'is', null) + .limit(1) + .executeTakeFirst() + return res || null }) ) }, @@ -656,16 +657,17 @@ export const lastMeetingByMeetingSeriesId = (parent: RootDataLoader) => { export const billingLeadersIdsByOrgId = (parent: RootDataLoader) => { return new DataLoader( async (keys) => { - const r = await getRethink() + const pg = getKysely() const res = await Promise.all( - keys.map((orgId) => { - return 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') - .run() + keys.map(async (orgId) => { + const rows = await pg + .selectFrom('OrganizationUser') + .select('userId') + .where('orgId', '=', orgId) + .where('removedAt', 'is', null) + .where('role', 'in', ['BILLING_LEADER', 'ORG_ADMIN']) + .execute() + return rows.map((row) => row.userId) }) ) return res @@ -740,21 +742,22 @@ export const samlByOrgId = (parent: RootDataLoader) => { export const isOrgVerified = (parent: RootDataLoader) => { return new DataLoader( async (orgIds) => { - const orgUsersRes = await parent.get('organizationUsersByOrgId').loadMany(orgIds) - const orgUsersWithRole = orgUsersRes - .filter(isValid) - .flat() - .filter(({role}) => role && ['BILLING_LEADER', 'ORG_ADMIN'].includes(role)) - const orgUsersUserIds = orgUsersWithRole.map((orgUser) => orgUser.userId) - const usersRes = await parent.get('users').loadMany(orgUsersUserIds) - const verifiedUsers = usersRes.filter(isValid).filter(isUserVerified) - const verifiedOrgUsers = orgUsersWithRole.filter((orgUser) => - verifiedUsers.some((user) => user.id === orgUser.userId) - ) return await Promise.all( orgIds.map(async (orgId) => { - const isUserVerified = verifiedOrgUsers.some((orgUser) => orgUser.orgId === orgId) - if (isUserVerified) return true + const [organization, orgUsers] = await Promise.all([ + parent.get('organizations').loadNonNull(orgId), + parent.get('organizationUsersByOrgId').load(orgId) + ]) + const orgLeaders = orgUsers.filter( + ({role}) => role && ['BILLING_LEADER', 'ORG_ADMIN'].includes(role) + ) + const orgLeaderUsers = await Promise.all( + orgLeaders.map(({userId}) => parent.get('users').loadNonNull(userId)) + ) + const isALeaderVerifiedAtOrgDomain = orgLeaderUsers.some( + (user) => isUserVerified(user) && user.domain === organization.activeDomain + ) + if (isALeaderVerifiedAtOrgDomain) return true const isOrgSAML = await parent.get('samlByOrgId').load(orgId) return !!isOrgSAML }) diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 543647daedb..2f6cb7daf09 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -81,3 +81,29 @@ export const organizationsByActiveDomain = foreignKeyLoaderMaker( return selectOrganizations().where('activeDomain', 'in', activeDomains).execute() } ) + +export const organizationUsersByUserId = foreignKeyLoaderMaker( + 'organizationUsers', + 'userId', + async (userIds) => { + return getKysely() + .selectFrom('OrganizationUser') + .selectAll() + .where('userId', 'in', userIds) + .where('removedAt', 'is', null) + .execute() + } +) + +export const organizationUsersByOrgId = foreignKeyLoaderMaker( + 'organizationUsers', + 'orgId', + async (orgIds) => { + return getKysely() + .selectFrom('OrganizationUser') + .selectAll() + .where('orgId', 'in', orgIds) + .where('removedAt', 'is', null) + .execute() + } +) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index dd10a3f0d75..48f9907c636 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -131,3 +131,7 @@ export const selectOrganizations = () => export const organizations = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectOrganizations().where('id', 'in', ids).execute() }) + +export const organizationUsers = primaryKeyLoaderMaker((ids: readonly string[]) => { + return getKysely().selectFrom('OrganizationUser').selectAll().where('id', 'in', ids).execute() +}) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index 4385bb32fd9..913c7dcc87f 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -116,32 +116,6 @@ export const meetingMembersByUserId = new RethinkForeignKeyLoaderMaker( } ) -export const organizationUsersByOrgId = new RethinkForeignKeyLoaderMaker( - 'organizationUsers', - 'orgId', - async (orgIds) => { - const r = await getRethink() - return r - .table('OrganizationUser') - .getAll(r.args(orgIds), {index: 'orgId'}) - .filter({removedAt: null}) - .run() - } -) - -export const organizationUsersByUserId = new RethinkForeignKeyLoaderMaker( - 'organizationUsers', - 'userId', - async (userIds) => { - const r = await getRethink() - return r - .table('OrganizationUser') - .getAll(r.args(userIds), {index: 'userId'}) - .filter({removedAt: null}) - .run() - } -) - export const scalesByTeamId = new RethinkForeignKeyLoaderMaker( 'templateScales', 'teamId', diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index 78a198aa9b9..ef78ee05b50 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -13,7 +13,6 @@ export const meetingMembers = new RethinkPrimaryKeyLoaderMaker('MeetingMember') export const newMeetings = new RethinkPrimaryKeyLoaderMaker('NewMeeting') export const newFeatures = new RethinkPrimaryKeyLoaderMaker('NewFeature') export const notifications = new RethinkPrimaryKeyLoaderMaker('Notification') -export const organizationUsers = new RethinkPrimaryKeyLoaderMaker('OrganizationUser') export const templateScales = new RethinkPrimaryKeyLoaderMaker('TemplateScale') export const slackAuths = new RethinkPrimaryKeyLoaderMaker('SlackAuth') export const slackNotifications = new RethinkPrimaryKeyLoaderMaker('SlackNotification') diff --git a/packages/server/graphql/mutations/archiveOrganization.ts b/packages/server/graphql/mutations/archiveOrganization.ts index cbe6942d08b..7d8235c51f8 100644 --- a/packages/server/graphql/mutations/archiveOrganization.ts +++ b/packages/server/graphql/mutations/archiveOrganization.ts @@ -2,7 +2,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import removeTeamsLimitObjects from '../../billing/helpers/removeTeamsLimitObjects' -import getRethink from '../../database/rethinkDriver' import Team from '../../database/types/Team' import User from '../../database/types/User' import getKysely from '../../postgres/getKysely' @@ -29,10 +28,8 @@ export default { {orgId}: {orgId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} - const now = new Date() // AUTH const viewerId = getUserId(authToken) @@ -89,14 +86,6 @@ export default { .where('orgId', '=', orgId) .where('removedAt', 'is', null) .execute(), - r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({removedAt: null}) - .update({ - removedAt: now - }) - .run(), removeTeamsLimitObjects(orgId, dataLoader) ]) diff --git a/packages/server/graphql/mutations/helpers/createNewOrg.ts b/packages/server/graphql/mutations/helpers/createNewOrg.ts index d4b069caee2..391fa04da43 100644 --- a/packages/server/graphql/mutations/helpers/createNewOrg.ts +++ b/packages/server/graphql/mutations/helpers/createNewOrg.ts @@ -1,6 +1,5 @@ -import getRethink from '../../../database/rethinkDriver' import Organization from '../../../database/types/Organization' -import OrganizationUser from '../../../database/types/OrganizationUser' +import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' import insertOrgUserAudit from '../../../postgres/helpers/insertOrgUserAudit' import getDomainFromEmail from '../../../utils/getDomainFromEmail' @@ -13,7 +12,6 @@ export default async function createNewOrg( leaderEmail: string, dataLoader: DataLoaderWorker ) { - const r = await getRethink() const userDomain = getDomainFromEmail(leaderEmail) const isCompanyDomain = await dataLoader.get('isCompanyDomain').load(userDomain) const activeDomain = isCompanyDomain ? userDomain : undefined @@ -22,17 +20,16 @@ export default async function createNewOrg( name: orgName, activeDomain }) - const orgUser = new OrganizationUser({ - orgId, - userId: leaderUserId, - role: 'BILLING_LEADER', - tier: org.tier - }) await insertOrgUserAudit([orgId], leaderUserId, 'added') await getKysely() .with('Org', (qc) => qc.insertInto('Organization').values({...org, creditCard: null})) .insertInto('OrganizationUser') - .values(orgUser) + .values({ + id: generateUID(), + orgId, + userId: leaderUserId, + role: 'BILLING_LEADER', + tier: org.tier + }) .execute() - await r.table('OrganizationUser').insert(orgUser).run() } diff --git a/packages/server/graphql/mutations/helpers/removeFromOrg.ts b/packages/server/graphql/mutations/helpers/removeFromOrg.ts index dc6f2ad9934..ede6aba2610 100644 --- a/packages/server/graphql/mutations/helpers/removeFromOrg.ts +++ b/packages/server/graphql/mutations/helpers/removeFromOrg.ts @@ -2,7 +2,6 @@ import {sql} from 'kysely' import {InvoiceItemType} from 'parabol-client/types/constEnums' import adjustUserCount from '../../../billing/helpers/adjustUserCount' import getRethink from '../../../database/rethinkDriver' -import OrganizationUser from '../../../database/types/OrganizationUser' import getKysely from '../../../postgres/getKysely' import getTeamsByOrgIds from '../../../postgres/queries/getTeamsByOrgIds' import {Logger} from '../../../utils/Logger' @@ -19,7 +18,6 @@ const removeFromOrg = async ( ) => { const r = await getRethink() const pg = getKysely() - const now = new Date() const orgTeams = await getTeamsByOrgIds([orgId]) const teamIds = orgTeams.map((team) => team.id) const teamMemberIds = (await r @@ -44,23 +42,15 @@ const removeFromOrg = async ( return arr }, []) - const [_pgOrgUser, organizationUser, user] = await Promise.all([ + const [organizationUser, user] = await Promise.all([ pg .updateTable('OrganizationUser') .set({removedAt: sql`CURRENT_TIMESTAMP`}) .where('userId', '=', userId) .where('orgId', '=', orgId) .where('removedAt', 'is', null) - .returning('role') + .returning(['id', 'role']) .executeTakeFirstOrThrow(), - r - .table('OrganizationUser') - .getAll(userId, {index: 'userId'}) - .filter({orgId, removedAt: null}) - .nth(0) - .update({removedAt: now}, {returnChanges: true})('changes')(0)('new_val') - .default(null) - .run() as unknown as OrganizationUser, dataLoader.get('users').loadNonNull(userId) ]) @@ -83,13 +73,6 @@ const removeFromOrg = async ( .set({role: 'BILLING_LEADER'}) .where('id', '=', nextInLine.id) .execute() - await r - .table('OrganizationUser') - .get(nextInLine.id) - .update({ - role: 'BILLING_LEADER' - }) - .run() } else if (organization.tier !== 'starter') { await resolveDowngradeToStarter(orgId, organization.stripeSubscriptionId!, user, dataLoader) } diff --git a/packages/server/graphql/mutations/oldUpgradeToTeamTier.ts b/packages/server/graphql/mutations/oldUpgradeToTeamTier.ts index 7bf2e98adec..f4e0cafb5c1 100644 --- a/packages/server/graphql/mutations/oldUpgradeToTeamTier.ts +++ b/packages/server/graphql/mutations/oldUpgradeToTeamTier.ts @@ -1,6 +1,5 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {analytics} from '../../utils/analytics/analytics' import {getUserId} from '../../utils/authorization' @@ -31,7 +30,6 @@ export default { {orgId, stripeToken}: {orgId: string; stripeToken: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -73,12 +71,6 @@ export default { .where('orgId', '=', orgId) .where('removedAt', 'is', null) .execute() - await r - .table('OrganizationUser') - .getAll(viewerId, {index: 'userId'}) - .filter({removedAt: null, orgId}) - .update({role: 'BILLING_LEADER'}) - .run() const teams = await dataLoader.get('teamsByOrgIds').load(orgId) const teamIds = teams.map(({id}) => id) diff --git a/packages/server/graphql/private/mutations/autopauseUsers.ts b/packages/server/graphql/private/mutations/autopauseUsers.ts index 741f96be9af..f0df40b0375 100644 --- a/packages/server/graphql/private/mutations/autopauseUsers.ts +++ b/packages/server/graphql/private/mutations/autopauseUsers.ts @@ -1,6 +1,5 @@ import {InvoiceItemType, Threshold} from 'parabol-client/types/constEnums' import adjustUserCount from '../../../billing/helpers/adjustUserCount' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import getUserIdsToPause from '../../../postgres/queries/getUserIdsToPause' import {Logger} from '../../../utils/Logger' @@ -11,7 +10,6 @@ const autopauseUsers: MutationResolvers['autopauseUsers'] = async ( _args, {dataLoader} ) => { - const r = await getRethink() const pg = getKysely() // RESOLUTION const activeThresh = new Date(Date.now() - Threshold.AUTO_PAUSE) @@ -22,7 +20,7 @@ const autopauseUsers: MutationResolvers['autopauseUsers'] = async ( const skip = i * BATCH_SIZE const userIdBatch = userIdsToPause.slice(skip, skip + BATCH_SIZE) if (userIdBatch.length < 1) break - const pgResults = await pg + const results = await pg .selectFrom('OrganizationUser') .select(({fn}) => ['userId', fn.agg('array_agg', ['orgId']).as('orgIds')]) .where('userId', 'in', userIdBatch) @@ -30,18 +28,8 @@ const autopauseUsers: MutationResolvers['autopauseUsers'] = async ( .groupBy('userId') .execute() - // TEST in Phase 2! - console.log(pgResults) - - const results = (await ( - r - .table('OrganizationUser') - .getAll(r.args(userIdBatch), {index: 'userId'}) - .filter({removedAt: null}) - .group('userId') as any - )('orgId').run()) as {group: string; reduction: string[]}[] await Promise.allSettled( - results.map(async ({group: userId, reduction: orgIds}) => { + results.map(async ({userId, orgIds}) => { try { return await adjustUserCount(userId, orgIds, InvoiceItemType.AUTO_PAUSE_USER, dataLoader) } catch (e) { diff --git a/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts b/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts index e07c5fadcec..58747bb4825 100644 --- a/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts +++ b/packages/server/graphql/private/mutations/draftEnterpriseInvoice.ts @@ -1,5 +1,4 @@ import removeTeamsLimitObjects from '../../../billing/helpers/removeTeamsLimitObjects' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {getUserByEmail} from '../../../postgres/queries/getUsersByEmails' import IUser from '../../../postgres/types/IUser' @@ -18,7 +17,6 @@ const getBillingLeaderUser = async ( orgId: string, dataLoader: DataLoaderWorker ) => { - const r = await getRethink() const pg = getKysely() if (email) { const user = await getUserByEmail(email) @@ -40,12 +38,6 @@ const getBillingLeaderUser = async ( .where('orgId', '=', orgId) .where('removedAt', 'is', null) .execute() - await r - .table('OrganizationUser') - .getAll(userId, {index: 'userId'}) - .filter({removedAt: null, orgId}) - .update({role: 'BILLING_LEADER'}) - .run() } return user } diff --git a/packages/server/graphql/private/mutations/hardDeleteUser.ts b/packages/server/graphql/private/mutations/hardDeleteUser.ts index 22d3084aba1..80a28568d12 100644 --- a/packages/server/graphql/private/mutations/hardDeleteUser.ts +++ b/packages/server/graphql/private/mutations/hardDeleteUser.ts @@ -107,10 +107,6 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( teamMember: r.table('TeamMember').getAll(userIdToDelete, {index: 'userId'}).delete(), meetingMember: r.table('MeetingMember').getAll(userIdToDelete, {index: 'userId'}).delete(), notification: r.table('Notification').getAll(userIdToDelete, {index: 'userId'}).delete(), - organizationUser: r - .table('OrganizationUser') - .getAll(userIdToDelete, {index: 'userId'}) - .delete(), suggestedAction: r.table('SuggestedAction').getAll(userIdToDelete, {index: 'userId'}).delete(), createdTasks: r .table('Task') diff --git a/packages/server/graphql/private/mutations/toggleAllowInsights.ts b/packages/server/graphql/private/mutations/toggleAllowInsights.ts index 0d4a6d80cb4..a9346d7f865 100644 --- a/packages/server/graphql/private/mutations/toggleAllowInsights.ts +++ b/packages/server/graphql/private/mutations/toggleAllowInsights.ts @@ -1,4 +1,3 @@ -import {r, RValue} from 'rethinkdb-ts' import getKysely from '../../../postgres/getKysely' import {getUsersByEmails} from '../../../postgres/queries/getUsersByEmails' import {MutationResolvers} from '../resolverTypes' @@ -26,7 +25,7 @@ const toggleAllowInsights: MutationResolvers['toggleAllowInsights'] = async ( const userIds = users.map(({id}) => id) const recordsReplaced = await Promise.all( userIds.map(async (userId) => { - await pg + return await pg .updateTable('OrganizationUser') .set({suggestedTier}) .where('userId', '=', userId) @@ -34,18 +33,9 @@ const toggleAllowInsights: MutationResolvers['toggleAllowInsights'] = async ( .where('removedAt', 'is', null) .returning('id') .execute() - return r - .table('OrganizationUser') - .getAll(r.args(orgIds), {index: 'orgId'}) - .filter({ - userId - }) - .filter((row: RValue) => row('removedAt').default(null).eq(null)) - .update({suggestedTier})('replaced') - .run() }) ) - return {organizationUsersAffected: recordsReplaced.reduce((x, y) => x + y)} + return {organizationUsersAffected: recordsReplaced.flat().length} } export default toggleAllowInsights diff --git a/packages/server/graphql/private/mutations/upgradeToTeamTier.ts b/packages/server/graphql/private/mutations/upgradeToTeamTier.ts index e051a0ccb81..5530633f8a2 100644 --- a/packages/server/graphql/private/mutations/upgradeToTeamTier.ts +++ b/packages/server/graphql/private/mutations/upgradeToTeamTier.ts @@ -1,6 +1,5 @@ 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' @@ -41,7 +40,6 @@ const upgradeToTeamTier: MutationResolvers['upgradeToTeamTier'] = async ( return standardError(new Error('Customer does not have an orgId'), {userId}) } - const r = await getRethink() const pg = getKysely() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -107,12 +105,6 @@ const upgradeToTeamTier: MutationResolvers['upgradeToTeamTier'] = async ( .where('orgId', '=', orgId) .where('removedAt', 'is', null) .execute() - await r - .table('OrganizationUser') - .getAll(viewerId, {index: 'userId'}) - .filter({removedAt: null, orgId}) - .update({role: 'BILLING_LEADER'}) - .run() const teams = await dataLoader.get('teamsByOrgIds').load(orgId) const teamIds = teams.map(({id}) => id) diff --git a/packages/server/graphql/private/queries/suOrgCount.ts b/packages/server/graphql/private/queries/suOrgCount.ts index b2dd6f6afc3..52632b14d4e 100644 --- a/packages/server/graphql/private/queries/suOrgCount.ts +++ b/packages/server/graphql/private/queries/suOrgCount.ts @@ -1,11 +1,9 @@ -import getRethink from '../../../database/rethinkDriver' -import {RValue} from '../../../database/stricterR' import getKysely from '../../../postgres/getKysely' import {QueryResolvers} from '../resolverTypes' const suOrgCount: QueryResolvers['suOrgCount'] = async (_source, {minOrgSize, tier}) => { const pg = getKysely() - const pgResults = await pg + const result = await pg .with('BigOrgs', (qb) => qb .selectFrom('OrganizationUser') @@ -17,28 +15,9 @@ const suOrgCount: QueryResolvers['suOrgCount'] = async (_source, {minOrgSize, ti .having(({eb, fn}) => eb(fn.count('id'), '>=', minOrgSize)) ) .selectFrom('BigOrgs') - .select(({fn}) => fn.count('orgSize').as('count')) + .select(({fn}) => fn.count('orgSize').as('count')) .executeTakeFirstOrThrow() - - // TEST in Phase 2! - console.log(pgResults) - - const r = await getRethink() - return ( - r - .table('OrganizationUser') - .getAll( - [tier, false] as unknown as string, // super hacky type fix bc no fn overload is defined in the type file for this valid invocation - {index: 'tierInactive'} as unknown as undefined - ) - .filter({removedAt: null}) - .group('orgId') as any - ) - .count() - .ungroup() - .filter((group: RValue) => group('reduction').ge(minOrgSize)) - .count() - .run() + return result.count } export default suOrgCount diff --git a/packages/server/graphql/private/queries/suProOrgInfo.ts b/packages/server/graphql/private/queries/suProOrgInfo.ts index 3e604f7a5bb..c842ce85e5b 100644 --- a/packages/server/graphql/private/queries/suProOrgInfo.ts +++ b/packages/server/graphql/private/queries/suProOrgInfo.ts @@ -1,40 +1,25 @@ -import {sql} from 'kysely' -import getRethink from '../../../database/rethinkDriver' -import {RDatum} from '../../../database/stricterR' import {selectOrganizations} from '../../../dataloader/primaryKeyLoaderMakers' import getKysely from '../../../postgres/getKysely' import {QueryResolvers} from '../resolverTypes' const suProOrgInfo: QueryResolvers['suProOrgInfo'] = async (_source, {includeInactive}) => { - const r = await getRethink() const pg = getKysely() const proOrgs = await selectOrganizations().where('tier', '=', 'team').execute() - if (includeInactive) return proOrgs + if (includeInactive || proOrgs.length === 0) return proOrgs const proOrgIds = proOrgs.map(({id}) => id) const pgResults = await pg .selectFrom('OrganizationUser') - .select(({fn}) => fn.count('id').as('orgSize')) + .select(['orgId', ({fn}) => fn.count('id').as('orgSize')]) // use ANY to support case where proOrgIds is empty array. Please use `in` after RethinkDB is gone - .where('orgId', '=', sql`ANY(${proOrgIds})`) + .where('orgId', 'in', proOrgIds) .where('inactive', '=', false) .where('removedAt', 'is', null) .groupBy('orgId') .having(({eb, fn}) => eb(fn.count('id'), '>=', 1)) .execute() - const activeOrgIds = await ( - r - .table('OrganizationUser') - .getAll(r.args(proOrgIds), {index: 'orgId'}) - .filter({removedAt: null, inactive: false}) - .group('orgId') as RDatum - ) - .count() - .ungroup() - .filter((row: RDatum) => row('reduction').ge(1))('group') - .run() - console.log({pgResults, activeOrgIds}) + const activeOrgIds = pgResults.map(({orgId}) => orgId) return proOrgs.filter((org) => activeOrgIds.includes(org.id)) } diff --git a/packages/server/graphql/public/mutations/setOrgUserRole.ts b/packages/server/graphql/public/mutations/setOrgUserRole.ts index d00522221fe..64f378c95b6 100644 --- a/packages/server/graphql/public/mutations/setOrgUserRole.ts +++ b/packages/server/graphql/public/mutations/setOrgUserRole.ts @@ -21,7 +21,6 @@ const setOrgUserRole: MutationResolvers['setOrgUserRole'] = async ( {orgId, userId, role: roleToSet}, {authToken, dataLoader, socketId: mutatorId} ) => { - const r = await getRethink() const pg = getKysely() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -90,11 +89,6 @@ const setOrgUserRole: MutationResolvers['setOrgUserRole'] = async ( .set({role: roleToSet || null}) .where('id', '=', organizationUserId) .execute() - await r - .table('OrganizationUser') - .get(organizationUserId) - .update({role: roleToSet || null}) - .run() organizationUser.role = roleToSet || null if (roleToSet !== 'ORG_ADMIN') { const modificationType = roleToSet === 'BILLING_LEADER' ? 'add' : 'remove' diff --git a/packages/server/graphql/public/subscriptions/organizationSubscription.ts b/packages/server/graphql/public/subscriptions/organizationSubscription.ts index 3c6cff708e3..545bbd1e101 100644 --- a/packages/server/graphql/public/subscriptions/organizationSubscription.ts +++ b/packages/server/graphql/public/subscriptions/organizationSubscription.ts @@ -1,5 +1,5 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {getUserId} from '../../../utils/authorization' import getPubSub from '../../../utils/getPubSub' import {SubscriptionContext} from '../../graphql' @@ -10,12 +10,14 @@ const organizationSubscription: SubscriptionResolvers['orga subscribe: async (_source, _args, {authToken}) => { // AUTH const viewerId = getUserId(authToken) - const r = await getRethink() - const organizationUsers = await r - .table('OrganizationUser') - .getAll(viewerId, {index: 'userId'}) - .filter({removedAt: null}) - .run() + const pg = getKysely() + + const organizationUsers = await pg + .selectFrom('OrganizationUser') + .select('orgId') + .where('userId', '=', viewerId) + .where('removedAt', 'is', null) + .execute() const orgIds = organizationUsers.map(({orgId}) => orgId) // RESOLUTION diff --git a/packages/server/graphql/public/types/SetOrgUserRoleSuccess.ts b/packages/server/graphql/public/types/SetOrgUserRoleSuccess.ts index 9d70f951f12..3954347afdf 100644 --- a/packages/server/graphql/public/types/SetOrgUserRoleSuccess.ts +++ b/packages/server/graphql/public/types/SetOrgUserRoleSuccess.ts @@ -13,7 +13,7 @@ const SetOrgUserRoleSuccess: SetOrgUserRoleSuccessResolvers = { return dataLoader.get('organizations').loadNonNull(orgId) }, updatedOrgMember: async ({organizationUserId}, _args, {dataLoader}) => { - return dataLoader.get('organizationUsers').load(organizationUserId) + return dataLoader.get('organizationUsers').loadNonNull(organizationUserId) }, notificationsAdded: async ({notificationIdsAdded}, _args, {authToken, dataLoader}) => { if (!notificationIdsAdded.length) return [] diff --git a/packages/server/graphql/types/User.ts b/packages/server/graphql/types/User.ts index 0054e59fa36..292a6eab123 100644 --- a/packages/server/graphql/types/User.ts +++ b/packages/server/graphql/types/User.ts @@ -16,7 +16,6 @@ import { } from '../../../client/utils/constants' import groupReflections from '../../../client/utils/smartGroup/groupReflections' import MeetingMemberType from '../../database/types/MeetingMember' -import OrganizationUserType from '../../database/types/OrganizationUser' import SuggestedActionType from '../../database/types/SuggestedAction' import getKysely from '../../postgres/getKysely' import {getUserId, isSuperUser, isTeamMember} from '../../utils/authorization' @@ -83,7 +82,7 @@ const User: GraphQLObjectType = new GraphQLObjectType { const organizationUsers = await dataLoader.get('organizationUsersByUserId').load(userId) return organizationUsers.some( - (organizationUser: OrganizationUserType) => + (organizationUser) => organizationUser.role === 'BILLING_LEADER' || organizationUser.role === 'ORG_ADMIN' ) } @@ -379,17 +378,15 @@ const User: GraphQLObjectType = new GraphQLObjectType { const viewerId = getUserId(authToken) const organizationUsers = await dataLoader.get('organizationUsersByUserId').load(userId) - organizationUsers.sort((a: OrganizationUserType, b: OrganizationUserType) => - a.orgId > b.orgId ? 1 : -1 - ) + organizationUsers.sort((a, b) => (a.orgId > b.orgId ? 1 : -1)) if (viewerId === userId || isSuperUser(authToken)) { return organizationUsers } const viewerOrganizationUsers = await dataLoader .get('organizationUsersByUserId') .load(viewerId) - const viewerOrgIds = viewerOrganizationUsers.map(({orgId}: OrganizationUserType) => orgId) - return organizationUsers.filter((organizationUser: OrganizationUserType) => + const viewerOrgIds = viewerOrganizationUsers.map(({orgId}) => orgId) + return organizationUsers.filter((organizationUser) => viewerOrgIds.includes(organizationUser.orgId) ) } @@ -430,7 +427,7 @@ const User: GraphQLObjectType = new GraphQLObjectType { const organizationUsers = await dataLoader.get('organizationUsersByUserId').load(userId) const isAnyMemberOfPaidOrg = organizationUsers.some( - (organizationUser: OrganizationUserType) => organizationUser.tier !== 'starter' + (organizationUser) => organizationUser.tier !== 'starter' ) if (isAnyMemberOfPaidOrg) return null return overLimitCopy diff --git a/packages/server/postgres/queries/insertStripeQuantityMismatchLogging.ts b/packages/server/postgres/queries/insertStripeQuantityMismatchLogging.ts index 5e75fd08dd9..e2ca6c263dd 100644 --- a/packages/server/postgres/queries/insertStripeQuantityMismatchLogging.ts +++ b/packages/server/postgres/queries/insertStripeQuantityMismatchLogging.ts @@ -1,5 +1,5 @@ -import OrganizationUser from '../../database/types/OrganizationUser' import getPg from '../getPg' +import {OrganizationUser} from '../types' import {insertStripeQuantityMismatchLoggingQuery} from './generated/insertStripeQuantityMismatchLoggingQuery' const insertStripeQuantityMismatchLogging = async ( diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts new file mode 100644 index 00000000000..cdc9adef2ad --- /dev/null +++ b/packages/server/postgres/types/index.d.ts @@ -0,0 +1,3 @@ +import {Selectable} from 'kysely' +import {OrganizationUser as OrganizationUserPG} from '../pg.d' +export type OrganizationUser = Selectable diff --git a/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts b/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts index c5ddd92aec5..8dd9c8221e6 100644 --- a/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts +++ b/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts @@ -1,5 +1,4 @@ import {sql} from 'kysely' -import getRethink from '../database/rethinkDriver' import {DataLoaderInstance} from '../dataloader/RootDataLoader' import getKysely from '../postgres/getKysely' import getTeamsByOrgIds from '../postgres/queries/getTeamsByOrgIds' @@ -11,9 +10,7 @@ const safeArchiveEmptyStarterOrganization = async ( orgId: string, dataLoader: DataLoaderInstance ) => { - const r = await getRethink() const pg = getKysely() - const now = new Date() const orgTeams = await getTeamsByOrgIds([orgId]) const teamCountRemainingOnOldOrg = orgTeams.length @@ -26,12 +23,6 @@ const safeArchiveEmptyStarterOrganization = async ( .where('orgId', '=', orgId) .where('removedAt', 'is', null) .execute() - await r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({removedAt: null}) - .update({removedAt: now}) - .run() } export default safeArchiveEmptyStarterOrganization diff --git a/packages/server/utils/__tests__/RedisLockQueue.test.ts b/packages/server/utils/__tests__/RedisLockQueue.test.ts index bde386f290c..1dc0deeb044 100644 --- a/packages/server/utils/__tests__/RedisLockQueue.test.ts +++ b/packages/server/utils/__tests__/RedisLockQueue.test.ts @@ -4,7 +4,7 @@ import RedisLockQueue from '../RedisLockQueue' import getRedis from '../getRedis' afterAll(async () => { - getRedis().quit() + await getRedis().quit() }) test('lock calls are queued properly', async () => { diff --git a/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts b/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts index a17af53e9aa..52b67b82600 100644 --- a/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts +++ b/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts @@ -1,48 +1,17 @@ /* eslint-env jest */ import {Insertable} from 'kysely' -import {r} from 'rethinkdb-ts' import {createPGTables, truncatePGTables} from '../../__tests__/common' -import getRethinkConfig from '../../database/getRethinkConfig' -import getRethink from '../../database/rethinkDriver' import {TierEnum} from '../../database/types/Invoice' -import OrganizationUser from '../../database/types/OrganizationUser' import RootDataLoader from '../../dataloader/RootDataLoader' import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' import {User} from '../../postgres/pg' +import {OrganizationUser} from '../../postgres/types' import getRedis from '../getRedis' import {getEligibleOrgIdsByDomain} from '../isRequestToJoinDomainAllowed' -jest.mock('../../database/rethinkDriver') - -jest.mocked(getRethink).mockImplementation(() => { - return r as any -}) - const TEST_DB = 'isRequestToJoinDomainAllowedTest' -const config = getRethinkConfig() -const testConfig = { - ...config, - db: TEST_DB -} - -const createTables = async (...tables: string[]) => { - for (const tableName of tables) { - const structure = await r - .db('rethinkdb') - .table('table_config') - .filter({db: config.db, name: tableName}) - .run() - await r.tableCreate(tableName).run() - const {indexes} = structure[0] - for (const index of indexes) { - await r.table(tableName).indexCreate(index).run() - } - await r.table(tableName).indexWait().run() - } -} - type TestOrganizationUser = Partial< Pick > & {userId: string} @@ -80,20 +49,12 @@ const addOrg = async ( .insertInto('OrganizationUser') .values(orgUsers) .execute() - await r.table('OrganizationUser').insert(orgUsers).run() return orgId } beforeAll(async () => { - await r.connectPool(testConfig) const pg = getKysely(TEST_DB) - try { - await r.dbDrop(TEST_DB).run() - } catch (e) { - //ignore - } await pg.schema.createSchema(TEST_DB).ifNotExists().execute() - await r.dbCreate(TEST_DB).run() await createPGTables( 'Organization', 'User', @@ -102,16 +63,13 @@ beforeAll(async () => { 'SAMLDomain', 'OrganizationUser' ) - await createTables('OrganizationUser') }) afterEach(async () => { await truncatePGTables('Organization', 'User', 'OrganizationUser') - await r.table('OrganizationUser').delete().run() }) afterAll(async () => { - await r.getPoolMaster()?.drain() await getKysely().destroy() getRedis().quit() }) diff --git a/packages/server/utils/authorization.ts b/packages/server/utils/authorization.ts index b2c0f0b17f6..8617c3bab16 100644 --- a/packages/server/utils/authorization.ts +++ b/packages/server/utils/authorization.ts @@ -1,8 +1,8 @@ import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import getRethink from '../database/rethinkDriver' import AuthToken from '../database/types/AuthToken' -import {OrgUserRole} from '../database/types/OrganizationUser' import {DataLoaderWorker} from '../graphql/graphql' +import {OrganizationUser} from '../postgres/types' export const getUserId = (authToken: any) => { return authToken && typeof authToken === 'object' ? (authToken.sub as string) : '' @@ -55,7 +55,7 @@ const isUserAnyRoleIn = async ( userId: string, orgId: string, dataLoader: DataLoaderWorker, - roles: OrgUserRole[], + roles: NonNullable[], options?: Options ) => { const organizationUser = await dataLoader diff --git a/packages/server/utils/setTierForOrgUsers.ts b/packages/server/utils/setTierForOrgUsers.ts index 8131dd85f1b..28a68ce96f7 100644 --- a/packages/server/utils/setTierForOrgUsers.ts +++ b/packages/server/utils/setTierForOrgUsers.ts @@ -7,11 +7,9 @@ * and rejoins the same org, a new `OrganizationUser` row * will be created. */ -import getRethink from '../database/rethinkDriver' import getKysely from '../postgres/getKysely' const setTierForOrgUsers = async (orgId: string) => { - const r = await getRethink() const pg = getKysely() const organization = await getKysely() .selectFrom('Organization') @@ -25,15 +23,6 @@ const setTierForOrgUsers = async (orgId: string) => { .where('orgId', '=', orgId) .where('removedAt', 'is', null) .execute() - await r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({removedAt: null}) - .update({ - tier, - trialStartDate - }) - .run() } export default setTierForOrgUsers diff --git a/packages/server/utils/setUserTierForOrgId.ts b/packages/server/utils/setUserTierForOrgId.ts index 23e1480070a..5c494855b83 100644 --- a/packages/server/utils/setUserTierForOrgId.ts +++ b/packages/server/utils/setUserTierForOrgId.ts @@ -1,22 +1,15 @@ -import getRethink from '../database/rethinkDriver' import getKysely from '../postgres/getKysely' import setUserTierForUserIds from './setUserTierForUserIds' const setUserTierForOrgId = async (orgId: string) => { - const r = await getRethink() const pg = getKysely() - const _userIds = await pg + const orgUsers = await pg .selectFrom('OrganizationUser') .select('userId') .where('orgId', '=', orgId) .where('removedAt', 'is', null) .execute() - console.log({_userIds}) - const userIds = await r - .table('OrganizationUser') - .getAll(orgId, {index: 'orgId'}) - .filter({removedAt: null})('userId') - .run() + const userIds = orgUsers.map(({userId}) => userId) await setUserTierForUserIds(userIds) } diff --git a/packages/server/utils/setUserTierForUserIds.ts b/packages/server/utils/setUserTierForUserIds.ts index 2e1c9dae552..28bdc75962d 100644 --- a/packages/server/utils/setUserTierForUserIds.ts +++ b/packages/server/utils/setUserTierForUserIds.ts @@ -1,4 +1,3 @@ -import getRethink from '../database/rethinkDriver' import isValid from '../graphql/isValid' import getKysely from '../postgres/getKysely' import {analytics} from './analytics/analytics' @@ -10,20 +9,13 @@ import {analytics} from './analytics/analytics' // 'setTierForOrgUsers'. const setUserTierForUserId = async (userId: string) => { - const r = await getRethink() const pg = getKysely() - const _orgUsers = await pg + const orgUsers = await pg .selectFrom('OrganizationUser') .selectAll() .where('userId', '=', userId) .where('removedAt', 'is', null) .execute() - console.log({_orgUsers}) - const orgUsers = await r - .table('OrganizationUser') - .getAll(userId, {index: 'userId'}) - .filter({removedAt: null}) - .run() const orgIds = orgUsers.map((orgUser) => orgUser.orgId) if (orgIds.length === 0) return From 59eb73f78c2b9aebaca7f3562d9af1148e37c0ba Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Mon, 15 Jul 2024 19:20:53 +0100 Subject: [PATCH 329/529] =?UTF-8?q?fix:=20if=20the=20content=20of=20a=20ta?= =?UTF-8?q?sk=20is=20only=20spaces,=20it=20gets=20deleted=20as=20if=20i?= =?UTF-8?q?=E2=80=A6=20(#9968)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../containers/OutcomeCard/OutcomeCardContainer.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/modules/outcomeCard/containers/OutcomeCard/OutcomeCardContainer.tsx b/packages/client/modules/outcomeCard/containers/OutcomeCard/OutcomeCardContainer.tsx index 14e9804204c..41762fd484e 100644 --- a/packages/client/modules/outcomeCard/containers/OutcomeCard/OutcomeCardContainer.tsx +++ b/packages/client/modules/outcomeCard/containers/OutcomeCard/OutcomeCardContainer.tsx @@ -85,7 +85,7 @@ const OutcomeCardContainer = memo((props: Props) => { const editorEl = editorRef.current if (!editorEl || editorEl.type !== 'textarea') return const {value} = editorEl - if (!value && !isFocused) { + if (!value.trim() && !isFocused) { DeleteTaskMutation(atmosphere, {taskId}) } else { const initialContentState = editorState.getCurrentContent() @@ -100,7 +100,7 @@ const OutcomeCardContainer = memo((props: Props) => { return } const nextContentState = editorState.getCurrentContent() - const hasText = nextContentState.hasText() + const hasText = nextContentState.getPlainText().trim().length > 0 if (!hasText && !isFocused) { DeleteTaskMutation(atmosphere, {taskId}) } else { From 63bf930989f1493f54d36407a990863519b80528 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 15 Jul 2024 11:47:06 -0700 Subject: [PATCH 330/529] fix: add ClearAll dataloader method (#9975) Signed-off-by: Matt Krick --- .../server/billing/helpers/adjustUserCount.ts | 2 +- packages/server/dataloader/RootDataLoader.ts | 106 ++++++++--------- .../server/dataloader/customLoaderMakers.ts | 109 ++++++++++-------- .../dataloader/foreignKeyLoaderMaker.ts | 16 ++- .../server/dataloader/normalizeResults.ts | 6 +- .../dataloader/primaryKeyLoaderMakers.ts | 11 ++ .../dataloader/rethinkForeignKeyLoader.ts | 2 +- packages/server/graphql/DataLoaderCache.ts | 55 +++------ packages/server/graphql/getDataLoader.ts | 4 +- 9 files changed, 154 insertions(+), 157 deletions(-) diff --git a/packages/server/billing/helpers/adjustUserCount.ts b/packages/server/billing/helpers/adjustUserCount.ts index dec7219d6a6..3ddd78a3e4d 100644 --- a/packages/server/billing/helpers/adjustUserCount.ts +++ b/packages/server/billing/helpers/adjustUserCount.ts @@ -65,7 +65,6 @@ const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWork dataLoader.get('organizations').loadMany(orgIds), dataLoader.get('organizationUsersByUserId').load(userId) ]) - dataLoader.get('organizationUsersByUserId').clear(userId) const organizations = rawOrganizations.filter(isValid) const docs = orgIds.map((orgId) => { const oldOrganizationUser = organizationUsers.find( @@ -80,6 +79,7 @@ const addUser = async (orgIds: string[], user: IUser, dataLoader: DataLoaderWork tier: organization.tier } }) + dataLoader.clearAll('organizationUsers') await getKysely() .insertInto('OrganizationUser') .values(docs) diff --git a/packages/server/dataloader/RootDataLoader.ts b/packages/server/dataloader/RootDataLoader.ts index 1b2546ec1f8..146ae62e1e5 100644 --- a/packages/server/dataloader/RootDataLoader.ts +++ b/packages/server/dataloader/RootDataLoader.ts @@ -1,8 +1,6 @@ import DataLoader from 'dataloader' -import {DBType} from '../database/rethinkDriver' import RethinkForeignKeyLoaderMaker from './RethinkForeignKeyLoaderMaker' import RethinkPrimaryKeyLoaderMaker from './RethinkPrimaryKeyLoaderMaker' -import UpdatableCacheDataLoader from './UpdatableCacheDataLoader' import * as atlassianLoaders from './atlassianLoaders' import * as azureDevOpsLoaders from './azureDevOpsLoaders' import * as customLoaderMakers from './customLoaderMakers' @@ -41,79 +39,81 @@ const loaderMakers = { ...azureDevOpsLoaders } as const -type LoaderMakers = typeof loaderMakers -export type Loaders = keyof LoaderMakers +export type Loaders = keyof typeof loaderMakers -type PrimaryLoaderMakers = typeof rethinkPrimaryKeyLoaderMakers -type PrimaryLoaders = keyof PrimaryLoaderMakers -type Unprimary = T extends RethinkPrimaryKeyLoaderMaker ? DBType[U] : never -type TypeFromPrimary = Unprimary +export type AllPrimaryLoaders = + | keyof typeof primaryKeyLoaderMakers + | keyof typeof rethinkPrimaryKeyLoaderMakers +export type RegisterDependsOn = (primaryLoaders: AllPrimaryLoaders | AllPrimaryLoaders[]) => void -type ForeignLoaderMakers = typeof rethinkForeignKeyLoaderMakers -type ForeignLoaders = keyof ForeignLoaderMakers -type Unforeign = T extends RethinkForeignKeyLoaderMaker ? U : never -type TypeFromForeign = TypeFromPrimary> - -/** - * When adding a new loaders file like {@link atlassianLoaders} or {@link githubLoaders} - * this type has to include a typeof of newly added loaders - */ -type CustomLoaderMakers = typeof customLoaderMakers & - typeof atlassianLoaders & - typeof jiraServerLoaders & - typeof pollLoaders & - typeof integrationAuthLoaders & - typeof primaryKeyLoaderMakers & - typeof foreignKeyLoaderMakers & - typeof azureDevOpsLoaders & - typeof gitlabLoaders & - typeof gcalLoaders -type CustomLoaders = keyof CustomLoaderMakers -type Uncustom = T extends (parent: RootDataLoader) => infer U ? U : never -type TypeFromCustom = Uncustom - -// Use this if you don't need the dataloader to be shareable -export interface DataLoaderInstance { - get(loaderName: LoaderName): TypedDataLoader +// The RethinkDB logic is a leaky abstraction! It will be gone soon & this will be generic enough to put in its own package +interface GenericDataLoader { + clearAll(pkLoaderName: TPrimaryLoaderNames): void + get( + loaderName: LoaderName + ): Loader extends (...args: any[]) => any + ? ReturnType + : // can delete below this line after RethinkDB is gone + Loader extends RethinkPrimaryKeyLoaderMaker + ? ReturnType> + : Loader extends RethinkForeignKeyLoaderMaker + ? ReturnType< + typeof rethinkForeignKeyLoader< + (typeof rethinkPrimaryKeyLoaderMakers)[U] extends RethinkPrimaryKeyLoaderMaker< + infer V + > + ? V + : never + > + > + : never } -export type TypedDataLoader = LoaderName extends CustomLoaders - ? TypeFromCustom - : UpdatableCacheDataLoader< - string, - LoaderName extends ForeignLoaders - ? TypeFromForeign[] - : LoaderName extends PrimaryLoaders - ? TypeFromPrimary - : never - > +export type DataLoaderInstance = GenericDataLoader /** * This is the main dataloader */ -export default class RootDataLoader implements DataLoaderInstance { - dataLoaderOptions: DataLoader.Options +export default class RootDataLoader< + O extends DataLoader.Options = DataLoader.Options +> { + dataLoaderOptions: O // casted to any because access to the loaders will results in a creation if needed loaders: LoaderDict = {} as any - constructor(dataLoaderOptions: DataLoader.Options = {}) { + dependentLoaders: Record = {} + constructor(dataLoaderOptions: O = {} as O) { this.dataLoaderOptions = dataLoaderOptions } - get(loaderName: LoaderName): TypedDataLoader { + clearAll: DataLoaderInstance['clearAll'] = (pkLoaderName) => { + const dependencies = [pkLoaderName, ...(this.dependentLoaders[pkLoaderName] ?? [])] + dependencies.forEach((loaderName) => { + this.loaders[loaderName]?.clearAll() + }) + } + get: DataLoaderInstance['get'] = (loaderName) => { let loader = this.loaders[loaderName] - if (loader) return loader as TypedDataLoader - const loaderMaker = loaderMakers[loaderName] + if (loader) return loader + const loaderMaker = loaderMakers[loaderName as keyof typeof loaderMakers] if (loaderMaker instanceof RethinkPrimaryKeyLoaderMaker) { const {table} = loaderMaker loader = rethinkPrimaryKeyLoader(this.dataLoaderOptions, table) } else if (loaderMaker instanceof RethinkForeignKeyLoaderMaker) { const {fetch, field, pk} = loaderMaker - const basePkLoader = this.get(pk as PrimaryLoaders) + const basePkLoader = this.get(pk) as any loader = rethinkForeignKeyLoader(basePkLoader, this.dataLoaderOptions, field, fetch) } else { - loader = (loaderMaker as any)(this) + const dependsOn: RegisterDependsOn = (inPrimaryLoaders) => { + const primaryLoaders = Array.isArray(inPrimaryLoaders) + ? inPrimaryLoaders + : [inPrimaryLoaders] + primaryLoaders.forEach((primaryLoader) => { + ;(this.dependentLoaders[primaryLoader] ??= []).push(loaderName) + }) + } + loader = (loaderMaker as any)(this, dependsOn) } this.loaders[loaderName] = loader! - return loader as TypedDataLoader + return loader as any } } diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 3b3f3fe0b23..791a2725b35 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -33,7 +33,7 @@ import {Logger} from '../utils/Logger' import getRedis from '../utils/getRedis' import isUserVerified from '../utils/isUserVerified' import NullableDataLoader from './NullableDataLoader' -import RootDataLoader from './RootDataLoader' +import RootDataLoader, {RegisterDependsOn} from './RootDataLoader' import normalizeArrayResults from './normalizeArrayResults' import normalizeResults from './normalizeResults' import {selectTeams} from './primaryKeyLoaderMakers' @@ -84,7 +84,11 @@ export const serializeUserTasksKey = (key: UserTasksKey) => { return parts.join(':') } -export const commentCountByDiscussionId = (parent: RootDataLoader) => { +export const commentCountByDiscussionId = ( + parent: RootDataLoader, + dependsOn: RegisterDependsOn +) => { + dependsOn('comments') return new DataLoader( async (discussionIds) => { const r = await getRethink() @@ -142,7 +146,8 @@ export const meetingTaskEstimates = (parent: RootDataLoader) => { ) } -export const reactables = (parent: RootDataLoader) => { +export const reactables = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn(reactableLoaders.map((a) => a.loader)) return new DataLoader( async (keys) => { const reactableResults = (await Promise.all( @@ -163,7 +168,8 @@ export const reactables = (parent: RootDataLoader) => { ) } -export const userTasks = (parent: RootDataLoader) => { +export const userTasks = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('tasks') return new DataLoader( async (keys) => { const r = await getRethink() @@ -309,7 +315,8 @@ export const githubDimensionFieldMaps = (parent: RootDataLoader) => { ) } -export const meetingSettingsByType = (parent: RootDataLoader) => { +export const meetingSettingsByType = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('meetingSettings') return new DataLoader( async (keys) => { const r = await getRethink() @@ -389,7 +396,11 @@ export const organizationApprovedDomains = (parent: RootDataLoader) => { ) } -export const organizationUsersByUserIdOrgId = (parent: RootDataLoader) => { +export const organizationUsersByUserIdOrgId = ( + parent: RootDataLoader, + dependsOn: RegisterDependsOn +) => { + dependsOn('organizationUsers') return new DataLoader<{orgId: string; userId: string}, OrganizationUser | null, string>( async (keys) => { const pg = getKysely() @@ -416,7 +427,8 @@ export const organizationUsersByUserIdOrgId = (parent: RootDataLoader) => { ) } -export const meetingTemplatesByType = (parent: RootDataLoader) => { +export const meetingTemplatesByType = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('meetingTemplates') return new DataLoader( async (keys) => { const types = {} as Record @@ -452,7 +464,6 @@ export const meetingTemplatesByType = (parent: RootDataLoader) => { ) } -// :TODO:(jmtaber129): Generalize this to all meeting types if needed. export const teamMeetingTemplateByTeamId = (parent: RootDataLoader) => { return new DataLoader[], string>( async (teamIds) => { @@ -462,11 +473,7 @@ export const teamMeetingTemplateByTeamId = (parent: RootDataLoader) => { .selectAll() .where('teamId', 'in', teamIds) .execute() - return teamIds.map((teamId) => { - return teamMeetingTemplates.filter( - (teamMeetingTemplate) => teamMeetingTemplate.teamId === teamId - ) - }) + return normalizeArrayResults(teamIds, teamMeetingTemplates, 'teamId') }, { ...parent.dataLoaderOptions @@ -474,7 +481,8 @@ export const teamMeetingTemplateByTeamId = (parent: RootDataLoader) => { ) } -export const meetingTemplatesByOrgId = (parent: RootDataLoader) => { +export const meetingTemplatesByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('meetingTemplates') return new DataLoader( async (orgIds) => { const pg = getKysely() @@ -500,7 +508,8 @@ export const meetingTemplatesByOrgId = (parent: RootDataLoader) => { ) } -export const meetingTemplatesByTeamId = (parent: RootDataLoader) => { +export const meetingTemplatesByTeamId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('meetingTemplates') return new DataLoader( async (teamIds) => { const pg = getKysely() @@ -523,7 +532,8 @@ type MeetingStat = { meetingType: MeetingTypeEnum createdAt: Date } -export const meetingStatsByOrgId = (parent: RootDataLoader) => { +export const meetingStatsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('newMeetings') return new DataLoader( async (orgIds) => { const r = await getRethink() @@ -554,7 +564,8 @@ export const meetingStatsByOrgId = (parent: RootDataLoader) => { ) } -export const teamStatsByOrgId = (parent: RootDataLoader) => { +export const teamStatsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('teams') return new DataLoader( async (orgIds) => { const teamStatsByOrgId = await Promise.all( @@ -574,7 +585,11 @@ export const teamStatsByOrgId = (parent: RootDataLoader) => { ) } -export const taskIdsByTeamAndGitHubRepo = (parent: RootDataLoader) => { +export const taskIdsByTeamAndGitHubRepo = ( + parent: RootDataLoader, + dependsOn: RegisterDependsOn +) => { + dependsOn('tasks') return new DataLoader<{teamId: string; nameWithOwner: string}, string[], string>( async (keys) => { const r = await getRethink() @@ -612,7 +627,11 @@ export const meetingHighlightedTaskId = (parent: RootDataLoader) => { ) } -export const activeMeetingsByMeetingSeriesId = (parent: RootDataLoader) => { +export const activeMeetingsByMeetingSeriesId = ( + parent: RootDataLoader, + dependsOn: RegisterDependsOn +) => { + dependsOn('newMeetings') return new DataLoader( async (keys) => { const r = await getRethink() @@ -630,7 +649,11 @@ export const activeMeetingsByMeetingSeriesId = (parent: RootDataLoader) => { ) } -export const lastMeetingByMeetingSeriesId = (parent: RootDataLoader) => { +export const lastMeetingByMeetingSeriesId = ( + parent: RootDataLoader, + dependsOn: RegisterDependsOn +) => { + dependsOn('newMeetings') return new DataLoader( async (keys) => tracer.trace('lastMeetingByMeetingSeriesId', async () => { @@ -654,7 +677,8 @@ export const lastMeetingByMeetingSeriesId = (parent: RootDataLoader) => { ) } -export const billingLeadersIdsByOrgId = (parent: RootDataLoader) => { +export const billingLeadersIdsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('organizationUsers') return new DataLoader( async (keys) => { const pg = getKysely() @@ -678,27 +702,8 @@ export const billingLeadersIdsByOrgId = (parent: RootDataLoader) => { ) } -export const saml = (parent: RootDataLoader) => { - return new NullableDataLoader( - async (samlIds) => { - const pg = getKysely() - const res = await pg - .selectFrom('SAMLDomain') - .innerJoin('SAML', 'SAML.id', 'SAMLDomain.samlId') - .where('SAML.id', 'in', samlIds) - .groupBy('SAML.id') - .selectAll('SAML') - .select(({fn}) => [fn.agg('array_agg', ['SAMLDomain.domain']).as('domains')]) - .execute() - return samlIds.map((samlId) => res.find((row) => row.id === samlId)) - }, - { - ...parent.dataLoaderOptions - } - ) -} - -export const samlByDomain = (parent: RootDataLoader) => { +export const samlByDomain = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('saml') return new NullableDataLoader( async (domains) => { const pg = getKysely() @@ -710,15 +715,15 @@ export const samlByDomain = (parent: RootDataLoader) => { .selectAll('SAML') .select(({fn}) => [fn.agg('array_agg', ['SAMLDomain.domain']).as('domains')]) .execute() - return domains.map((domain) => res.find((row) => row.domains.includes(domain))) + return normalizeResults(domains, res) }, { ...parent.dataLoaderOptions } ) } - -export const samlByOrgId = (parent: RootDataLoader) => { +export const samlByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('saml') return new NullableDataLoader( async (orgIds) => { const pg = getKysely() @@ -730,7 +735,7 @@ export const samlByOrgId = (parent: RootDataLoader) => { .selectAll('SAML') .select(({fn}) => [fn.agg('array_agg', ['SAMLDomain.domain']).as('domains')]) .execute() - return orgIds.map((orgId) => res.find((row) => row.orgId === orgId)) + return normalizeResults(orgIds, res) }, { ...parent.dataLoaderOptions @@ -739,7 +744,8 @@ export const samlByOrgId = (parent: RootDataLoader) => { } // Check if the org has a founder or billing lead with a verified email and their email domain is the same as the org domain -export const isOrgVerified = (parent: RootDataLoader) => { +export const isOrgVerified = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('organizationUsers') return new DataLoader( async (orgIds) => { return await Promise.all( @@ -769,7 +775,8 @@ export const isOrgVerified = (parent: RootDataLoader) => { ) } -export const autoJoinTeamsByOrgId = (parent: RootDataLoader) => { +export const autoJoinTeamsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('teams') return new DataLoader( async (orgIds) => { const verificationResults = await parent.get('isOrgVerified').loadMany(orgIds) @@ -814,7 +821,8 @@ export const isCompanyDomain = (parent: RootDataLoader) => { ) } -export const favoriteTemplateIds = (parent: RootDataLoader) => { +export const favoriteTemplateIds = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('users') return new DataLoader( async (userIds) => { const pg = getKysely() @@ -862,7 +870,8 @@ export const fileStoreAsset = (parent: RootDataLoader) => { ) } -export const meetingCount = (parent: RootDataLoader) => { +export const meetingCount = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('selectTeams') return new DataLoader<{teamId: string; meetingType: MeetingTypeEnum}, number, string>( async (keys) => { const r = await getRethink() diff --git a/packages/server/dataloader/foreignKeyLoaderMaker.ts b/packages/server/dataloader/foreignKeyLoaderMaker.ts index 19c91cadfd1..88594e3d8e2 100644 --- a/packages/server/dataloader/foreignKeyLoaderMaker.ts +++ b/packages/server/dataloader/foreignKeyLoaderMaker.ts @@ -1,5 +1,5 @@ import DataLoader from 'dataloader' -import RootDataLoader from './RootDataLoader' +import RootDataLoader, {RegisterDependsOn} from './RootDataLoader' import UpdatableCacheDataLoader from './UpdatableCacheDataLoader' import * as primaryKeyLoaderMakers from './primaryKeyLoaderMakers' @@ -18,19 +18,17 @@ type LoaderType = */ export function foreignKeyLoaderMaker< LoaderName extends LoaderKeys, - KeyName extends keyof LoaderType + T extends LoaderType, + KeyName extends keyof T >( primaryLoaderKey: LoaderName, foreignKey: KeyName, - fetchFn: ( - keys: readonly LoaderType[KeyName][] - ) => Promise<(LoaderType & {id: string | number})[]> + fetchFn: (keys: readonly T[KeyName][]) => Promise<(T & {id?: string | number})[]> ) { - type T = LoaderType - type PrimaryKeyT = string | number type KeyValue = T[KeyName] - return (parent: RootDataLoader) => { - const primaryLoader = parent.get(primaryLoaderKey) as DataLoader + return (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn(primaryLoaderKey) + const primaryLoader = parent.get(primaryLoaderKey) as DataLoader return new UpdatableCacheDataLoader( async (ids) => { const items = await fetchFn(ids) diff --git a/packages/server/dataloader/normalizeResults.ts b/packages/server/dataloader/normalizeResults.ts index 4824d090d9c..5981b95ecb0 100644 --- a/packages/server/dataloader/normalizeResults.ts +++ b/packages/server/dataloader/normalizeResults.ts @@ -7,11 +7,7 @@ export const normalizeResults = { map[result[key]] = result }) - const mappedResults = [] as T[] - keys.forEach((key) => { - mappedResults.push(map[key]) - }) - return mappedResults + return keys.map((key) => map[key] as T) } export default normalizeResults diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 48f9907c636..5f199227b88 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -132,6 +132,17 @@ export const organizations = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectOrganizations().where('id', 'in', ids).execute() }) +export const saml = primaryKeyLoaderMaker((ids: readonly string[]) => { + return getKysely() + .selectFrom('SAMLDomain') + .innerJoin('SAML', 'SAML.id', 'SAMLDomain.samlId') + .where('SAML.id', 'in', ids) + .groupBy('SAML.id') + .selectAll('SAML') + .select(({fn}) => [fn.agg('array_agg', ['SAMLDomain.domain']).as('domains')]) + .execute() +}) + export const organizationUsers = primaryKeyLoaderMaker((ids: readonly string[]) => { return getKysely().selectFrom('OrganizationUser').selectAll().where('id', 'in', ids).execute() }) diff --git a/packages/server/dataloader/rethinkForeignKeyLoader.ts b/packages/server/dataloader/rethinkForeignKeyLoader.ts index a9b61cd888a..147c868d2d2 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoader.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoader.ts @@ -15,7 +15,7 @@ const rethinkForeignKeyLoader = ( }) return ids.map((id) => items.filter((item) => item[field] === id)) } - return new UpdatableCacheDataLoader(batchFn, options) + return new UpdatableCacheDataLoader(batchFn, options) } export default rethinkForeignKeyLoader diff --git a/packages/server/graphql/DataLoaderCache.ts b/packages/server/graphql/DataLoaderCache.ts index 458cbfd8e1e..184fbb745dc 100644 --- a/packages/server/graphql/DataLoaderCache.ts +++ b/packages/server/graphql/DataLoaderCache.ts @@ -1,41 +1,19 @@ -import getLoaderNameByTable from '../dataloader/getLoaderNameByTable' -import {Loaders, TypedDataLoader} from '../dataloader/RootDataLoader' - -export interface DataLoaderBase { - get: (loaderName: any) => unknown - loaders: { - [key: string]: { - clear(id: string): void - } - } -} - -export class CacheWorker { - cache: DataLoaderCache - dataLoaderBase: T +export class CacheWorker void; get: (id: any) => any}> { + cache: DataLoaderCache + dataLoaderWorker: T did: string disposeId: NodeJS.Timeout | undefined shared = false - - constructor(dataLoaderBase: T, did: string, cache: DataLoaderCache) { - this.dataLoaderBase = dataLoaderBase + get: T['get'] + clearAll: T['clearAll'] + constructor(dataLoaderWorker: T, did: string, cache: DataLoaderCache) { + this.dataLoaderWorker = dataLoaderWorker this.did = did this.cache = cache + this.get = this.dataLoaderWorker.get + this.clearAll = this.dataLoaderWorker.clearAll } - get = (dataLoaderName: LoaderName) => { - // Using Loaders here breaks the abstraction. - // However, when using T['get'], typescript control flow analysis breaks - // e.g. loader.load() // null | any[], but doesn't catch the null! - // I'm OK with breaking the abstraction because we only have 1 RootDataLoader - // Given more time, could probably use a refactor here - return this.dataLoaderBase.get(dataLoaderName) as TypedDataLoader - } - - clear = (table: string, id: string) => { - const loaderName = getLoaderNameByTable(table) - this.dataLoaderBase.loaders[loaderName]?.clear(id) - } dispose(force?: boolean) { const ttl = force || !this.shared ? 0 : this.cache.ttl clearTimeout(this.disposeId!) @@ -53,16 +31,21 @@ export class CacheWorker { /** * A cache of dataloaders, see {@link getDataLoader} for usage */ -export default class DataLoaderCache { +export default class DataLoaderCache< + T extends new (...args: any) => any = new (...args: any) => any +> { ttl: number - workers: {[did: string]: CacheWorker} = {} + workers: {[did: string]: CacheWorker>} = {} nextId = 0 - constructor({ttl} = {ttl: 500}) { + DataLoaderWorkerConstructor: T + constructor(DataLoaderWorkerConstructor: T, {ttl} = {ttl: 500}) { + this.DataLoaderWorkerConstructor = DataLoaderWorkerConstructor this.ttl = ttl } - add(did: string, dataLoaderBase: T) { - this.workers[did] = new CacheWorker(dataLoaderBase, did, this) + add(did: string) { + const dataLoaderWorker = new this.DataLoaderWorkerConstructor() + this.workers[did] = new CacheWorker(dataLoaderWorker, did, this) return this.workers[did]! } diff --git a/packages/server/graphql/getDataLoader.ts b/packages/server/graphql/getDataLoader.ts index 8066c9a1f50..a796ecb9ffb 100644 --- a/packages/server/graphql/getDataLoader.ts +++ b/packages/server/graphql/getDataLoader.ts @@ -3,7 +3,7 @@ import numToBase64 from '../utils/numToBase64' import DataLoaderCache from './DataLoaderCache' import getNodeId from './getNodeId' -const dataLoaderCache = new DataLoaderCache() +const dataLoaderCache = new DataLoaderCache(RootDataLoader) const NODE_ID = getNodeId() let nextId = 0 @@ -18,7 +18,7 @@ const getDataLoader = (did?: string) => { // if the viewer is logged in, give them their own dataloader that they can quickly reuse for subsequent requests or share with others // if not logged in, just make a new anonymous loader. const id = did || `${NODE_ID}:${numToBase64(nextId++)}` - return dataLoaderCache.use(id) || dataLoaderCache.add(id, new RootDataLoader()) + return dataLoaderCache.use(id) || dataLoaderCache.add(id) } export default getDataLoader From 3e9e05bc12ca6ddfdb50911157c92931c7a4895c Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Mon, 15 Jul 2024 19:47:57 +0100 Subject: [PATCH 331/529] chore(postgresql): upgrade to v16 (#9976) --- .github/workflows/build.yml | 2 +- .github/workflows/test.yml | 2 +- docker/images/postgres/Dockerfile | 2 +- docker/stacks/development/docker-compose.yml | 7 +++---- docker/stacks/single-tenant-host/docker-compose.yml | 4 +--- 5 files changed, 7 insertions(+), 10 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index d16c22e623f..6c8d455a6f1 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -22,7 +22,7 @@ jobs: id-token: "write" services: postgres: - image: pgvector/pgvector:0.7.0-pg15 + image: pgvector/pgvector:0.7.0-pg16 # This env variables must be the same in the file PARABOL_BUILD_ENV_PATH env: POSTGRES_PASSWORD: "temppassword" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 1c09d7111d6..bf71c93c8cd 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -17,7 +17,7 @@ jobs: id-token: "write" services: postgres: - image: pgvector/pgvector:0.7.0-pg15 + image: pgvector/pgvector:0.7.0-pg16 # This env variables must be the same in the file PARABOL_BUILD_ENV_PATH env: POSTGRES_PASSWORD: "temppassword" diff --git a/docker/images/postgres/Dockerfile b/docker/images/postgres/Dockerfile index 5ef9848889a..6941f8d794b 100644 --- a/docker/images/postgres/Dockerfile +++ b/docker/images/postgres/Dockerfile @@ -1,4 +1,4 @@ -FROM postgres:15.7 +FROM postgres:16.3 ARG PGVECTOR_VERSION=v0.7.0 ARG PSQL_MAJOR_VERSION=15 diff --git a/docker/stacks/development/docker-compose.yml b/docker/stacks/development/docker-compose.yml index 0b73269d898..2715841faf6 100644 --- a/docker/stacks/development/docker-compose.yml +++ b/docker/stacks/development/docker-compose.yml @@ -1,4 +1,3 @@ -version: "3.9" name: parabol-dev services: @@ -11,11 +10,11 @@ services: networks: - parabol-network volumes: + - "./datadog/dd-conf.d:/etc/datadog-agent/conf.d/local.d/" + - "../../../dev/logs:/var/log/datadog/logs" - /var/run/docker.sock:/var/run/docker.sock - /proc/:/host/proc/:ro - /sys/fs/cgroup:/host/sys/fs/cgroup:ro - - ".datadog/dd-conf.d:/etc/datadog-agent/conf.d/local.d/" - - "../../../dev/logs:/var/log/datadog/logs" rethinkdb: image: rethinkdb:2.4.2 restart: unless-stopped @@ -39,7 +38,7 @@ services: networks: - parabol-network pgadmin: - image: dpage/pgadmin4:8.3 + image: dpage/pgadmin4:8.9 depends_on: - postgres env_file: ../../../.env diff --git a/docker/stacks/single-tenant-host/docker-compose.yml b/docker/stacks/single-tenant-host/docker-compose.yml index 93d0ebf8ac1..7a5e74d7dd5 100644 --- a/docker/stacks/single-tenant-host/docker-compose.yml +++ b/docker/stacks/single-tenant-host/docker-compose.yml @@ -1,5 +1,3 @@ -version: "3.9" - services: rethinkdb: container_name: rethinkdb @@ -17,7 +15,7 @@ services: postgres: container_name: postgres profiles: ["databases"] - image: pgvector/pgvector:0.7.0-pg15 + image: pgvector/pgvector:0.7.0-pg16 restart: always env_file: .env environment: From c09bd756bbaf102670106d087bfd306ef9426ae2 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Mon, 15 Jul 2024 15:17:24 -0700 Subject: [PATCH 332/529] chore(release): release v7.38.8 (#9977) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 14 ++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 26 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 475369ad6f0..235d9944aa9 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.38.7" + ".": "7.38.8" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 656a15d1a3b..2b5c2e1d39e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.38.8](https://github.com/ParabolInc/parabol/compare/v7.38.7...v7.38.8) (2024-07-15) + + +### Fixed + +* add ClearAll dataloader method ([#9975](https://github.com/ParabolInc/parabol/issues/9975)) ([63bf930](https://github.com/ParabolInc/parabol/commit/63bf930989f1493f54d36407a990863519b80528)) +* if the content of a task is only spaces, it gets deleted as if i… ([#9968](https://github.com/ParabolInc/parabol/issues/9968)) ([59eb73f](https://github.com/ParabolInc/parabol/commit/59eb73f78c2b9aebaca7f3562d9af1148e37c0ba)) + + +### Changed + +* **postgresql:** upgrade to v16 ([#9976](https://github.com/ParabolInc/parabol/issues/9976)) ([3e9e05b](https://github.com/ParabolInc/parabol/commit/3e9e05bc12ca6ddfdb50911157c92931c7a4895c)) +* **rethinkdb:** OrganizationUser: Phase 3 ([#9965](https://github.com/ParabolInc/parabol/issues/9965)) ([0cff6dc](https://github.com/ParabolInc/parabol/commit/0cff6dcb7308b851b14eeeba08fd6229a59a228c)) + ## [7.38.7](https://github.com/ParabolInc/parabol/compare/v7.38.6...v7.38.7) (2024-07-11) diff --git a/package.json b/package.json index 7ce3c8a461e..6d5e470c742 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.7", + "version": "7.38.8", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 5265fc7acec..1a99804fe7d 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.38.7", + "version": "7.38.8", "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.7" + "parabol-server": "7.38.8" } } diff --git a/packages/client/package.json b/packages/client/package.json index 24c27b75d56..ab4874d7a0f 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.7", + "version": "7.38.8", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 07bcac80f0d..2af9621ce95 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.38.7", + "version": "7.38.8", "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 c3907a446e7..974793819b3 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.38.7", + "version": "7.38.8", "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.7", - "parabol-server": "7.38.7", + "parabol-client": "7.38.8", + "parabol-server": "7.38.8", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index d518821a563..fc15d73a0ea 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.7", + "version": "7.38.8", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 40c02e32bd9..fefdf958196 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.7", + "version": "7.38.8", "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.7", + "parabol-client": "7.38.8", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 4d71de4a6804c819223e73eb85b47a55c2bccac8 Mon Sep 17 00:00:00 2001 From: Rafael Romero Date: Tue, 16 Jul 2024 10:14:52 +0100 Subject: [PATCH 333/529] fix(postgresql): install postgresql-server-dev-16 in the local postgres Docker image --- docker/images/postgres/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/images/postgres/Dockerfile b/docker/images/postgres/Dockerfile index 6941f8d794b..324035f160c 100644 --- a/docker/images/postgres/Dockerfile +++ b/docker/images/postgres/Dockerfile @@ -1,6 +1,6 @@ FROM postgres:16.3 ARG PGVECTOR_VERSION=v0.7.0 -ARG PSQL_MAJOR_VERSION=15 +ARG PSQL_MAJOR_VERSION=16 COPY extensions /extensions From f4a9f11ff652a24edd2d92fb714bc8d293f7d76f Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 16 Jul 2024 09:30:47 -0700 Subject: [PATCH 334/529] fix: pull pgvector from image (#9981) Signed-off-by: Matt Krick --- docker/images/postgres/Dockerfile | 20 ------------------- docker/images/postgres/extensions/install.sql | 1 - docker/stacks/development/docker-compose.yml | 3 +-- 3 files changed, 1 insertion(+), 23 deletions(-) delete mode 100644 docker/images/postgres/Dockerfile delete mode 100644 docker/images/postgres/extensions/install.sql diff --git a/docker/images/postgres/Dockerfile b/docker/images/postgres/Dockerfile deleted file mode 100644 index 324035f160c..00000000000 --- a/docker/images/postgres/Dockerfile +++ /dev/null @@ -1,20 +0,0 @@ -FROM postgres:16.3 -ARG PGVECTOR_VERSION=v0.7.0 -ARG PSQL_MAJOR_VERSION=16 - -COPY extensions /extensions - -RUN apt-get update && apt-get install -y \ - build-essential \ - locales \ - postgresql-server-dev-${PSQL_MAJOR_VERSION} \ - git - -# PGVector -RUN git clone --branch ${PGVECTOR_VERSION} \ - https://github.com/pgvector/pgvector.git /extensions/pgvector && \ - cd extensions/pgvector && make clean && make && make install - -RUN localedef -i en_US -c -f UTF-8 -A /usr/share/locale/locale.alias en_US.UTF-8 - -COPY extensions/install.sql /docker-entrypoint-initdb.d/ diff --git a/docker/images/postgres/extensions/install.sql b/docker/images/postgres/extensions/install.sql deleted file mode 100644 index 5e2d0c13454..00000000000 --- a/docker/images/postgres/extensions/install.sql +++ /dev/null @@ -1 +0,0 @@ -CREATE EXTENSION IF NOT EXISTS "vector"; diff --git a/docker/stacks/development/docker-compose.yml b/docker/stacks/development/docker-compose.yml index 2715841faf6..82ac79c3551 100644 --- a/docker/stacks/development/docker-compose.yml +++ b/docker/stacks/development/docker-compose.yml @@ -27,8 +27,7 @@ services: networks: - parabol-network postgres: - build: - context: "../../images/postgres" + image: pgvector/pgvector:0.7.0-pg16 restart: unless-stopped env_file: ../../../.env ports: From 5cb5a9c19db30af5bbd904146bb561c96659f4d8 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 16 Jul 2024 10:43:18 -0700 Subject: [PATCH 335/529] chore(release): release v7.38.9 (#9980) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 235d9944aa9..c6c0806508f 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.38.8" + ".": "7.38.9" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 2b5c2e1d39e..df800bb7921 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.38.9](https://github.com/ParabolInc/parabol/compare/v7.38.8...v7.38.9) (2024-07-16) + + +### Fixed + +* **postgresql:** install postgresql-server-dev-16 in the local postgres Docker image ([4d71de4](https://github.com/ParabolInc/parabol/commit/4d71de4a6804c819223e73eb85b47a55c2bccac8)) +* pull pgvector from image ([#9981](https://github.com/ParabolInc/parabol/issues/9981)) ([f4a9f11](https://github.com/ParabolInc/parabol/commit/f4a9f11ff652a24edd2d92fb714bc8d293f7d76f)) + ## [7.38.8](https://github.com/ParabolInc/parabol/compare/v7.38.7...v7.38.8) (2024-07-15) diff --git a/package.json b/package.json index 6d5e470c742..d6e0e6be007 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.8", + "version": "7.38.9", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 1a99804fe7d..4572c976ac8 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.38.8", + "version": "7.38.9", "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.8" + "parabol-server": "7.38.9" } } diff --git a/packages/client/package.json b/packages/client/package.json index ab4874d7a0f..72f1eef2f48 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.8", + "version": "7.38.9", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 2af9621ce95..8bd4030a0c3 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.38.8", + "version": "7.38.9", "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 974793819b3..bf615a6fcee 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.38.8", + "version": "7.38.9", "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.8", - "parabol-server": "7.38.8", + "parabol-client": "7.38.9", + "parabol-server": "7.38.9", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index fc15d73a0ea..9aab707fb40 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.8", + "version": "7.38.9", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index fefdf958196..d7624e05302 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.8", + "version": "7.38.9", "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.8", + "parabol-client": "7.38.9", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From cec70635bb76dafb4bd71f661268268e34467e3c Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 16 Jul 2024 12:17:51 -0700 Subject: [PATCH 336/529] chore: parallelize codecheck (#9983) Signed-off-by: Matt Krick --- .github/workflows/style-check.yml | 46 +++++++++++++++++++++++++++++++ .github/workflows/test.yml | 4 +-- package.json | 2 +- 3 files changed, 49 insertions(+), 3 deletions(-) create mode 100644 .github/workflows/style-check.yml diff --git a/.github/workflows/style-check.yml b/.github/workflows/style-check.yml new file mode 100644 index 00000000000..c32ada2e9a6 --- /dev/null +++ b/.github/workflows/style-check.yml @@ -0,0 +1,46 @@ +name: Style Check +on: + push: + branches-ignore: + - "release-please--**" + - "release/v**" +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true +jobs: + style-check: + runs-on: ubuntu-latest + permissions: + contents: "read" + id-token: "write" + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version-file: package.json + # Caching yarn dir & running yarn install is too slow + # Instead, we aggressively cache node_modules below to avoid calling install + + - name: Setup environment variables + run: | + NODE_VERSION=$(jq -r -j '.engines.node|ltrimstr("^")' package.json) + echo NODE_VERSION=$NODE_VERSION >> $GITHUB_ENV + echo NODE_VERSION=$NODE_VERSION + + - name: Get cached node modules + id: cache + uses: actions/cache@v3 + with: + path: | + **/node_modules + key: node_modules-${{ runner.arch }}-${{ env.NODE_VERSION }}-${{ hashFiles('yarn.lock') }} + + - name: Install node_modules + if: steps.cache.outputs.cache-hit != 'true' + run: yarn install --immutable + + - name: Check Code Quality + run: yarn codecheck diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index bf71c93c8cd..b294e8933c6 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -99,8 +99,8 @@ jobs: - name: Kysely Codegen run: yarn pg:generate - - name: Check Code Quality - run: yarn codecheck + - name: Typecheck + run: yarn typecheck - name: Run server tests run: yarn test:server -- --reporters=default --reporters=jest-junit diff --git a/package.json b/package.json index d6e0e6be007..62f539d39e7 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "typecheck": "nx run-many --target=typecheck", "lintcheck": "nx run-many --target=lint:check", "stylecheck": "nx run-many --target=prettier:check", - "codecheck": "concurrently --names \"typecheck,lintcheck,stylecheck\" \"yarn typecheck\" \"yarn lintcheck\" \"yarn stylecheck\" ", + "codecheck": "concurrently --names \"lintcheck,stylecheck\" \"yarn lintcheck\" \"yarn stylecheck\" ", "ultrahook": "export $(grep ^ULTRAHOOK_API_KEY .env | tr -d \"'\") && ultrahook -k $ULTRAHOOK_API_KEY dev 3000", "precommit": "nx run-many --target=precommit --parallel=1", "postcheckout": "node scripts/generateGraphQLArtifacts.js &>/dev/null &", From 02870268ea8b57e31aec28295cd6d5bac9797b8c Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Wed, 17 Jul 2024 20:54:48 +0100 Subject: [PATCH 337/529] fix: colors of the prompts for the threat level retro match now the prompts' names (#9956) --- ...720689742934_fixThreatLevelPromptColors.ts | 42 +++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 packages/server/postgres/migrations/1720689742934_fixThreatLevelPromptColors.ts diff --git a/packages/server/postgres/migrations/1720689742934_fixThreatLevelPromptColors.ts b/packages/server/postgres/migrations/1720689742934_fixThreatLevelPromptColors.ts new file mode 100644 index 00000000000..72cd4145d7c --- /dev/null +++ b/packages/server/postgres/migrations/1720689742934_fixThreatLevelPromptColors.ts @@ -0,0 +1,42 @@ +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' + +export async function up() { + await connectRethinkDB() + + await r + .table('ReflectPrompt') + .get('threatLevelPremortemTemplateSeverePrompt') + .update({groupColor: '#FD6157'}) + .run() + await r + .table('ReflectPrompt') + .get('threatLevelPremortemTemplateElevatedPrompt') + .update({groupColor: '#FFCC63'}) + .run() + await r + .table('ReflectPrompt') + .get('threatLevelPremortemTemplateLowPrompt') + .update({groupColor: '#66BC8C'}) + .run() +} + +export async function down() { + await connectRethinkDB() + + await r + .table('ReflectPrompt') + .get('threatLevelPremortemTemplateSeverePrompt') + .update({groupColor: '#66BC8C'}) + .run() + await r + .table('ReflectPrompt') + .get('threatLevelPremortemTemplateElevatedPrompt') + .update({groupColor: '#FD6157'}) + .run() + await r + .table('ReflectPrompt') + .get('threatLevelPremortemTemplateLowPrompt') + .update({groupColor: '#FFCC63'}) + .run() +} From 4f883fe7e557d4f6d121bc11ab68b30646d3d5e4 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 17 Jul 2024 14:19:06 -0700 Subject: [PATCH 338/529] fix: SAML return values from dataloader (#9991) Signed-off-by: Matt Krick --- packages/server/dataloader/customLoaderMakers.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 791a2725b35..8443e74599f 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -715,7 +715,8 @@ export const samlByDomain = (parent: RootDataLoader, dependsOn: RegisterDependsO .selectAll('SAML') .select(({fn}) => [fn.agg('array_agg', ['SAMLDomain.domain']).as('domains')]) .execute() - return normalizeResults(domains, res) + // not the same as normalizeResults + return domains.map((domain) => res.find((row) => row.domains.includes(domain))) }, { ...parent.dataLoaderOptions @@ -735,7 +736,8 @@ export const samlByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn .selectAll('SAML') .select(({fn}) => [fn.agg('array_agg', ['SAMLDomain.domain']).as('domains')]) .execute() - return normalizeResults(orgIds, res) + // not the same as normalizeResults + return orgIds.map((orgId) => res.find((row) => row.orgId === orgId)) }, { ...parent.dataLoaderOptions From 01118399562f4df17fb3f67c8f112fb24a54aefb Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 17 Jul 2024 14:23:42 -0700 Subject: [PATCH 339/529] chore(release): release v7.38.10 (#9984) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index c6c0806508f..4d7295d26c4 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.38.9" + ".": "7.38.10" } diff --git a/CHANGELOG.md b/CHANGELOG.md index df800bb7921..b617a3f406c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.38.10](https://github.com/ParabolInc/parabol/compare/v7.38.9...v7.38.10) (2024-07-17) + + +### Fixed + +* colors of the prompts for the threat level retro match now the prompts' names ([#9956](https://github.com/ParabolInc/parabol/issues/9956)) ([0287026](https://github.com/ParabolInc/parabol/commit/02870268ea8b57e31aec28295cd6d5bac9797b8c)) +* SAML return values from dataloader ([#9991](https://github.com/ParabolInc/parabol/issues/9991)) ([4f883fe](https://github.com/ParabolInc/parabol/commit/4f883fe7e557d4f6d121bc11ab68b30646d3d5e4)) + + +### Changed + +* parallelize codecheck ([#9983](https://github.com/ParabolInc/parabol/issues/9983)) ([cec7063](https://github.com/ParabolInc/parabol/commit/cec70635bb76dafb4bd71f661268268e34467e3c)) + ## [7.38.9](https://github.com/ParabolInc/parabol/compare/v7.38.8...v7.38.9) (2024-07-16) diff --git a/package.json b/package.json index 62f539d39e7..d9eb8c2c99d 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.9", + "version": "7.38.10", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 4572c976ac8..f6ecbc6abc2 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.38.9", + "version": "7.38.10", "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.9" + "parabol-server": "7.38.10" } } diff --git a/packages/client/package.json b/packages/client/package.json index 72f1eef2f48..3b8b0fa106c 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.9", + "version": "7.38.10", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 8bd4030a0c3..a69d556fe61 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.38.9", + "version": "7.38.10", "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 bf615a6fcee..051d70fcf8e 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.38.9", + "version": "7.38.10", "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.9", - "parabol-server": "7.38.9", + "parabol-client": "7.38.10", + "parabol-server": "7.38.10", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 9aab707fb40..ba52f6301c5 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.9", + "version": "7.38.10", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index d7624e05302..8207d84ee74 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.9", + "version": "7.38.10", "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.9", + "parabol-client": "7.38.10", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 5c453794fa3ebb6f1948a2f6c8c7f70be2a0d009 Mon Sep 17 00:00:00 2001 From: Jordan Husney Date: Thu, 18 Jul 2024 11:41:46 -0700 Subject: [PATCH 340/529] fix: Filipino checkin greeting (#9997) The `content` and `language` fields of the Filipino checkin question were reversed --- packages/client/utils/makeCheckinGreeting.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/utils/makeCheckinGreeting.ts b/packages/client/utils/makeCheckinGreeting.ts index 09203e4595f..bab54693d96 100644 --- a/packages/client/utils/makeCheckinGreeting.ts +++ b/packages/client/utils/makeCheckinGreeting.ts @@ -76,8 +76,8 @@ const greetings = [ language: 'Arabic' }, { - content: 'Filipino', - language: 'Kumusta' + content: 'Kumusta', + language: 'Filipino' }, { content: 'Pryvit', From 6d01097ef68bbbe33eea1faf1367d7ad6120f1db Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 18 Jul 2024 14:51:14 -0700 Subject: [PATCH 341/529] chore: move some integrations to SDL pattern (#10000) Signed-off-by: Matt Krick --- codegen.json | 20 ++ .../server/dataloader/azureDevOpsLoaders.ts | 4 +- .../server/dataloader/jiraServerLoaders.ts | 6 +- .../graphql/private/typeDefs/_legacy.graphql | 198 ------------- packages/server/graphql/public/rootSchema.ts | 48 +-- .../typeDefs/AtlassianIntegration.graphql | 60 +++- .../typeDefs/AzureDevOpsIntegration.graphql | 91 ++++++ .../typeDefs/AzureDevOpsRemoteProject.graphql | 29 ++ .../typeDefs/AzureDevOpsSearchQuery.graphql | 29 ++ .../typeDefs/AzureDevOpsWorkItem.graphql | 80 +++++ .../public/typeDefs/GitHubIntegration.graphql | 51 ++++ .../public/typeDefs/GitHubSearchQuery.graphql | 19 ++ .../public/typeDefs/GitLabIntegration.graphql | 84 ++++++ .../public/typeDefs/GitLabSearchQuery.graphql | 24 ++ .../public/typeDefs/JiraSearchQuery.graphql | 29 ++ .../typeDefs/JiraServerIntegration.graphql | 49 ++++ .../public/typeDefs/JiraServerIssue.graphql | 102 +++++++ .../typeDefs/MSTeamsIntegration.graphql | 14 + .../typeDefs/MattermostIntegration.graphql | 14 + .../public/typeDefs/Organization.graphql | 58 +++- .../graphql/public/typeDefs/PageInfo.graphql | 24 ++ .../typeDefs/PageInfoDateCursor.graphql | 24 ++ .../public/typeDefs/SlackIntegration.graphql | 74 +++++ .../typeDefs/TeamMemberIntegrations.graphql | 10 +- .../graphql/public/typeDefs/_legacy.graphql | 277 ------------------ .../public/types/AtlassianIntegration.ts | 34 ++- .../public/types/AzureDevOpsIntegration.ts | 77 +++++ .../public/types/AzureDevOpsRemoteProject.ts | 14 + .../public/types/AzureDevOpsSearchQuery.ts | 7 + .../public/types/AzureDevOpsWorkItem.ts | 25 ++ .../graphql/public/types/GitHubIntegration.ts | 30 ++ .../graphql/public/types/GitLabIntegration.ts | 155 ++++++++++ .../graphql/public/types/JiraSearchQuery.ts | 8 + .../public/types/JiraServerIntegration.ts | 208 +++++++++++++ .../graphql/public/types/JiraServerIssue.ts | 46 +++ .../public/types/JiraServerRemoteProject.ts | 25 ++ .../public/types/MSTeamsIntegration.ts | 24 ++ .../public/types/MattermostIntegration.ts | 27 ++ .../graphql/public/types/Organization.ts | 101 ++++++- .../graphql/public/types/SlackIntegration.ts | 18 ++ .../public/types/TeamMemberIntegrations.ts | 31 +- .../graphql/public/types/_xGitLabProject.ts | 9 + packages/server/graphql/rootTypes.ts | 2 - .../graphql/types/AtlassianIntegration.ts | 90 +----- .../graphql/types/AzureDevOpsIntegration.ts | 170 ----------- .../graphql/types/AzureDevOpsRemoteProject.ts | 59 ---- .../graphql/types/AzureDevOpsSearchQuery.ts | 41 --- .../graphql/types/AzureDevOpsWorkItem.ts | 102 ------- .../server/graphql/types/GitHubIntegration.ts | 81 +---- .../server/graphql/types/GitHubSearchQuery.ts | 25 -- .../server/graphql/types/GitLabIntegration.ts | 242 --------------- .../server/graphql/types/GitLabSearchQuery.ts | 29 -- .../server/graphql/types/JiraSearchQuery.ts | 40 --- .../graphql/types/JiraServerIntegration.ts | 265 ----------------- .../server/graphql/types/JiraServerIssue.ts | 117 -------- .../graphql/types/JiraServerRemoteProject.ts | 61 ---- .../graphql/types/MSTeamsIntegration.ts | 35 --- .../graphql/types/MattermostIntegration.ts | 43 --- packages/server/graphql/types/Organization.ts | 238 +-------------- .../server/graphql/types/SlackIntegration.ts | 84 +----- .../graphql/types/TeamMemberIntegrations.ts | 71 +---- 61 files changed, 1745 insertions(+), 2307 deletions(-) create mode 100644 packages/server/graphql/public/typeDefs/AzureDevOpsIntegration.graphql create mode 100644 packages/server/graphql/public/typeDefs/AzureDevOpsRemoteProject.graphql create mode 100644 packages/server/graphql/public/typeDefs/AzureDevOpsSearchQuery.graphql create mode 100644 packages/server/graphql/public/typeDefs/AzureDevOpsWorkItem.graphql create mode 100644 packages/server/graphql/public/typeDefs/GitHubIntegration.graphql create mode 100644 packages/server/graphql/public/typeDefs/GitHubSearchQuery.graphql create mode 100644 packages/server/graphql/public/typeDefs/GitLabIntegration.graphql create mode 100644 packages/server/graphql/public/typeDefs/GitLabSearchQuery.graphql create mode 100644 packages/server/graphql/public/typeDefs/JiraSearchQuery.graphql create mode 100644 packages/server/graphql/public/typeDefs/JiraServerIntegration.graphql create mode 100644 packages/server/graphql/public/typeDefs/JiraServerIssue.graphql create mode 100644 packages/server/graphql/public/typeDefs/MSTeamsIntegration.graphql create mode 100644 packages/server/graphql/public/typeDefs/MattermostIntegration.graphql create mode 100644 packages/server/graphql/public/typeDefs/PageInfo.graphql create mode 100644 packages/server/graphql/public/typeDefs/PageInfoDateCursor.graphql create mode 100644 packages/server/graphql/public/typeDefs/SlackIntegration.graphql create mode 100644 packages/server/graphql/public/types/AzureDevOpsIntegration.ts create mode 100644 packages/server/graphql/public/types/AzureDevOpsRemoteProject.ts create mode 100644 packages/server/graphql/public/types/AzureDevOpsSearchQuery.ts create mode 100644 packages/server/graphql/public/types/AzureDevOpsWorkItem.ts create mode 100644 packages/server/graphql/public/types/GitHubIntegration.ts create mode 100644 packages/server/graphql/public/types/GitLabIntegration.ts create mode 100644 packages/server/graphql/public/types/JiraSearchQuery.ts create mode 100644 packages/server/graphql/public/types/JiraServerIntegration.ts create mode 100644 packages/server/graphql/public/types/JiraServerIssue.ts create mode 100644 packages/server/graphql/public/types/JiraServerRemoteProject.ts create mode 100644 packages/server/graphql/public/types/MSTeamsIntegration.ts create mode 100644 packages/server/graphql/public/types/MattermostIntegration.ts create mode 100644 packages/server/graphql/public/types/SlackIntegration.ts delete mode 100644 packages/server/graphql/types/AzureDevOpsIntegration.ts delete mode 100644 packages/server/graphql/types/AzureDevOpsRemoteProject.ts delete mode 100644 packages/server/graphql/types/AzureDevOpsSearchQuery.ts delete mode 100644 packages/server/graphql/types/AzureDevOpsWorkItem.ts delete mode 100644 packages/server/graphql/types/GitHubSearchQuery.ts delete mode 100644 packages/server/graphql/types/GitLabIntegration.ts delete mode 100644 packages/server/graphql/types/GitLabSearchQuery.ts delete mode 100644 packages/server/graphql/types/JiraSearchQuery.ts delete mode 100644 packages/server/graphql/types/JiraServerIntegration.ts delete mode 100644 packages/server/graphql/types/JiraServerIssue.ts delete mode 100644 packages/server/graphql/types/JiraServerRemoteProject.ts delete mode 100644 packages/server/graphql/types/MSTeamsIntegration.ts delete mode 100644 packages/server/graphql/types/MattermostIntegration.ts diff --git a/codegen.json b/codegen.json index b4f07f4e750..8b189ff16ad 100644 --- a/codegen.json +++ b/codegen.json @@ -43,7 +43,19 @@ "packages/server/graphql/public/resolverTypes.ts": { "config": { "contextType": "../graphql#GQLContext", + "showUnusedMappers": false, "mappers": { + "_xGitLabProject": "./types/_xGitLabProject#_xGitLabProjectSource as _xGitLabProject", + "JiraServerIntegration": "./types/JiraServerIntegration#JiraServerIntegrationSource", + "GitHubIntegration": "../../postgres/queries/getGitHubAuthByUserIdTeamId#GitHubAuth", + "GitLabIntegration": "./types/GitLabIntegration#GitLabIntegrationSource", + "MattermostIntegration": "./types/MattermostIntegration#MattermostIntegrationSource", + "MSTeamsIntegration": "./types/MSTeamsIntegration#MSTeamsIntegrationSource", + "SlackIntegration": "../../database/types/SlackAuth#default as SlackAuthDB", + "SlackNotification": "../../database/types/SlackNotification#default as SlackNotificationDB", + "AzureDevOpsIntegration": ".types/AzureDevOpsIntegration#AzureDevOpsIntegrationSource", + "AzureDevOpsWorkItem": "../../dataloader/azureDevOpsLoaders#AzureDevOpsWorkItem", + "AzureDevOpsRemoteProject": "./types/AzureDevOpsRemoteProject#AzureDevOpsRemoteProjectSource", "AcceptRequestToJoinDomainSuccess": "./types/AcceptRequestToJoinDomainSuccess#AcceptRequestToJoinDomainSuccessSource", "AcceptTeamInvitationPayload": "./types/AcceptTeamInvitationPayload#AcceptTeamInvitationPayloadSource", "ActionMeeting": "../../database/types/MeetingAction#default", @@ -56,6 +68,8 @@ "AddedNotification": "./types/AddedNotification#AddedNotificationSource", "AgendaItem": "../../database/types/AgendaItem#default as AgendaItemDB", "ArchiveTeamPayload": "./types/ArchiveTeamPayload#ArchiveTeamPayloadSource", + "AtlassianIntegration": "../../postgres/queries/getAtlassianAuthByUserIdTeamId#AtlassianAuth as AtlassianAuthDB", + "JiraSearchQuery": "../../database/types/JiraSearchQuery#default as JiraSearchQueryDB", "AuthTokenPayload": "./types/AuthTokenPayload#AuthTokenPayloadSource", "AutogroupSuccess": "./types/AutogroupSuccess#AutogroupSuccessSource", "BatchArchiveTasksSuccess": "./types/BatchArchiveTasksSuccess#BatchArchiveTasksSuccessSource", @@ -71,8 +85,12 @@ "GcalIntegration": "./types/GcalIntegration#GcalIntegrationSource", "GenerateGroupsSuccess": "./types/GenerateGroupsSuccess#GenerateGroupsSuccessSource", "GetTemplateSuggestionSuccess": "./types/GetTemplateSuggestionSuccess#GetTemplateSuggestionSuccessSource", + "IntegrationProviderWebhook": "../../postgres/queries/getIntegrationProvidersByIds#TIntegrationProvider", + "IntegrationProviderOAuth1": "../../postgres/queries/getIntegrationProvidersByIds#TIntegrationProvider", "IntegrationProviderOAuth2": "../../postgres/queries/getIntegrationProvidersByIds#TIntegrationProvider", "InviteToTeamPayload": "./types/InviteToTeamPayload#InviteToTeamPayloadSource", + "JiraServerIssue": "./types/JiraServerIssue#JiraServerIssueSource", + "JiraServerRemoteProject": "../../dataloader/jiraServerLoaders#JiraServerProject", "JiraIssue": "./types/JiraIssue#JiraIssueSource", "JiraRemoteProject": "../types/JiraRemoteProject#JiraRemoteProjectSource", "MeetingSeries": "../../postgres/types/MeetingSeries#MeetingSeries", @@ -126,6 +144,8 @@ "TeamHealthStage": "./types/TeamHealthStage#TeamHealthStageSource", "TeamInvitation": "../../database/types/TeamInvitation#default", "TeamMember": "../../database/types/TeamMember#default as TeamMemberDB", + "TeamMemberIntegrationAuthWebhook": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", + "TeamMemberIntegrationAuthOAuth1": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", "TeamMemberIntegrationAuthOAuth2": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", "TeamMemberIntegrations": "./types/TeamMemberIntegrations#TeamMemberIntegrationsSource", "TeamPromptMeeting": "../../database/types/MeetingTeamPrompt#default as MeetingTeamPromptDB", diff --git a/packages/server/dataloader/azureDevOpsLoaders.ts b/packages/server/dataloader/azureDevOpsLoaders.ts index 87d3c968903..e9fb91d6df6 100644 --- a/packages/server/dataloader/azureDevOpsLoaders.ts +++ b/packages/server/dataloader/azureDevOpsLoaders.ts @@ -103,6 +103,8 @@ export interface AzureDevOpsWorkItem { type: string descriptionHTML: string service: 'azureDevOps' + teamId: string + userId: string } export interface AzureUserInfo { @@ -123,7 +125,7 @@ export interface AzureAccountProject extends TeamProjectReference { service: 'azureDevOps' } -interface AzureProject extends ProjectRes { +export interface AzureProject extends ProjectRes { userId: string teamId: string service: 'azureDevOps' diff --git a/packages/server/dataloader/jiraServerLoaders.ts b/packages/server/dataloader/jiraServerLoaders.ts index 7f7d200b979..0cd151234fe 100644 --- a/packages/server/dataloader/jiraServerLoaders.ts +++ b/packages/server/dataloader/jiraServerLoaders.ts @@ -55,6 +55,8 @@ type TeamUserKey = { export type JiraServerProject = JiraServerRestProject & { service: 'jiraServer' providerId: number + userId: string + teamId: string } export const jiraServerIssue = (parent: RootDataLoader) => { @@ -123,7 +125,9 @@ export const allJiraServerProjects = (parent: RootDataLoader) => { .map((project) => ({ ...project, service: 'jiraServer' as const, - providerId: provider.id + providerId: provider.id, + userId, + teamId })) }) ) diff --git a/packages/server/graphql/private/typeDefs/_legacy.graphql b/packages/server/graphql/private/typeDefs/_legacy.graphql index 7530f49fefe..a8291647a0b 100644 --- a/packages/server/graphql/private/typeDefs/_legacy.graphql +++ b/packages/server/graphql/private/typeDefs/_legacy.graphql @@ -982,35 +982,6 @@ type StandardMutationError { message: String! } -""" -A jira search query including all filters selected when the query was executed -""" -type JiraSearchQuery { - """ - shortid - """ - id: ID! - - """ - The query string, either simple or JQL depending on the isJQL flag - """ - queryString: String! - - """ - true if the queryString is JQL, else false - """ - isJQL: Boolean! - - """ - The list of project keys selected as a filter. null if not set - """ - projectKeyFilters: [ID!]! - - """ - the time the search query was last used. Used for sorting - """ - lastUsedAt: DateTime! -} """ The auth credentials for a token, specific to a team member @@ -1057,97 +1028,8 @@ interface TeamMemberIntegrationAuth { provider: IntegrationProvider! } -""" -A connection to a list of items. -""" -type JiraServerIssueConnection { - """ - Page info with cursors coerced to ISO8601 dates - """ - pageInfo: PageInfo - - """ - A list of edges. - """ - edges: [JiraServerIssueEdge!]! - - """ - An error with the connection, if any - """ - error: StandardMutationError -} - -""" -An edge in a connection. -""" -type JiraServerIssueEdge { - """ - The item at the end of the edge - """ - node: JiraServerIssue! - cursor: String -} - -""" -The Jira Issue that comes direct from Jira Server -""" -type JiraServerIssue implements TaskIntegration { - id: ID! - issueKey: ID! - projectKey: ID! - projectName: String! - - """ - The parabol teamId this issue was fetched for - """ - teamId: ID! - - """ - The parabol userId this issue was fetched for - """ - userId: ID! - - """ - The url to access the issue - """ - url: String! - - """ - The plaintext summary of the jira issue - """ - summary: String! - description: String! - - """ - The description converted into raw HTML - """ - descriptionHTML: String! - - """ - The timestamp the issue was last updated - """ - updatedAt: DateTime! -} - -""" -A GitHub search query including all filters selected when the query was executed -""" -type GitHubSearchQuery { - """ - shortid - """ - id: ID! - """ - The query string in GitHub format, including repository filters. e.g. is:issue is:open - """ - queryString: String! - """ - the time the search query was last used. Used for sorting - """ - lastUsedAt: DateTime! -} """ The event that triggers a slack notification @@ -1174,86 +1056,6 @@ enum SlackNotificationEventTypeEnum { member } -""" -The Azure DevOps auth + integration helpers for a specific team member -""" -type AzureDevOpsIntegration { - """ - The OAuth2 Authorization for this team member - """ - auth: TeamMemberIntegrationAuthOAuth2 - - """ - Composite key in ado:teamId:userId format - """ - id: ID! - - """ - true if the auth is valid, else false - """ - isActive: Boolean! - - """ - The access token to Azure DevOps. null if no access token available or the viewer is not the user - """ - accessToken: ID - - """ - The Azure DevOps account ID - """ - accountId: ID! - - """ - The Azure DevOps instance IDs that the user has granted - """ - instanceIds: [ID!]! - - """ - The timestamp the provider was created - """ - createdAt: DateTime! - - """ - The team that the token is linked to - """ - teamId: ID! - - """ - The timestamp the token was updated at - """ - updatedAt: DateTime! - - """ - The user that the access token is attached to - """ - userId: ID! - - """ - The cloud provider the team member may choose to integrate with. Nullable based on env vars - """ - cloudProvider: IntegrationProviderOAuth2 - - """ - The non-global providers shared with the team or organization - """ - sharedProviders: [IntegrationProviderOAuth2!]! -} - -""" -The Azure DevOps Issue that comes direct from Azure DevOps -""" -type AzureDevOpsWorkItem { - """ - GUID instanceId:issueKey - """ - id: ID! - - """ - URL to the issue - """ - url: String! -} - """ All the user details for a specific meeting """ diff --git a/packages/server/graphql/public/rootSchema.ts b/packages/server/graphql/public/rootSchema.ts index e65b1459189..9ae8dce9f77 100644 --- a/packages/server/graphql/public/rootSchema.ts +++ b/packages/server/graphql/public/rootSchema.ts @@ -21,8 +21,8 @@ import permissions from './permissions' // Resolvers from SDL first definitions import resolvers from './resolvers' -// Schema from legacy TypeScript first definitions -const legacyParabolSchema = new GraphQLSchema({ +// Schema from legacy TypeScript first definitions instead of SDL pattern +const legacyTypeDefs = new GraphQLSchema({ query, mutation, // defining a placeholder subscription because there's a bug in nest-graphql-schema that prefixes to _xGitHubSubscription if missing @@ -30,8 +30,18 @@ const legacyParabolSchema = new GraphQLSchema({ types: rootTypes }) -const {schema: legacyParabolWithGitHubSchema, githubRequest} = nestGitHubEndpoint({ - parentSchema: legacyParabolSchema, +const importAllStrings = (context: __WebpackModuleApi.RequireContext) => { + return context.keys().map((id) => context(id).default) +} + +// Merge old POJO definitions with SDL definitions +const parabolTypeDefs = mergeSchemas({ + schemas: [legacyTypeDefs], + typeDefs: importAllStrings(require.context('./typeDefs', false, /.graphql$/)) +}) + +const {schema: typeDefsWithGitHub, githubRequest} = nestGitHubEndpoint({ + parentSchema: parabolTypeDefs, parentType: 'GitHubIntegration', fieldName: 'api', resolveEndpointContext: ({accessToken}) => ({ @@ -42,8 +52,8 @@ const {schema: legacyParabolWithGitHubSchema, githubRequest} = nestGitHubEndpoin schemaIDL: githubSchema }) -const {schema: legacyParabolWithGitLabSchema, gitlabRequest} = nestGitLabEndpoint({ - parentSchema: legacyParabolSchema, +const {schema: typeDefsWithGitHubGitLab, gitlabRequest} = nestGitLabEndpoint({ + parentSchema: typeDefsWithGitHub, parentType: 'GitLabIntegration', fieldName: 'api', resolveEndpointContext: async ( @@ -67,25 +77,15 @@ const {schema: legacyParabolWithGitLabSchema, gitlabRequest} = nestGitLabEndpoin schemaIDL: gitlabSchema }) -const importAllStrings = (context: __WebpackModuleApi.RequireContext) => { - return context.keys().map((id) => context(id).default) -} - -// Types from SDL first -const typeDefs = importAllStrings(require.context('./typeDefs', false, /.graphql$/)) - -const legacyParabolWithNestedSchema = mergeSchemas({ - schemas: [legacyParabolWithGitHubSchema, legacyParabolWithGitLabSchema], - typeDefs -}) - // IMPORTANT! mergeSchemas has a bug where resolvers will be overwritten by the default resolvers // See https://github.com/ardatan/graphql-tools/issues/4367 -const parabolWithNestedResolversSchema = addResolversToSchema({ - schema: legacyParabolWithNestedSchema, - resolvers: composeResolvers(resolvers, permissions), - inheritResolversFromInterfaces: true -}) +const publicSchema = resolveTypesForMutationPayloads( + addResolversToSchema({ + schema: typeDefsWithGitHubGitLab, + resolvers: composeResolvers(resolvers, permissions), + inheritResolversFromInterfaces: true + }) +) const addRequestors = (schema: GraphQLSchema) => { const finalSchema = schema as any @@ -97,6 +97,6 @@ const addRequestors = (schema: GraphQLSchema) => { } } -const rootSchema = addRequestors(resolveTypesForMutationPayloads(parabolWithNestedResolversSchema)) +const rootSchema = addRequestors(publicSchema) export default rootSchema diff --git a/packages/server/graphql/public/typeDefs/AtlassianIntegration.graphql b/packages/server/graphql/public/typeDefs/AtlassianIntegration.graphql index c40ed3b2f59..d8cdf020b6d 100644 --- a/packages/server/graphql/public/typeDefs/AtlassianIntegration.graphql +++ b/packages/server/graphql/public/typeDefs/AtlassianIntegration.graphql @@ -1,4 +1,62 @@ -extend type AtlassianIntegration { +""" +The atlassian auth + integration helpers for a specific team member +""" +type AtlassianIntegration { + """ + Composite key in atlassiani:teamId:userId format + """ + id: ID! + + """ + true if the auth is valid, else false + """ + isActive: Boolean! + + """ + The access token to atlassian, useful for 1 hour. null if no access token available or the viewer is not the user + """ + accessToken: ID + + """ + *The atlassian account ID + """ + accountId: ID! + + """ + The atlassian cloud IDs that the user has granted + """ + cloudIds: [ID!]! + + """ + The timestamp the provider was created + """ + createdAt: DateTime! + + """ + *The team that the token is linked to + """ + teamId: ID! + + """ + The timestamp the token was updated at + """ + updatedAt: DateTime! + + """ + The user that the access token is attached to + """ + userId: ID! + + """ + A list of projects accessible by this team member. empty if viewer is not the user + """ + projects: [JiraRemoteProject!]! + + """ + the list of suggested search queries, sorted by most recent. Guaranteed to be < 60 days old + """ + jiraSearchQueries: [JiraSearchQuery!]! + """ A list of issues coming straight from the jira integration for a specific team member """ diff --git a/packages/server/graphql/public/typeDefs/AzureDevOpsIntegration.graphql b/packages/server/graphql/public/typeDefs/AzureDevOpsIntegration.graphql new file mode 100644 index 00000000000..b181d25642e --- /dev/null +++ b/packages/server/graphql/public/typeDefs/AzureDevOpsIntegration.graphql @@ -0,0 +1,91 @@ +""" +The Azure DevOps auth + integration helpers for a specific team member +""" +type AzureDevOpsIntegration { + """ + The OAuth2 Authorization for this team member + """ + auth: TeamMemberIntegrationAuthOAuth2 + + """ + Composite key in ado:teamId:userId format + """ + id: ID! + + """ + The Azure DevOps account ID + """ + accountId: ID! + + """ + The Azure DevOps instance IDs that the user has granted + """ + instanceIds: [ID!]! + + """ + The timestamp the provider was created + """ + createdAt: DateTime! + + """ + The team that the token is linked to + """ + teamId: ID! + + """ + The timestamp the token was updated at + """ + updatedAt: DateTime! + + """ + The user that the access token is attached to + """ + userId: ID! + + """ + A list of work items coming straight from the azure dev ops integration for a specific team member + """ + workItems( + first: Int = 100 + + """ + the datetime cursor + """ + after: DateTime + + """ + A string of text to search for, or WIQL if isWIQL is true + """ + queryString: String + + """ + A list of projects to restrict the search to, if null will search all + """ + projectKeyFilters: [String!]! + + """ + true if the queryString is WIQL, else false + """ + isWIQL: Boolean! + ): AzureDevOpsWorkItemConnection! + + """ + A list of projects coming straight from the azure dev ops integration for a specific team member + """ + projects: [AzureDevOpsRemoteProject!]! + + """ + The cloud provider the team member may choose to integrate with. Nullable based on env vars + """ + cloudProvider: IntegrationProviderOAuth2 + + """ + The non-global providers shared with the team or organization + """ + sharedProviders: [IntegrationProviderOAuth2!]! + + """ + the list of suggested search queries, sorted by most recent. Guaranteed to be < 60 days old + """ + azureDevOpsSearchQueries: [AzureDevOpsSearchQuery!]! +} diff --git a/packages/server/graphql/public/typeDefs/AzureDevOpsRemoteProject.graphql b/packages/server/graphql/public/typeDefs/AzureDevOpsRemoteProject.graphql new file mode 100644 index 00000000000..facf1700511 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/AzureDevOpsRemoteProject.graphql @@ -0,0 +1,29 @@ +""" +A project fetched from Azure DevOps in real time +""" +type AzureDevOpsRemoteProject implements RepoIntegration { + id: ID! + service: IntegrationProviderServiceEnum! + + """ + The parabol teamId this issue was fetched for + """ + teamId: ID! + + """ + The parabol userId this issue was fetched for + """ + userId: ID! + lastUpdateTime: DateTime! + self: ID! + + """ + The instance ID that the project lives on + """ + instanceId: ID! + name: String! + revision: Int! + state: String! + url: String! + visibility: String! +} diff --git a/packages/server/graphql/public/typeDefs/AzureDevOpsSearchQuery.graphql b/packages/server/graphql/public/typeDefs/AzureDevOpsSearchQuery.graphql new file mode 100644 index 00000000000..dddd73fa718 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/AzureDevOpsSearchQuery.graphql @@ -0,0 +1,29 @@ +""" +An Azure DevOps search query including all filters selected when the query was executed +""" +type AzureDevOpsSearchQuery { + """ + shortid + """ + id: ID! + + """ + The query string, either simple or WIQL depending on the isWIQL flag + """ + queryString: String! + + """ + A list of projects to restrict the search to + """ + projectKeyFilters: [String!]! + + """ + true if the queryString is WIQL, else false + """ + isWIQL: Boolean! + + """ + the time the search query was last used. Used for sorting + """ + lastUsedAt: DateTime! +} diff --git a/packages/server/graphql/public/typeDefs/AzureDevOpsWorkItem.graphql b/packages/server/graphql/public/typeDefs/AzureDevOpsWorkItem.graphql new file mode 100644 index 00000000000..9a95773dec4 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/AzureDevOpsWorkItem.graphql @@ -0,0 +1,80 @@ +""" +The Azure DevOps Work Item that comes direct from Azure DevOps +""" +type AzureDevOpsWorkItem implements TaskIntegration { + """ + GUID instanceId:projectKey:issueKey + """ + id: ID! + + """ + URL to the issue + """ + url: String! + + """ + The id of the issue from Azure, e.g. 7 + """ + issueKey: String! + + """ + Title of the work item + """ + title: String! + + """ + Name or id of the Team Project the work item belongs to + """ + teamProject: String! + + """ + The Azure DevOps Remote Project the work item belongs to + """ + project: AzureDevOpsRemoteProject! + + """ + The Current State of the Work item + """ + state: String! + + """ + The Type of the Work item + """ + type: String! + + """ + The description converted into raw HTML + """ + descriptionHTML: String! +} + +""" +A connection to a list of items. +""" +type AzureDevOpsWorkItemConnection { + """ + Page info with cursors coerced to ISO8601 dates + """ + pageInfo: PageInfoDateCursor + + """ + A list of edges. + """ + edges: [AzureDevOpsWorkItemEdge!]! + + """ + An error with the connection, if any + """ + error: StandardMutationError +} + +""" +An edge in a connection. +""" +type AzureDevOpsWorkItemEdge { + """ + The item at the end of the edge + """ + node: AzureDevOpsWorkItem! + cursor: DateTime +} diff --git a/packages/server/graphql/public/typeDefs/GitHubIntegration.graphql b/packages/server/graphql/public/typeDefs/GitHubIntegration.graphql new file mode 100644 index 00000000000..e84cd02dd81 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/GitHubIntegration.graphql @@ -0,0 +1,51 @@ +type GitHubIntegration { + """ + composite key + """ + id: ID! + + """ + The access token to github. good forever + """ + accessToken: ID + + """ + The timestamp the provider was created + """ + createdAt: DateTime! + + """ + true if an access token exists, else false + """ + isActive: Boolean! + + """ + the list of suggested search queries, sorted by most recent. Guaranteed to be < 60 days old + """ + githubSearchQueries: [GitHubSearchQuery!]! + + """ + *The GitHub login used for queries + """ + login: ID! + + """ + The comma-separated list of scopes requested from GitHub + """ + scope: String! + + """ + *The team that the token is linked to + """ + teamId: ID! + + """ + The timestamp the token was updated at + """ + updatedAt: DateTime! + + """ + The user that the access token is attached to + """ + userId: ID! +} diff --git a/packages/server/graphql/public/typeDefs/GitHubSearchQuery.graphql b/packages/server/graphql/public/typeDefs/GitHubSearchQuery.graphql new file mode 100644 index 00000000000..c8ed9ca3a71 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/GitHubSearchQuery.graphql @@ -0,0 +1,19 @@ +""" +A GitHub search query including all filters selected when the query was executed +""" +type GitHubSearchQuery { + """ + shortid + """ + id: ID! + + """ + The query string in GitHub format, including repository filters. e.g. is:issue is:open + """ + queryString: String! + + """ + the time the search query was last used. Used for sorting + """ + lastUsedAt: DateTime! +} diff --git a/packages/server/graphql/public/typeDefs/GitLabIntegration.graphql b/packages/server/graphql/public/typeDefs/GitLabIntegration.graphql new file mode 100644 index 00000000000..6308f2a967a --- /dev/null +++ b/packages/server/graphql/public/typeDefs/GitLabIntegration.graphql @@ -0,0 +1,84 @@ +""" +Gitlab integration data for a given team member +""" +type GitLabIntegration { + """ + The OAuth2 Authorization for this team member + """ + auth: TeamMemberIntegrationAuthOAuth2 + + """ + The cloud provider the team member may choose to integrate with. Nullable based on env vars + """ + cloudProvider: IntegrationProviderOAuth2 + + """ + The non-global providers shared with the team or organization + """ + sharedProviders: [IntegrationProviderOAuth2!]! + gitlabSearchQueries: [GitLabSearchQuery!]! + + """ + A list of projects accessible by this team member + """ + projects: [RepoIntegration!]! + projectsIssues( + first: Int! + + """ + the stringified cursors for pagination + """ + after: String + + """ + the ids of the projects selected as filters + """ + projectsIds: [String] + + """ + the search query that the user enters to filter issues + """ + searchQuery: String! + + """ + the sort string that defines the order of the returned issues + """ + sort: String! + + """ + the state of issues, e.g. opened or closed + """ + state: String! + ): GitLabIntegrationConnection! +} + +""" +A connection to a list of items. +""" +type GitLabIntegrationConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of edges. + """ + edges: [GitLabIntegrationEdge!]! + + """ + An error with the connection, if any + """ + error: StandardMutationError +} + +""" +An edge in a connection. +""" +type GitLabIntegrationEdge { + """ + The item at the end of the edge + """ + node: TaskIntegration! + cursor: String +} diff --git a/packages/server/graphql/public/typeDefs/GitLabSearchQuery.graphql b/packages/server/graphql/public/typeDefs/GitLabSearchQuery.graphql new file mode 100644 index 00000000000..f3e99b1a20e --- /dev/null +++ b/packages/server/graphql/public/typeDefs/GitLabSearchQuery.graphql @@ -0,0 +1,24 @@ +""" +A GitLab search query including the search query and the project filters +""" +type GitLabSearchQuery { + """ + shortid + """ + id: ID! + + """ + The query string used to search GitLab issue titles and descriptions + """ + queryString: String! + + """ + The list of ids of projects that have been selected as a filter. Null if none have been selected + """ + selectedProjectsIds: [ID!] + + """ + the time the search query was last used. Used for sorting + """ + lastUsedAt: DateTime! +} diff --git a/packages/server/graphql/public/typeDefs/JiraSearchQuery.graphql b/packages/server/graphql/public/typeDefs/JiraSearchQuery.graphql new file mode 100644 index 00000000000..3f084e94a55 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/JiraSearchQuery.graphql @@ -0,0 +1,29 @@ +""" +A jira search query including all filters selected when the query was executed +""" +type JiraSearchQuery { + """ + shortid + """ + id: ID! + + """ + The query string, either simple or JQL depending on the isJQL flag + """ + queryString: String! + + """ + true if the queryString is JQL, else false + """ + isJQL: Boolean! + + """ + The list of project keys selected as a filter. null if not set + """ + projectKeyFilters: [ID!]! + + """ + the time the search query was last used. Used for sorting + """ + lastUsedAt: DateTime! +} diff --git a/packages/server/graphql/public/typeDefs/JiraServerIntegration.graphql b/packages/server/graphql/public/typeDefs/JiraServerIntegration.graphql new file mode 100644 index 00000000000..81127b8cb96 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/JiraServerIntegration.graphql @@ -0,0 +1,49 @@ +""" +Jira Server integration data for a given team member +""" +type JiraServerIntegration { + """ + Composite key in jiraServer:providerId format + """ + id: ID + + """ + The OAuth1 Authorization for this team member + """ + auth: TeamMemberIntegrationAuthOAuth1 + + """ + The non-global providers shared with the team or organization + """ + sharedProviders: [IntegrationProviderOAuth1!]! + + """ + A list of issues coming straight from the jira integration for a specific team member + """ + issues( + first: Int = 25 + after: String = "-1" + + """ + A string of text to search for, or JQL if isJQL is true + """ + queryString: String + + """ + true if the queryString is JQL, else false + """ + isJQL: Boolean! + projectKeyFilters: [ID!] + ): JiraServerIssueConnection! + + """ + A list of projects accessible by this team member. empty if viewer is not the user + """ + projects: [JiraServerRemoteProject!]! + providerId: ID + + """ + the list of suggested search queries, sorted by most recent. Guaranteed to be < 60 days old + """ + searchQueries: [JiraSearchQuery!]! +} diff --git a/packages/server/graphql/public/typeDefs/JiraServerIssue.graphql b/packages/server/graphql/public/typeDefs/JiraServerIssue.graphql new file mode 100644 index 00000000000..38ca5e9c7a1 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/JiraServerIssue.graphql @@ -0,0 +1,102 @@ +""" +A connection to a list of items. +""" +type JiraServerIssueConnection { + """ + Information to aid in pagination. + """ + pageInfo: PageInfo! + + """ + A list of edges. + """ + edges: [JiraServerIssueEdge!]! + + """ + An error with the connection, if any + """ + error: StandardMutationError +} + +""" +Information about pagination in a connection. +""" +type PageInfo { + """ + When paginating forwards, are there more items? + """ + hasNextPage: Boolean! + + """ + When paginating backwards, are there more items? + """ + hasPreviousPage: Boolean! + + """ + When paginating backwards, the cursor to continue. + """ + startCursor: String + + """ + When paginating forwards, the cursor to continue. + """ + endCursor: String +} + +""" +An edge in a connection. +""" +type JiraServerIssueEdge { + """ + The item at the end of the edge + """ + node: JiraServerIssue! + cursor: String +} + +""" +The Jira Issue that comes direct from Jira Server +""" +type JiraServerIssue implements TaskIntegration { + """ + GUID providerId:repositoryId:issueId + """ + id: ID! + issueKey: ID! + issueType: ID! + projectId: ID! + projectKey: ID! + projectName: String! + + """ + The parabol teamId this issue was fetched for + """ + teamId: ID! + + """ + The parabol userId this issue was fetched for + """ + userId: ID! + + """ + The url to access the issue + """ + url: String! + + """ + The plaintext summary of the jira issue + """ + summary: String! + description: String! + + """ + The description converted into raw HTML + """ + descriptionHTML: String! + possibleEstimationFieldNames: [String!]! + + """ + The timestamp the issue was last updated + """ + updatedAt: DateTime! +} diff --git a/packages/server/graphql/public/typeDefs/MSTeamsIntegration.graphql b/packages/server/graphql/public/typeDefs/MSTeamsIntegration.graphql new file mode 100644 index 00000000000..51ef034678a --- /dev/null +++ b/packages/server/graphql/public/typeDefs/MSTeamsIntegration.graphql @@ -0,0 +1,14 @@ +""" +Integration Auth and shared providers available to the team member +""" +type MSTeamsIntegration { + """ + The OAuth2 Authorization for this team member + """ + auth: TeamMemberIntegrationAuthWebhook + + """ + The non-global providers shared with the team or organization + """ + sharedProviders: [IntegrationProviderWebhook!]! +} diff --git a/packages/server/graphql/public/typeDefs/MattermostIntegration.graphql b/packages/server/graphql/public/typeDefs/MattermostIntegration.graphql new file mode 100644 index 00000000000..e072cb55448 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/MattermostIntegration.graphql @@ -0,0 +1,14 @@ +""" +Integration Auth and shared providers available to the team member +""" +type MattermostIntegration { + """ + The OAuth2 Authorization for this team member + """ + auth: TeamMemberIntegrationAuthWebhook + + """ + The non-global providers shared with the team or organization + """ + sharedProviders: [IntegrationProviderWebhook!]! +} diff --git a/packages/server/graphql/public/typeDefs/Organization.graphql b/packages/server/graphql/public/typeDefs/Organization.graphql index 139dc3118e0..49201cbd7ea 100644 --- a/packages/server/graphql/public/typeDefs/Organization.graphql +++ b/packages/server/graphql/public/typeDefs/Organization.graphql @@ -27,39 +27,40 @@ type Organization { """ creditCard: CreditCard - """ - The assumed company this organizaiton belongs to - """ - company: Company - """ true if the viewer is the billing leader for the org """ isBillingLeader: Boolean! """ - Basic meeting metadata for aggregated stats across the entire organization. - Includes metadata on teams the viewer is not apart of + true if the viewer holds the the org admin role on the org """ - meetingStats: [MeetingStat!]! + isOrgAdmin: Boolean! + """ The name of the organization """ name: String! """ - The org avatar + Number of teams with 3+ meetings (>1 attendee) that met within last 30 days """ - picture: URL + activeTeamCount: Int! - tier: TierEnum! + """ + All the teams in the organization. If the viewer is not a billing lead, org admin, super user, or they do not have the publicTeams flag, return the teams they are a member of. + """ + allTeams: [Team!]! - billingTier: TierEnum! + """ + all the teams the viewer is on in the organization + """ + viewerTeams: [Team!]! """ - When the trial started, iff there is a trial active + all the teams that the viewer does not belong to that are in the organization. Only visible if the org has the publicTeams flag set to true. """ - trialStartDate: DateTime + publicTeams: [Team!]! """ THe datetime the current billing cycle ends @@ -141,6 +142,29 @@ type Organization { """ billingLeaders: [OrganizationUser!]! + """ + The assumed company this organizaiton belongs to + """ + company: Company + + """ + Basic meeting metadata for aggregated stats across the entire organization. + Includes metadata on teams the viewer is not apart of + """ + meetingStats: [MeetingStat!]! + + """ + The org avatar + """ + picture: URL + tier: TierEnum! + billingTier: TierEnum! + + """ + When the trial started, iff there is a trial active + """ + trialStartDate: DateTime + """ Minimal details about all teams in the organization """ @@ -155,6 +179,12 @@ type Organization { The SAML record attached to the Organization, if any """ saml: SAML + + """ + A list of domains approved by the organization to join. + Empty if all domains are allowed + """ + approvedDomains: [String!]! } type MeetingStat { diff --git a/packages/server/graphql/public/typeDefs/PageInfo.graphql b/packages/server/graphql/public/typeDefs/PageInfo.graphql new file mode 100644 index 00000000000..766d3468bfb --- /dev/null +++ b/packages/server/graphql/public/typeDefs/PageInfo.graphql @@ -0,0 +1,24 @@ +""" +Information about pagination in a connection. +""" +type PageInfo { + """ + When paginating forwards, are there more items? + """ + hasNextPage: Boolean! + + """ + When paginating backwards, are there more items? + """ + hasPreviousPage: Boolean! + + """ + When paginating backwards, the cursor to continue. + """ + startCursor: String + + """ + When paginating forwards, the cursor to continue. + """ + endCursor: String +} diff --git a/packages/server/graphql/public/typeDefs/PageInfoDateCursor.graphql b/packages/server/graphql/public/typeDefs/PageInfoDateCursor.graphql new file mode 100644 index 00000000000..c8e358eefee --- /dev/null +++ b/packages/server/graphql/public/typeDefs/PageInfoDateCursor.graphql @@ -0,0 +1,24 @@ +""" +Information about pagination in a connection. +""" +type PageInfoDateCursor { + """ + When paginating forwards, are there more items? + """ + hasNextPage: Boolean! + + """ + When paginating backwards, are there more items? + """ + hasPreviousPage: Boolean! + + """ + When paginating backwards, the cursor to continue. + """ + startCursor: DateTime + + """ + When paginating forwards, the cursor to continue. + """ + endCursor: DateTime +} diff --git a/packages/server/graphql/public/typeDefs/SlackIntegration.graphql b/packages/server/graphql/public/typeDefs/SlackIntegration.graphql new file mode 100644 index 00000000000..057e5660ca2 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/SlackIntegration.graphql @@ -0,0 +1,74 @@ +""" +OAuth token for a team member +""" +type SlackIntegration { + """ + shortid + """ + id: ID! + + """ + true if the auth is updated & ready to use for all features, else false + """ + isActive: Boolean! + + """ + the parabol bot user id + """ + botUserId: ID + + """ + the parabol bot access token, used as primary communication + """ + botAccessToken: ID + + """ + The timestamp the provider was created + """ + createdAt: DateTime! + + """ + The default channel to assign to new team notifications + """ + defaultTeamChannelId: String! + + """ + The id of the team in slack + """ + slackTeamId: ID + + """ + The name of the team in slack + """ + slackTeamName: String + + """ + The userId in slack + """ + slackUserId: ID! + + """ + The name of the user in slack + """ + slackUserName: String! + + """ + *The team that the token is linked to + """ + teamId: ID! + + """ + The timestamp the token was updated at + """ + updatedAt: DateTime! + + """ + The id of the user that integrated Slack + """ + userId: ID! + + """ + A list of events and the slack channels they get posted to + """ + notifications: [SlackNotification!]! +} diff --git a/packages/server/graphql/public/typeDefs/TeamMemberIntegrations.graphql b/packages/server/graphql/public/typeDefs/TeamMemberIntegrations.graphql index ed1b9ad2792..02b49984d19 100644 --- a/packages/server/graphql/public/typeDefs/TeamMemberIntegrations.graphql +++ b/packages/server/graphql/public/typeDefs/TeamMemberIntegrations.graphql @@ -17,11 +17,6 @@ type TeamMemberIntegrations { """ jiraServer: JiraServerIntegration! - """ - All things associated with a Gcal integration for a team member - """ - gcal: GcalIntegration - """ All things associated with a GitHub integration for a team member """ @@ -51,4 +46,9 @@ type TeamMemberIntegrations { All things associated with a Microsoft Teams integration for a team member """ msTeams: MSTeamsIntegration! + + """ + All things associated with a Gcal integration for a team member + """ + gcal: GcalIntegration } diff --git a/packages/server/graphql/public/typeDefs/_legacy.graphql b/packages/server/graphql/public/typeDefs/_legacy.graphql index d88cc2d86a6..8034caa107f 100644 --- a/packages/server/graphql/public/typeDefs/_legacy.graphql +++ b/packages/server/graphql/public/typeDefs/_legacy.graphql @@ -856,66 +856,6 @@ enum TeamDrawer { scalar Email -""" -The atlassian auth + integration helpers for a specific team member -""" -type AtlassianIntegration { - """ - Composite key in atlassiani:teamId:userId format - """ - id: ID! - - """ - true if the auth is valid, else false - """ - isActive: Boolean! - - """ - The access token to atlassian, useful for 1 hour. null if no access token available or the viewer is not the user - """ - accessToken: ID - - """ - *The atlassian account ID - """ - accountId: ID! - - """ - The atlassian cloud IDs that the user has granted - """ - cloudIds: [ID!]! - - """ - The timestamp the provider was created - """ - createdAt: DateTime! - - """ - *The team that the token is linked to - """ - teamId: ID! - - """ - The timestamp the token was updated at - """ - updatedAt: DateTime! - - """ - The user that the access token is attached to - """ - userId: ID! - - """ - A list of projects accessible by this team member. empty if viewer is not the user - """ - projects: [JiraRemoteProject!]! - - """ - the list of suggested search queries, sorted by most recent. Guaranteed to be < 60 days old - """ - jiraSearchQueries: [JiraSearchQuery!]! -} - interface TaskIntegration { id: ID! } @@ -1013,26 +953,6 @@ type JiraSearchQuery { lastUsedAt: DateTime! } -""" -Jira Server integration data for a given team member -""" -type JiraServerIntegration { - """ - The OAuth1 Authorization for this team member - """ - auth: TeamMemberIntegrationAuthOAuth1 - - """ - The non-global providers shared with the team or organization - """ - sharedProviders: [IntegrationProviderOAuth1!]! - - """ - A list of projects accessible by this team member. empty if viewer is not the user - """ - projects: [JiraServerRemoteProject!]! -} - """ An integration token that connects via OAuth1 """ @@ -1145,101 +1065,6 @@ type JiraServerRemoteProject implements RepoIntegration { projectCategory: JiraRemoteProjectCategory! } -""" -OAuth token for a team member -""" -type GitHubIntegration { - """ - composite key - """ - id: ID! - - """ - The access token to github. good forever - """ - accessToken: ID - - """ - The timestamp the provider was created - """ - createdAt: DateTime! - - """ - true if an access token exists, else false - """ - isActive: Boolean! - - """ - the list of suggested search queries, sorted by most recent. Guaranteed to be < 60 days old - """ - githubSearchQueries: [GitHubSearchQuery!]! - - """ - *The GitHub login used for queries - """ - login: ID! - - """ - The comma-separated list of scopes requested from GitHub - """ - scope: String! - - """ - *The team that the token is linked to - """ - teamId: ID! - - """ - The timestamp the token was updated at - """ - updatedAt: DateTime! - - """ - The user that the access token is attached to - """ - userId: ID! -} - -""" -A GitHub search query including all filters selected when the query was executed -""" -type GitHubSearchQuery { - """ - shortid - """ - id: ID! - - """ - The query string in GitHub format, including repository filters. e.g. is:issue is:open - """ - queryString: String! - - """ - the time the search query was last used. Used for sorting - """ - lastUsedAt: DateTime! -} - -""" -Gitlab integration data for a given team member -""" -type GitLabIntegration { - """ - The OAuth2 Authorization for this team member - """ - auth: TeamMemberIntegrationAuthOAuth2 - - """ - The cloud provider the team member may choose to integrate with. Nullable based on env vars - """ - cloudProvider: IntegrationProviderOAuth2 - - """ - The non-global providers shared with the team or organization - """ - sharedProviders: [IntegrationProviderOAuth2!]! -} - """ An integration token that connects via OAuth2 """ @@ -1295,33 +1120,6 @@ type TeamMemberIntegrationAuthOAuth2 implements TeamMemberIntegrationAuth { scopes: String! } -""" -Integration Auth and shared providers available to the team member -""" -type MattermostIntegration { - """ - The OAuth2 Authorization for this team member - """ - auth: TeamMemberIntegrationAuthWebhook - - """ - The non-global providers shared with the team or organization - """ - sharedProviders: [IntegrationProviderWebhook!]! -} - -type MSTeamsIntegration { - """ - The OAuth2 Authorization for this team member - """ - auth: TeamMemberIntegrationAuthWebhook - - """ - The non-global providers shared with the team or organization - """ - sharedProviders: [IntegrationProviderWebhook!]! -} - """ An integration authorization that connects via Webhook auth strategy """ @@ -1367,81 +1165,6 @@ type TeamMemberIntegrationAuthWebhook implements TeamMemberIntegrationAuth { provider: IntegrationProviderWebhook! } -""" -OAuth token for a team member -""" -type SlackIntegration { - """ - shortid - """ - id: ID! - - """ - true if the auth is updated & ready to use for all features, else false - """ - isActive: Boolean! - - """ - the parabol bot user id - """ - botUserId: ID - - """ - the parabol bot access token, used as primary communication - """ - botAccessToken: ID - - """ - The timestamp the provider was created - """ - createdAt: DateTime! - - """ - The default channel to assign to new team notifications - """ - defaultTeamChannelId: String! - - """ - The id of the team in slack - """ - slackTeamId: ID - - """ - The name of the team in slack - """ - slackTeamName: String - - """ - The userId in slack - """ - slackUserId: ID! - - """ - The name of the user in slack - """ - slackUserName: String! - - """ - *The team that the token is linked to - """ - teamId: ID! - - """ - The timestamp the token was updated at - """ - updatedAt: DateTime! - - """ - The id of the user that integrated Slack - """ - userId: ID! - - """ - A list of events and the slack channels they get posted to - """ - notifications: [SlackNotification!]! -} - """ an event trigger and slack channel to receive it """ diff --git a/packages/server/graphql/public/types/AtlassianIntegration.ts b/packages/server/graphql/public/types/AtlassianIntegration.ts index 4a1fadc7b51..4b68534f42c 100644 --- a/packages/server/graphql/public/types/AtlassianIntegration.ts +++ b/packages/server/graphql/public/types/AtlassianIntegration.ts @@ -1,5 +1,8 @@ -import {downloadAndCacheImages, updateJiraImageUrls} from '../../../utils/atlassian/jiraImages' +import ms from 'ms' +import AtlassianIntegrationId from '../../../../client/shared/gqlIds/AtlassianIntegrationId' +import updateJiraSearchQueries from '../../../postgres/queries/updateJiraSearchQueries' import AtlassianServerManager from '../../../utils/AtlassianServerManager' +import {downloadAndCacheImages, updateJiraImageUrls} from '../../../utils/atlassian/jiraImages' import {getUserId} from '../../../utils/authorization' import standardError from '../../../utils/standardError' import {AtlassianIntegrationResolvers} from '../resolverTypes' @@ -80,6 +83,35 @@ const AtlassianIntegration: AtlassianIntegrationResolvers = { hasPreviousPage: false } } + }, + id: ({teamId, userId}) => AtlassianIntegrationId.join(teamId, userId), + + isActive: ({accessToken}) => !!accessToken, + + accessToken: async ({accessToken, userId}, _args, {authToken}) => { + const viewerId = getUserId(authToken) + return viewerId === userId ? accessToken : null + }, + + projects: ({teamId, userId}, _args, {authToken, dataLoader}) => { + const viewerId = getUserId(authToken) + if (viewerId !== userId) return [] + return dataLoader.get('allJiraProjects').load({teamId, userId}) + }, + + jiraSearchQueries: async ({teamId, userId, jiraSearchQueries}) => { + const expirationThresh = ms('60d') + const thresh = new Date(Date.now() - expirationThresh) + const searchQueries = jiraSearchQueries || [] + const unexpiredQueries = searchQueries.filter((query) => query.lastUsedAt > thresh) + if (unexpiredQueries.length < searchQueries.length) { + await updateJiraSearchQueries({ + jiraSearchQueries: searchQueries, + teamId, + userId + }) + } + return unexpiredQueries } } diff --git a/packages/server/graphql/public/types/AzureDevOpsIntegration.ts b/packages/server/graphql/public/types/AzureDevOpsIntegration.ts new file mode 100644 index 00000000000..3df7a8b401a --- /dev/null +++ b/packages/server/graphql/public/types/AzureDevOpsIntegration.ts @@ -0,0 +1,77 @@ +import {getUserId, isTeamMember} from '../../../utils/authorization' +import standardError from '../../../utils/standardError' +import connectionFromTasks from '../../queries/helpers/connectionFromTasks' +import {AzureDevOpsIntegrationResolvers} from '../resolverTypes' + +export type AzureDevOpsIntegrationSource = { + teamId: string + userId: string +} +type WorkItemArgs = { + first: number + after?: string + queryString: string | null + projectKeyFilters: string[] | null + isWIQL: boolean +} + +const AzureDevOpsIntegration: AzureDevOpsIntegrationResolvers = { + auth: async ({teamId, userId}, _args, {dataLoader}) => { + return dataLoader + .get('teamMemberIntegrationAuths') + .load({service: 'azureDevOps', teamId, userId}) + }, + + id: ({teamId, userId}) => `ado:${teamId}:${userId}`, + + workItems: async ({teamId, userId}, args, {authToken, dataLoader}) => { + const {first, queryString, projectKeyFilters, isWIQL} = args as WorkItemArgs + const viewerId = getUserId(authToken) + if (!isTeamMember(authToken, teamId)) { + const err = new Error('Cannot access another team members user stories') + standardError(err, {tags: {teamId, userId}, userId: viewerId}) + return connectionFromTasks([], 0, err) + } + const allUserWorkItems = await dataLoader + .get('azureDevOpsAllWorkItems') + .load({teamId, userId, queryString, projectKeyFilters, isWIQL}) + if (!allUserWorkItems) { + return connectionFromTasks([], 0, undefined) + } else { + const workItems = Array.from( + allUserWorkItems.map((userWorkItem) => { + return { + ...userWorkItem, + updatedAt: new Date() + } + }) + ) + return connectionFromTasks(workItems, first, undefined) + } + }, + + projects: ({teamId, userId}, _args, {authToken, dataLoader}) => { + const viewerId = getUserId(authToken) + if (viewerId !== userId) return [] + return dataLoader.get('allAzureDevOpsProjects').load({teamId, userId}) + }, + + cloudProvider: async (_source, _args, {dataLoader}) => { + const [globalProvider] = await dataLoader + .get('sharedIntegrationProviders') + .load({service: 'azureDevOps', orgTeamIds: ['aGhostTeam'], teamIds: []}) + return globalProvider! + }, + + sharedProviders: async ({teamId}, _args, {dataLoader}) => { + const team = await dataLoader.get('teams').loadNonNull(teamId) + const {orgId} = team + const orgTeams = await dataLoader.get('teamsByOrgIds').load(orgId) + const orgTeamIds = orgTeams.map(({id}) => id) + return dataLoader + .get('sharedIntegrationProviders') + .load({service: 'azureDevOps', orgTeamIds, teamIds: [teamId]}) + } +} + +export default AzureDevOpsIntegration diff --git a/packages/server/graphql/public/types/AzureDevOpsRemoteProject.ts b/packages/server/graphql/public/types/AzureDevOpsRemoteProject.ts new file mode 100644 index 00000000000..99b9bf2abbc --- /dev/null +++ b/packages/server/graphql/public/types/AzureDevOpsRemoteProject.ts @@ -0,0 +1,14 @@ +import {AzureAccountProject, AzureProject} from '../../../dataloader/azureDevOpsLoaders' +import {getInstanceId} from '../../../utils/azureDevOps/azureDevOpsFieldTypeToId' +import {AzureDevOpsRemoteProjectResolvers} from '../resolverTypes' + +// This type is almost certainly wrong, but during the refactor to SDL I didn't want to mess with runtime logic +export type AzureDevOpsRemoteProjectSource = AzureProject | AzureAccountProject + +const AzureDevOpsRemoteProject: AzureDevOpsRemoteProjectResolvers = { + __isTypeOf: ({service}) => service === 'azureDevOps', + service: () => 'azureDevOps', + instanceId: ({url}) => getInstanceId(new URL(url)) +} + +export default AzureDevOpsRemoteProject diff --git a/packages/server/graphql/public/types/AzureDevOpsSearchQuery.ts b/packages/server/graphql/public/types/AzureDevOpsSearchQuery.ts new file mode 100644 index 00000000000..cd5b02bef7b --- /dev/null +++ b/packages/server/graphql/public/types/AzureDevOpsSearchQuery.ts @@ -0,0 +1,7 @@ +import {AzureDevOpsSearchQueryResolvers} from '../resolverTypes' + +const AzureDevOpsSearchQuery: AzureDevOpsSearchQueryResolvers = { + isWIQL: ({isWIQL}) => !!isWIQL +} + +export default AzureDevOpsSearchQuery diff --git a/packages/server/graphql/public/types/AzureDevOpsWorkItem.ts b/packages/server/graphql/public/types/AzureDevOpsWorkItem.ts new file mode 100644 index 00000000000..1d626072e09 --- /dev/null +++ b/packages/server/graphql/public/types/AzureDevOpsWorkItem.ts @@ -0,0 +1,25 @@ +import AzureDevOpsIssueId from 'parabol-client/shared/gqlIds/AzureDevOpsIssueId' +import {getInstanceId} from '../../../utils/azureDevOps/azureDevOpsFieldTypeToId' +import {AzureDevOpsWorkItemResolvers} from '../resolverTypes' + +const AzureDevOpsWorkItem: AzureDevOpsWorkItemResolvers = { + __isTypeOf: ({service}) => service === 'azureDevOps', + id: ({id, teamProject, url}) => { + const instanceId = getInstanceId(url) + return AzureDevOpsIssueId.join(instanceId, teamProject, id) + }, + + issueKey: async ({id}) => { + return id + }, + + project: async ({teamId, userId, teamProject, url}, _args, {dataLoader}) => { + const instanceId = getInstanceId(url) + const res = await dataLoader + .get('azureDevOpsProject') + .load({instanceId, projectId: teamProject, userId, teamId}) + return res! + } +} + +export default AzureDevOpsWorkItem diff --git a/packages/server/graphql/public/types/GitHubIntegration.ts b/packages/server/graphql/public/types/GitHubIntegration.ts new file mode 100644 index 00000000000..417f9655383 --- /dev/null +++ b/packages/server/graphql/public/types/GitHubIntegration.ts @@ -0,0 +1,30 @@ +import ms from 'ms' +import GitHubIntegrationId from '../../../../client/shared/gqlIds/GitHubIntegrationId' +import updateGitHubSearchQueries from '../../../postgres/queries/updateGitHubSearchQueries' +import {getUserId} from '../../../utils/authorization' +import {GitHubIntegrationResolvers} from '../resolverTypes' + +const GitHubIntegration: GitHubIntegrationResolvers = { + id: ({teamId, userId}) => GitHubIntegrationId.join(teamId, userId), + + accessToken: async ({accessToken, userId}, _args, {authToken}) => { + const viewerId = getUserId(authToken) + return viewerId === userId ? accessToken : null + }, + + isActive: ({accessToken}) => !!accessToken, + + githubSearchQueries: async ({githubSearchQueries, teamId, userId}) => { + const expirationThresh = ms('60d') + const thresh = new Date(Date.now() - expirationThresh) + const unexpiredQueries = githubSearchQueries.filter( + (query) => new Date(query.lastUsedAt) > thresh + ) + if (unexpiredQueries.length < githubSearchQueries.length) { + await updateGitHubSearchQueries({teamId, userId, githubSearchQueries: unexpiredQueries}) + } + return unexpiredQueries + } +} + +export default GitHubIntegration diff --git a/packages/server/graphql/public/types/GitLabIntegration.ts b/packages/server/graphql/public/types/GitLabIntegration.ts new file mode 100644 index 00000000000..ecc26f9905c --- /dev/null +++ b/packages/server/graphql/public/types/GitLabIntegration.ts @@ -0,0 +1,155 @@ +import GitLabServerManager from '../../../integrations/gitlab/GitLabServerManager' +import {GetProjectIssuesQuery, IssuableState, IssueSort} from '../../../types/gitlabTypes' +import sendToSentry from '../../../utils/sendToSentry' +import fetchGitLabProjects from '../../queries/helpers/fetchGitLabProjects' +import {GitLabIntegrationResolvers} from '../resolverTypes' + +export type GitLabIntegrationSource = { + teamId: string + userId: string +} + +type ProjectIssuesRes = NonNullable['issues']> +type ProjectIssueEdgeNullable = Pick< + NonNullable[number]>, + 'cursor' | 'node' +> +type ProjectIssueNode = NonNullable +type ProjectIssueEdge = ProjectIssueEdgeNullable & {node: ProjectIssueNode} + +type CursorDetails = { + fullPath: string + cursor: string +} +const GitLabIntegration: GitLabIntegrationResolvers = { + auth: async ({teamId, userId}, _args, {dataLoader}) => { + return dataLoader.get('freshGitlabAuth').load({teamId, userId}) + }, + + cloudProvider: async (_source, _args, {dataLoader}) => { + const [globalProvider] = await dataLoader + .get('sharedIntegrationProviders') + .load({service: 'gitlab', orgTeamIds: ['aGhostTeam'], teamIds: []}) + return globalProvider! + }, + + sharedProviders: async ({teamId}, _args, {dataLoader}) => { + const team = await dataLoader.get('teams').loadNonNull(teamId) + const {orgId} = team + const orgTeams = await dataLoader.get('teamsByOrgIds').load(orgId) + const orgTeamIds = orgTeams.map(({id}) => id) + return dataLoader + .get('sharedIntegrationProviders') + .load({service: 'gitlab', orgTeamIds, teamIds: [teamId]}) + }, + + gitlabSearchQueries: async () => [], + + projects: async ({teamId, userId}, _args, context, info) => { + return fetchGitLabProjects(teamId, userId, context, info) + }, + + projectsIssues: async ({teamId, userId}, args, context, info) => { + const {projectsIds} = args + const after = args?.after ?? '' + const {dataLoader} = context + const emptyConnection = {edges: [], pageInfo: {hasNextPage: false, hasPreviousPage: false}} + const auth = await dataLoader + .get('teamMemberIntegrationAuths') + .load({service: 'gitlab', teamId, userId}) + if (!auth?.accessToken) return emptyConnection + const {providerId} = auth + const provider = await dataLoader.get('integrationProviders').load(providerId) + if (!provider?.serverBaseUrl) return emptyConnection + const manager = new GitLabServerManager(auth, context, info, provider.serverBaseUrl) + const [projectsData, projectsErr] = await manager.getProjects({ + ids: projectsIds as string[], + first: 50 // if no project filters have been selected, get the 50 most recently used projects + }) + if (projectsErr) { + sendToSentry(new Error('Unable to get GitLab projects in projectsIssues query'), {userId}) + return emptyConnection + } + const projectsFullPaths = new Set() + projectsData.projects?.edges?.forEach((edge) => { + if (edge?.node?.fullPath) { + projectsFullPaths.add(edge?.node?.fullPath) + } + }) + let parsedAfter: CursorDetails[] | null + try { + parsedAfter = after.length ? JSON.parse(after) : null + } catch (e) { + sendToSentry(new Error('Error parsing after'), {userId, tags: {after}}) + return emptyConnection + } + const isValidJSON = parsedAfter?.every( + (cursorsDetails) => + typeof cursorsDetails.cursor === 'string' && typeof cursorsDetails.fullPath === 'string' + ) + if (isValidJSON === false) { + sendToSentry(new Error('after arg has an invalid JSON structure'), { + userId, + tags: {after} + }) + return emptyConnection + } + + const projectsIssuesPromises = Array.from(projectsFullPaths).map((fullPath) => { + const after = parsedAfter?.find((cursor) => cursor.fullPath === fullPath)?.cursor ?? '' + return manager.getProjectIssues({ + ...args, + fullPath, + after, + sort: args.sort as IssueSort, + state: args.state as IssuableState + }) + }) + const projectsIssues = [] as ProjectIssueEdge[] + const errors = [] as Error[] + let hasNextPage = false + const endCursor = [] as CursorDetails[] + const projectsIssuesResponses = await Promise.all(projectsIssuesPromises) + for (const res of projectsIssuesResponses) { + const [projectIssuesData, err] = res + if (err) { + errors.push(err) + sendToSentry(err, {userId}) + continue + } + const {project} = projectIssuesData + if (!project?.issues) continue + const {fullPath, issues} = project + const {edges, pageInfo} = issues + if (pageInfo.hasNextPage) { + hasNextPage = true + const currentCursorDetails = endCursor.find( + (cursorDetails) => cursorDetails.fullPath === fullPath + ) + const newCursor = pageInfo.endCursor ?? '' + if (currentCursorDetails) currentCursorDetails.cursor = newCursor + else endCursor.push({fullPath, cursor: newCursor}) + } + edges?.forEach((edge) => { + if (!edge?.node) return + const {node, cursor} = edge + projectsIssues.push({cursor, node}) + }) + } + + const firstEdge = projectsIssues[0] + const stringifiedEndCursor = JSON.stringify(endCursor) + return { + error: errors[0], + edges: projectsIssues, + pageInfo: { + startCursor: firstEdge && firstEdge.cursor, + endCursor: stringifiedEndCursor, + hasNextPage, + hasPreviousPage: false + } + } + } +} + +export default GitLabIntegration diff --git a/packages/server/graphql/public/types/JiraSearchQuery.ts b/packages/server/graphql/public/types/JiraSearchQuery.ts new file mode 100644 index 00000000000..7d6beca33c3 --- /dev/null +++ b/packages/server/graphql/public/types/JiraSearchQuery.ts @@ -0,0 +1,8 @@ +import {JiraSearchQueryResolvers} from '../resolverTypes' + +const JiraSearchQuery: JiraSearchQueryResolvers = { + id: ({id}) => `JiraSearchQuery:${id}`, + projectKeyFilters: ({projectKeyFilters}) => projectKeyFilters || [] +} + +export default JiraSearchQuery diff --git a/packages/server/graphql/public/types/JiraServerIntegration.ts b/packages/server/graphql/public/types/JiraServerIntegration.ts new file mode 100644 index 00000000000..029a57cbcfb --- /dev/null +++ b/packages/server/graphql/public/types/JiraServerIntegration.ts @@ -0,0 +1,208 @@ +import IntegrationProviderId from '~/shared/gqlIds/IntegrationProviderId' +import IntegrationRepoId from '~/shared/gqlIds/IntegrationRepoId' +import TeamMember from '../../../database/types/TeamMember' +import JiraServerRestManager from '../../../integrations/jiraServer/JiraServerRestManager' +import {IntegrationProviderJiraServer} from '../../../postgres/queries/getIntegrationProvidersByIds' +import getLatestIntegrationSearchQueries from '../../../postgres/queries/getLatestIntegrationSearchQueries' +import {getUserId} from '../../../utils/authorization' +import standardError from '../../../utils/standardError' +import {JiraServerIntegrationResolvers} from '../resolverTypes' + +export type JiraServerIntegrationSource = { + teamId: string + userId: string +} +type IssueArgs = { + first: number + after: string + queryString: string | null + isJQL: boolean + projectKeyFilters: string[] | null +} + +const JiraServerIntegration: JiraServerIntegrationResolvers = { + id: async ({teamId, userId}, _args, {dataLoader}) => { + const auth = await dataLoader + .get('teamMemberIntegrationAuths') + .load({service: 'jiraServer', teamId, userId}) + + if (!auth) { + return null + } + + return `jiraServer:${teamId}:${auth.providerId}` + }, + + auth: async ({teamId, userId}, _args, {dataLoader}) => { + const auth = await dataLoader + .get('teamMemberIntegrationAuths') + .load({service: 'jiraServer', teamId, userId}) + return auth! + }, + + sharedProviders: async ({teamId, userId}, _args, {dataLoader}) => { + const teamMembers = await dataLoader.get('teamMembersByUserId').load(userId) + const teamMember = teamMembers.find((teamMember: TeamMember) => teamMember.teamId === teamId) + if (!teamMember) return [] + + const team = await dataLoader.get('teams').loadNonNull(teamMember.teamId) + const {orgId} = team + const orgTeams = await dataLoader.get('teamsByOrgIds').load(orgId) + const orgTeamIds = orgTeams.map(({id}) => id) + + const providers = await dataLoader.get('sharedIntegrationProviders').load({ + service: 'jiraServer', + orgTeamIds: [...orgTeamIds, 'aGhostTeam'], + teamIds: [teamId] + }) + + return providers + }, + + issues: async ({teamId, userId}, args, {authToken, dataLoader}) => { + const {first, after, queryString, isJQL, projectKeyFilters} = args as IssueArgs + const viewerId = getUserId(authToken) + const emptyConnection = {edges: [], pageInfo: {hasNextPage: false, hasPreviousPage: false}} + if (viewerId !== userId) { + const err = new Error('Cannot access another team members issues') + standardError(err, {tags: {teamId, userId}, userId: viewerId}) + return emptyConnection + } + + const auth = await dataLoader + .get('teamMemberIntegrationAuths') + .load({service: 'jiraServer', teamId, userId}) + + if (!auth) { + return emptyConnection + } + + const provider = await dataLoader.get('integrationProviders').loadNonNull(auth.providerId) + + const integrationManager = new JiraServerRestManager( + auth, + provider as IntegrationProviderJiraServer + ) + + if (!integrationManager) { + return emptyConnection + } + + const projectKeys = (projectKeyFilters ?? []).map( + (projectKeyFilter) => IntegrationRepoId.split(projectKeyFilter).projectKey! + ) + + // Request one extra item to see if there are more results + const maxResults = first + 1 + // Relay requires the cursor to be a string + const afterInt = parseInt(after, 10) + const startAt = afterInt + 1 + const issueRes = await integrationManager.getIssues( + queryString, + isJQL, + projectKeys, + maxResults, + startAt + ) + + if (issueRes instanceof Error) { + return { + ...emptyConnection, + error: { + message: issueRes.message + } + } + } + + const {issues} = issueRes + + const mappedIssues = issues.map((issue) => { + const {project, issuetype, summary, description, updated} = issue.fields + return { + ...issue, + userId, + teamId, + providerId: provider.id, + issueKey: issue.key, + description: description ?? '', + descriptionHTML: issue.renderedFields.description, + projectId: project.id, + projectKey: project.key, + projectName: project.name, + issueType: issuetype.id, + summary, + service: 'jiraServer' as const, + updatedAt: new Date(updated) + } + }) + + const nodes = mappedIssues.slice(0, first) + const edges = mappedIssues.map((node, index) => ({ + cursor: `${index + afterInt}`, + node + })) + + const firstEdge = edges[0] + + return { + edges, + pageInfo: { + startCursor: firstEdge && firstEdge.cursor, + endCursor: firstEdge ? edges[edges.length - 1]!.cursor : null, + hasNextPage: mappedIssues.length > nodes.length, + hasPreviousPage: false + } + } + }, + + projects: async ({teamId, userId}, _args, {dataLoader}) => { + return dataLoader.get('allJiraServerProjects').load({teamId, userId}) + }, + + providerId: async ({teamId, userId}, _args, {dataLoader}) => { + const auth = await dataLoader + .get('teamMemberIntegrationAuths') + .load({service: 'jiraServer', teamId, userId}) + + if (!auth) { + return null + } + + return IntegrationProviderId.join(auth.providerId) + }, + + searchQueries: async ({teamId, userId}, _args, {dataLoader}) => { + const auth = await dataLoader + .get('teamMemberIntegrationAuths') + .load({service: 'jiraServer', teamId, userId}) + + if (!auth) { + return [] + } + + const searchQueries = await getLatestIntegrationSearchQueries({ + teamId, + userId, + service: 'jiraServer', + providerId: auth.providerId + }) + + return searchQueries.map((searchQuery) => { + const query = searchQuery.query as { + queryString: string | null + isJQL: boolean + projectKeyFilters: string[] | null + } + + return { + id: String(searchQuery.id), + queryString: query.queryString || '', + isJQL: query.isJQL, + projectKeyFilters: query.projectKeyFilters || [], + lastUsedAt: searchQuery.lastUsedAt + } + }) + } +} + +export default JiraServerIntegration diff --git a/packages/server/graphql/public/types/JiraServerIssue.ts b/packages/server/graphql/public/types/JiraServerIssue.ts new file mode 100644 index 00000000000..c6269b5ebac --- /dev/null +++ b/packages/server/graphql/public/types/JiraServerIssue.ts @@ -0,0 +1,46 @@ +import JiraServerIssueId from '~/shared/gqlIds/JiraServerIssueId' +import {JiraServerIssue as JiraServerRestIssue} from '../../../dataloader/jiraServerLoaders' +import {JiraServerIssueResolvers} from '../resolverTypes' + +export type JiraServerIssueSource = JiraServerRestIssue & { + userId: string + teamId: string + providerId: number +} + +const VOTE_FIELD_ID_BLACKLIST = ['description', 'summary'] +const VOTE_FIELD_ALLOWED_TYPES = ['string', 'number'] + +const JiraServerIssue: JiraServerIssueResolvers = { + __isTypeOf: ({service}) => service === 'jiraServer', + id: ({id, projectId, providerId}) => { + return JiraServerIssueId.join(providerId, projectId, id) + }, + + url: ({issueKey, self}) => { + const {origin} = new URL(self) + return `${origin}/browse/${issueKey}` + }, + + possibleEstimationFieldNames: async ( + {teamId, userId, providerId, issueType, projectId}, + _args, + {dataLoader} + ) => { + const issueMeta = await dataLoader + .get('jiraServerFieldTypes') + .load({teamId, userId, projectId, issueType, providerId}) + if (!issueMeta) return [] + const fieldNames = issueMeta + .filter( + ({fieldId, operations, schema}) => + !VOTE_FIELD_ID_BLACKLIST.includes(fieldId) && + operations.includes('set') && + VOTE_FIELD_ALLOWED_TYPES.includes(schema.type) + ) + .map(({name}) => name) + return fieldNames + } +} + +export default JiraServerIssue diff --git a/packages/server/graphql/public/types/JiraServerRemoteProject.ts b/packages/server/graphql/public/types/JiraServerRemoteProject.ts new file mode 100644 index 00000000000..14ad9a4f6f8 --- /dev/null +++ b/packages/server/graphql/public/types/JiraServerRemoteProject.ts @@ -0,0 +1,25 @@ +import IntegrationRepoId from 'parabol-client/shared/gqlIds/IntegrationRepoId' +import JiraServerRestManager from '../../../integrations/jiraServer/JiraServerRestManager' +import {IntegrationProviderJiraServer} from '../../../postgres/queries/getIntegrationProvidersByIds' +import defaultJiraProjectAvatar from '../../../utils/defaultJiraProjectAvatar' +import {JiraServerRemoteProjectResolvers} from '../resolverTypes' + +const JiraServerRemoteProject: JiraServerRemoteProjectResolvers = { + __isTypeOf: ({service}) => service === 'jiraServer', + id: (item) => IntegrationRepoId.join(item), + service: () => 'jiraServer', + + avatar: async ({avatarUrls, teamId, userId}, _args, {dataLoader}) => { + const url = avatarUrls['48x48'] + const auth = await dataLoader + .get('teamMemberIntegrationAuths') + .load({service: 'jiraServer', teamId, userId}) + if (!auth) return defaultJiraProjectAvatar + const provider = await dataLoader.get('integrationProviders').loadNonNull(auth.providerId) + const manager = new JiraServerRestManager(auth, provider as IntegrationProviderJiraServer) + const avatar = await manager.getProjectAvatar(url) + return avatar || defaultJiraProjectAvatar + } +} + +export default JiraServerRemoteProject diff --git a/packages/server/graphql/public/types/MSTeamsIntegration.ts b/packages/server/graphql/public/types/MSTeamsIntegration.ts new file mode 100644 index 00000000000..21e7089142a --- /dev/null +++ b/packages/server/graphql/public/types/MSTeamsIntegration.ts @@ -0,0 +1,24 @@ +import {MsTeamsIntegrationResolvers} from '../resolverTypes' + +export type MSTeamsIntegrationSource = { + teamId: string + userId: string +} + +const MSTeamsIntegration: MsTeamsIntegrationResolvers = { + auth: async ({teamId, userId}, _args, {dataLoader}) => { + return dataLoader.get('teamMemberIntegrationAuths').load({service: 'msTeams', teamId, userId}) + }, + + sharedProviders: async ({teamId}, _args, {dataLoader}) => { + const team = await dataLoader.get('teams').loadNonNull(teamId) + const {orgId} = team + const orgTeams = await dataLoader.get('teamsByOrgIds').load(orgId) + const orgTeamIds = orgTeams.map(({id}) => id) + return dataLoader + .get('sharedIntegrationProviders') + .load({service: 'msTeams', orgTeamIds, teamIds: [teamId]}) + } +} + +export default MSTeamsIntegration diff --git a/packages/server/graphql/public/types/MattermostIntegration.ts b/packages/server/graphql/public/types/MattermostIntegration.ts new file mode 100644 index 00000000000..994f81cc89b --- /dev/null +++ b/packages/server/graphql/public/types/MattermostIntegration.ts @@ -0,0 +1,27 @@ +import {MattermostIntegrationResolvers} from '../resolverTypes' + +export type MattermostIntegrationSource = { + teamId: string + userId: string +} + +const MattermostIntegration: MattermostIntegrationResolvers = { + auth: async ({teamId, userId}, _args, {dataLoader}) => { + const res = await dataLoader + .get('teamMemberIntegrationAuths') + .load({service: 'mattermost', teamId, userId}) + return res! + }, + + sharedProviders: async ({teamId}, _args, {dataLoader}) => { + const team = await dataLoader.get('teams').loadNonNull(teamId) + const {orgId} = team + const orgTeams = await dataLoader.get('teamsByOrgIds').load(orgId) + const orgTeamIds = orgTeams.map(({id}) => id) + return dataLoader + .get('sharedIntegrationProviders') + .load({service: 'mattermost', orgTeamIds, teamIds: [teamId]}) + } +} + +export default MattermostIntegration diff --git a/packages/server/graphql/public/types/Organization.ts b/packages/server/graphql/public/types/Organization.ts index 15b84bd3907..5610172e74b 100644 --- a/packages/server/graphql/public/types/Organization.ts +++ b/packages/server/graphql/public/types/Organization.ts @@ -1,8 +1,15 @@ import {ExtractTypeFromQueryBuilderSelect} from '../../../../client/types/generics' import {selectOrganizations} from '../../../dataloader/primaryKeyLoaderMakers' -import {isSuperUser} from '../../../utils/authorization' +import { + getUserId, + isSuperUser, + isTeamMember, + isUserBillingLeader, + isUserOrgAdmin +} from '../../../utils/authorization' import {getFeatureTier} from '../../types/helpers/getFeatureTier' import {OrganizationResolvers} from '../resolverTypes' +import getActiveTeamCountByOrgIds from './helpers/getActiveTeamCountByOrgIds' export interface OrganizationSource extends ExtractTypeFromQueryBuilderSelect {} @@ -36,6 +43,98 @@ const Organization: OrganizationResolvers = { saml: async ({id: orgId}, _args, {dataLoader}) => { const saml = await dataLoader.get('samlByOrgId').load(orgId) return saml || null + }, + + isBillingLeader: async ({id: orgId}, _args, {authToken, dataLoader}) => { + const viewerId = getUserId(authToken) + return isUserBillingLeader(viewerId, orgId, dataLoader) + }, + + isOrgAdmin: async ({id: orgId}, _args, {authToken, dataLoader}) => { + const viewerId = getUserId(authToken) + return isUserOrgAdmin(viewerId, orgId, dataLoader) + }, + + activeTeamCount: async ({id: orgId}) => { + return getActiveTeamCountByOrgIds(orgId) + }, + + allTeams: async ({id: orgId}, _args, {dataLoader, authToken}) => { + const viewerId = getUserId(authToken) + const [allTeamsOnOrg, organization, isOrgAdmin, isBillingLeader] = await Promise.all([ + dataLoader.get('teamsByOrgIds').load(orgId), + dataLoader.get('organizations').loadNonNull(orgId), + isUserOrgAdmin(viewerId, orgId, dataLoader), + isUserBillingLeader(viewerId, orgId, dataLoader) + ]) + const sortedTeamsOnOrg = allTeamsOnOrg.sort((a, b) => a.name.localeCompare(b.name)) + const hasPublicTeamsFlag = !!organization.featureFlags?.includes('publicTeams') + if (isBillingLeader || isOrgAdmin || isSuperUser(authToken) || hasPublicTeamsFlag) { + const viewerTeams = sortedTeamsOnOrg.filter((team) => authToken.tms.includes(team.id)) + const otherTeams = sortedTeamsOnOrg.filter((team) => !authToken.tms.includes(team.id)) + return [...viewerTeams, ...otherTeams] + } else { + return sortedTeamsOnOrg.filter((team) => authToken.tms.includes(team.id)) + } + }, + + viewerTeams: async ({id: orgId}, _args, {dataLoader, authToken}) => { + const allTeamsOnOrg = await dataLoader.get('teamsByOrgIds').load(orgId) + return allTeamsOnOrg + .filter((team) => authToken.tms.includes(team.id)) + .sort((a, b) => a.name.localeCompare(b.name)) + }, + + publicTeams: async ({id: orgId}, _args, {dataLoader, authToken}) => { + const [allTeamsOnOrg, organization] = await Promise.all([ + dataLoader.get('teamsByOrgIds').load(orgId), + dataLoader.get('organizations').loadNonNull(orgId) + ]) + const hasPublicTeamsFlag = !!organization.featureFlags?.includes('publicTeams') + if (!isSuperUser(authToken) || !hasPublicTeamsFlag) return [] + const publicTeams = allTeamsOnOrg.filter((team) => !isTeamMember(authToken, team.id)) + return publicTeams + }, + + viewerOrganizationUser: async ({id: orgId}, _args, {dataLoader, authToken}) => { + const viewerId = getUserId(authToken) + return dataLoader.get('organizationUsersByUserIdOrgId').load({userId: viewerId, orgId}) + }, + + organizationUsers: async ({id: orgId}, _args, {dataLoader}) => { + const organizationUsers = await dataLoader.get('organizationUsersByOrgId').load(orgId) + organizationUsers.sort((a, b) => (a.orgId > b.orgId ? 1 : -1)) + const edges = organizationUsers.map((node) => ({ + cursor: node.id, + node + })) + // TODO implement pagination + const firstEdge = edges[0] + return { + edges, + pageInfo: { + endCursor: firstEdge ? edges[edges.length - 1]!.cursor : null, + hasNextPage: false, + hasPreviousPage: false + } + } + }, + + orgUserCount: async ({id: orgId}, _args, {dataLoader}) => { + const organizationUsers = await dataLoader.get('organizationUsersByOrgId').load(orgId) + const inactiveUserCount = organizationUsers.filter(({inactive}) => inactive).length + return { + inactiveUserCount, + activeUserCount: organizationUsers.length - inactiveUserCount + } + }, + + billingLeaders: async ({id: orgId}, _args, {dataLoader}) => { + const organizationUsers = await dataLoader.get('organizationUsersByOrgId').load(orgId) + return organizationUsers.filter( + (organizationUser) => + organizationUser.role === 'BILLING_LEADER' || organizationUser.role === 'ORG_ADMIN' + ) } } diff --git a/packages/server/graphql/public/types/SlackIntegration.ts b/packages/server/graphql/public/types/SlackIntegration.ts new file mode 100644 index 00000000000..7f9d623fd48 --- /dev/null +++ b/packages/server/graphql/public/types/SlackIntegration.ts @@ -0,0 +1,18 @@ +import {getUserId} from '../../../utils/authorization' +import {SlackIntegrationResolvers} from '../resolverTypes' + +const SlackIntegration: SlackIntegrationResolvers = { + isActive: ({isActive, botAccessToken}) => !!(isActive && botAccessToken), + + botAccessToken: async ({botAccessToken, userId}, _args, {authToken}) => { + const viewerId = getUserId(authToken) + return viewerId === userId ? botAccessToken : null + }, + + notifications: async ({userId, teamId}, _args, {dataLoader}) => { + const slackNotifications = await dataLoader.get('slackNotificationsByTeamId').load(teamId) + return slackNotifications.filter((notification) => notification.userId === userId) + } +} + +export default SlackIntegration diff --git a/packages/server/graphql/public/types/TeamMemberIntegrations.ts b/packages/server/graphql/public/types/TeamMemberIntegrations.ts index 9c2c2e63924..a30c2ff8957 100644 --- a/packages/server/graphql/public/types/TeamMemberIntegrations.ts +++ b/packages/server/graphql/public/types/TeamMemberIntegrations.ts @@ -1,14 +1,41 @@ +import TeamMemberIntegrationsId from '../../../../client/shared/gqlIds/TeamMemberIntegrationsId' +import {isTeamMember} from '../../../utils/authorization' import {TeamMemberIntegrationsResolvers} from '../resolverTypes' export type TeamMemberIntegrationsSource = { teamId: string userId: string } - const TeamMemberIntegrations: TeamMemberIntegrationsResolvers = { gcal: async ({teamId, userId}) => { return {teamId, userId} - } + }, + + id: ({teamId, userId}) => TeamMemberIntegrationsId.join(teamId, userId), + + atlassian: async ({teamId, userId}, _args, {authToken, dataLoader}) => { + if (!isTeamMember(authToken, teamId)) return null + return dataLoader.get('freshAtlassianAuth').load({teamId, userId}) + }, + + jiraServer: (source) => source, + + github: async ({teamId, userId}, _args, {authToken, dataLoader}) => { + if (!isTeamMember(authToken, teamId)) return null + return dataLoader.get('githubAuth').load({teamId, userId}) + }, + + gitlab: (source) => source, + mattermost: (source) => source, + + slack: async ({teamId, userId}, _args, {authToken, dataLoader}) => { + if (!isTeamMember(authToken, teamId)) return null + const auths = await dataLoader.get('slackAuthByUserId').load(userId) + return auths.find((auth) => auth.teamId === teamId)! + }, + + azureDevOps: (source) => source, + msTeams: (source) => source } export default TeamMemberIntegrations diff --git a/packages/server/graphql/public/types/_xGitLabProject.ts b/packages/server/graphql/public/types/_xGitLabProject.ts index 45b7a6b5b52..30a1c23b2b1 100644 --- a/packages/server/graphql/public/types/_xGitLabProject.ts +++ b/packages/server/graphql/public/types/_xGitLabProject.ts @@ -1,5 +1,14 @@ import {_XGitLabProjectResolvers} from '../resolverTypes' +// There's a bug in GraphQL Codegen that allow mappers to start with `_` +// So, we overwrite the GitLab native object with this type, too +export type _xGitLabProjectSource = { + __typename: 'Project' + service: 'gitlab' + id: string + fullPath: string +} + const _xGitLabProject: _XGitLabProjectResolvers = { __isTypeOf: ({id}) => id.startsWith('gid://'), service: () => 'gitlab' diff --git a/packages/server/graphql/rootTypes.ts b/packages/server/graphql/rootTypes.ts index b676deb4ce1..4a83c7b4672 100644 --- a/packages/server/graphql/rootTypes.ts +++ b/packages/server/graphql/rootTypes.ts @@ -4,7 +4,6 @@ import AgendaItemsPhase from './types/AgendaItemsPhase' import AuthIdentityGoogle from './types/AuthIdentityGoogle' import AuthIdentityLocal from './types/AuthIdentityLocal' import AuthIdentityMicrosoft from './types/AuthIdentityMicrosoft' -import AzureDevOpsWorkItem from './types/AzureDevOpsWorkItem' import CheckInPhase from './types/CheckInPhase' import Comment from './types/Comment' import DiscussPhase from './types/DiscussPhase' @@ -71,7 +70,6 @@ const rootTypes = [ TeamPromptMeetingMember, TeamPromptMeetingSettings, Comment, - AzureDevOpsWorkItem, JiraDimensionField, RenamePokerTemplatePayload, UserTiersCount diff --git a/packages/server/graphql/types/AtlassianIntegration.ts b/packages/server/graphql/types/AtlassianIntegration.ts index 5b7c9f1d516..0827a077c3d 100644 --- a/packages/server/graphql/types/AtlassianIntegration.ts +++ b/packages/server/graphql/types/AtlassianIntegration.ts @@ -1,92 +1,8 @@ -import {GraphQLBoolean, GraphQLID, GraphQLList, GraphQLNonNull, GraphQLObjectType} from 'graphql' -import ms from 'ms' -import AtlassianIntegrationId from '../../../client/shared/gqlIds/AtlassianIntegrationId' -import {AtlassianAuth} from '../../postgres/queries/getAtlassianAuthByUserIdTeamId' -import updateJiraSearchQueries from '../../postgres/queries/updateJiraSearchQueries' -import {getUserId} from '../../utils/authorization' -import {GQLContext} from '../graphql' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import JiraRemoteProject from './JiraRemoteProject' -import JiraSearchQuery from './JiraSearchQuery' +import {GraphQLObjectType} from 'graphql' -const AtlassianIntegration = new GraphQLObjectType({ +const AtlassianIntegration = new GraphQLObjectType({ name: 'AtlassianIntegration', - description: 'The atlassian auth + integration helpers for a specific team member', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'Composite key in atlassiani:teamId:userId format', - resolve: ({teamId, userId}: {teamId: string; userId: string}) => - AtlassianIntegrationId.join(teamId, userId) - }, - isActive: { - description: 'true if the auth is valid, else false', - type: new GraphQLNonNull(GraphQLBoolean), - resolve: ({accessToken}) => !!accessToken - }, - accessToken: { - description: - 'The access token to atlassian, useful for 1 hour. null if no access token available or the viewer is not the user', - type: GraphQLID, - resolve: async ({accessToken, userId}, _args: unknown, {authToken}) => { - const viewerId = getUserId(authToken) - return viewerId === userId ? accessToken : null - } - }, - accountId: { - type: new GraphQLNonNull(GraphQLID), - description: '*The atlassian account ID' - }, - cloudIds: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLID))), - description: 'The atlassian cloud IDs that the user has granted' - }, - createdAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the provider was created' - }, - teamId: { - type: new GraphQLNonNull(GraphQLID), - description: '*The team that the token is linked to' - }, - updatedAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the token was updated at' - }, - userId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The user that the access token is attached to' - }, - projects: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(JiraRemoteProject))), - description: - 'A list of projects accessible by this team member. empty if viewer is not the user', - resolve: ({teamId, userId}: AtlassianAuth, _args: unknown, {authToken, dataLoader}) => { - const viewerId = getUserId(authToken) - if (viewerId !== userId) return [] - return dataLoader.get('allJiraProjects').load({teamId, userId}) - } - }, - jiraSearchQueries: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(JiraSearchQuery))), - description: - 'the list of suggested search queries, sorted by most recent. Guaranteed to be < 60 days old', - resolve: async ({teamId, userId, jiraSearchQueries}) => { - const expirationThresh = ms('60d') - const thresh = new Date(Date.now() - expirationThresh) - const searchQueries = jiraSearchQueries || [] - const unexpiredQueries = searchQueries.filter((query) => query.lastUsedAt > thresh) - if (unexpiredQueries.length < searchQueries.length) { - await updateJiraSearchQueries({ - jiraSearchQueries: searchQueries, - teamId, - userId - }) - } - return unexpiredQueries - } - } - }) + fields: {} }) export default AtlassianIntegration diff --git a/packages/server/graphql/types/AzureDevOpsIntegration.ts b/packages/server/graphql/types/AzureDevOpsIntegration.ts deleted file mode 100644 index ceb7e2f68bc..00000000000 --- a/packages/server/graphql/types/AzureDevOpsIntegration.ts +++ /dev/null @@ -1,170 +0,0 @@ -import { - GraphQLBoolean, - GraphQLID, - GraphQLInt, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLString -} from 'graphql' -import {getUserId, isTeamMember} from '../../utils/authorization' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import connectionFromTasks from '../queries/helpers/connectionFromTasks' -import AzureDevOpsRemoteProject from './AzureDevOpsRemoteProject' -import AzureDevOpsSearchQuery from './AzureDevOpsSearchQuery' -import {AzureDevOpsWorkItemConnection} from './AzureDevOpsWorkItem' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import IntegrationProviderOAuth2 from './IntegrationProviderOAuth2' -import TeamMemberIntegrationAuthOAuth2 from './TeamMemberIntegrationAuthOAuth2' - -type WorkItemArgs = { - first: number - after?: string - queryString: string | null - projectKeyFilters: string[] | null - isWIQL: boolean -} - -const AzureDevOpsIntegration = new GraphQLObjectType({ - name: 'AzureDevOpsIntegration', - description: 'The Azure DevOps auth + integration helpers for a specific team member', - fields: () => ({ - auth: { - description: 'The OAuth2 Authorization for this team member', - type: TeamMemberIntegrationAuthOAuth2, - resolve: async ( - {teamId, userId}: {teamId: string; userId: string}, - _args: unknown, - {dataLoader} - ) => { - return dataLoader - .get('teamMemberIntegrationAuths') - .load({service: 'azureDevOps', teamId, userId}) - } - }, - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'Composite key in ado:teamId:userId format', - resolve: ({teamId, userId}: {teamId: string; userId: string}) => `ado:${teamId}:${userId}` - }, - accountId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The Azure DevOps account ID' - }, - instanceIds: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLID))), - description: 'The Azure DevOps instance IDs that the user has granted' - }, - createdAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the provider was created' - }, - teamId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The team that the token is linked to' - }, - updatedAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the token was updated at' - }, - userId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The user that the access token is attached to' - }, - workItems: { - type: new GraphQLNonNull(AzureDevOpsWorkItemConnection), - description: - 'A list of work items coming straight from the azure dev ops integration for a specific team member', - args: { - first: { - type: GraphQLInt, - defaultValue: 100 - }, - after: { - type: GraphQLISO8601Type, - description: 'the datetime cursor' - }, - queryString: { - type: GraphQLString, - description: 'A string of text to search for, or WIQL if isWIQL is true' - }, - projectKeyFilters: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString))), - description: 'A list of projects to restrict the search to, if null will search all' - }, - isWIQL: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'true if the queryString is WIQL, else false' - } - }, - resolve: async ({teamId, userId}, args: any, {authToken, dataLoader}: GQLContext) => { - const {first, queryString, projectKeyFilters, isWIQL} = args as WorkItemArgs - const viewerId = getUserId(authToken) - if (!isTeamMember(authToken, teamId)) { - const err = new Error('Cannot access another team members user stories') - standardError(err, {tags: {teamId, userId}, userId: viewerId}) - return connectionFromTasks([], 0, err) - } - const allUserWorkItems = await dataLoader - .get('azureDevOpsAllWorkItems') - .load({teamId, userId, queryString, projectKeyFilters, isWIQL}) - if (!allUserWorkItems) { - return connectionFromTasks([], 0, undefined) - } else { - const workItems = Array.from( - allUserWorkItems.map((userWorkItem) => { - return { - ...userWorkItem, - updatedAt: new Date() - } - }) - ) - return connectionFromTasks(workItems, first, undefined) - } - } - }, - projects: { - // Create a new object for ADO Projects (new schema object) - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(AzureDevOpsRemoteProject))), - description: - 'A list of projects coming straight from the azure dev ops integration for a specific team member', - resolve: ({teamId, userId}, _args: unknown, {authToken, dataLoader}) => { - const viewerId = getUserId(authToken) - if (viewerId !== userId) return [] - return dataLoader.get('allAzureDevOpsProjects').load({teamId, userId}) - } - }, - cloudProvider: { - description: - 'The cloud provider the team member may choose to integrate with. Nullable based on env vars', - type: IntegrationProviderOAuth2, - resolve: async (_source: unknown, _args: unknown, {dataLoader}) => { - const [globalProvider] = await dataLoader - .get('sharedIntegrationProviders') - .load({service: 'azureDevOps', orgTeamIds: ['aGhostTeam'], teamIds: []}) - return globalProvider - } - }, - sharedProviders: { - description: 'The non-global providers shared with the team or organization', - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(IntegrationProviderOAuth2))), - resolve: async ({teamId}: {teamId: string}, _args: unknown, {dataLoader}) => { - const team = await dataLoader.get('teams').loadNonNull(teamId) - const {orgId} = team - const orgTeams = await dataLoader.get('teamsByOrgIds').load(orgId) - const orgTeamIds = orgTeams.map(({id}) => id) - return dataLoader - .get('sharedIntegrationProviders') - .load({service: 'azureDevOps', orgTeamIds, teamIds: [teamId]}) - } - }, - azureDevOpsSearchQueries: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(AzureDevOpsSearchQuery))), - description: - 'the list of suggested search queries, sorted by most recent. Guaranteed to be < 60 days old' - } - }) -}) - -export default AzureDevOpsIntegration diff --git a/packages/server/graphql/types/AzureDevOpsRemoteProject.ts b/packages/server/graphql/types/AzureDevOpsRemoteProject.ts deleted file mode 100644 index c020477530c..00000000000 --- a/packages/server/graphql/types/AzureDevOpsRemoteProject.ts +++ /dev/null @@ -1,59 +0,0 @@ -import {GraphQLID, GraphQLInt, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql' -import {getInstanceId} from '../../utils/azureDevOps/azureDevOpsFieldTypeToId' -import {GQLContext} from '../graphql' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import IntegrationProviderServiceEnum from './IntegrationProviderServiceEnum' -import RepoIntegration, {repoIntegrationFields} from './RepoIntegration' - -const AzureDevOpsRemoteProject = new GraphQLObjectType({ - name: 'AzureDevOpsRemoteProject', - description: 'A project fetched from Azure DevOps in real time', - interfaces: () => [RepoIntegration], - isTypeOf: ({service}) => service === 'azureDevOps', - fields: () => ({ - ...repoIntegrationFields(), - id: { - type: new GraphQLNonNull(GraphQLID) - }, - service: { - type: new GraphQLNonNull(IntegrationProviderServiceEnum), - resolve: () => 'azureDevOps' - }, - teamId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The parabol teamId this issue was fetched for' - }, - userId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The parabol userId this issue was fetched for' - }, - lastUpdateTime: { - type: new GraphQLNonNull(GraphQLISO8601Type) - }, - self: { - type: new GraphQLNonNull(GraphQLID) - }, - instanceId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The instance ID that the project lives on', - resolve: ({url}: {url: string}) => getInstanceId(new URL(url)) - }, - name: { - type: new GraphQLNonNull(GraphQLString) - }, - revision: { - type: new GraphQLNonNull(GraphQLInt) - }, - state: { - type: new GraphQLNonNull(GraphQLString) - }, - url: { - type: new GraphQLNonNull(GraphQLString) - }, - visibility: { - type: new GraphQLNonNull(GraphQLString) - } - }) -}) - -export default AzureDevOpsRemoteProject diff --git a/packages/server/graphql/types/AzureDevOpsSearchQuery.ts b/packages/server/graphql/types/AzureDevOpsSearchQuery.ts deleted file mode 100644 index 58a42a2bb8f..00000000000 --- a/packages/server/graphql/types/AzureDevOpsSearchQuery.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { - GraphQLBoolean, - GraphQLID, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLString -} from 'graphql' -import {GQLContext} from '../graphql' -import GraphQLISO8601Type from './GraphQLISO8601Type' - -const AzureDevOpsSearchQuery = new GraphQLObjectType({ - name: 'AzureDevOpsSearchQuery', - description: - 'An Azure DevOps search query including all filters selected when the query was executed', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'shortid' - }, - queryString: { - type: new GraphQLNonNull(GraphQLString), - description: 'The query string, either simple or WIQL depending on the isWIQL flag' - }, - projectKeyFilters: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString))), - description: 'A list of projects to restrict the search to' - }, - isWIQL: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'true if the queryString is WIQL, else false', - resolve: ({isWIQL}) => !!isWIQL - }, - lastUsedAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'the time the search query was last used. Used for sorting' - } - }) -}) - -export default AzureDevOpsSearchQuery diff --git a/packages/server/graphql/types/AzureDevOpsWorkItem.ts b/packages/server/graphql/types/AzureDevOpsWorkItem.ts deleted file mode 100644 index b0ebf944690..00000000000 --- a/packages/server/graphql/types/AzureDevOpsWorkItem.ts +++ /dev/null @@ -1,102 +0,0 @@ -import {GraphQLID, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql' -import AzureDevOpsIssueId from 'parabol-client/shared/gqlIds/AzureDevOpsIssueId' -import {getInstanceId} from '../../utils/azureDevOps/azureDevOpsFieldTypeToId' -import connectionDefinitions from '../connectionDefinitions' -import {GQLContext} from '../graphql' -import AzureDevOpsRemoteProject from './AzureDevOpsRemoteProject' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import PageInfoDateCursor from './PageInfoDateCursor' -import StandardMutationError from './StandardMutationError' -import TaskIntegration from './TaskIntegration' - -const AzureDevOpsWorkItem = new GraphQLObjectType({ - name: 'AzureDevOpsWorkItem', - description: 'The Azure DevOps Work Item that comes direct from Azure DevOps', - interfaces: () => [TaskIntegration], - isTypeOf: ({service}) => service === 'azureDevOps', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'GUID instanceId:projectKey:issueKey', - resolve: ({id, teamProject, url}: {id: string; teamProject: string; url: string}) => { - const instanceId = getInstanceId(url) - return AzureDevOpsIssueId.join(instanceId, teamProject, id) - } - }, - issueKey: { - type: new GraphQLNonNull(GraphQLString), - description: 'The id of the issue from Azure, e.g. 7', - resolve: async ({id}: {id: string}) => { - return id - } - }, - title: { - type: new GraphQLNonNull(GraphQLString), - description: 'Title of the work item' - }, - // TODO: change teamProject name: https://github.com/ParabolInc/parabol/issues/7073 - teamProject: { - type: new GraphQLNonNull(GraphQLString), - description: 'Name or id of the Team Project the work item belongs to' - }, - project: { - type: new GraphQLNonNull(AzureDevOpsRemoteProject), - description: 'The Azure DevOps Remote Project the work item belongs to', - resolve: async ( - { - teamId, - userId, - teamProject, - url - }: {teamId: string; userId: string; teamProject: string; url: string}, - _args: unknown, - {dataLoader}: GQLContext - ) => { - const instanceId = getInstanceId(url) - return dataLoader - .get('azureDevOpsProject') - .load({instanceId, projectId: teamProject, userId, teamId}) - } - }, - url: { - type: new GraphQLNonNull(GraphQLString), - description: 'URL to the issue' - }, - state: { - type: new GraphQLNonNull(GraphQLString), - description: 'The Current State of the Work item' - }, - type: { - type: new GraphQLNonNull(GraphQLString), - description: 'The Type of the Work item' - }, - descriptionHTML: { - type: new GraphQLNonNull(GraphQLString), - description: 'The description converted into raw HTML' - } - }) -}) - -const {connectionType, edgeType} = connectionDefinitions({ - name: AzureDevOpsWorkItem.name, - nodeType: AzureDevOpsWorkItem, - edgeFields: () => ({ - cursor: { - type: GraphQLISO8601Type - } - }), - connectionFields: () => ({ - pageInfo: { - type: PageInfoDateCursor, - description: 'Page info with cursors coerced to ISO8601 dates' - }, - error: { - type: StandardMutationError, - description: 'An error with the connection, if any' - } - }) -}) - -export const AzureDevOpsWorkItemConnection = connectionType -export const AzureDevOpsWorkItemEdge = edgeType -export default AzureDevOpsWorkItem diff --git a/packages/server/graphql/types/GitHubIntegration.ts b/packages/server/graphql/types/GitHubIntegration.ts index d6d76319f14..42a7bdf1924 100644 --- a/packages/server/graphql/types/GitHubIntegration.ts +++ b/packages/server/graphql/types/GitHubIntegration.ts @@ -1,83 +1,8 @@ -import { - GraphQLBoolean, - GraphQLID, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLString -} from 'graphql' -import ms from 'ms' -import GitHubIntegrationId from '../../../client/shared/gqlIds/GitHubIntegrationId' -import {GitHubAuth} from '../../postgres/queries/getGitHubAuthByUserIdTeamId' -import updateGitHubSearchQueries from '../../postgres/queries/updateGitHubSearchQueries' -import {getUserId} from '../../utils/authorization' -import {GQLContext} from '../graphql' -import GitHubSearchQuery from './GitHubSearchQuery' -import GraphQLISO8601Type from './GraphQLISO8601Type' +import {GraphQLObjectType} from 'graphql' -const GitHubIntegration = new GraphQLObjectType({ +const GitHubIntegration = new GraphQLObjectType({ name: 'GitHubIntegration', - description: 'OAuth token for a team member', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'composite key', - resolve: ({teamId, userId}) => GitHubIntegrationId.join(teamId, userId) - }, - accessToken: { - description: 'The access token to github. good forever', - type: GraphQLID, - resolve: async ({accessToken, userId}, _args: unknown, {authToken}) => { - const viewerId = getUserId(authToken) - return viewerId === userId ? accessToken : null - } - }, - createdAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the provider was created' - }, - isActive: { - description: 'true if an access token exists, else false', - type: new GraphQLNonNull(GraphQLBoolean), - resolve: ({accessToken}) => !!accessToken - }, - githubSearchQueries: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GitHubSearchQuery))), - description: - 'the list of suggested search queries, sorted by most recent. Guaranteed to be < 60 days old', - resolve: async ({githubSearchQueries, teamId, userId}: GitHubAuth) => { - const expirationThresh = ms('60d') - const thresh = new Date(Date.now() - expirationThresh) - const unexpiredQueries = githubSearchQueries.filter( - (query) => new Date(query.lastUsedAt) > thresh - ) - if (unexpiredQueries.length < githubSearchQueries.length) { - await updateGitHubSearchQueries({teamId, userId, githubSearchQueries: unexpiredQueries}) - } - return unexpiredQueries - } - }, - login: { - type: new GraphQLNonNull(GraphQLID), - description: '*The GitHub login used for queries' - }, - scope: { - type: new GraphQLNonNull(GraphQLString), - description: 'The comma-separated list of scopes requested from GitHub' - }, - teamId: { - type: new GraphQLNonNull(GraphQLID), - description: '*The team that the token is linked to' - }, - updatedAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the token was updated at' - }, - userId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The user that the access token is attached to' - } - }) + fields: {} }) export default GitHubIntegration diff --git a/packages/server/graphql/types/GitHubSearchQuery.ts b/packages/server/graphql/types/GitHubSearchQuery.ts deleted file mode 100644 index 283c34e15f0..00000000000 --- a/packages/server/graphql/types/GitHubSearchQuery.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {GraphQLID, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql' -import {GQLContext} from '../graphql' -import GraphQLISO8601Type from './GraphQLISO8601Type' - -const GitHubSearchQuery = new GraphQLObjectType({ - name: 'GitHubSearchQuery', - description: 'A GitHub search query including all filters selected when the query was executed', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'shortid' - }, - queryString: { - type: new GraphQLNonNull(GraphQLString), - description: - 'The query string in GitHub format, including repository filters. e.g. is:issue is:open' - }, - lastUsedAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'the time the search query was last used. Used for sorting' - } - }) -}) - -export default GitHubSearchQuery diff --git a/packages/server/graphql/types/GitLabIntegration.ts b/packages/server/graphql/types/GitLabIntegration.ts deleted file mode 100644 index 7093bf686f5..00000000000 --- a/packages/server/graphql/types/GitLabIntegration.ts +++ /dev/null @@ -1,242 +0,0 @@ -import {GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql' -import GitLabServerManager from '../../integrations/gitlab/GitLabServerManager' -import {GetProjectIssuesQuery} from '../../types/gitlabTypes' -import sendToSentry from '../../utils/sendToSentry' -import connectionDefinitions from '../connectionDefinitions' -import {GQLContext} from '../graphql' -import connectionFromTasks from '../queries/helpers/connectionFromTasks' -import fetchGitLabProjects from '../queries/helpers/fetchGitLabProjects' -import GitLabSearchQuery from './GitLabSearchQuery' -import IntegrationProviderOAuth2 from './IntegrationProviderOAuth2' -import RepoIntegration from './RepoIntegration' -import StandardMutationError from './StandardMutationError' -import TaskIntegration from './TaskIntegration' -import TeamMemberIntegrationAuthOAuth2 from './TeamMemberIntegrationAuthOAuth2' - -type ProjectIssuesRes = NonNullable['issues']> -type ProjectIssue = NonNullable[0]>['node']> -type ProjectIssueEdge = { - node: ProjectIssue - cursor: string | Date -} -export type ProjectsIssuesArgs = { - first: number - after?: string - projectsIds: string[] | null - searchQuery: string - sort: string - state: string - fullPath: string -} -type CursorDetails = { - fullPath: string - cursor: string -} - -const GitLabIntegration = new GraphQLObjectType({ - name: 'GitLabIntegration', - description: 'Gitlab integration data for a given team member', - fields: () => ({ - auth: { - description: 'The OAuth2 Authorization for this team member', - type: TeamMemberIntegrationAuthOAuth2, - resolve: async ( - {teamId, userId}: {teamId: string; userId: string}, - _args: unknown, - {dataLoader} - ) => { - return dataLoader.get('freshGitlabAuth').load({teamId, userId}) - } - }, - cloudProvider: { - description: - 'The cloud provider the team member may choose to integrate with. Nullable based on env vars', - type: IntegrationProviderOAuth2, - resolve: async (_source: unknown, _args: unknown, {dataLoader}) => { - const [globalProvider] = await dataLoader - .get('sharedIntegrationProviders') - .load({service: 'gitlab', orgTeamIds: ['aGhostTeam'], teamIds: []}) - return globalProvider - } - }, - sharedProviders: { - description: 'The non-global providers shared with the team or organization', - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(IntegrationProviderOAuth2))), - resolve: async ({teamId}: {teamId: string}, _args: unknown, {dataLoader}) => { - const team = await dataLoader.get('teams').loadNonNull(teamId) - const {orgId} = team - const orgTeams = await dataLoader.get('teamsByOrgIds').load(orgId) - const orgTeamIds = orgTeams.map(({id}) => id) - return dataLoader - .get('sharedIntegrationProviders') - .load({service: 'gitlab', orgTeamIds, teamIds: [teamId]}) - } - }, - gitlabSearchQueries: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GitLabSearchQuery))), - resolve: async () => [] - }, - projects: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(RepoIntegration))), - description: 'A list of projects accessible by this team member', - resolve: async ( - {teamId, userId}: {teamId: string; userId: string}, - _args: unknown, - context, - info - ) => { - return fetchGitLabProjects(teamId, userId, context, info) - } - }, - projectsIssues: { - type: new GraphQLNonNull(GitLabProjectIssuesConnection), - args: { - first: { - type: GraphQLNonNull(GraphQLInt) - }, - after: { - type: GraphQLString, - description: 'the stringified cursors for pagination' - }, - projectsIds: { - type: GraphQLList(GraphQLString), - description: 'the ids of the projects selected as filters' - }, - searchQuery: { - type: GraphQLNonNull(GraphQLString), - description: 'the search query that the user enters to filter issues' - }, - sort: { - type: GraphQLNonNull(GraphQLString), - description: 'the sort string that defines the order of the returned issues' - }, - state: { - type: GraphQLNonNull(GraphQLString), - description: 'the state of issues, e.g. opened or closed' - } - }, - resolve: async ( - {teamId, userId}: {teamId: string; userId: string}, - args: any, - context, - info - ) => { - const {projectsIds, after = ''} = args as ProjectsIssuesArgs - const {dataLoader} = context - const auth = await dataLoader - .get('teamMemberIntegrationAuths') - .load({service: 'gitlab', teamId, userId}) - if (!auth?.accessToken) return [] - const {providerId} = auth - const provider = await dataLoader.get('integrationProviders').load(providerId) - if (!provider?.serverBaseUrl) return [] - const manager = new GitLabServerManager(auth, context, info, provider.serverBaseUrl) - const [projectsData, projectsErr] = await manager.getProjects({ - ids: projectsIds, - first: 50 // if no project filters have been selected, get the 50 most recently used projects - }) - if (projectsErr) { - sendToSentry(new Error('Unable to get GitLab projects in projectsIssues query'), {userId}) - return connectionFromTasks([], 0) - } - const projectsFullPaths = new Set() - projectsData.projects?.edges?.forEach((edge) => { - if (edge?.node?.fullPath) { - projectsFullPaths.add(edge?.node?.fullPath) - } - }) - let parsedAfter: CursorDetails[] | null - try { - parsedAfter = after.length ? JSON.parse(after) : null - } catch (e) { - sendToSentry(new Error('Error parsing after'), {userId, tags: {after}}) - return connectionFromTasks([], 0) - } - const isValidJSON = parsedAfter?.every( - (cursorsDetails) => - typeof cursorsDetails.cursor === 'string' && typeof cursorsDetails.fullPath === 'string' - ) - if (isValidJSON === false) { - sendToSentry(new Error('after arg has an invalid JSON structure'), { - userId, - tags: {after} - }) - return connectionFromTasks([], 0) - } - - const projectsIssuesPromises = Array.from(projectsFullPaths).map((fullPath) => { - const after = parsedAfter?.find((cursor) => cursor.fullPath === fullPath)?.cursor ?? '' - return manager.getProjectIssues({ - ...args, - fullPath, - after - }) - }) - const projectsIssues = [] as ProjectIssueEdge[] - const errors = [] as Error[] - let hasNextPage = false - const endCursor = [] as CursorDetails[] - const projectsIssuesResponses = await Promise.all(projectsIssuesPromises) - for (const res of projectsIssuesResponses) { - const [projectIssuesData, err] = res - if (err) { - errors.push(err) - sendToSentry(err, {userId}) - return - } - const {project} = projectIssuesData - if (!project?.issues) continue - const {fullPath, issues} = project - const {edges, pageInfo} = issues - if (pageInfo.hasNextPage) { - hasNextPage = true - const currentCursorDetails = endCursor.find( - (cursorDetails) => cursorDetails.fullPath === fullPath - ) - const newCursor = pageInfo.endCursor ?? '' - if (currentCursorDetails) currentCursorDetails.cursor = newCursor - else endCursor.push({fullPath, cursor: newCursor}) - } - edges?.forEach((edge) => { - if (!edge?.node) return - const {node, cursor} = edge - projectsIssues.push({cursor, node}) - }) - } - - const firstEdge = projectsIssues[0] - const stringifiedEndCursor = JSON.stringify(endCursor) - return { - error: errors[0], - edges: projectsIssues, - pageInfo: { - startCursor: firstEdge && firstEdge.cursor, - endCursor: stringifiedEndCursor, - hasNextPage - } - } - } - } - // The GitLab schema get injected here as 'api' - }) -}) - -const {connectionType, edgeType} = connectionDefinitions({ - name: GitLabIntegration.name, - nodeType: TaskIntegration, - edgeFields: () => ({ - cursor: { - type: GraphQLString - } - }), - connectionFields: () => ({ - error: { - type: StandardMutationError, - description: 'An error with the connection, if any' - } - }) -}) - -export const GitLabProjectIssuesConnection = connectionType -export const GitLabProjectIssuesEdge = edgeType -export default GitLabIntegration diff --git a/packages/server/graphql/types/GitLabSearchQuery.ts b/packages/server/graphql/types/GitLabSearchQuery.ts deleted file mode 100644 index 6004ee349c5..00000000000 --- a/packages/server/graphql/types/GitLabSearchQuery.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {GraphQLID, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql' -import {GQLContext} from '../graphql' -import GraphQLISO8601Type from './GraphQLISO8601Type' - -const GitLabSearchQuery = new GraphQLObjectType({ - name: 'GitLabSearchQuery', - description: 'A GitLab search query including the search query and the project filters', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'shortid' - }, - queryString: { - type: new GraphQLNonNull(GraphQLString), - description: 'The query string used to search GitLab issue titles and descriptions' - }, - selectedProjectsIds: { - type: new GraphQLList(new GraphQLNonNull(GraphQLID)), - description: - 'The list of ids of projects that have been selected as a filter. Null if none have been selected' - }, - lastUsedAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'the time the search query was last used. Used for sorting' - } - }) -}) - -export default GitLabSearchQuery diff --git a/packages/server/graphql/types/JiraSearchQuery.ts b/packages/server/graphql/types/JiraSearchQuery.ts deleted file mode 100644 index 1c8c86d0803..00000000000 --- a/packages/server/graphql/types/JiraSearchQuery.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { - GraphQLBoolean, - GraphQLID, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLString -} from 'graphql' -import {globalIdField} from 'graphql-relay' -import {GQLContext} from '../graphql' -import GraphQLISO8601Type from './GraphQLISO8601Type' - -// Used for both Jira and Jira Server -const JiraSearchQuery = new GraphQLObjectType({ - name: 'JiraSearchQuery', - description: 'A jira search query including all filters selected when the query was executed', - fields: () => ({ - id: globalIdField(), - queryString: { - type: new GraphQLNonNull(GraphQLString), - description: 'The query string, either simple or JQL depending on the isJQL flag' - }, - isJQL: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'true if the queryString is JQL, else false', - resolve: ({isJQL}) => !!isJQL - }, - projectKeyFilters: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLID))), - description: 'The list of project keys selected as a filter', - resolve: ({projectKeyFilters}) => projectKeyFilters || [] - }, - lastUsedAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'the time the search query was last used. Used for sorting' - } - }) -}) - -export default JiraSearchQuery diff --git a/packages/server/graphql/types/JiraServerIntegration.ts b/packages/server/graphql/types/JiraServerIntegration.ts deleted file mode 100644 index 2f10905f418..00000000000 --- a/packages/server/graphql/types/JiraServerIntegration.ts +++ /dev/null @@ -1,265 +0,0 @@ -import { - GraphQLBoolean, - GraphQLID, - GraphQLInt, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLString -} from 'graphql' -import IntegrationProviderId from '~/shared/gqlIds/IntegrationProviderId' -import IntegrationRepoId from '~/shared/gqlIds/IntegrationRepoId' -import TeamMember from '../../database/types/TeamMember' -import JiraServerRestManager from '../../integrations/jiraServer/JiraServerRestManager' -import {IntegrationProviderJiraServer} from '../../postgres/queries/getIntegrationProvidersByIds' -import getLatestIntegrationSearchQueries from '../../postgres/queries/getLatestIntegrationSearchQueries' -import {getUserId} from '../../utils/authorization' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import connectionFromTasks from '../queries/helpers/connectionFromTasks' -import IntegrationProviderOAuth1 from './IntegrationProviderOAuth1' -import JiraSearchQuery from './JiraSearchQuery' -import {JiraServerIssueConnection} from './JiraServerIssue' -import JiraServerRemoteProject from './JiraServerRemoteProject' -import TeamMemberIntegrationAuthOAuth1 from './TeamMemberIntegrationAuthOAuth1' - -type IssueArgs = { - first: number - after: string - queryString: string | null - isJQL: boolean - projectKeyFilters: string[] | null -} - -const JiraServerIntegration = new GraphQLObjectType<{teamId: string; userId: string}, GQLContext>({ - name: 'JiraServerIntegration', - description: 'Jira Server integration data for a given team member', - fields: () => ({ - id: { - type: GraphQLID, - description: 'Composite key in jiraServer:providerId format', - resolve: async ({teamId, userId}: {teamId: string; userId: string}, _args, {dataLoader}) => { - const auth = await dataLoader - .get('teamMemberIntegrationAuths') - .load({service: 'jiraServer', teamId, userId}) - - if (!auth) { - return null - } - - return `jiraServer:${teamId}:${auth.providerId}` - } - }, - auth: { - description: 'The OAuth1 Authorization for this team member', - type: TeamMemberIntegrationAuthOAuth1, - resolve: async ({teamId, userId}, _args, {dataLoader}) => { - const auth = await dataLoader - .get('teamMemberIntegrationAuths') - .load({service: 'jiraServer', teamId, userId}) - return auth - } - }, - sharedProviders: { - description: 'The non-global providers shared with the team or organization', - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(IntegrationProviderOAuth1))), - resolve: async ({teamId, userId}, _args, {dataLoader}) => { - const teamMembers = await dataLoader.get('teamMembersByUserId').load(userId) - const teamMember = teamMembers.find( - (teamMember: TeamMember) => teamMember.teamId === teamId - ) - if (!teamMember) return [] - - const team = await dataLoader.get('teams').loadNonNull(teamMember.teamId) - const {orgId} = team - const orgTeams = await dataLoader.get('teamsByOrgIds').load(orgId) - const orgTeamIds = orgTeams.map(({id}) => id) - - const providers = await dataLoader.get('sharedIntegrationProviders').load({ - service: 'jiraServer', - orgTeamIds: [...orgTeamIds, 'aGhostTeam'], - teamIds: [teamId] - }) - return providers - } - }, - issues: { - type: new GraphQLNonNull(JiraServerIssueConnection), - description: - 'A list of issues coming straight from the jira integration for a specific team member', - args: { - first: { - type: GraphQLInt, - defaultValue: 25 - }, - after: { - type: GraphQLString, - defaultValue: '-1' - }, - queryString: { - type: GraphQLString, - description: 'A string of text to search for, or JQL if isJQL is true' - }, - isJQL: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'true if the queryString is JQL, else false' - }, - projectKeyFilters: { - type: new GraphQLList(new GraphQLNonNull(GraphQLID)), - descrption: - 'A list of projects to restrict the search to. format is cloudId:projectKey. If null, will search all' - } - }, - resolve: async ({teamId, userId}, args: any, {authToken, dataLoader}) => { - const {first, after, queryString, isJQL, projectKeyFilters} = args as IssueArgs - const viewerId = getUserId(authToken) - if (viewerId !== userId) { - const err = new Error('Cannot access another team members issues') - standardError(err, {tags: {teamId, userId}, userId: viewerId}) - return connectionFromTasks([], 0, err) - } - - const auth = await dataLoader - .get('teamMemberIntegrationAuths') - .load({service: 'jiraServer', teamId, userId}) - - if (!auth) { - return null - } - - const provider = await dataLoader.get('integrationProviders').loadNonNull(auth.providerId) - - const integrationManager = new JiraServerRestManager( - auth, - provider as IntegrationProviderJiraServer - ) - - if (!integrationManager) { - return null - } - - const projectKeys = (projectKeyFilters ?? []).map( - (projectKeyFilter) => IntegrationRepoId.split(projectKeyFilter).projectKey! - ) - - // Request one extra item to see if there are more results - const maxResults = first + 1 - // Relay requires the cursor to be a string - const afterInt = parseInt(after, 10) - const startAt = afterInt + 1 - const issueRes = await integrationManager.getIssues( - queryString, - isJQL, - projectKeys, - maxResults, - startAt - ) - - if (issueRes instanceof Error) { - return connectionFromTasks([], first, { - message: issueRes.message - }) - } - - const {issues} = issueRes - - const mappedIssues = issues.map((issue) => { - const {project, issuetype, summary, description, updated} = issue.fields - return { - ...issue, - userId, - teamId, - providerId: provider.id, - issueKey: issue.key, - description: description ?? '', - descriptionHTML: issue.renderedFields.description, - projectId: project.id, - projectKey: project.key, - projectName: project.name, - issueType: issuetype.id, - summary, - service: 'jiraServer' as const, - updatedAt: new Date(updated) - } - }) - - const nodes = mappedIssues.slice(0, first) - const edges = mappedIssues.map((node, index) => ({ - cursor: `${index + afterInt}`, - node - })) - - const firstEdge = edges[0] - - return { - edges, - pageInfo: { - startCursor: firstEdge && firstEdge.cursor, - endCursor: firstEdge ? edges[edges.length - 1]!.cursor : null, - hasNextPage: mappedIssues.length > nodes.length - } - } - } - }, - projects: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(JiraServerRemoteProject))), - description: - 'A list of projects accessible by this team member. empty if viewer is not the user', - resolve: async ({teamId, userId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('allJiraServerProjects').load({teamId, userId}) - } - }, - providerId: { - type: GraphQLID, - resolve: async ({teamId, userId}, _args: unknown, {dataLoader}) => { - const auth = await dataLoader - .get('teamMemberIntegrationAuths') - .load({service: 'jiraServer', teamId, userId}) - - if (!auth) { - return null - } - - return IntegrationProviderId.join(auth.providerId) - } - }, - searchQueries: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(JiraSearchQuery))), - description: - 'the list of suggested search queries, sorted by most recent. Guaranteed to be < 60 days old', - resolve: async ({teamId, userId}, _args: unknown, {dataLoader}) => { - const auth = await dataLoader - .get('teamMemberIntegrationAuths') - .load({service: 'jiraServer', teamId, userId}) - - if (!auth) { - return [] - } - - const searchQueries = await getLatestIntegrationSearchQueries({ - teamId, - userId, - service: 'jiraServer', - providerId: auth.providerId - }) - - return searchQueries.map((searchQuery) => { - const query = searchQuery.query as { - queryString: string | null - isJQL: boolean - projectKeyFilters: string[] | null - } - - return { - id: searchQuery.id, - queryString: query.queryString, - isJQL: query.isJQL, - projectKeyFilters: query.projectKeyFilters, - lastUpdatedAt: searchQuery.lastUsedAt - } - }) - } - } - }) -}) -export default JiraServerIntegration diff --git a/packages/server/graphql/types/JiraServerIssue.ts b/packages/server/graphql/types/JiraServerIssue.ts deleted file mode 100644 index 3047f751c28..00000000000 --- a/packages/server/graphql/types/JiraServerIssue.ts +++ /dev/null @@ -1,117 +0,0 @@ -import {GraphQLID, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql' -import JiraServerIssueId from '~/shared/gqlIds/JiraServerIssueId' -import {JiraServerIssue as JiraServerRestIssue} from '../../dataloader/jiraServerLoaders' -import connectionDefinitions from '../connectionDefinitions' -import {GQLContext} from '../graphql' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import StandardMutationError from './StandardMutationError' -import TaskIntegration from './TaskIntegration' - -type JiraServerIssueSource = JiraServerRestIssue & { - userId: string - teamId: string - providerId: number -} - -const VOTE_FIELD_ID_BLACKLIST = ['description', 'summary'] -const VOTE_FIELD_ALLOWED_TYPES = ['string', 'number'] - -const JiraServerIssue = new GraphQLObjectType({ - name: 'JiraServerIssue', - description: 'The Jira Issue that comes direct from Jira Server', - interfaces: () => [TaskIntegration], - isTypeOf: ({service}) => service === 'jiraServer', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'GUID providerId:repositoryId:issueId', - resolve: ({id, projectId, providerId}) => { - return JiraServerIssueId.join(providerId, projectId, id) - } - }, - issueKey: { - type: new GraphQLNonNull(GraphQLID) - }, - issueType: { - type: new GraphQLNonNull(GraphQLID) - }, - projectId: { - type: new GraphQLNonNull(GraphQLID) - }, - projectKey: { - type: new GraphQLNonNull(GraphQLID) - }, - projectName: { - type: new GraphQLNonNull(GraphQLString) - }, - teamId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The parabol teamId this issue was fetched for' - }, - userId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The parabol userId this issue was fetched for' - }, - url: { - type: new GraphQLNonNull(GraphQLString), - description: 'The url to access the issue', - resolve: ({issueKey, self}) => { - const {origin} = new URL(self) - return `${origin}/browse/${issueKey}` - } - }, - summary: { - type: new GraphQLNonNull(GraphQLString), - description: 'The plaintext summary of the jira issue' - }, - description: { - type: new GraphQLNonNull(GraphQLString) - }, - descriptionHTML: { - type: new GraphQLNonNull(GraphQLString), - description: 'The description converted into raw HTML' - }, - possibleEstimationFieldNames: { - type: new GraphQLNonNull(GraphQLList(GraphQLNonNull(GraphQLString))), - resolve: async ({teamId, userId, providerId, issueType, projectId}, _args, {dataLoader}) => { - const issueMeta = await dataLoader - .get('jiraServerFieldTypes') - .load({teamId, userId, projectId, issueType, providerId}) - if (!issueMeta) return [] - const fieldNames = issueMeta - .filter( - ({fieldId, operations, schema}) => - !VOTE_FIELD_ID_BLACKLIST.includes(fieldId) && - operations.includes('set') && - VOTE_FIELD_ALLOWED_TYPES.includes(schema.type) - ) - .map(({name}) => name) - return fieldNames - } - }, - updatedAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the issue was last updated' - } - }) -}) - -const {connectionType, edgeType} = connectionDefinitions({ - name: JiraServerIssue.name, - nodeType: JiraServerIssue, - edgeFields: () => ({ - cursor: { - type: GraphQLString - } - }), - connectionFields: () => ({ - error: { - type: StandardMutationError, - description: 'An error with the connection, if any' - } - }) -}) - -export const JiraServerIssueConnection = connectionType -export const JiraServerIssueEdge = edgeType -export default JiraServerIssue diff --git a/packages/server/graphql/types/JiraServerRemoteProject.ts b/packages/server/graphql/types/JiraServerRemoteProject.ts deleted file mode 100644 index e69873dc2dd..00000000000 --- a/packages/server/graphql/types/JiraServerRemoteProject.ts +++ /dev/null @@ -1,61 +0,0 @@ -import {GraphQLID, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql' -import IntegrationRepoId from 'parabol-client/shared/gqlIds/IntegrationRepoId' -import JiraServerRestManager from '../../integrations/jiraServer/JiraServerRestManager' -import {IntegrationProviderJiraServer} from '../../postgres/queries/getIntegrationProvidersByIds' -import defaultJiraProjectAvatar from '../../utils/defaultJiraProjectAvatar' -import {GQLContext} from '../graphql' -import IntegrationProviderServiceEnum from './IntegrationProviderServiceEnum' -import JiraRemoteAvatarUrls from './JiraRemoteAvatarUrls' -import JiraRemoteProjectCategory from './JiraRemoteProjectCategory' -import RepoIntegration, {repoIntegrationFields} from './RepoIntegration' - -const JiraServerRemoteProject = new GraphQLObjectType({ - name: 'JiraServerRemoteProject', - description: 'A project fetched from Jira in real time', - interfaces: () => [RepoIntegration], - isTypeOf: ({service}) => service === 'jiraServer', - fields: () => ({ - ...repoIntegrationFields(), - id: { - type: new GraphQLNonNull(GraphQLID), - resolve: (item) => IntegrationRepoId.join(item) - }, - service: { - type: new GraphQLNonNull(IntegrationProviderServiceEnum), - resolve: () => 'jiraServer' - }, - teamId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The parabol teamId this issue was fetched for' - }, - userId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The parabol userId this issue was fetched for' - }, - name: { - type: new GraphQLNonNull(GraphQLString) - }, - avatar: { - type: new GraphQLNonNull(GraphQLString), - resolve: async ({avatarUrls, teamId, userId}, _args: unknown, {dataLoader}) => { - const url = avatarUrls['48x48'] - const auth = await dataLoader - .get('teamMemberIntegrationAuths') - .load({service: 'jiraServer', teamId, userId}) - if (!auth) return defaultJiraProjectAvatar - const provider = await dataLoader.get('integrationProviders').loadNonNull(auth.providerId) - const manager = new JiraServerRestManager(auth, provider as IntegrationProviderJiraServer) - const avatar = await manager.getProjectAvatar(url) - return avatar || defaultJiraProjectAvatar - } - }, - avatarUrls: { - type: new GraphQLNonNull(JiraRemoteAvatarUrls) - }, - projectCategory: { - type: new GraphQLNonNull(JiraRemoteProjectCategory) - } - }) -}) - -export default JiraServerRemoteProject diff --git a/packages/server/graphql/types/MSTeamsIntegration.ts b/packages/server/graphql/types/MSTeamsIntegration.ts deleted file mode 100644 index 3871bafa650..00000000000 --- a/packages/server/graphql/types/MSTeamsIntegration.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {GraphQLList, GraphQLNonNull, GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import IntegrationProviderWebhook from './IntegrationProviderWebhook' -import TeamMemberIntegrationAuthWebhook from './TeamMemberIntegrationAuthWebhook' - -const MSTeamsIntegration = new GraphQLObjectType({ - name: 'MSTeamsIntegration', - description: 'Integration Auth and shared providers available to the team member', - fields: () => ({ - auth: { - description: 'The Webhook Authorization for this team member', - type: TeamMemberIntegrationAuthWebhook, - resolve: async ({teamId, userId}, _args, {dataLoader}) => { - return dataLoader - .get('teamMemberIntegrationAuths') - .load({service: 'msTeams', teamId, userId}) - } - }, - sharedProviders: { - description: 'The non-global providers shared with the team or organization', - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(IntegrationProviderWebhook))), - resolve: async ({teamId}: {teamId: string}, _args: unknown, {dataLoader}: GQLContext) => { - const team = await dataLoader.get('teams').loadNonNull(teamId) - const {orgId} = team - const orgTeams = await dataLoader.get('teamsByOrgIds').load(orgId) - const orgTeamIds = orgTeams.map(({id}) => id) - return dataLoader - .get('sharedIntegrationProviders') - .load({service: 'msTeams', orgTeamIds, teamIds: [teamId]}) - } - } - }) -}) - -export default MSTeamsIntegration diff --git a/packages/server/graphql/types/MattermostIntegration.ts b/packages/server/graphql/types/MattermostIntegration.ts deleted file mode 100644 index 3d6a168c34a..00000000000 --- a/packages/server/graphql/types/MattermostIntegration.ts +++ /dev/null @@ -1,43 +0,0 @@ -import {GraphQLList, GraphQLNonNull, GraphQLObjectType} from 'graphql' -import {getUserId} from '../../utils/authorization' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import IntegrationProviderWebhook from './IntegrationProviderWebhook' -import TeamMemberIntegrationAuthWebhook from './TeamMemberIntegrationAuthWebhook' - -const MattermostIntegration = new GraphQLObjectType({ - name: 'MattermostIntegration', - description: 'Integration Auth and shared providers available to the team member', - fields: () => ({ - auth: { - description: 'The OAuth2 Authorization for this team member', - type: TeamMemberIntegrationAuthWebhook, - resolve: async ({teamId, userId}, _args, {dataLoader}) => { - return dataLoader - .get('teamMemberIntegrationAuths') - .load({service: 'mattermost', teamId, userId}) - } - }, - sharedProviders: { - description: 'The non-global providers shared with the team or organization', - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(IntegrationProviderWebhook))), - resolve: async ( - {teamId}: {teamId: string}, - _args: unknown, - {authToken, dataLoader}: GQLContext - ) => { - const viewerId = getUserId(authToken) - const team = await dataLoader.get('teams').load(teamId) - if (!team) return standardError(new Error('Team not found'), {userId: viewerId}) - const {orgId} = team - const orgTeams = await dataLoader.get('teamsByOrgIds').load(orgId) - const orgTeamIds = orgTeams.map(({id}) => id) - return dataLoader - .get('sharedIntegrationProviders') - .load({service: 'mattermost', orgTeamIds, teamIds: [teamId]}) - } - } - }) -}) - -export default MattermostIntegration diff --git a/packages/server/graphql/types/Organization.ts b/packages/server/graphql/types/Organization.ts index 452c765de79..988cd5c2cc2 100644 --- a/packages/server/graphql/types/Organization.ts +++ b/packages/server/graphql/types/Organization.ts @@ -1,243 +1,9 @@ -import { - GraphQLBoolean, - GraphQLID, - GraphQLInt, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLString -} from 'graphql' -import { - getUserId, - isSuperUser, - isTeamMember, - isUserBillingLeader, - isUserOrgAdmin -} from '../../utils/authorization' +import {GraphQLObjectType} from 'graphql' import {GQLContext} from '../graphql' -import getActiveTeamCountByOrgIds from '../public/types/helpers/getActiveTeamCountByOrgIds' -import {resolveForBillingLeaders} from '../resolvers' -import CreditCard from './CreditCard' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import OrgUserCount from './OrgUserCount' -import OrganizationUser, {OrganizationUserConnection} from './OrganizationUser' -import Team from './Team' const Organization: GraphQLObjectType = new GraphQLObjectType({ name: 'Organization', - description: 'An organization', - fields: () => ({ - id: {type: new GraphQLNonNull(GraphQLID), description: 'The unique organization ID'}, - activeDomain: { - type: GraphQLString, - description: - 'The top level domain this organization is linked to, null if only generic emails used' - }, - isActiveDomainTouched: { - type: new GraphQLNonNull(GraphQLBoolean), - description: - 'false if the activeDomain is null or was set automatically via a heuristic, true if set manually', - resolve: ({isActiveDomainTouched}) => !!isActiveDomainTouched - }, - createdAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The datetime the organization was created' - }, - creditCard: { - type: CreditCard, - description: 'The safe credit card details', - resolve: resolveForBillingLeaders('creditCard') - }, - isBillingLeader: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'true if the viewer holds the billing leader role on the org', - resolve: async ({id: orgId}, _args: unknown, {authToken, dataLoader}) => { - const viewerId = getUserId(authToken) - return isUserBillingLeader(viewerId, orgId, dataLoader) - } - }, - isOrgAdmin: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'true if the viewer holds the the org admin role on the org', - resolve: async ({id: orgId}, _args: unknown, {authToken, dataLoader}) => { - const viewerId = getUserId(authToken) - return isUserOrgAdmin(viewerId, orgId, dataLoader) - } - }, - name: { - type: new GraphQLNonNull(GraphQLString), - description: 'The name of the organization' - }, - activeTeamCount: { - type: new GraphQLNonNull(GraphQLInt), - description: 'Number of teams with 3+ meetings (>1 attendee) that met within last 30 days', - resolve: async ({id: orgId}) => { - return getActiveTeamCountByOrgIds(orgId) - } - }, - allTeams: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Team))), - description: - 'All the teams in the organization. If the viewer is not a billing lead, org admin, super user, or they do not have the publicTeams flag, return the teams they are a member of.', - resolve: async ({id: orgId}, _args: unknown, {dataLoader, authToken}) => { - const viewerId = getUserId(authToken) - const [allTeamsOnOrg, organization, isOrgAdmin, isBillingLeader] = await Promise.all([ - dataLoader.get('teamsByOrgIds').load(orgId), - dataLoader.get('organizations').loadNonNull(orgId), - isUserOrgAdmin(viewerId, orgId, dataLoader), - isUserBillingLeader(viewerId, orgId, dataLoader) - ]) - const sortedTeamsOnOrg = allTeamsOnOrg.sort((a, b) => a.name.localeCompare(b.name)) - const hasPublicTeamsFlag = !!organization.featureFlags?.includes('publicTeams') - if (isBillingLeader || isOrgAdmin || isSuperUser(authToken) || hasPublicTeamsFlag) { - const viewerTeams = sortedTeamsOnOrg.filter((team) => authToken.tms.includes(team.id)) - const otherTeams = sortedTeamsOnOrg.filter((team) => !authToken.tms.includes(team.id)) - return [...viewerTeams, ...otherTeams] - } else { - return sortedTeamsOnOrg.filter((team) => authToken.tms.includes(team.id)) - } - } - }, - viewerTeams: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Team))), - description: 'all the teams the viewer is on in the organization', - resolve: async ({id: orgId}, _args: unknown, {dataLoader, authToken}) => { - const allTeamsOnOrg = await dataLoader.get('teamsByOrgIds').load(orgId) - return allTeamsOnOrg - .filter((team) => authToken.tms.includes(team.id)) - .sort((a, b) => a.name.localeCompare(b.name)) - } - }, - publicTeams: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Team))), - description: - 'all the teams that the viewer does not belong to that are in the organization. Only visible if the org has the publicTeams flag set to true.', - resolve: async ({id: orgId}, _args: unknown, {dataLoader, authToken}) => { - const [allTeamsOnOrg, organization] = await Promise.all([ - dataLoader.get('teamsByOrgIds').load(orgId), - dataLoader.get('organizations').loadNonNull(orgId) - ]) - const hasPublicTeamsFlag = !!organization.featureFlags?.includes('publicTeams') - if (!isSuperUser(authToken) || !hasPublicTeamsFlag) return [] - const publicTeams = allTeamsOnOrg.filter((team) => !isTeamMember(authToken, team.id)) - return publicTeams - } - }, - periodEnd: { - type: GraphQLISO8601Type, - description: 'THe datetime the current billing cycle ends', - resolve: resolveForBillingLeaders('periodEnd') - }, - periodStart: { - type: GraphQLISO8601Type, - description: 'The datetime the current billing cycle starts', - resolve: resolveForBillingLeaders('periodStart') - }, - tierLimitExceededAt: { - type: GraphQLISO8601Type, - description: 'Flag the organization as exceeding the tariff limits by setting a datetime' - }, - scheduledLockAt: { - type: GraphQLISO8601Type, - description: 'Schedule the organization to be locked at' - }, - lockedAt: { - type: GraphQLISO8601Type, - description: 'Organization locked at' - }, - retroMeetingsOffered: { - deprecationReason: 'Unlimited retros for all!', - type: new GraphQLNonNull(GraphQLInt), - description: 'The total number of retroMeetings given to the team' - }, - retroMeetingsRemaining: { - deprecationReason: 'Unlimited retros for all!', - type: new GraphQLNonNull(GraphQLInt), - description: 'Number of retro meetings that can be run (if not pro)' - }, - showConversionModal: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'true if should show the org the conversion modal, else false', - resolve: ({showConversionModal}) => !!showConversionModal - }, - stripeId: { - type: GraphQLID, - description: 'The customerId from stripe', - resolve: resolveForBillingLeaders('stripeId') - }, - stripeSubscriptionId: { - type: GraphQLID, - description: 'The subscriptionId from stripe', - resolve: resolveForBillingLeaders('stripeSubscriptionId') - }, - upcomingInvoiceEmailSentAt: { - type: GraphQLISO8601Type, - description: 'The last upcoming invoice email that was sent, null if never sent' - }, - updatedAt: { - type: GraphQLISO8601Type, - description: 'The datetime the organization was last updated' - }, - viewerOrganizationUser: { - type: OrganizationUser, - description: 'The OrganizationUser of the viewer', - resolve: async ({id: orgId}, _args: unknown, {dataLoader, authToken}: GQLContext) => { - const viewerId = getUserId(authToken) - return dataLoader.get('organizationUsersByUserIdOrgId').load({userId: viewerId, orgId}) - } - }, - organizationUsers: { - args: { - after: { - type: GraphQLString - }, - first: { - type: GraphQLInt - } - }, - type: new GraphQLNonNull(OrganizationUserConnection), - resolve: async ({id: orgId}: {id: string}, _args: unknown, {dataLoader}: GQLContext) => { - const organizationUsers = await dataLoader.get('organizationUsersByOrgId').load(orgId) - organizationUsers.sort((a, b) => (a.orgId > b.orgId ? 1 : -1)) - const edges = organizationUsers.map((node) => ({ - cursor: node.id, - node - })) - // TODO implement pagination - const firstEdge = edges[0] - return { - edges, - pageInfo: { - endCursor: firstEdge ? edges[edges.length - 1]!.cursor : null, - hasNextPage: false - } - } - } - }, - orgUserCount: { - type: new GraphQLNonNull(OrgUserCount), - description: 'The count of active & inactive users', - resolve: async ({id: orgId}: {id: string}, _args: unknown, {dataLoader}: GQLContext) => { - const organizationUsers = await dataLoader.get('organizationUsersByOrgId').load(orgId) - const inactiveUserCount = organizationUsers.filter(({inactive}) => inactive).length - return { - inactiveUserCount, - activeUserCount: organizationUsers.length - inactiveUserCount - } - } - }, - billingLeaders: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(OrganizationUser))), - description: 'The leaders of the org', - resolve: async ({id: orgId}: {id: string}, _args: unknown, {dataLoader}: GQLContext) => { - const organizationUsers = await dataLoader.get('organizationUsersByOrgId').load(orgId) - return organizationUsers.filter( - (organizationUser) => - organizationUser.role === 'BILLING_LEADER' || organizationUser.role === 'ORG_ADMIN' - ) - } - } - }) + fields: {} }) export default Organization diff --git a/packages/server/graphql/types/SlackIntegration.ts b/packages/server/graphql/types/SlackIntegration.ts index f7da0f259a8..ae9c958569d 100644 --- a/packages/server/graphql/types/SlackIntegration.ts +++ b/packages/server/graphql/types/SlackIntegration.ts @@ -1,86 +1,8 @@ -import { - GraphQLBoolean, - GraphQLID, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLString -} from 'graphql' -import {getUserId} from '../../utils/authorization' -import {GQLContext} from '../graphql' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import SlackNotification from './SlackNotification' +import {GraphQLObjectType} from 'graphql' -const SlackIntegration = new GraphQLObjectType({ +const SlackIntegration = new GraphQLObjectType({ name: 'SlackIntegration', - description: 'OAuth token for a team member', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'shortid' - }, - isActive: { - description: 'true if the auth is updated & ready to use for all features, else false', - type: new GraphQLNonNull(GraphQLBoolean), - resolve: ({isActive, botAccessToken}) => !!(isActive && botAccessToken) - }, - botUserId: { - type: GraphQLID, - description: 'the parabol bot user id' - }, - botAccessToken: { - type: GraphQLID, - description: 'the parabol bot access token, used as primary communication', - resolve: async ({botAccessToken, userId}, _args: unknown, {authToken}) => { - const viewerId = getUserId(authToken) - return viewerId === userId ? botAccessToken : null - } - }, - createdAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the provider was created' - }, - defaultTeamChannelId: { - type: new GraphQLNonNull(GraphQLString), - description: 'The default channel to assign to new team notifications' - }, - slackTeamId: { - type: GraphQLID, - description: 'The id of the team in slack' - }, - slackTeamName: { - type: GraphQLString, - description: 'The name of the team in slack' - }, - slackUserId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The userId in slack' - }, - slackUserName: { - type: new GraphQLNonNull(GraphQLString), - description: 'The name of the user in slack' - }, - teamId: { - type: new GraphQLNonNull(GraphQLID), - description: '*The team that the token is linked to' - }, - updatedAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the token was updated at' - }, - userId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The id of the user that integrated Slack' - }, - notifications: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(SlackNotification))), - description: 'A list of events and the slack channels they get posted to', - resolve: async ({userId, teamId}, _args: unknown, {dataLoader}) => { - const slackNotifications = await dataLoader.get('slackNotificationsByTeamId').load(teamId) - return slackNotifications.filter((notification) => notification.userId === userId) - } - } - }) + fields: {} }) export default SlackIntegration diff --git a/packages/server/graphql/types/TeamMemberIntegrations.ts b/packages/server/graphql/types/TeamMemberIntegrations.ts index a6ed6a58348..a8be71d8b19 100644 --- a/packages/server/graphql/types/TeamMemberIntegrations.ts +++ b/packages/server/graphql/types/TeamMemberIntegrations.ts @@ -1,76 +1,9 @@ -import {GraphQLID, GraphQLNonNull, GraphQLObjectType} from 'graphql' -import TeamMemberIntegrationsId from '../../../client/shared/gqlIds/TeamMemberIntegrationsId' -import {isTeamMember} from '../../utils/authorization' +import {GraphQLObjectType} from 'graphql' import {GQLContext} from '../graphql' -import AtlassianIntegration from './AtlassianIntegration' -import AzureDevOpsIntegration from './AzureDevOpsIntegration' -import GitHubIntegration from './GitHubIntegration' -import GitLabIntegration from './GitLabIntegration' -import JiraServerIntegration from './JiraServerIntegration' -import MSTeamsIntegration from './MSTeamsIntegration' -import MattermostIntegration from './MattermostIntegration' -import SlackIntegration from './SlackIntegration' const TeamMemberIntegrations = new GraphQLObjectType<{teamId: string; userId: string}, GQLContext>({ name: 'TeamMemberIntegrations', - description: 'All the available integrations available for this team member', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'composite', - resolve: ({teamId, userId}) => TeamMemberIntegrationsId.join(teamId, userId) - }, - atlassian: { - type: AtlassianIntegration, - description: 'All things associated with an Atlassian integration for a team member', - resolve: async ({teamId, userId}, _args: unknown, {authToken, dataLoader}) => { - if (!isTeamMember(authToken, teamId)) return null - return dataLoader.get('freshAtlassianAuth').load({teamId, userId}) - } - }, - jiraServer: { - type: new GraphQLNonNull(JiraServerIntegration), - description: 'All things associated with a Jira Server integration for a team member', - resolve: (source) => source - }, - github: { - type: GitHubIntegration, - description: 'All things associated with a GitHub integration for a team member', - resolve: async ({teamId, userId}, _args: unknown, {authToken, dataLoader}) => { - if (!isTeamMember(authToken, teamId)) return null - return dataLoader.get('githubAuth').load({teamId, userId}) - } - }, - gitlab: { - type: new GraphQLNonNull(GitLabIntegration), - description: 'All things associated with a GitLab integration for a team member', - resolve: (source) => source - }, - mattermost: { - type: new GraphQLNonNull(MattermostIntegration), - description: 'All things associated with a Mattermost integration for a team member', - resolve: (source) => source - }, - slack: { - type: SlackIntegration, - description: 'All things associated with a slack integration for a team member', - resolve: async ({teamId, userId}, _args: unknown, {authToken, dataLoader}) => { - if (!isTeamMember(authToken, teamId)) return null - const auths = await dataLoader.get('slackAuthByUserId').load(userId) - return auths.find((auth) => auth.teamId === teamId) - } - }, - azureDevOps: { - type: new GraphQLNonNull(AzureDevOpsIntegration), - description: 'All things associated with a A integration for a team member', - resolve: (source) => source - }, - msTeams: { - type: new GraphQLNonNull(MSTeamsIntegration), - description: 'All things associated with a Microsoft Teams integration for a team member', - resolve: (source) => source - } - }) + fields: {} }) export default TeamMemberIntegrations From b0c2cf2aa40aa08c8de24bfdcb2a9a150178271e Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 18 Jul 2024 17:08:15 -0700 Subject: [PATCH 342/529] chore(rethinkdb): TeamMember: Phase 1 (#9979) Signed-off-by: Matt Krick --- .../client/hooks/useSnacksForNewMeetings.ts | 2 +- packages/server/database/types/Meeting.ts | 2 +- packages/server/dataloader/RootDataLoader.ts | 24 +- .../dataloader/rethinkForeignKeyLoader.ts | 12 +- packages/server/graphql/mutations/addTeam.ts | 3 +- .../server/graphql/mutations/createTask.ts | 14 +- .../server/graphql/mutations/deleteTask.ts | 11 +- .../server/graphql/mutations/endCheckIn.ts | 9 +- .../mutations/helpers/createTeamAndLeader.ts | 29 +- .../mutations/helpers/removeFromOrg.ts | 18 +- .../mutations/helpers/removeTeamMember.ts | 30 +- .../mutations/helpers/safeEndRetrospective.ts | 9 +- .../mutations/helpers/softDeleteUser.ts | 12 +- .../server/graphql/mutations/moveTeamToOrg.ts | 8 +- .../graphql/mutations/promoteToTeamLead.ts | 29 +- .../graphql/mutations/setPokerSpectate.ts | 9 +- .../graphql/mutations/toggleTeamDrawer.ts | 11 + .../server/graphql/mutations/updateTask.ts | 13 +- .../graphql/mutations/updateTeamName.ts | 7 +- .../private/mutations/backupOrganization.ts | 299 ++++-------------- .../private/mutations/changeEmailDomain.ts | 47 ++- .../private/mutations/hardDeleteUser.ts | 133 +++----- .../graphql/private/mutations/updateEmail.ts | 18 +- .../graphql/private/typeDefs/_legacy.graphql | 106 ------- .../mutations/acceptRequestToJoinDomain.ts | 50 ++- .../public/mutations/requestToJoinDomain.ts | 21 +- .../public/mutations/updateUserProfile.ts | 25 +- .../public/mutations/uploadUserImage.ts | 15 +- .../public/typeDefs/ActionMeeting.graphql | 8 +- .../public/typeDefs/NewMeeting.graphql | 8 +- .../public/typeDefs/PokerMeeting.graphql | 8 +- .../typeDefs/RetrospectiveMeeting.graphql | 8 +- .../public/typeDefs/TeamPromptMeeting.graphql | 8 +- .../server/graphql/public/types/Company.ts | 11 +- .../graphql/public/types/DomainJoinRequest.ts | 12 +- .../server/graphql/public/types/NewMeeting.ts | 1 + .../public/types/helpers/getTeamsByOrgIds.ts | 12 - .../graphql/queries/verifiedInvitation.ts | 6 +- packages/server/graphql/resolvers.ts | 7 +- .../1720730818119_TeamMember-phase1.ts | 55 ++++ .../server/postgres/queries/getTeamsByIds.ts | 32 -- .../postgres/queries/getTeamsByOrgIds.ts | 18 -- .../server/postgres/queries/removeUserTms.ts | 19 -- .../queries/src/getTeamsByIdsQuery.sql | 6 - .../queries/src/getTeamsByOrgIdsQuery.sql | 7 - .../queries/src/removeUserTmsQuery.sql | 7 - .../safeMutations/acceptTeamInvitation.ts | 107 ++++--- .../server/safeMutations/addTeamIdToTMS.ts | 13 - .../safeMutations/insertNewTeamMember.ts | 18 +- .../safeArchiveEmptyStarterOrganization.ts | 4 +- .../server/safeMutations/safeArchiveTeam.ts | 20 +- .../safeQueries/getNewTeamLeadUserId.ts | 28 -- packages/server/utils/authorization.ts | 11 - 53 files changed, 539 insertions(+), 861 deletions(-) delete mode 100644 packages/server/graphql/public/types/helpers/getTeamsByOrgIds.ts create mode 100644 packages/server/postgres/migrations/1720730818119_TeamMember-phase1.ts delete mode 100644 packages/server/postgres/queries/getTeamsByIds.ts delete mode 100644 packages/server/postgres/queries/getTeamsByOrgIds.ts delete mode 100644 packages/server/postgres/queries/removeUserTms.ts delete mode 100644 packages/server/postgres/queries/src/getTeamsByIdsQuery.sql delete mode 100644 packages/server/postgres/queries/src/getTeamsByOrgIdsQuery.sql delete mode 100644 packages/server/postgres/queries/src/removeUserTmsQuery.sql delete mode 100644 packages/server/safeMutations/addTeamIdToTMS.ts delete mode 100644 packages/server/safeQueries/getNewTeamLeadUserId.ts diff --git a/packages/client/hooks/useSnacksForNewMeetings.ts b/packages/client/hooks/useSnacksForNewMeetings.ts index 2a41b9735ab..9ae1717f066 100644 --- a/packages/client/hooks/useSnacksForNewMeetings.ts +++ b/packages/client/hooks/useSnacksForNewMeetings.ts @@ -43,7 +43,7 @@ const useSnacksForNewMeetings = (meetingsRef: readonly useSnacksForNewMeetings_m const [snackedMeeting] = sortedMeetings if (!snackedMeeting) return const {id: meetingId, createdBy, createdByUser, name: meetingName} = snackedMeeting - const {preferredName} = createdByUser + const preferredName = createdByUser?.preferredName ?? 'Unknown' const isInit = createdBy === viewerId const name = isInit ? 'You' : preferredName atmosphere.eventEmitter.emit('addSnackbar', { diff --git a/packages/server/database/types/Meeting.ts b/packages/server/database/types/Meeting.ts index c77195f25ad..70baaefa945 100644 --- a/packages/server/database/types/Meeting.ts +++ b/packages/server/database/types/Meeting.ts @@ -27,7 +27,7 @@ export default abstract class Meeting { isLegacy?: boolean // true if old version of action meeting createdAt = new Date() updatedAt = new Date() - createdBy: string + createdBy: string | null endedAt: Date | undefined | null = undefined facilitatorStageId: string | undefined facilitatorUserId: string diff --git a/packages/server/dataloader/RootDataLoader.ts b/packages/server/dataloader/RootDataLoader.ts index 146ae62e1e5..039146acd95 100644 --- a/packages/server/dataloader/RootDataLoader.ts +++ b/packages/server/dataloader/RootDataLoader.ts @@ -48,7 +48,7 @@ export type RegisterDependsOn = (primaryLoaders: AllPrimaryLoaders | AllPrimaryL // The RethinkDB logic is a leaky abstraction! It will be gone soon & this will be generic enough to put in its own package interface GenericDataLoader { - clearAll(pkLoaderName: TPrimaryLoaderNames): void + clearAll(pkLoaderName: TPrimaryLoaderNames | TPrimaryLoaderNames[]): void get( loaderName: LoaderName ): Loader extends (...args: any[]) => any @@ -85,8 +85,9 @@ export default class RootDataLoader< this.dataLoaderOptions = dataLoaderOptions } - clearAll: DataLoaderInstance['clearAll'] = (pkLoaderName) => { - const dependencies = [pkLoaderName, ...(this.dependentLoaders[pkLoaderName] ?? [])] + clearAll: DataLoaderInstance['clearAll'] = (inPkLoaderName) => { + const pkLoaderNames = Array.isArray(inPkLoaderName) ? inPkLoaderName : [inPkLoaderName] + const dependencies = pkLoaderNames.flatMap((pk) => [pk, ...(this.dependentLoaders[pk] ?? [])]) dependencies.forEach((loaderName) => { this.loaders[loaderName]?.clearAll() }) @@ -95,22 +96,19 @@ export default class RootDataLoader< let loader = this.loaders[loaderName] if (loader) return loader const loaderMaker = loaderMakers[loaderName as keyof typeof loaderMakers] + const dependsOn: RegisterDependsOn = (inPrimaryLoaders) => { + const primaryLoaders = Array.isArray(inPrimaryLoaders) ? inPrimaryLoaders : [inPrimaryLoaders] + primaryLoaders.forEach((primaryLoader) => { + ;(this.dependentLoaders[primaryLoader] ??= []).push(loaderName) + }) + } if (loaderMaker instanceof RethinkPrimaryKeyLoaderMaker) { const {table} = loaderMaker loader = rethinkPrimaryKeyLoader(this.dataLoaderOptions, table) } else if (loaderMaker instanceof RethinkForeignKeyLoaderMaker) { const {fetch, field, pk} = loaderMaker - const basePkLoader = this.get(pk) as any - loader = rethinkForeignKeyLoader(basePkLoader, this.dataLoaderOptions, field, fetch) + loader = rethinkForeignKeyLoader(this, dependsOn, pk, field, fetch) } else { - const dependsOn: RegisterDependsOn = (inPrimaryLoaders) => { - const primaryLoaders = Array.isArray(inPrimaryLoaders) - ? inPrimaryLoaders - : [inPrimaryLoaders] - primaryLoaders.forEach((primaryLoader) => { - ;(this.dependentLoaders[primaryLoader] ??= []).push(loaderName) - }) - } loader = (loaderMaker as any)(this, dependsOn) } this.loaders[loaderName] = loader! diff --git a/packages/server/dataloader/rethinkForeignKeyLoader.ts b/packages/server/dataloader/rethinkForeignKeyLoader.ts index 147c868d2d2..dec36574113 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoader.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoader.ts @@ -1,13 +1,17 @@ -import DataLoader from 'dataloader' import {DBType} from '../database/rethinkDriver' +import RootDataLoader, {RegisterDependsOn} from './RootDataLoader' import UpdatableCacheDataLoader from './UpdatableCacheDataLoader' +import * as rethinkPrimaryKeyLoaderMakers from './rethinkPrimaryKeyLoaderMakers' const rethinkForeignKeyLoader = ( - standardLoader: DataLoader, - options: DataLoader.Options, + parent: RootDataLoader, + dependsOn: RegisterDependsOn, + primaryKeyLoaderName: keyof typeof rethinkPrimaryKeyLoaderMakers, field: string, fetchFn: (ids: readonly string[]) => any[] | Promise ) => { + const standardLoader = parent.get(primaryKeyLoaderName) + dependsOn(primaryKeyLoaderName) const batchFn = async (ids: readonly string[]) => { const items = await fetchFn(ids) items.forEach((item) => { @@ -15,7 +19,7 @@ const rethinkForeignKeyLoader = ( }) return ids.map((id) => items.filter((item) => item[field] === id)) } - return new UpdatableCacheDataLoader(batchFn, options) + return new UpdatableCacheDataLoader(batchFn, {...parent.dataLoaderOptions}) } export default rethinkForeignKeyLoader diff --git a/packages/server/graphql/mutations/addTeam.ts b/packages/server/graphql/mutations/addTeam.ts index 2663a74cf26..5735ba30d97 100644 --- a/packages/server/graphql/mutations/addTeam.ts +++ b/packages/server/graphql/mutations/addTeam.ts @@ -3,7 +3,6 @@ import {SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import AuthToken from '../../database/types/AuthToken' import generateUID from '../../generateUID' -import getTeamsByOrgIds from '../../postgres/queries/getTeamsByOrgIds' import removeSuggestedAction from '../../safeMutations/removeSuggestedAction' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isUserInOrg} from '../../utils/authorization' @@ -54,7 +53,7 @@ export default { // VALIDATION const [orgTeams, organization, viewer] = await Promise.all([ - getTeamsByOrgIds([orgId], {isArchived: false}), + dataLoader.get('teamsByOrgIds').load(orgId), dataLoader.get('organizations').loadNonNull(orgId), dataLoader.get('users').loadNonNull(viewerId) ]) diff --git a/packages/server/graphql/mutations/createTask.ts b/packages/server/graphql/mutations/createTask.ts index c75cf0d6ff6..1a4e2167f78 100644 --- a/packages/server/graphql/mutations/createTask.ts +++ b/packages/server/graphql/mutations/createTask.ts @@ -7,7 +7,6 @@ import MeetingMemberId from '../../../client/shared/gqlIds/MeetingMemberId' import getRethink from '../../database/rethinkDriver' import NotificationTaskInvolves from '../../database/types/NotificationTaskInvolves' import Task, {TaskServiceEnum} from '../../database/types/Task' -import TeamMember from '../../database/types/TeamMember' import generateUID from '../../generateUID' import updatePrevUsedRepoIntegrationsCache from '../../integrations/updatePrevUsedRepoIntegrationsCache' import {analytics} from '../../utils/analytics/analytics' @@ -229,17 +228,10 @@ export default { userId, updatedAt } - - const {teamMembers} = await r({ + const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) + await r({ task: r.table('Task').insert(task), - history: r.table('TaskHistory').insert(history), - teamMembers: r - .table('TeamMember') - .getAll(teamId, {index: 'teamId'}) - .filter({ - isNotRemoved: true - }) - .coerceTo('array') as unknown as TeamMember[] + history: r.table('TaskHistory').insert(history) }).run() handleAddTaskNotifications(teamMembers, task, viewerId, teamId, { diff --git a/packages/server/graphql/mutations/deleteTask.ts b/packages/server/graphql/mutations/deleteTask.ts index af72e20f315..bd47dca2a49 100644 --- a/packages/server/graphql/mutations/deleteTask.ts +++ b/packages/server/graphql/mutations/deleteTask.ts @@ -37,19 +37,16 @@ export default { } // RESOLUTION - const {subscribedUserIds} = await r({ + const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) + const subscribedUserIds = teamMembers.map(({userId}) => userId) + await r({ task: r.table('Task').get(taskId).delete(), taskHistory: r .table('TaskHistory') .between([taskId, r.minval], [taskId, r.maxval], { index: 'taskIdUpdatedAt' }) - .delete(), - subscribedUserIds: r - .table('TeamMember') - .getAll(teamId, {index: 'teamId'}) - .filter({isNotRemoved: true})('userId') - .coerceTo('array') as unknown as string[] + .delete() }).run() const {tags, userId: taskUserId} = task diff --git a/packages/server/graphql/mutations/endCheckIn.ts b/packages/server/graphql/mutations/endCheckIn.ts index c97e2f7cd03..f009f8347d3 100644 --- a/packages/server/graphql/mutations/endCheckIn.ts +++ b/packages/server/graphql/mutations/endCheckIn.ts @@ -248,12 +248,9 @@ export default { const pg = getKysely() await pg.insertInto('TimelineEvent').values(events).execute() if (team.isOnboardTeam) { - const teamLeadUserId = await r - .table('TeamMember') - .getAll(teamId, {index: 'teamId'}) - .filter({isLead: true}) - .nth(0)('userId') - .run() + const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) + const teamLeader = teamMembers.find(({isLead}) => isLead)! + const {userId: teamLeadUserId} = teamLeader const removedSuggestedActionId = await removeSuggestedAction( teamLeadUserId, diff --git a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts index 7452113d85d..b6a3f3c650a 100644 --- a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts +++ b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts @@ -1,3 +1,5 @@ +import {sql} from 'kysely' +import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' import getRethink from '../../../database/rethinkDriver' import MeetingSettingsAction from '../../../database/types/MeetingSettingsAction' import MeetingSettingsPoker from '../../../database/types/MeetingSettingsPoker' @@ -7,7 +9,6 @@ import TimelineEventCreatedTeam from '../../../database/types/TimelineEventCreat import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' import getKysely from '../../../postgres/getKysely' import IUser from '../../../postgres/types/IUser' -import addTeamIdToTMS from '../../../safeMutations/addTeamIdToTMS' import insertNewTeamMember from '../../../safeMutations/insertNewTeamMember' interface ValidNewTeam { @@ -24,7 +25,7 @@ export default async function createTeamAndLeader( dataLoader: DataLoaderInstance ) { const r = await getRethink() - const {id: userId} = user + const {id: userId, picture, preferredName, email} = user const {id: teamId, orgId} = newTeam const organization = await dataLoader.get('organizations').loadNonNull(orgId) const {tier, trialStartDate} = organization @@ -44,14 +45,32 @@ export default async function createTeamAndLeader( const pg = getKysely() await Promise.all([ pg - .with('Team', (qc) => qc.insertInto('Team').values(verifiedTeam)) + .with('TeamInsert', (qc) => qc.insertInto('Team').values(verifiedTeam)) + .with('UserUpdate', (qc) => + qc + .updateTable('User') + .set({tms: sql`arr_append_uniq("tms", ${teamId})`}) + .where('id', '=', userId) + ) + .with('TeamMemberInsert', (qc) => + qc.insertInto('TeamMember').values({ + id: TeamMemberId.join(teamId, userId), + teamId, + userId, + picture, + preferredName, + email, + isLead: true, + openDrawer: 'manageTeam' + }) + ) .insertInto('TimelineEvent') .values(timelineEvent) .execute(), // add meeting settings r.table('MeetingSettings').insert(meetingSettings).run(), // denormalize common fields to team member - insertNewTeamMember(user, teamId), - addTeamIdToTMS(userId, teamId) + insertNewTeamMember(user, teamId, dataLoader) ]) + dataLoader.clearAll(['teams', 'users', 'teamMembers', 'timelineEvents', 'meetingSettings']) } diff --git a/packages/server/graphql/mutations/helpers/removeFromOrg.ts b/packages/server/graphql/mutations/helpers/removeFromOrg.ts index ede6aba2610..a65f2ff74c1 100644 --- a/packages/server/graphql/mutations/helpers/removeFromOrg.ts +++ b/packages/server/graphql/mutations/helpers/removeFromOrg.ts @@ -1,9 +1,7 @@ import {sql} from 'kysely' import {InvoiceItemType} from 'parabol-client/types/constEnums' import adjustUserCount from '../../../billing/helpers/adjustUserCount' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' -import getTeamsByOrgIds from '../../../postgres/queries/getTeamsByOrgIds' import {Logger} from '../../../utils/Logger' import setUserTierForUserIds from '../../../utils/setUserTierForUserIds' import {DataLoaderWorker} from '../../graphql' @@ -16,15 +14,15 @@ const removeFromOrg = async ( evictorUserId: string | undefined, dataLoader: DataLoaderWorker ) => { - const r = await getRethink() const pg = getKysely() - const orgTeams = await getTeamsByOrgIds([orgId]) + // TODO consider a teamMembersByOrgId dataloader if this pattern pops up more + const [orgTeams, allTeamMembers] = await Promise.all([ + dataLoader.get('teamsByOrgIds').load(orgId), + dataLoader.get('teamMembersByUserId').load(userId) + ]) const teamIds = orgTeams.map((team) => team.id) - const teamMemberIds = (await r - .table('TeamMember') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter({userId, isNotRemoved: true})('id') - .run()) as string[] + const teamMembers = allTeamMembers.filter((teamMember) => teamIds.includes(teamMember.teamId)) + const teamMemberIds = teamMembers.map((teamMember) => teamMember.id) const perTeamRes = await Promise.all( teamMemberIds.map((teamMemberId) => { @@ -53,7 +51,7 @@ const removeFromOrg = async ( .executeTakeFirstOrThrow(), dataLoader.get('users').loadNonNull(userId) ]) - + dataLoader.clearAll('organizationUsers') // need to make sure the org doc is updated before adjusting this const {role} = organizationUser if (role && ['BILLING_LEADER', 'ORG_ADMIN'].includes(role)) { diff --git a/packages/server/graphql/mutations/helpers/removeTeamMember.ts b/packages/server/graphql/mutations/helpers/removeTeamMember.ts index 3be41b574a0..eba334bec0c 100644 --- a/packages/server/graphql/mutations/helpers/removeTeamMember.ts +++ b/packages/server/graphql/mutations/helpers/removeTeamMember.ts @@ -7,8 +7,7 @@ import EstimateStage from '../../../database/types/EstimateStage' import NotificationKickedOut from '../../../database/types/NotificationKickedOut' import Task from '../../../database/types/Task' import UpdatesStage from '../../../database/types/UpdatesStage' -import removeUserTms from '../../../postgres/queries/removeUserTms' -import updateTeamByTeamId from '../../../postgres/queries/updateTeamByTeamId' +import getKysely from '../../../postgres/getKysely' import archiveTasksForDB from '../../../safeMutations/archiveTasksForDB' import errorFilter from '../../errorFilter' import {DataLoaderWorker} from '../../graphql' @@ -27,10 +26,11 @@ const removeTeamMember = async ( ) => { const {evictorUserId} = options const r = await getRethink() + const pg = getKysely() const now = new Date() const {userId, teamId} = fromTeamMemberId(teamMemberId) // see if they were a leader, make a new guy leader so later we can reassign tasks - const activeTeamMembers = await r.table('TeamMember').getAll(teamId, {index: 'teamId'}).run() + const activeTeamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) const teamMember = activeTeamMembers.find((t) => t.id === teamMemberId) const {isLead, isNotRemoved} = teamMember ?? {} // if the guy being removed is the leader & not the last, pick a new one. else, use him @@ -40,17 +40,18 @@ const removeTeamMember = async ( } if (activeTeamMembers.length === 1) { - const updates = { - isArchived: true, - updatedAt: new Date() - } await Promise.all([ // archive single-person teams - updateTeamByTeamId(updates, teamId), + pg.updateTable('Team').set({isArchived: true}).where('id', '=', teamId).execute(), // delete all tasks belonging to a 1-person team r.table('Task').getAll(teamId, {index: 'teamId'}).delete() ]) } else if (isLead) { + await pg + .updateTable('TeamMember') + .set(({not}) => ({isLead: not('isLead')})) + .where('id', 'in', [teamMemberId, teamLeader.id]) + .execute() // assign new leader, remove old leader flag await r({ newTeamLead: r.table('TeamMember').get(teamLeader.id).update({ @@ -60,6 +61,11 @@ const removeTeamMember = async ( }).run() } + await pg + .updateTable('TeamMember') + .set({isNotRemoved: false}) + .where('id', '=', teamMemberId) + .execute() // assign active tasks to the team lead const {integratedTasksToArchive, reassignedTasks} = await r({ teamMember: r.table('TeamMember').get(teamMemberId).update({ @@ -92,8 +98,12 @@ const removeTeamMember = async ( )('changes')('new_val') .default([]) as unknown as Task[] }).run() - - await removeUserTms(teamId, userId) + await pg + .updateTable('User') + .set(({fn, ref, val}) => ({tms: fn('ARRAY_REMOVE', [ref('tms'), val(teamId)])})) + .where('id', '=', userId) + .execute() + dataLoader.clearAll(['users', 'teamMembers']) const user = await dataLoader.get('users').load(userId) let notificationId: string | undefined diff --git a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts index a76ba6fe7c8..3f3f0b4be4f 100644 --- a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts @@ -168,12 +168,9 @@ const safeEndRetrospective = async ({ await pg.insertInto('TimelineEvent').values(events).execute() if (team.isOnboardTeam) { - const teamLeadUserId = await r - .table('TeamMember') - .getAll(teamId, {index: 'teamId'}) - .filter({isLead: true}) - .nth(0)('userId') - .run() + const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) + const teamLead = teamMembers.find((teamMember) => teamMember.isLead)! + const teamLeadUserId = teamLead.userId const removedSuggestedActionId = await removeSuggestedAction(teamLeadUserId, 'tryRetroMeeting') if (removedSuggestedActionId) { diff --git a/packages/server/graphql/mutations/helpers/softDeleteUser.ts b/packages/server/graphql/mutations/helpers/softDeleteUser.ts index d427d9a2a0e..6fb1419e336 100644 --- a/packages/server/graphql/mutations/helpers/softDeleteUser.ts +++ b/packages/server/graphql/mutations/helpers/softDeleteUser.ts @@ -1,5 +1,3 @@ -import TeamMemberId from 'parabol-client/shared/gqlIds/TeamMemberId' -import getRethink from '../../../database/rethinkDriver' import removeAtlassianAuth from '../../../postgres/queries/removeAtlassianAuth' import removeGitHubAuth from '../../../postgres/queries/removeGitHubAuth' import getDeletedEmail from '../../../utils/getDeletedEmail' @@ -14,20 +12,14 @@ const removeAtlassianAuths = async (userId: string, teamIds: string[]) => Promise.all(teamIds.map((teamId) => removeAtlassianAuth(userId, teamId))) const softDeleteUser = async (userIdToDelete: string, dataLoader: DataLoaderWorker) => { - const r = await getRethink() const orgUsers = await dataLoader.get('organizationUsersByUserId').load(userIdToDelete) const orgIds = orgUsers.map((orgUser) => orgUser.orgId) await Promise.all( orgIds.map((orgId) => removeFromOrg(userIdToDelete, orgId, undefined, dataLoader)) ) - const teamMemberIds = await r - .table('TeamMember') - .getAll(userIdToDelete, {index: 'userId'}) - .getField('id') - .coerceTo('array') - .run() - const teamIds = teamMemberIds.map((id) => TeamMemberId.split(id).teamId) + const teamMembers = await dataLoader.get('teamMembersByUserId').load(userIdToDelete) + const teamIds = teamMembers.map(({teamId}) => teamId) await Promise.all([ removeAtlassianAuths(userIdToDelete, teamIds), diff --git a/packages/server/graphql/mutations/moveTeamToOrg.ts b/packages/server/graphql/mutations/moveTeamToOrg.ts index 972a7a81043..84e432021b0 100644 --- a/packages/server/graphql/mutations/moveTeamToOrg.ts +++ b/packages/server/graphql/mutations/moveTeamToOrg.ts @@ -4,7 +4,6 @@ import adjustUserCount from '../../billing/helpers/adjustUserCount' import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' import getKysely from '../../postgres/getKysely' -import getTeamsByIds from '../../postgres/queries/getTeamsByIds' import updateMeetingTemplateOrgId from '../../postgres/queries/updateMeetingTemplateOrgId' import updateTeamByTeamId from '../../postgres/queries/updateTeamByTeamId' import safeArchiveEmptyStarterOrganization from '../../safeMutations/safeArchiveEmptyStarterOrganization' @@ -28,9 +27,9 @@ const moveToOrg = async ( // AUTH const su = isSuperUser(authToken) // VALIDATION - const [org, teams, isPaidResult] = await Promise.all([ + const [org, team, isPaidResult] = await Promise.all([ dataLoader.get('organizations').loadNonNull(orgId), - getTeamsByIds([teamId]), + dataLoader.get('teams').load(teamId), pg .selectFrom('Team') .select('isPaid') @@ -39,7 +38,6 @@ const moveToOrg = async ( .limit(1) .executeTakeFirst() ]) - const team = teams[0] if (!team) { return standardError(new Error('Did not find the team')) } @@ -101,7 +99,7 @@ const moveToOrg = async ( updateMeetingTemplateOrgId(currentOrgId, orgId), updateTeamByTeamId(updates, teamId) ]) - + dataLoader.clearAll('teams') // if no teams remain on the org, remove it await safeArchiveEmptyStarterOrganization(currentOrgId, dataLoader) diff --git a/packages/server/graphql/mutations/promoteToTeamLead.ts b/packages/server/graphql/mutations/promoteToTeamLead.ts index 975346055f1..68f9513bc3d 100644 --- a/packages/server/graphql/mutations/promoteToTeamLead.ts +++ b/packages/server/graphql/mutations/promoteToTeamLead.ts @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import TeamMemberId from '../../../client/shared/gqlIds/TeamMemberId' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId, isSuperUser, isUserOrgAdmin} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -27,22 +28,18 @@ export default { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const r = await getRethink() + const pg = getKysely() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const viewerId = getUserId(authToken) // AUTH - const [oldLeadTeamMemberId, team] = await Promise.all([ - r - .table('TeamMember') - .getAll(teamId, {index: 'teamId'}) - .filter({isNotRemoved: true, isLead: true}) - .nth(0)('id') - .default(null) - .run(), + const [teamMembers, team] = await Promise.all([ + dataLoader.get('teamMembersByTeamId').load(teamId), dataLoader.get('teams').loadNonNull(teamId) ]) - + const oldLead = teamMembers.find(({isLead}) => isLead)! + const {id: oldLeadTeamMemberId} = oldLead if (!isSuperUser(authToken)) { const isOrgAdmin = await isUserOrgAdmin(viewerId, team.orgId, dataLoader) const viewerTeamMemberId = TeamMemberId.join(teamId, viewerId) @@ -52,18 +49,18 @@ export default { } // VALIDATION - const promoteeOnTeam = await r - .table('TeamMember') - .getAll(teamId, {index: 'teamId'}) - .filter({userId, isNotRemoved: true}) - .nth(0) - .default(null) - .run() + const promoteeOnTeam = teamMembers.find((teamMember) => teamMember.userId === userId) if (!promoteeOnTeam) { return standardError(new Error('Team not found'), {userId: viewerId}) } // RESOLUTION + await pg + .updateTable('TeamMember') + .set(({not}) => ({isLead: not('isLead')})) + .where('id', 'in', [oldLeadTeamMemberId, promoteeOnTeam.id]) + .execute() + dataLoader.clearAll('teamMembers') await r({ teamLead: r.table('TeamMember').get(oldLeadTeamMemberId).update({ isLead: false diff --git a/packages/server/graphql/mutations/setPokerSpectate.ts b/packages/server/graphql/mutations/setPokerSpectate.ts index fe32bdce360..7f007738d8d 100644 --- a/packages/server/graphql/mutations/setPokerSpectate.ts +++ b/packages/server/graphql/mutations/setPokerSpectate.ts @@ -4,6 +4,7 @@ import toTeamMemberId from '../../../client/utils/relay/toTeamMemberId' import getRethink from '../../database/rethinkDriver' import EstimateStage from '../../database/types/EstimateStage' import PokerMeetingMember from '../../database/types/PokerMeetingMember' +import getKysely from '../../postgres/getKysely' import {getUserId} from '../../utils/authorization' import getPhase from '../../utils/getPhase' import publish from '../../utils/publish' @@ -29,6 +30,7 @@ const setPokerSpectate = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { const r = await getRethink() + const pg = getKysely() const viewerId = getUserId(authToken) const now = new Date() const operationId = dataLoader.share() @@ -60,6 +62,11 @@ const setPokerSpectate = { // RESOLUTION const teamMemberId = toTeamMemberId(teamId, viewerId) + await pg + .updateTable('TeamMember') + .set({isSpectatingPoker: isSpectating}) + .where('id', '=', teamMemberId) + .execute() await r({ meetingMember: r.table('MeetingMember').get(meetingMemberId).update({isSpectating}), teamMember: r @@ -67,7 +74,7 @@ const setPokerSpectate = { .get(teamMemberId) .update({isSpectatingPoker: isSpectating, updatedAt: now}) }).run() - + dataLoader.clearAll('teamMembers') // mutate the dataLoader cache meetingMember.isSpectating = isSpectating const dirtyStages: EstimateStage[] = [] diff --git a/packages/server/graphql/mutations/toggleTeamDrawer.ts b/packages/server/graphql/mutations/toggleTeamDrawer.ts index dc2873f9112..aa9ae06abb2 100644 --- a/packages/server/graphql/mutations/toggleTeamDrawer.ts +++ b/packages/server/graphql/mutations/toggleTeamDrawer.ts @@ -1,6 +1,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import getRethink from '../../database/rethinkDriver' import {RValue} from '../../database/stricterR' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' @@ -27,6 +28,7 @@ const toggleTeamDrawer = { {authToken}: GQLContext ) => { const r = await getRethink() + const pg = getKysely() const viewerId = getUserId(authToken) //AUTH @@ -37,6 +39,14 @@ const toggleTeamDrawer = { // RESOLUTION const userId = getUserId(authToken) const viewerTeamMemberId = `${userId}::${teamId}` + + await pg + .updateTable('TeamMember') + .set(({fn, ref, val}) => ({ + openDrawer: fn('NULLIF', [val(teamDrawerType), ref('openDrawer')]) + })) + .where('id', '=', viewerTeamMemberId) + .execute() await r .table('TeamMember') .get(viewerTeamMemberId) @@ -48,6 +58,7 @@ const toggleTeamDrawer = { ) })) .run() + return {teamMemberId: viewerTeamMemberId} } } diff --git a/packages/server/graphql/mutations/updateTask.ts b/packages/server/graphql/mutations/updateTask.ts index 1d627dd5c33..45b647ceefb 100644 --- a/packages/server/graphql/mutations/updateTask.ts +++ b/packages/server/graphql/mutations/updateTask.ts @@ -6,7 +6,6 @@ import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' import getRethink from '../../database/rethinkDriver' import {RValue} from '../../database/stricterR' import Task, {AreaEnum as TAreaEnum, TaskStatusEnum} from '../../database/types/Task' -import TeamMember from '../../database/types/TeamMember' import generateUID from '../../generateUID' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -120,20 +119,14 @@ export default { ) }) } - const {newTask, teamMembers} = await r({ + const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) + const {newTask} = await r({ newTask: r .table('Task') .get(taskId) .update(nextTask, {returnChanges: true})('changes')(0)('new_val') .default(null) as unknown as Task, - history: taskHistory, - teamMembers: r - .table('TeamMember') - .getAll(teamId, {index: 'teamId'}) - .filter({ - isNotRemoved: true - }) - .coerceTo('array') as unknown as TeamMember[] + history: taskHistory }).run() // TODO: get users in the same location const usersToIgnore = await getUsersToIgnore(viewerId, teamId) diff --git a/packages/server/graphql/mutations/updateTeamName.ts b/packages/server/graphql/mutations/updateTeamName.ts index 708ae917889..cdac8297925 100644 --- a/packages/server/graphql/mutations/updateTeamName.ts +++ b/packages/server/graphql/mutations/updateTeamName.ts @@ -1,7 +1,6 @@ import {GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import teamNameValidation from 'parabol-client/validation/teamNameValidation' -import getTeamsByIds from '../../postgres/queries/getTeamsByIds' import updateTeamByTeamId from '../../postgres/queries/updateTeamByTeamId' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' @@ -36,11 +35,10 @@ export default { } // VALIDATION - const [teams, viewer] = await Promise.all([ - getTeamsByIds([teamId]), + const [team, viewer] = await Promise.all([ + dataLoader.get('teams').loadNonNull(teamId), dataLoader.get('users').loadNonNull(viewerId) ]) - const team = teams[0]! const oldName = team.name const newName = updatedTeam.name const orgTeams = await dataLoader.get('teamsByOrgIds').load(team.orgId) @@ -59,6 +57,7 @@ export default { updatedAt: now } await updateTeamByTeamId(dbUpdate, teamId) + dataLoader.clearAll('teams') analytics.teamNameChanged(viewer, teamId, oldName, newName, oldName.endsWith('’s Team')) const data = {teamId} diff --git a/packages/server/graphql/private/mutations/backupOrganization.ts b/packages/server/graphql/private/mutations/backupOrganization.ts index d05c2e55189..0bdc6647248 100644 --- a/packages/server/graphql/private/mutations/backupOrganization.ts +++ b/packages/server/graphql/private/mutations/backupOrganization.ts @@ -3,96 +3,77 @@ import fs from 'fs' import path from 'path' import util from 'util' import getProjectRoot from '../../../../../scripts/webpack/utils/getProjectRoot' -import getRethink from '../../../database/rethinkDriver' -import {RDatum, RValue} from '../../../database/stricterR' +import getKysely from '../../../postgres/getKysely' import getPg from '../../../postgres/getPg' import getPgConfig from '../../../postgres/getPgConfig' -import getTeamsByOrgIds from '../../../postgres/queries/getTeamsByOrgIds' import {MutationResolvers} from '../resolverTypes' const exec = util.promisify(childProcess.exec) const dumpPgDataToOrgBackupSchema = async (orgIds: string[]) => { - const pg = getPg() - const client = await pg.connect() - // rethink client for when we need to join with rethink - const r = await getRethink() + const pg = getKysely() // fetch needed items upfront - const teams = await client.query('SELECT "id" FROM "Team" WHERE "orgId" = ANY ($1);', [orgIds]) - const teamIds = teams.rows.map((team) => team.id) - const [userIds, templateRefIds] = await Promise.all([ - r - .table('TeamMember') - .getAll(r.args(teamIds), {index: 'teamId'})('userId') - .coerceTo('array') - .distinct() - .run(), - ( - r - .table('NewMeeting') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((row: RDatum) => row.hasFields('templateRefId')) as any - )('templateRefId') - .coerceTo('array') - .distinct() - .run() - ]) - const templateRefs = await client.query( - `SELECT jsonb_array_elements("template" -> 'dimensions') -> 'scaleRefId' AS "scaleRefId" FROM "TemplateRef" WHERE "id" = ANY ($1);`, - [templateRefIds] - ) - const templateScaleRefIds = templateRefs.rows.map(({scaleRefId}) => scaleRefId) + const teams = await pg.selectFrom('Team').select('id').where('orgId', 'in', orgIds).execute() + const teamIds = teams.map(({id}) => id) + const orgUsers = await pg + .selectFrom('OrganizationUser') + .select('userId') + .where('orgId', 'in', orgIds) + .where('removedAt', 'is', null) + .execute() + const userIds = orgUsers.map(({userId}) => userId) + console.log({teamIds, userIds}) - try { - // do all inserts here - await client.query('BEGIN') - await client.query(`DROP SCHEMA IF EXISTS "orgBackup" CASCADE;`) - await client.query(`CREATE SCHEMA "orgBackup";`) - await client.query(`CREATE TABLE "orgBackup"."PgMigrations" AS (SELECT * FROM "PgMigrations");`) - await client.query( - `CREATE TABLE "orgBackup"."OrganizationUserAudit" AS (SELECT * FROM "OrganizationUserAudit" WHERE "orgId" = ANY ($1));`, - [orgIds] - ) - await client.query( - `CREATE TABLE "orgBackup"."Team" AS (SELECT * FROM "Team" WHERE "orgId" = ANY ($1));`, - [orgIds] - ) - await client.query( - `CREATE TABLE "orgBackup"."GitHubAuth" AS (SELECT * FROM "GitHubAuth" WHERE "teamId" = ANY ($1));`, - [teamIds] - ) - await client.query( - `CREATE TABLE "orgBackup"."AtlassianAuth" AS (SELECT * FROM "AtlassianAuth" WHERE "teamId" = ANY ($1));`, - [teamIds] - ) - await client.query( - `CREATE TABLE "orgBackup"."Discussion" AS (SELECT * FROM "Discussion" WHERE "teamId" = ANY ($1));`, - [teamIds] - ) - await client.query( - `CREATE TABLE "orgBackup"."TaskEstimate" AS (SELECT * FROM "TaskEstimate" WHERE "userId" = ANY ($1));`, - [userIds] - ) - await client.query( - `CREATE TABLE "orgBackup"."User" AS (SELECT * FROM "User" WHERE "id" = ANY ($1));`, - [userIds] - ) - await client.query( - `CREATE TABLE "orgBackup"."TemplateRef" AS (SELECT * FROM "TemplateRef" WHERE "id" = ANY ($1));`, - [templateRefIds] - ) - await client.query( - `CREATE TABLE "orgBackup"."TemplateScaleRef" AS (SELECT * FROM "TemplateScaleRef" WHERE "id" = ANY ($1));`, - [templateScaleRefIds] - ) - await client.query('COMMIT') - } catch (e) { - await client.query('ROLLBACK') - throw e - } finally { - client.release() - } + // try { + // // do all inserts here + // await client.query('BEGIN') + // await client.query(`DROP SCHEMA IF EXISTS "orgBackup" CASCADE;`) + // await client.query(`CREATE SCHEMA "orgBackup";`) + // await client.query(`CREATE TABLE "orgBackup"."PgMigrations" AS (SELECT * FROM "PgMigrations");`) + // await client.query( + // `CREATE TABLE "orgBackup"."OrganizationUserAudit" AS (SELECT * FROM "OrganizationUserAudit" WHERE "orgId" = ANY ($1));`, + // [orgIds] + // ) + // await client.query( + // `CREATE TABLE "orgBackup"."Team" AS (SELECT * FROM "Team" WHERE "orgId" = ANY ($1));`, + // [orgIds] + // ) + // await client.query( + // `CREATE TABLE "orgBackup"."GitHubAuth" AS (SELECT * FROM "GitHubAuth" WHERE "teamId" = ANY ($1));`, + // [teamIds] + // ) + // await client.query( + // `CREATE TABLE "orgBackup"."AtlassianAuth" AS (SELECT * FROM "AtlassianAuth" WHERE "teamId" = ANY ($1));`, + // [teamIds] + // ) + // await client.query( + // `CREATE TABLE "orgBackup"."Discussion" AS (SELECT * FROM "Discussion" WHERE "teamId" = ANY ($1));`, + // [teamIds] + // ) + // await client.query( + // `CREATE TABLE "orgBackup"."TaskEstimate" AS (SELECT * FROM "TaskEstimate" WHERE "userId" = ANY ($1));`, + // [userIds] + // ) + // await client.query( + // `CREATE TABLE "orgBackup"."User" AS (SELECT * FROM "User" WHERE "id" = ANY ($1));`, + // [userIds] + // ) + // await client.query( + // `CREATE TABLE "orgBackup"."TemplateRef" AS (SELECT * FROM "TemplateRef" WHERE "id" = ANY ($1));`, + // [templateRefIds] + // ) + // await client.query( + // `CREATE TABLE "orgBackup"."TemplateScaleRef" AS (SELECT * FROM "TemplateScaleRef" WHERE "id" = ANY ($1));`, + // [templateScaleRefIds] + // ) + // await client.query('COMMIT') + // } catch (e) { + // await client.query('ROLLBACK') + // throw e + // } finally { + // client.release() + // } } const backupPgOrganization = async (orgIds: string[]) => { @@ -118,163 +99,7 @@ const backupPgOrganization = async (orgIds: string[]) => { const backupOrganization: MutationResolvers['backupOrganization'] = async (_source, {orgIds}) => { // RESOLUTION await backupPgOrganization(orgIds) - - const r = await getRethink() - const DESTINATION = 'orgBackup' - - // create the DB - try { - await r.dbDrop(DESTINATION).run() - } catch (e) { - // db never existed. all good - } - await r.dbCreate(DESTINATION).run() - // create all the tables - await (r.tableList() as any) - .forEach((table: RValue) => { - return r.db(DESTINATION).tableCreate(table) - }) - .run() - - // now create all the indexes - await (r.tableList() as any) - .forEach((table: RValue) => { - return r - .table(table) - .indexStatus() - .forEach((idx) => { - return r - .db(DESTINATION) - .table(table) - .indexCreate(idx('index'), idx('function'), { - geo: idx('geo') as any as boolean, - multi: idx('multi') as any as boolean - }) - }) - }) - .run() - - // get all the teams for the orgIds - const teams = await getTeamsByOrgIds(orgIds) - const teamIds = teams.map((team) => team.id) - await r({ - // easy things to clone - migrations: r - .table('_migrations' as any) - .coerceTo('array') - .do((items: RValue) => - r - .db(DESTINATION) - .table('_migrations' as any) - .insert(items) - ), - agendaItem: (r.table('AgendaItem').getAll(r.args(teamIds), {index: 'teamId'}) as any) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('AgendaItem').insert(items)), - atlassianAuth: (r.table('AtlassianAuth').getAll(r.args(teamIds), {index: 'teamId'}) as any) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('AtlassianAuth').insert(items)), - invoice: (r.table('Invoice').filter((row: RDatum) => r(orgIds).contains(row('orgId'))) as any) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('Invoice').insert(items)), - invoiceItemHook: ( - r.table('InvoiceItemHook').filter((row: RDatum) => r(orgIds).contains(row('orgId'))) as any - ) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('InvoiceItemHook').insert(items)), - meetingMember: (r.table('MeetingMember').getAll(r.args(teamIds), {index: 'teamId'}) as any) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('MeetingMember').insert(items)), - meetingSettings: (r.table('MeetingSettings').getAll(r.args(teamIds), {index: 'teamId'}) as any) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('MeetingSettings').insert(items)), - newMeeting: (r.table('NewMeeting').getAll(r.args(teamIds), {index: 'teamId'}) as any) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('NewMeeting').insert(items)), - reflectPrompt: (r.table('ReflectPrompt').getAll(r.args(teamIds), {index: 'teamId'}) as any) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('ReflectPrompt').insert(items)), - templateDimension: ( - r - .table('TemplateDimension') - .filter((row: RDatum) => r(teamIds).contains(row('teamId'))) as any - ) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('TemplateDimension').insert(items)), - templateScale: ( - r.table('TemplateScale').filter((row: RDatum) => r(teamIds).contains(row('teamId'))) as any - ) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('TemplateScale').insert(items)), - slackAuth: (r.table('SlackAuth').getAll(r.args(teamIds), {index: 'teamId'}) as any) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('SlackAuth').insert(items)), - slackNotification: ( - r.table('SlackNotification').getAll(r.args(teamIds), {index: 'teamId'}) as any - ) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('SlackNotification').insert(items)), - task: (r.table('Task').getAll(r.args(teamIds), {index: 'teamId'}) as any) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('Task').insert(items)), - teamInvitation: (r.table('TeamInvitation').getAll(r.args(teamIds), {index: 'teamId'}) as any) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('TeamInvitation').insert(items)), - teamMember: (r.table('TeamMember').getAll(r.args(teamIds), {index: 'teamId'}) as any) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('TeamMember').insert(items)), - // hard things to clone - userIds: r - .table('TeamMember') - .getAll(r.args(teamIds), {index: 'teamId'})('userId') - .coerceTo('array') - .distinct() - .do((userIds: RValue) => { - return r({ - notification: (r.table('Notification').getAll(r.args(userIds), {index: 'userId'}) as any) - .filter((notification: RValue) => - r.branch( - notification('teamId').default(null).ne(null), - r(teamIds).contains(notification('teamId')), - notification('orgId').default(null).ne(null), - r(orgIds).contains(notification('orgId')), - true - ) - ) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('Notification').insert(items)), - suggestedAction: ( - r.table('SuggestedAction').getAll(r.args(userIds), {index: 'userId'}) as any - ) - .filter((row: RValue) => - r.or(row('teamId').default(null).eq(null), r(teamIds).contains(row('teamId'))) - ) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('SuggestedAction').insert(items)) - }) - }), - meetingIds: r - .table('NewMeeting') - .getAll(r.args(teamIds), {index: 'teamId'})('id') - .coerceTo('array') - .do((meetingIds: RValue) => { - return r({ - agendaItemComments: r - .table('AgendaItem') - .getAll(r.args(meetingIds), {index: 'meetingId'})('id') - .coerceTo('array') - .do((discussionIds: RValue) => { - return ( - r.table('Comment').getAll(r.args(discussionIds), {index: 'discussionId'}) as any - ) - .coerceTo('array') - .do((items: RValue) => r.db(DESTINATION).table('Comment').insert(items)) - }) - }) - }) - }).run() - - return `Success! 'orgBackup' contains all the records for ${orgIds.join(', ')}` + return `Not yet implemented!` } export default backupOrganization diff --git a/packages/server/graphql/private/mutations/changeEmailDomain.ts b/packages/server/graphql/private/mutations/changeEmailDomain.ts index 85228837367..0365503d134 100644 --- a/packages/server/graphql/private/mutations/changeEmailDomain.ts +++ b/packages/server/graphql/private/mutations/changeEmailDomain.ts @@ -3,7 +3,6 @@ import {r} from 'rethinkdb-ts' import {RDatum, RValue} from '../../../database/stricterR' import getKysely from '../../../postgres/getKysely' import getUsersbyDomain from '../../../postgres/queries/getUsersByDomain' -import updateUserEmailDomainsToPG from '../../../postgres/queries/updateUserEmailDomainsToPG' import {MutationResolvers} from '../../private/resolverTypes' const changeEmailDomain: MutationResolvers['changeEmailDomain'] = async ( @@ -47,18 +46,41 @@ const changeEmailDomain: MutationResolvers['changeEmailDomain'] = async ( const pg = getKysely() const [updatedUserRes] = await Promise.all([ - updateUserEmailDomainsToPG(normalizedNewDomain, userIdsToUpdate), pg - .updateTable('OrganizationApprovedDomain') + .with('TeamMembersUpdate', (qc) => + qc + .updateTable('TeamMember') + .set({ + email: sql`CONCAT(LEFT(email, POSITION('@' in email)), ${normalizedNewDomain}::VARCHAR)` + }) + .where('userId', 'in', userIdsToUpdate) + ) + .with('OrganizationApprovedDomainUpdate', (qc) => + qc + .updateTable('OrganizationApprovedDomain') + .set({ + domain: sql`REPLACE("domain", ${normalizedOldDomain}, ${normalizedNewDomain})` + }) + .where('domain', 'like', normalizedOldDomain) + ) + .with('OrganizationUpdate', (qc) => + qc + .updateTable('Organization') + .set({activeDomain: normalizedNewDomain}) + .where('activeDomain', '=', normalizedOldDomain) + ) + .with('SAMLUpdate', (qc) => + qc + .updateTable('SAMLDomain') + .set({domain: normalizedNewDomain}) + .where('domain', '=', normalizedOldDomain) + ) + .updateTable('User') .set({ - domain: sql`REPLACE("domain", ${normalizedOldDomain}, ${normalizedNewDomain})` + email: sql`CONCAT(LEFT(email, POSITION('@' in email)), ${normalizedNewDomain}::VARCHAR)` }) - .where('domain', 'like', normalizedOldDomain) - .execute(), - pg - .updateTable('Organization') - .set({activeDomain: normalizedNewDomain}) - .where('activeDomain', '=', normalizedOldDomain) + .where('id', 'in', userIdsToUpdate) + .returning('id') .execute(), r .table('TeamMember') @@ -67,11 +89,6 @@ const changeEmailDomain: MutationResolvers['changeEmailDomain'] = async ( email: row('email').split('@').nth(0).add(`@${normalizedNewDomain}`) })) .run(), - pg - .updateTable('SAMLDomain') - .set({domain: normalizedNewDomain}) - .where('domain', '=', normalizedOldDomain) - .execute(), r .table('Invoice') .filter((row: RDatum) => diff --git a/packages/server/graphql/private/mutations/hardDeleteUser.ts b/packages/server/graphql/private/mutations/hardDeleteUser.ts index 80a28568d12..7bc41619d2b 100644 --- a/packages/server/graphql/private/mutations/hardDeleteUser.ts +++ b/packages/server/graphql/private/mutations/hardDeleteUser.ts @@ -1,6 +1,6 @@ -import TeamMemberId from 'parabol-client/shared/gqlIds/TeamMemberId' import getRethink from '../../../database/rethinkDriver' import {RValue} from '../../../database/stricterR' +import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' import getPg from '../../../postgres/getPg' import {getUserByEmail} from '../../../postgres/queries/getUsersByEmails' import {getUserById} from '../../../postgres/queries/getUsersByIds' @@ -10,6 +10,36 @@ import sendAccountRemovedEvent from '../../mutations/helpers/sendAccountRemovedE import softDeleteUser from '../../mutations/helpers/softDeleteUser' import {MutationResolvers} from '../resolverTypes' +const setFacilitatedUserIdOrDelete = async ( + userIdToDelete: string, + teamIds: string[], + dataLoader: DataLoaderInstance +) => { + const r = await getRethink() + const facilitatedMeetings = await r + .table('NewMeeting') + .getAll(r.args(teamIds), {index: 'teamId'}) + .filter((row: RValue) => row('createdBy').eq(userIdToDelete)) + .run() + facilitatedMeetings.map(async (meeting) => { + const {id: meetingId} = meeting + const meetingMembers = await dataLoader.get('meetingMembersByMeetingId').load(meetingId) + const otherMember = meetingMembers.find(({userId}) => userId !== userIdToDelete) + if (otherMember) { + await r + .table('NewMeeting') + .get(meetingId) + .update({ + facilitatorUserId: otherMember.userId + }) + .run() + } else { + // single-person meeting must be deleted because facilitatorUserId must be non-null + await r.table('NewMeeting').get(meetingId).delete().run() + } + }) +} + const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( _source, {userId, email, reasonText}, @@ -32,78 +62,31 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( const userIdToDelete = user.id // get team ids and meetingIds - const [teamMemberIds, meetingIds] = await Promise.all([ - r - .table('TeamMember') - .getAll(userIdToDelete, {index: 'userId'}) - .getField('id') - .coerceTo('array') - .run(), - r - .table('MeetingMember') - .getAll(userIdToDelete, {index: 'userId'}) - .getField('meetingId') - .coerceTo('array') - .run() + const [teamMembers, meetingMembers] = await Promise.all([ + dataLoader.get('teamMembersByUserId').load(userIdToDelete), + dataLoader.get('meetingMembersByUserId').load(userIdToDelete) ]) - const teamIds = teamMemberIds.map((id) => TeamMemberId.split(id).teamId) + const teamIds = teamMembers.map(({teamId}) => teamId) + const teamMemberIds = teamMembers.map(({id}) => id) + const meetingIds = meetingMembers.map(({meetingId}) => meetingId) - // need to fetch these upfront - const [onePersonMeetingIds, swapFacilitatorUpdates, swapCreatedByUserUpdates, discussions] = - await Promise.all([ - ( - r - .table('MeetingMember') - .getAll(r.args(meetingIds), {index: 'meetingId'}) - .group('meetingId') as any - ) - .count() - .ungroup() - .filter((row: RValue) => row('reduction').le(1)) - .map((row: RValue) => row('group')) - .coerceTo('array') - .run(), - r - .table('NewMeeting') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((row: RValue) => row('facilitatorUserId').eq(userIdToDelete)) - .merge((meeting: RValue) => ({ - otherTeamMember: r - .table('TeamMember') - .getAll(meeting('teamId'), {index: 'teamId'}) - .filter((row: RValue) => row('userId').ne(userIdToDelete)) - .nth(0) - .getField('userId') - .default(null) - })) - .filter(r.row.hasFields('otherTeamMember')) - .pluck('id', 'otherTeamMember') - .run(), - r - .table('NewMeeting') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((row: RValue) => row('createdBy').eq(userIdToDelete)) - .merge((meeting: RValue) => ({ - otherTeamMember: r - .table('TeamMember') - .getAll(meeting('teamId'), {index: 'teamId'}) - .filter((row: RValue) => row('userId').ne(userIdToDelete)) - .nth(0) - .getField('userId') - .default(null) - })) - .filter(r.row.hasFields('otherTeamMember')) - .pluck('id', 'otherTeamMember') - .run(), - pg.query(`SELECT "id" FROM "Discussion" WHERE "teamId" = ANY ($1);`, [teamIds]) - ]) + const discussions = await pg.query(`SELECT "id" FROM "Discussion" WHERE "teamId" = ANY ($1);`, [ + teamIds + ]) const teamDiscussionIds = discussions.rows.map(({id}) => id) // soft delete first for side effects const tombstoneId = await softDeleteUser(userIdToDelete, dataLoader) // all other writes + await setFacilitatedUserIdOrDelete(userIdToDelete, teamIds, dataLoader) await r({ + nullifyCreatedBy: r + .table('NewMeeting') + .getAll(r.args(teamIds), {index: 'teamId'}) + .filter((row: RValue) => row('createdBy').eq(userIdToDelete)) + .update({createdBy: null}) + .run(), teamMember: r.table('TeamMember').getAll(userIdToDelete, {index: 'userId'}).delete(), meetingMember: r.table('MeetingMember').getAll(userIdToDelete, {index: 'userId'}).delete(), notification: r.table('Notification').getAll(userIdToDelete, {index: 'userId'}).delete(), @@ -140,27 +123,7 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( .update({ createdBy: tombstoneId, isAnonymous: true - }), - onePersonMeetings: r - .table('NewMeeting') - .getAll(r.args(onePersonMeetingIds), {index: 'id'}) - .delete(), - swapFacilitator: r(swapFacilitatorUpdates).forEach((update) => - r - .table('NewMeeting') - .get(update('id')) - .update({ - facilitatorUserId: update('otherTeamMember') as unknown as string - }) - ), - swapCreatedByUser: r(swapCreatedByUserUpdates).forEach((update) => - r - .table('NewMeeting') - .get(update('id')) - .update({ - createdBy: update('otherTeamMember') as unknown as string - }) - ) + }) }).run() // now postgres, after FKs are added then triggers should take care of children diff --git a/packages/server/graphql/private/mutations/updateEmail.ts b/packages/server/graphql/private/mutations/updateEmail.ts index a4afec21b74..e06fe860c56 100644 --- a/packages/server/graphql/private/mutations/updateEmail.ts +++ b/packages/server/graphql/private/mutations/updateEmail.ts @@ -1,10 +1,11 @@ import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {getUserByEmail} from '../../../postgres/queries/getUsersByEmails' -import updateUser from '../../../postgres/queries/updateUser' import {MutationResolvers} from '../resolverTypes' const updateEmail: MutationResolvers['updateEmail'] = async (_source, {oldEmail, newEmail}) => { const r = await getRethink() + const pg = getKysely() // VALIDATION if (oldEmail === newEmail) { @@ -18,19 +19,22 @@ const updateEmail: MutationResolvers['updateEmail'] = async (_source, {oldEmail, // RESOLUTION const {id: userId} = user - const updates = { - email: newEmail, - updatedAt: new Date() - } await Promise.all([ + pg + .with('TeamMemberUpdate', (qc) => + qc.updateTable('TeamMember').set({email: newEmail}).where('userId', '=', userId) + ) + .updateTable('User') + .set({email: newEmail}) + .where('id', '=', userId) + .execute(), r .table('TeamMember') .getAll(userId, {index: 'userId'}) .update({ email: newEmail }) - .run(), - updateUser(updates, userId) + .run() ]) return true diff --git a/packages/server/graphql/private/typeDefs/_legacy.graphql b/packages/server/graphql/private/typeDefs/_legacy.graphql index a8291647a0b..d702b048134 100644 --- a/packages/server/graphql/private/typeDefs/_legacy.graphql +++ b/packages/server/graphql/private/typeDefs/_legacy.graphql @@ -756,112 +756,6 @@ interface NewMeetingStage { timeRemaining: Float } -""" -A team meeting history for all previous meetings -""" -interface NewMeeting { - """ - The unique meeting id. shortid. - """ - id: ID! - - """ - The timestamp the meeting was created - """ - createdAt: DateTime! - - """ - The id of the user that created the meeting - """ - createdBy: ID! - - """ - The user that created the meeting - """ - createdByUser: User! - - """ - The timestamp the meeting officially ended - """ - endedAt: DateTime - - """ - The location of the facilitator in the meeting - """ - facilitatorStageId: ID! - - """ - The userId (or anonymousId) of the most recent facilitator - """ - facilitatorUserId: ID! - - """ - The facilitator team member - """ - facilitator: TeamMember! - - """ - The team members that were active during the time of the meeting - """ - meetingMembers: [MeetingMember!]! - - """ - The auto-incrementing meeting number for the team - """ - meetingNumber: Int! - meetingType: MeetingTypeEnum! - - """ - The name of the meeting - """ - name: String! - - """ - The organization this meeting belongs to - """ - organization: Organization! - - """ - The phases the meeting will go through, including all phase-specific state - """ - phases: [NewMeetingPhase!]! - - """ - true if should show the org the conversion modal, else false - """ - showConversionModal: Boolean! - - """ - The time the meeting summary was emailed to the team - """ - summarySentAt: DateTime - - """ - foreign key for team - """ - teamId: ID! - - """ - The team that ran the meeting - """ - team: Team! - - """ - The last time a meeting was updated (stage completed, finished, etc) - """ - updatedAt: DateTime - - """ - The meeting member of the viewer - """ - viewerMeetingMember: MeetingMember - - """ - Is this locked for starter plans? - """ - locked: Boolean! -} - """ An item that can be put in a thread """ diff --git a/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts b/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts index 29014431ff3..33a5ada82b6 100644 --- a/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts +++ b/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts @@ -1,12 +1,11 @@ +import {sql} from 'kysely' import DomainJoinRequestId from 'parabol-client/shared/gqlIds/DomainJoinRequestId' import {InvoiceItemType, SubscriptionChannel} from 'parabol-client/types/constEnums' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' +import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' import adjustUserCount from '../../../billing/helpers/adjustUserCount' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' -import getTeamsByIds from '../../../postgres/queries/getTeamsByIds' import {getUserById} from '../../../postgres/queries/getUsersByIds' -import addTeamIdToTMS from '../../../safeMutations/addTeamIdToTMS' import insertNewTeamMember from '../../../safeMutations/insertNewTeamMember' import {Logger} from '../../../utils/Logger' import RedisLock from '../../../utils/RedisLock' @@ -14,6 +13,7 @@ import {getUserId} from '../../../utils/authorization' import publish from '../../../utils/publish' import setUserTierForUserIds from '../../../utils/setUserTierForUserIds' import standardError from '../../../utils/standardError' +import isValid from '../../isValid' import {MutationResolvers} from '../resolverTypes' // TODO (EXPERIMENT: prompt-to-join-org): some parts are borrowed from acceptTeamInvitation, create generic functions @@ -27,7 +27,6 @@ const acceptRequestToJoinDomain: MutationResolvers['acceptRequestToJoinDomain'] const viewerId = getUserId(authToken) const now = new Date() const pg = getKysely() - const r = await getRethink() // Fetch the request that is not expired const request = await pg @@ -44,21 +43,20 @@ const acceptRequestToJoinDomain: MutationResolvers['acceptRequestToJoinDomain'] const {createdBy, domain} = request // Viewer must be a lead of the provided teamIds - const validTeamMembers = await r - .table('TeamMember') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter({isLead: true, userId: viewerId}) - .run() + const viewerTeamMembers = await dataLoader.get('teamMembersByUserId').load(viewerId) + const validTeamMembers = viewerTeamMembers.filter( + ({isLead, teamId}) => isLead && teamIds.includes(teamId) + ) if (!validTeamMembers.length) { return standardError(new Error('Not a team leader')) } // Provided request domain should match team's organizations activeDomain - const leadTeams = await getTeamsByIds(validTeamMembers.map((teamMember) => teamMember.teamId)) - const teamOrgs = await Promise.all( - leadTeams.map((t) => dataLoader.get('organizations').loadNonNull(t.orgId)) - ) + const leadTeams = (await dataLoader.get('teams').loadMany(teamIds)).filter(isValid) + const orgIds = leadTeams.map((team) => team.orgId) + const teamOrgs = (await dataLoader.get('organizations').loadMany(orgIds)).filter(isValid) + const validOrgIds = teamOrgs.filter((org) => org.activeDomain === domain).map(({id}) => id) if (!validOrgIds.length) { @@ -86,14 +84,32 @@ const acceptRequestToJoinDomain: MutationResolvers['acceptRequestToJoinDomain'] return standardError(new Error('User not found')) } - const {id: userId} = user + const {id: userId, picture, preferredName, email} = user for (const validTeam of validTeams) { const {id: teamId, orgId} = validTeam const [organizationUser] = await Promise.all([ dataLoader.get('organizationUsersByUserIdOrgId').load({orgId, userId}), - insertNewTeamMember(user, teamId), - addTeamIdToTMS(userId, teamId) + pg + .with('UserUpdate', (qc) => + qc + .updateTable('User') + .set({tms: sql`arr_append_uniq("tms", ${teamId})`}) + .where('id', '=', userId) + ) + .insertInto('TeamMember') + .values({ + id: TeamMemberId.join(teamId, userId), + teamId, + userId, + picture, + preferredName, + email, + openDrawer: 'manageTeam' + }) + .onConflict((oc) => oc.column('id').doUpdateSet({isNotRemoved: true})) + .execute(), + insertNewTeamMember(user, teamId, dataLoader) ]) if (!organizationUser) { @@ -105,7 +121,7 @@ const acceptRequestToJoinDomain: MutationResolvers['acceptRequestToJoinDomain'] await setUserTierForUserIds([userId]) } } - + dataLoader.clearAll(['users', 'teamMembers']) await redisLock.unlock() // Send the new team member a welcome & a new token diff --git a/packages/server/graphql/public/mutations/requestToJoinDomain.ts b/packages/server/graphql/public/mutations/requestToJoinDomain.ts index cbcfd9cd095..cd2dad2c9f0 100644 --- a/packages/server/graphql/public/mutations/requestToJoinDomain.ts +++ b/packages/server/graphql/public/mutations/requestToJoinDomain.ts @@ -2,11 +2,11 @@ import ms from 'ms' import getRethink from '../../../database/rethinkDriver' import NotificationRequestToJoinOrg from '../../../database/types/NotificationRequestToJoinOrg' import getKysely from '../../../postgres/getKysely' -import getTeamIdsByOrgIds from '../../../postgres/queries/getTeamIdsByOrgIds' import {getUserId} from '../../../utils/authorization' import getDomainFromEmail from '../../../utils/getDomainFromEmail' import {getEligibleOrgIdsByDomain} from '../../../utils/isRequestToJoinDomainAllowed' import standardError from '../../../utils/standardError' +import isValid from '../../isValid' import {MutationResolvers} from '../resolverTypes' import publishNotification from './helpers/publishNotification' @@ -48,18 +48,13 @@ const requestToJoinDomain: MutationResolvers['requestToJoinDomain'] = async ( return {success: true} } - const teamIds = await getTeamIdsByOrgIds(orgIds) - - const leadUserIds = (await r - .table('TeamMember') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter({ - isNotRemoved: true, - isLead: true - }) - .pluck('userId') - .distinct()('userId') - .run()) as string[] + const orgTeams = (await dataLoader.get('teamsByOrgIds').loadMany(orgIds)).filter(isValid).flat() + const teamIds = orgTeams.map(({id}) => id) + const teamMembers = (await dataLoader.get('teamMembersByTeamId').loadMany(teamIds)) + .filter(isValid) + .flat() + const leadTeamMembers = teamMembers.filter(({isLead}) => isLead) + const leadUserIds = [...new Set(leadTeamMembers.map(({userId}) => userId))] const notificationsToInsert = leadUserIds.map((userId) => { return new NotificationRequestToJoinOrg({ diff --git a/packages/server/graphql/public/mutations/updateUserProfile.ts b/packages/server/graphql/public/mutations/updateUserProfile.ts index ea6ee6b9f4e..72816963520 100644 --- a/packages/server/graphql/public/mutations/updateUserProfile.ts +++ b/packages/server/graphql/public/mutations/updateUserProfile.ts @@ -2,7 +2,7 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import linkify from 'parabol-client/utils/linkify' import getRethink from '../../../database/rethinkDriver' import TeamMember from '../../../database/types/TeamMember' -import updateUser from '../../../postgres/queries/updateUser' +import getKysely from '../../../postgres/getKysely' import {analytics} from '../../../utils/analytics/analytics' import {getUserId, isAuthenticated} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -15,6 +15,7 @@ const updateUserProfile: MutationResolvers['updateUserProfile'] = async ( {authToken, dataLoader, socketId: mutatorId} ) => { const r = await getRethink() + const pg = getKysely() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -49,17 +50,31 @@ const updateUserProfile: MutationResolvers['updateUserProfile'] = async ( const updateObj = { preferredName: normalizedPreferredName } - const [teamMembers] = await Promise.all([ + await Promise.all([ + pg + .with('TeamMemberUpdate', (qc) => + qc + .updateTable('TeamMember') + .set({preferredName: normalizedPreferredName}) + .where('userId', '=', userId) + ) + .updateTable('User') + .set({preferredName: normalizedPreferredName}) + .where('id', '=', userId) + .execute(), r .table('TeamMember') .getAll(userId, {index: 'userId'}) .update(updateObj, {returnChanges: true})('changes')('new_val') .default([]) - .run() as unknown as TeamMember[], - updateUser(updateObj, userId) + .run() as unknown as TeamMember[] ]) + dataLoader.clearAll(['users', 'teamMembers']) - const user = await dataLoader.get('users').loadNonNull(userId) + const [user, teamMembers] = await Promise.all([ + dataLoader.get('users').loadNonNull(userId), + dataLoader.get('teamMembersByUserId').load(userId) + ]) if (normalizedPreferredName) { analytics.accountNameChanged(user, normalizedPreferredName) analytics.identify({ diff --git a/packages/server/graphql/public/mutations/uploadUserImage.ts b/packages/server/graphql/public/mutations/uploadUserImage.ts index 05c1e9fa3eb..394f3053066 100644 --- a/packages/server/graphql/public/mutations/uploadUserImage.ts +++ b/packages/server/graphql/public/mutations/uploadUserImage.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 updateUser from '../../../postgres/queries/updateUser' import {getUserId, isAuthenticated} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -15,6 +16,7 @@ const uploadUserImage: MutationResolvers['uploadUserImage'] = async ( {authToken, dataLoader, socketId: mutatorId} ) => { const r = await getRethink() + const pg = getKysely() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -32,15 +34,26 @@ const uploadUserImage: MutationResolvers['uploadUserImage'] = async ( const manager = getFileStoreManager() const publicLocation = await manager.putUserAvatar(normalBuffer, userId, normalExt) - const [teamMembers] = await Promise.all([ + await Promise.all([ + pg + .with('TeamMemberUpdate', (qc) => + qc.updateTable('TeamMember').set({picture: publicLocation}).where('userId', '=', userId) + ) + .updateTable('User') + .set({picture: publicLocation}) + .where('id', '=', userId) + .execute(), r .table('TeamMember') .getAll(userId, {index: 'userId'}) .update({picture: publicLocation}, {returnChanges: true})('changes')('new_val') .default([]) .run() as unknown as TeamMember[], + updateUser({picture: publicLocation}, userId) ]) + dataLoader.clearAll(['users', 'teamMembers']) + const teamMembers = await dataLoader.get('teamMembersByUserId').load(userId) const teamIds = teamMembers.map(({teamId}) => teamId) teamIds.forEach((teamId) => { const data = {userId, teamIds: [teamId]} diff --git a/packages/server/graphql/public/typeDefs/ActionMeeting.graphql b/packages/server/graphql/public/typeDefs/ActionMeeting.graphql index 03ce53d2076..8b855e1bad0 100644 --- a/packages/server/graphql/public/typeDefs/ActionMeeting.graphql +++ b/packages/server/graphql/public/typeDefs/ActionMeeting.graphql @@ -13,14 +13,14 @@ type ActionMeeting implements NewMeeting { createdAt: DateTime! """ - The id of the user that created the meeting + The id of the user that created the meeting, null if user was hard deleted """ - createdBy: ID! + createdBy: ID """ - The user that created the meeting + The user that created the meeting, null if user was hard deleted """ - createdByUser: User! + createdByUser: User """ The timestamp the meeting officially ended diff --git a/packages/server/graphql/public/typeDefs/NewMeeting.graphql b/packages/server/graphql/public/typeDefs/NewMeeting.graphql index c7ddd86ca3c..37094a312b2 100644 --- a/packages/server/graphql/public/typeDefs/NewMeeting.graphql +++ b/packages/server/graphql/public/typeDefs/NewMeeting.graphql @@ -13,14 +13,14 @@ interface NewMeeting { createdAt: DateTime! """ - The id of the user that created the meeting + The id of the user that created the meeting, null if user was hard deleted """ - createdBy: ID! + createdBy: ID """ - The user that created the meeting + The user that created the meeting, null if user was hard deleted """ - createdByUser: User! + createdByUser: User """ The timestamp the meeting officially ended diff --git a/packages/server/graphql/public/typeDefs/PokerMeeting.graphql b/packages/server/graphql/public/typeDefs/PokerMeeting.graphql index c215840584e..678549a82ec 100644 --- a/packages/server/graphql/public/typeDefs/PokerMeeting.graphql +++ b/packages/server/graphql/public/typeDefs/PokerMeeting.graphql @@ -13,14 +13,14 @@ type PokerMeeting implements NewMeeting { createdAt: DateTime! """ - The id of the user that created the meeting + The id of the user that created the meeting, null if user was hard deleted """ - createdBy: ID! + createdBy: ID """ - The user that created the meeting + The user that created the meeting, null if user was hard deleted """ - createdByUser: User! + createdByUser: User """ The timestamp the meeting officially ended diff --git a/packages/server/graphql/public/typeDefs/RetrospectiveMeeting.graphql b/packages/server/graphql/public/typeDefs/RetrospectiveMeeting.graphql index 6efc4675094..f9ff31de25e 100644 --- a/packages/server/graphql/public/typeDefs/RetrospectiveMeeting.graphql +++ b/packages/server/graphql/public/typeDefs/RetrospectiveMeeting.graphql @@ -63,14 +63,14 @@ type RetrospectiveMeeting implements NewMeeting { createdAt: DateTime! """ - The id of the user that created the meeting + The id of the user that created the meeting, null if user was hard deleted """ - createdBy: ID! + createdBy: ID """ - The user that created the meeting + The user that created the meeting, null if user was hard deleted """ - createdByUser: User! + createdByUser: User """ Disables anonymity of reflections diff --git a/packages/server/graphql/public/typeDefs/TeamPromptMeeting.graphql b/packages/server/graphql/public/typeDefs/TeamPromptMeeting.graphql index 24035c7b32f..928d4fcd633 100644 --- a/packages/server/graphql/public/typeDefs/TeamPromptMeeting.graphql +++ b/packages/server/graphql/public/typeDefs/TeamPromptMeeting.graphql @@ -23,14 +23,14 @@ type TeamPromptMeeting implements NewMeeting { createdAt: DateTime! """ - The id of the user that created the meeting + The id of the user that created the meeting, null if user was hard deleted """ - createdBy: ID! + createdBy: ID """ - The user that created the meeting + The user that created the meeting, null if user was hard deleted """ - createdByUser: User! + createdByUser: User """ The timestamp the meeting officially ended diff --git a/packages/server/graphql/public/types/Company.ts b/packages/server/graphql/public/types/Company.ts index cd21ce19d81..97ee27b50ec 100644 --- a/packages/server/graphql/public/types/Company.ts +++ b/packages/server/graphql/public/types/Company.ts @@ -8,7 +8,6 @@ import {DataLoaderWorker} from '../../graphql' import isValid from '../../isValid' import {CompanyResolvers} from '../resolverTypes' import getActiveTeamCountByOrgIds from './helpers/getActiveTeamCountByOrgIds' -import {getTeamsByOrgIds} from './helpers/getTeamsByOrgIds' export type CompanySource = {id: string} @@ -79,7 +78,9 @@ const Company: CompanyResolvers = { // if there aren't 2 active users, abort if (activeOrganizationUsers.length < 2) return 0 // get the unarchived teams - const unarchivedTeams = await getTeamsByOrgIds(allOrgIds, dataLoader, false) + const unarchivedTeams = (await dataLoader.get('teamsByOrgIds').loadMany(allOrgIds)) + .filter(isValid) + .flat() // if there aren't any unarchived teams, abort if (unarchivedTeams.length === 0) return 0 // create teamMemberIds @@ -122,7 +123,7 @@ const Company: CompanyResolvers = { const r = await getRethink() const organizations = await getSuggestedTierOrganizations(domain, authToken, dataLoader) const orgIds = organizations.map(({id}) => id) - const teams = await getTeamsByOrgIds(orgIds, dataLoader, true) + const teams = (await dataLoader.get('teamsByOrgIds').loadMany(orgIds)).filter(isValid).flat() const teamIds = teams.map(({id}) => id) if (teamIds.length === 0) return 0 const lastMetAt = await r @@ -139,7 +140,7 @@ const Company: CompanyResolvers = { const r = await getRethink() const organizations = await getSuggestedTierOrganizations(domain, authToken, dataLoader) const orgIds = organizations.map(({id}) => id) - const teams = await getTeamsByOrgIds(orgIds, dataLoader, true) + const teams = (await dataLoader.get('teamsByOrgIds').loadMany(orgIds)).filter(isValid).flat() const teamIds = teams.map(({id}) => id) if (teamIds.length === 0) return 0 const filterFn = after ? (meeting: any) => meeting('createdAt').ge(after) : () => true @@ -156,7 +157,7 @@ const Company: CompanyResolvers = { const r = await getRethink() const organizations = await getSuggestedTierOrganizations(domain, authToken, dataLoader) const orgIds = organizations.map(({id}) => id) - const teams = await getTeamsByOrgIds(orgIds, dataLoader, true) + const teams = (await dataLoader.get('teamsByOrgIds').loadMany(orgIds)).filter(isValid).flat() const teamIds = teams.map(({id}) => id) if (teamIds.length === 0) return 0 return ( diff --git a/packages/server/graphql/public/types/DomainJoinRequest.ts b/packages/server/graphql/public/types/DomainJoinRequest.ts index 772526f3cbd..4b01c6577b4 100644 --- a/packages/server/graphql/public/types/DomainJoinRequest.ts +++ b/packages/server/graphql/public/types/DomainJoinRequest.ts @@ -1,5 +1,5 @@ import DomainJoinRequestId from 'parabol-client/shared/gqlIds/DomainJoinRequestId' -import getRethink from '../../../database/rethinkDriver' +import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' import {getUserId} from '../../../utils/authorization' import {GQLContext} from '../../graphql' import isValid from '../../isValid' @@ -15,7 +15,6 @@ const DomainJoinRequest: DomainJoinRequestResolvers = { }, teams: async ({id}, _args, {authToken, dataLoader}: GQLContext) => { const viewerId = getUserId(authToken) - const r = await getRethink() const user = (await dataLoader.get('users').loadNonNull(viewerId))! const teamIds = user.tms @@ -23,11 +22,10 @@ const DomainJoinRequest: DomainJoinRequestResolvers = { const {domain} = request - const leadTeamMembers = await r - .table('TeamMember') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter({isLead: true, userId: viewerId}) - .run() + const teamMemberIds = teamIds.map((teamId) => TeamMemberId.join(teamId, viewerId)) + const leadTeamMembers = (await dataLoader.get('teamMembers').loadMany(teamMemberIds)) + .filter(isValid) + .filter(({isLead}) => isLead) const leadTeamIds = leadTeamMembers.map((teamMember) => teamMember.teamId) const leadTeams = (await dataLoader.get('teams').loadMany(leadTeamIds)).filter(isValid) diff --git a/packages/server/graphql/public/types/NewMeeting.ts b/packages/server/graphql/public/types/NewMeeting.ts index d4e8f36c565..030f8b42d06 100644 --- a/packages/server/graphql/public/types/NewMeeting.ts +++ b/packages/server/graphql/public/types/NewMeeting.ts @@ -15,6 +15,7 @@ const NewMeeting: NewMeetingResolvers = { return resolveTypeLookup[meetingType as keyof typeof resolveTypeLookup] }, createdByUser: ({createdBy}, _args, {dataLoader}) => { + if (!createdBy) return null return dataLoader.get('users').loadNonNull(createdBy) }, facilitator: ({facilitatorUserId, teamId}, _args, {dataLoader}) => { diff --git a/packages/server/graphql/public/types/helpers/getTeamsByOrgIds.ts b/packages/server/graphql/public/types/helpers/getTeamsByOrgIds.ts deleted file mode 100644 index f6f726b5d8a..00000000000 --- a/packages/server/graphql/public/types/helpers/getTeamsByOrgIds.ts +++ /dev/null @@ -1,12 +0,0 @@ -import errorFilter from '../../../errorFilter' -import {DataLoaderWorker} from '../../../graphql' - -export const getTeamsByOrgIds = async ( - orgIds: string[], - dataLoader: DataLoaderWorker, - includeArchived: boolean -) => { - const teamsByOrgId = (await dataLoader.get('teamsByOrgIds').loadMany(orgIds)).filter(errorFilter) - const teams = teamsByOrgId.flat() - return includeArchived ? teams : teams.filter(({isArchived}) => !isArchived) -} diff --git a/packages/server/graphql/queries/verifiedInvitation.ts b/packages/server/graphql/queries/verifiedInvitation.ts index 130c9e40a04..c4f959b4bc4 100644 --- a/packages/server/graphql/queries/verifiedInvitation.ts +++ b/packages/server/graphql/queries/verifiedInvitation.ts @@ -4,7 +4,6 @@ import {InvitationTokenError} from 'parabol-client/types/constEnums' import util from 'util' import {AuthIdentityTypeEnum} from '../../../client/types/constEnums' import getRethink from '../../database/rethinkDriver' -import getTeamsByIds from '../../postgres/queries/getTeamsByIds' import {getUserByEmail} from '../../postgres/queries/getUsersByEmails' import IUser from '../../postgres/types/IUser' import getBestInvitationMeeting from '../../utils/getBestInvitationMeeting' @@ -59,11 +58,10 @@ export default { meetingId: maybeMeetingId, teamId } = teamInvitation - const [teams, inviter] = await Promise.all([ - getTeamsByIds([teamId]), + const [team, inviter] = await Promise.all([ + dataLoader.get('teams').loadNonNull(teamId), dataLoader.get('users').load(invitedBy) ]) - const team = teams[0]! const bestMeeting = await getBestInvitationMeeting(teamId, maybeMeetingId, dataLoader) const meetingType = bestMeeting?.meetingType ?? null const meetingId = bestMeeting?.id ?? null diff --git a/packages/server/graphql/resolvers.ts b/packages/server/graphql/resolvers.ts index b292612f10a..708f54a04db 100644 --- a/packages/server/graphql/resolvers.ts +++ b/packages/server/graphql/resolvers.ts @@ -9,12 +9,11 @@ import Task from '../database/types/Task' import TeamMember from '../database/types/TeamMember' import User from '../database/types/User' import {Loaders} from '../dataloader/RootDataLoader' -import {IGetTeamsByIdsQueryResult} from '../postgres/queries/generated/getTeamsByIdsQuery' -import {Team} from '../postgres/queries/getTeamsByIds' import {AnyMeeting} from '../postgres/types/Meeting' import {getUserId, isSuperUser, isUserBillingLeader} from '../utils/authorization' import {GQLContext} from './graphql' import isValid from './isValid' +import {TeamSource} from './public/types/Team' export const resolveNewMeeting = ( { @@ -77,7 +76,7 @@ export const resolveTasks = async ( } export const resolveTeam = ( - {team, teamId}: {teamId?: string; team?: IGetTeamsByIdsQueryResult}, + {team, teamId}: {teamId?: string; team?: TeamSource}, _args: unknown, {dataLoader}: GQLContext ) => { @@ -89,7 +88,7 @@ export const resolveTeam = ( } export const resolveTeams = ( - {teamIds, teams}: {teamIds: string; teams: Team[]}, + {teamIds, teams}: {teamIds: string; teams: TeamSource[]}, _args: unknown, {dataLoader}: GQLContext ) => { diff --git a/packages/server/postgres/migrations/1720730818119_TeamMember-phase1.ts b/packages/server/postgres/migrations/1720730818119_TeamMember-phase1.ts new file mode 100644 index 00000000000..c1008de261d --- /dev/null +++ b/packages/server/postgres/migrations/1720730818119_TeamMember-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() + await client.query(` + DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'TeamDrawerEnum') THEN + CREATE TYPE "TeamDrawerEnum" AS ENUM ( + 'agenda', + 'manageTeam' + ); + END IF; + CREATE TABLE IF NOT EXISTS "TeamMember" ( + "id" VARCHAR(100) PRIMARY KEY, + "isNotRemoved" BOOLEAN NOT NULL DEFAULT TRUE, + "isLead" BOOLEAN NOT NULL DEFAULT FALSE, + "isSpectatingPoker" BOOLEAN NOT NULL DEFAULT FALSE, + "email" "citext" NOT NULL, + "openDrawer" "TeamDrawerEnum", + "picture" VARCHAR(2056) NOT NULL, + "preferredName" VARCHAR(100) NOT NULL, + "teamId" VARCHAR(100) NOT NULL, + "userId" VARCHAR(100) NOT NULL, + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + CONSTRAINT "fk_userId" + FOREIGN KEY("userId") + REFERENCES "User"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_teamId" + FOREIGN KEY("teamId") + REFERENCES "Team"("id") + ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS "idx_TeamMember_teamId" ON "TeamMember"("teamId") WHERE "isNotRemoved" = TRUE; + CREATE INDEX IF NOT EXISTS "idx_TeamMember_userId" ON "TeamMember"("userId") WHERE "isNotRemoved" = TRUE; + DROP TRIGGER IF EXISTS "update_TeamMember_updatedAt" ON "TeamMember"; + CREATE TRIGGER "update_TeamMember_updatedAt" BEFORE UPDATE ON "TeamMember" 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 "TeamMember"; + DROP TYPE "TeamDrawerEnum"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/queries/getTeamsByIds.ts b/packages/server/postgres/queries/getTeamsByIds.ts deleted file mode 100644 index 97ce5f2be80..00000000000 --- a/packages/server/postgres/queries/getTeamsByIds.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {isNotNull} from 'parabol-client/utils/predicates' -import getPg from '../getPg' -import {getTeamsByIdsQuery, IGetTeamsByIdsQueryResult} from './generated/getTeamsByIdsQuery' -import {IGetTeamsByOrgIdsQueryResult} from './generated/getTeamsByOrgIdsQuery' - -export interface Team extends Omit {} - -export const mapToTeam = (result: IGetTeamsByIdsQueryResult[] | IGetTeamsByOrgIdsQueryResult[]) => { - return result.map((team) => { - return { - ...team, - jiraDimensionFields: team.jiraDimensionFields - .filter(isNotNull) - .map((jiraDimensionField: any) => ({ - dimensionName: jiraDimensionField.dimensionName, - cloudId: jiraDimensionField.cloudId, - projectKey: jiraDimensionField.projectKey, - issueKey: jiraDimensionField.issueKey, - fieldName: jiraDimensionField.fieldName, - fieldType: jiraDimensionField.fieldType, - fieldId: jiraDimensionField.fieldId - })) - } as Team - }) -} - -const getTeamsByIds = async (teamIds: string[] | readonly string[]) => { - const teams = await getTeamsByIdsQuery.run({ids: teamIds} as any, getPg()) - return mapToTeam(teams) -} - -export default getTeamsByIds diff --git a/packages/server/postgres/queries/getTeamsByOrgIds.ts b/packages/server/postgres/queries/getTeamsByOrgIds.ts deleted file mode 100644 index 6ef094ff415..00000000000 --- a/packages/server/postgres/queries/getTeamsByOrgIds.ts +++ /dev/null @@ -1,18 +0,0 @@ -import getPg from '../getPg' -import {getTeamsByOrgIdsQuery} from './generated/getTeamsByOrgIdsQuery' -import {mapToTeam} from './getTeamsByIds' - -const getTeamsByOrgIds = async ( - orgIds: string[] | readonly string[], - options: {isArchived?: boolean} = {} -) => { - const {isArchived} = options - const queryParameters = { - orgIds, - isArchived: !!isArchived - } - const teams = await getTeamsByOrgIdsQuery.run(queryParameters as any, getPg()) - return mapToTeam(teams) -} - -export default getTeamsByOrgIds diff --git a/packages/server/postgres/queries/removeUserTms.ts b/packages/server/postgres/queries/removeUserTms.ts deleted file mode 100644 index 0eb46e1f138..00000000000 --- a/packages/server/postgres/queries/removeUserTms.ts +++ /dev/null @@ -1,19 +0,0 @@ -import getPg from '../getPg' -import catchAndLog from '../utils/catchAndLog' -import {removeUserTmsQuery} from './generated/removeUserTmsQuery' - -const removeUserTms = async (teamIdsToRemove: string | string[], userIds: string | string[]) => { - userIds = typeof userIds === 'string' ? [userIds] : userIds - teamIdsToRemove = typeof teamIdsToRemove === 'string' ? [teamIdsToRemove] : teamIdsToRemove - await catchAndLog(() => - removeUserTmsQuery.run( - { - ids: userIds as string[], - teamIds: teamIdsToRemove as string[] - }, - getPg() - ) - ) -} - -export default removeUserTms diff --git a/packages/server/postgres/queries/src/getTeamsByIdsQuery.sql b/packages/server/postgres/queries/src/getTeamsByIdsQuery.sql deleted file mode 100644 index 6f5f770a11f..00000000000 --- a/packages/server/postgres/queries/src/getTeamsByIdsQuery.sql +++ /dev/null @@ -1,6 +0,0 @@ -/* - @name getTeamsByIdsQuery - @param ids -> (...) -*/ -SELECT * FROM "Team" -WHERE id IN :ids; diff --git a/packages/server/postgres/queries/src/getTeamsByOrgIdsQuery.sql b/packages/server/postgres/queries/src/getTeamsByOrgIdsQuery.sql deleted file mode 100644 index 9ff12cc53b1..00000000000 --- a/packages/server/postgres/queries/src/getTeamsByOrgIdsQuery.sql +++ /dev/null @@ -1,7 +0,0 @@ -/* - @name getTeamsByOrgIdsQuery - @param orgIds -> (...) -*/ -SELECT * FROM "Team" -WHERE "orgId" IN :orgIds -AND "isArchived" = :isArchived; diff --git a/packages/server/postgres/queries/src/removeUserTmsQuery.sql b/packages/server/postgres/queries/src/removeUserTmsQuery.sql deleted file mode 100644 index c7f10e3f370..00000000000 --- a/packages/server/postgres/queries/src/removeUserTmsQuery.sql +++ /dev/null @@ -1,7 +0,0 @@ -/* - @name removeUserTmsQuery - @param ids -> (...) -*/ -UPDATE "User" SET - tms = arr_diff(tms, :teamIds) -WHERE id IN :ids; diff --git a/packages/server/safeMutations/acceptTeamInvitation.ts b/packages/server/safeMutations/acceptTeamInvitation.ts index 13c055771c9..0be315f8a1f 100644 --- a/packages/server/safeMutations/acceptTeamInvitation.ts +++ b/packages/server/safeMutations/acceptTeamInvitation.ts @@ -1,49 +1,62 @@ +import {sql} from 'kysely' import {InvoiceItemType} from 'parabol-client/types/constEnums' +import TeamMemberId from '../../client/shared/gqlIds/TeamMemberId' import adjustUserCount from '../billing/helpers/adjustUserCount' import getRethink from '../database/rethinkDriver' import SuggestedActionCreateNewTeam from '../database/types/SuggestedActionCreateNewTeam' +import {DataLoaderInstance} from '../dataloader/RootDataLoader' import generateUID from '../generateUID' import {DataLoaderWorker} from '../graphql/graphql' import {TeamSource} from '../graphql/public/types/Team' -import getNewTeamLeadUserId from '../safeQueries/getNewTeamLeadUserId' +import getKysely from '../postgres/getKysely' import {Logger} from '../utils/Logger' import setUserTierForUserIds from '../utils/setUserTierForUserIds' -import addTeamIdToTMS from './addTeamIdToTMS' import insertNewTeamMember from './insertNewTeamMember' -const handleFirstAcceptedInvitation = async (team: TeamSource): Promise => { +const handleFirstAcceptedInvitation = async ( + team: TeamSource, + dataLoader: DataLoaderInstance +): Promise => { const r = await getRethink() const now = new Date() const {id: teamId, isOnboardTeam} = team if (!isOnboardTeam) return null - const newTeamLeadUserId = await getNewTeamLeadUserId(teamId) - if (newTeamLeadUserId) { - await r - .table('SuggestedAction') - .insert([ - { - id: generateUID(), - createdAt: now, - priority: 3, - removedAt: null, - teamId, - type: 'tryRetroMeeting', - userId: newTeamLeadUserId - }, - new SuggestedActionCreateNewTeam({userId: newTeamLeadUserId}), - { - id: generateUID(), - createdAt: now, - priority: 5, - removedAt: null, - teamId, - type: 'tryActionMeeting', - userId: newTeamLeadUserId - } - ]) - .run() - } - return newTeamLeadUserId + const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) + const teamLead = teamMembers.find((tm) => tm.isLead)! + const {userId} = teamLead + const isNewTeamLead = await r + .table('SuggestedAction') + .getAll(userId, {index: 'userId'}) + .filter({type: 'tryRetroMeeting'}) + .count() + .eq(0) + .run() + if (!isNewTeamLead) return null + await r + .table('SuggestedAction') + .insert([ + { + id: generateUID(), + createdAt: now, + priority: 3, + removedAt: null, + teamId, + type: 'tryRetroMeeting', + userId + }, + new SuggestedActionCreateNewTeam({userId}), + { + id: generateUID(), + createdAt: now, + priority: 5, + removedAt: null, + teamId, + type: 'tryActionMeeting', + userId + } + ]) + .run() + return userId } const acceptTeamInvitation = async ( @@ -52,16 +65,16 @@ const acceptTeamInvitation = async ( dataLoader: DataLoaderWorker ) => { const r = await getRethink() + const pg = getKysely() const now = new Date() const {id: teamId, orgId} = team const [user, organizationUser] = await Promise.all([ dataLoader.get('users').loadNonNull(userId), dataLoader.get('organizationUsersByUserIdOrgId').load({userId, orgId}) ]) - const {email} = user - const teamLeadUserIdWithNewActions = await handleFirstAcceptedInvitation(team) - const [, invitationNotificationIds] = await Promise.all([ - insertNewTeamMember(user, teamId), + const {email, picture, preferredName} = user + const teamLeadUserIdWithNewActions = await handleFirstAcceptedInvitation(team, dataLoader) + const [invitationNotificationIds] = await Promise.all([ r .table('Notification') .getAll(userId, {index: 'userId'}) @@ -76,8 +89,26 @@ const acceptTeamInvitation = async ( )('changes')('new_val')('id') .default([]) .run(), - // add the team to the user doc - addTeamIdToTMS(userId, teamId), + pg + .with('UserUpdate', (qc) => + qc + .updateTable('User') + .set({tms: sql`arr_append_uniq("tms", ${teamId})`}) + .where('id', '=', userId) + ) + .insertInto('TeamMember') + .values({ + id: TeamMemberId.join(teamId, userId), + teamId, + userId, + picture, + preferredName, + email, + openDrawer: 'manageTeam' + }) + .onConflict((oc) => oc.column('id').doUpdateSet({isNotRemoved: true})) + .execute(), + insertNewTeamMember(user, teamId, dataLoader), r .table('TeamInvitation') .getAll(teamId, {index: 'teamId'}) @@ -89,7 +120,7 @@ const acceptTeamInvitation = async ( }) .run() ]) - + dataLoader.clearAll(['teamMembers', 'users']) if (!organizationUser) { // clear the cache, adjustUserCount will mutate these dataLoader.get('organizationUsersByUserIdOrgId').clear({userId, orgId}) diff --git a/packages/server/safeMutations/addTeamIdToTMS.ts b/packages/server/safeMutations/addTeamIdToTMS.ts deleted file mode 100644 index faa223723d3..00000000000 --- a/packages/server/safeMutations/addTeamIdToTMS.ts +++ /dev/null @@ -1,13 +0,0 @@ -import {sql} from 'kysely' -import getKysely from '../postgres/getKysely' - -const addTeamIdToTMS = async (userId: string, teamId: string) => { - const pg = getKysely() - return pg - .updateTable('User') - .set({tms: sql`arr_append_uniq("tms", ${teamId})`}) - .where('id', '=', userId) - .execute() -} - -export default addTeamIdToTMS diff --git a/packages/server/safeMutations/insertNewTeamMember.ts b/packages/server/safeMutations/insertNewTeamMember.ts index b2facb88023..6a63ba6f0e1 100644 --- a/packages/server/safeMutations/insertNewTeamMember.ts +++ b/packages/server/safeMutations/insertNewTeamMember.ts @@ -1,22 +1,18 @@ import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import getRethink from '../database/rethinkDriver' import TeamMember from '../database/types/TeamMember' +import {DataLoaderInstance} from '../dataloader/RootDataLoader' import IUser from '../postgres/types/IUser' -const insertNewTeamMember = async (user: IUser, teamId: string) => { +const insertNewTeamMember = async (user: IUser, teamId: string, dataLoader: DataLoaderInstance) => { const r = await getRethink() const now = new Date() const {id: userId} = user const teamMemberId = toTeamMemberId(teamId, userId) - const [teamMemberCount, existingTeamMember] = await Promise.all([ - r - .table('TeamMember') - .getAll(teamId, {index: 'teamId'}) - .filter({isNotRemoved: true}) - .count() - .run(), - r.table('TeamMember').get(teamMemberId).run() - ]) + const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) + const existingTeamMember = teamMembers.find((tm) => tm.userId === userId) + const teamMemberCount = teamMembers.length + if (!user) { throw new Error('User does not exist') } @@ -24,6 +20,7 @@ const insertNewTeamMember = async (user: IUser, teamId: string) => { existingTeamMember.isNotRemoved = true existingTeamMember.updatedAt = now await r.table('TeamMember').get(teamMemberId).replace(existingTeamMember).run() + dataLoader.clearAll('teamMembers') return existingTeamMember } @@ -38,6 +35,7 @@ const insertNewTeamMember = async (user: IUser, teamId: string) => { isLead, openDrawer: 'manageTeam' }) + await r.table('TeamMember').insert(teamMember).run() return teamMember } diff --git a/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts b/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts index 8dd9c8221e6..95ca32ed441 100644 --- a/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts +++ b/packages/server/safeMutations/safeArchiveEmptyStarterOrganization.ts @@ -1,7 +1,6 @@ import {sql} from 'kysely' import {DataLoaderInstance} from '../dataloader/RootDataLoader' import getKysely from '../postgres/getKysely' -import getTeamsByOrgIds from '../postgres/queries/getTeamsByOrgIds' // Only does something if the organization is empty & not paid // safeArchiveTeam & downgradeToStarter should be called before calling this @@ -11,7 +10,7 @@ const safeArchiveEmptyStarterOrganization = async ( dataLoader: DataLoaderInstance ) => { const pg = getKysely() - const orgTeams = await getTeamsByOrgIds([orgId]) + const orgTeams = await dataLoader.get('teamsByOrgIds').load(orgId) const teamCountRemainingOnOldOrg = orgTeams.length if (teamCountRemainingOnOldOrg > 0) return @@ -23,6 +22,7 @@ const safeArchiveEmptyStarterOrganization = async ( .where('orgId', '=', orgId) .where('removedAt', 'is', null) .execute() + dataLoader.clearAll('organizationUsers') } export default safeArchiveEmptyStarterOrganization diff --git a/packages/server/safeMutations/safeArchiveTeam.ts b/packages/server/safeMutations/safeArchiveTeam.ts index a0c2522e4ff..2e4f9878a0e 100644 --- a/packages/server/safeMutations/safeArchiveTeam.ts +++ b/packages/server/safeMutations/safeArchiveTeam.ts @@ -2,19 +2,13 @@ import getRethink from '../database/rethinkDriver' import {RDatum} from '../database/stricterR' import {DataLoaderWorker} from '../graphql/graphql' import getKysely from '../postgres/getKysely' -import removeUserTms from '../postgres/queries/removeUserTms' const safeArchiveTeam = async (teamId: string, dataLoader: DataLoaderWorker) => { const r = await getRethink() const pg = getKysely() const now = new Date() - const userIds = await r - .table('TeamMember') - .getAll(teamId, {index: 'teamId'}) - .filter({isNotRemoved: true})('userId') - .run() - await removeUserTms(teamId, userIds) - const users = await Promise.all(userIds.map((userId) => dataLoader.get('users').load(userId))) + const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) + const userIds = teamMembers.map((tm) => tm.userId) const [rethinkResult, pgResult] = await Promise.all([ r({ invitations: r @@ -40,9 +34,15 @@ const safeArchiveTeam = async (teamId: string, dataLoader: DataLoaderWorker) => .set({isArchived: true}) .where('id', '=', teamId) .returningAll() - .executeTakeFirst() + .executeTakeFirst(), + pg + .updateTable('User') + .set(({fn, ref, val}) => ({tms: fn('ARRAY_REMOVE', [ref('tms'), val(teamId)])})) + .where('id', 'in', userIds) + .execute() ]) - + dataLoader.clearAll(['teamMembers', 'users', 'teams']) + const users = await Promise.all(userIds.map((userId) => dataLoader.get('users').load(userId))) return {...rethinkResult, team: pgResult ?? null, users} } diff --git a/packages/server/safeQueries/getNewTeamLeadUserId.ts b/packages/server/safeQueries/getNewTeamLeadUserId.ts deleted file mode 100644 index 797a3f3e623..00000000000 --- a/packages/server/safeQueries/getNewTeamLeadUserId.ts +++ /dev/null @@ -1,28 +0,0 @@ -import getRethink from '../database/rethinkDriver' -import {RDatum} from '../database/stricterR' - -// returns the userId of the team lead if they have never received the initial starting suggested actions -const getNewTeamLeadUserId = async (teamId: string) => { - const r = await getRethink() - return r - .table('TeamMember') - .getAll(teamId, {index: 'teamId'}) - .filter({isLead: true}) - .nth(0)('userId') - .default('') - .do((userId: RDatum) => { - return r.branch( - r - .table('SuggestedAction') - .getAll(userId, {index: 'userId'}) - .filter({type: 'tryRetroMeeting'}) - .count() - .eq(0), - userId, - null - ) - }) - .run() -} - -export default getNewTeamLeadUserId diff --git a/packages/server/utils/authorization.ts b/packages/server/utils/authorization.ts index 8617c3bab16..d082ce15e78 100644 --- a/packages/server/utils/authorization.ts +++ b/packages/server/utils/authorization.ts @@ -22,17 +22,6 @@ export const isTeamMember = (authToken: AuthToken, teamId: string) => { return Array.isArray(tms) && tms.includes(teamId) } -// export const isPastOrPresentTeamMember = async (viewerId: string, teamId: string) => { -// const r = await getRethink() -// return r -// .table('TeamMember') -// .getAll(teamId, {index: 'teamId'}) -// .filter({userId: viewerId}) -// .count() -// .ge(1) -// .run() -// } - export const isTeamLead = async (userId: string, teamId: string, dataLoader: DataLoaderWorker) => { const r = await getRethink() const teamMemberId = toTeamMemberId(teamId, userId) From 7100a231fc6bbc750398daec03d882e5afc7a57c Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Fri, 19 Jul 2024 08:41:47 -0700 Subject: [PATCH 343/529] chore(rethinkdb): TaskHistory: One-shot (#10004) Signed-off-by: Matt Krick --- packages/server/database/rethinkDriver.ts | 4 --- .../graphql/mutations/changeTaskTeam.ts | 20 +---------- .../server/graphql/mutations/createTask.ts | 15 ++------ .../server/graphql/mutations/deleteTask.ts | 8 +---- .../graphql/mutations/helpers/addSeedTasks.ts | 19 +--------- .../server/graphql/mutations/updateTask.ts | 36 +------------------ 6 files changed, 6 insertions(+), 96 deletions(-) diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index fdada8558b9..80a8e5817e6 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -156,10 +156,6 @@ export type RethinkSchema = { | 'userId' | 'integrationHash' } - TaskHistory: { - type: any - index: 'taskIdUpdatedAt' | 'teamMemberId' - } TeamInvitation: { type: TeamInvitation index: 'email' | 'teamId' | 'token' diff --git a/packages/server/graphql/mutations/changeTaskTeam.ts b/packages/server/graphql/mutations/changeTaskTeam.ts index bd861bdb164..e229536106c 100644 --- a/packages/server/graphql/mutations/changeTaskTeam.ts +++ b/packages/server/graphql/mutations/changeTaskTeam.ts @@ -2,9 +2,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import removeEntityKeepText from 'parabol-client/utils/draftjs/removeEntityKeepText' import getRethink from '../../database/rethinkDriver' -import {RValue} from '../../database/stricterR' import Task from '../../database/types/Task' -import generateUID from '../../generateUID' import {AtlassianAuth} from '../../postgres/queries/getAtlassianAuthByUserIdTeamId' import {GitHubAuth} from '../../postgres/queries/getGitHubAuthByUserIdTeamId' import upsertAtlassianAuths from '../../postgres/queries/upsertAtlassianAuths' @@ -155,23 +153,7 @@ export default { .filter({teamId}) .delete({returnChanges: true})('changes')(0)('old_val') .default(null), - newTask: r.table('Task').get(taskId).update(updates), - taskHistory: r - .table('TaskHistory') - .between([taskId, r.minval], [taskId, r.maxval], { - index: 'taskIdUpdatedAt' - }) - .orderBy({index: 'taskIdUpdatedAt'}) - .nth(-1) - .default(null) - .do((taskHistoryRecord: RValue) => { - // prepopulated cards will not have a history - return r.branch( - taskHistoryRecord.ne(null), - r.table('TaskHistory').insert(taskHistoryRecord.merge(updates, {id: generateUID()})), - null - ) - }) + newTask: r.table('Task').get(taskId).update(updates) }).run() if (deletedConflictingIntegrationTask) { diff --git a/packages/server/graphql/mutations/createTask.ts b/packages/server/graphql/mutations/createTask.ts index 1a4e2167f78..3aadc09b20a 100644 --- a/packages/server/graphql/mutations/createTask.ts +++ b/packages/server/graphql/mutations/createTask.ts @@ -7,7 +7,6 @@ import MeetingMemberId from '../../../client/shared/gqlIds/MeetingMemberId' import getRethink from '../../database/rethinkDriver' import NotificationTaskInvolves from '../../database/types/NotificationTaskInvolves' import Task, {TaskServiceEnum} from '../../database/types/Task' -import generateUID from '../../generateUID' import updatePrevUsedRepoIntegrationsCache from '../../integrations/updatePrevUsedRepoIntegrationsCache' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' @@ -218,20 +217,10 @@ export default { threadParentId, userId }) - const {id: taskId, updatedAt} = task - const history = { - id: generateUID(), - content, - taskId, - status, - teamId, - userId, - updatedAt - } + const {id: taskId} = task const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) await r({ - task: r.table('Task').insert(task), - history: r.table('TaskHistory').insert(history) + task: r.table('Task').insert(task) }).run() handleAddTaskNotifications(teamMembers, task, viewerId, teamId, { diff --git a/packages/server/graphql/mutations/deleteTask.ts b/packages/server/graphql/mutations/deleteTask.ts index bd47dca2a49..04288d2d8d5 100644 --- a/packages/server/graphql/mutations/deleteTask.ts +++ b/packages/server/graphql/mutations/deleteTask.ts @@ -40,13 +40,7 @@ export default { const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) const subscribedUserIds = teamMembers.map(({userId}) => userId) await r({ - task: r.table('Task').get(taskId).delete(), - taskHistory: r - .table('TaskHistory') - .between([taskId, r.minval], [taskId, r.maxval], { - index: 'taskIdUpdatedAt' - }) - .delete() + task: r.table('Task').get(taskId).delete() }).run() const {tags, userId: taskUserId} = task diff --git a/packages/server/graphql/mutations/helpers/addSeedTasks.ts b/packages/server/graphql/mutations/helpers/addSeedTasks.ts index 16cae8ce2e7..0bfc175e13c 100644 --- a/packages/server/graphql/mutations/helpers/addSeedTasks.ts +++ b/packages/server/graphql/mutations/helpers/addSeedTasks.ts @@ -3,7 +3,6 @@ import getTagsFromEntityMap from 'parabol-client/utils/draftjs/getTagsFromEntity import makeAppURL from 'parabol-client/utils/makeAppURL' import appOrigin from '../../../appOrigin' import getRethink from '../../../database/rethinkDriver' -import {RValue} from '../../../database/stricterR' import {TaskStatusEnum} from '../../../database/types/Task' import generateUID from '../../../generateUID' import {convertHtmlToTaskContent} from '../../../utils/draftjs/convertHtmlToTaskContent' @@ -52,21 +51,5 @@ export default async (userId: string, teamId: string) => { updatedAt: now })) - return r - .table('Task') - .insert(seedTasks, {returnChanges: true}) - .do((result: RValue) => { - return r.table('TaskHistory').insert( - result('changes').map((change: RValue) => ({ - id: generateUID(), - content: change('new_val')('content'), - taskId: change('new_val')('id'), - status: change('new_val')('status'), - teamId: change('new_val')('teamId'), - userId: change('new_val')('userId'), - updatedAt: change('new_val')('updatedAt') - })) - ) - }) - .run() + return r.table('Task').insert(seedTasks).run() } diff --git a/packages/server/graphql/mutations/updateTask.ts b/packages/server/graphql/mutations/updateTask.ts index 45b647ceefb..63c868fc6dd 100644 --- a/packages/server/graphql/mutations/updateTask.ts +++ b/packages/server/graphql/mutations/updateTask.ts @@ -1,12 +1,9 @@ import {GraphQLNonNull, GraphQLObjectType} from 'graphql' -import ms from 'ms' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import extractTextFromDraftString from 'parabol-client/utils/draftjs/extractTextFromDraftString' import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' import getRethink from '../../database/rethinkDriver' -import {RValue} from '../../database/stricterR' import Task, {AreaEnum as TAreaEnum, TaskStatusEnum} from '../../database/types/Task' -import generateUID from '../../generateUID' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -17,8 +14,6 @@ import {validateTaskUserIsTeamMember} from './createTask' import getUsersToIgnore from './helpers/getUsersToIgnore' import publishChangeNotifications from './helpers/publishChangeNotifications' -const DEBOUNCE_TIME = ms('5m') - type UpdateTaskInput = { id: string content?: string | null @@ -91,42 +86,13 @@ export default { updatedAt: isSortOrderUpdate ? task.updatedAt : now }) - let taskHistory - if (!isSortOrderUpdate) { - // if this is anything but a sort update, log it to history - const mergeDoc = { - content: nextTask.content, - taskId, - status, - userId: nextTask.userId, - teamId: nextTask.teamId, - updatedAt: now, - tags: nextTask.tags - } - taskHistory = r - .table('TaskHistory') - .between([taskId, r.minval], [taskId, r.maxval], { - index: 'taskIdUpdatedAt' - }) - .orderBy({index: 'taskIdUpdatedAt'}) - .nth(-1) - .default({updatedAt: r.epochTime(0)}) - .do((lastDoc: RValue) => { - return r.branch( - lastDoc('updatedAt').gt(r.epochTime((now.getTime() - DEBOUNCE_TIME) / 1000)), - r.table('TaskHistory').get(lastDoc('id')).update(mergeDoc), - r.table('TaskHistory').insert(lastDoc.merge(mergeDoc, {id: generateUID()})) - ) - }) - } const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) const {newTask} = await r({ newTask: r .table('Task') .get(taskId) .update(nextTask, {returnChanges: true})('changes')(0)('new_val') - .default(null) as unknown as Task, - history: taskHistory + .default(null) as unknown as Task }).run() // TODO: get users in the same location const usersToIgnore = await getUsersToIgnore(viewerId, teamId) From 28553e49c64206fde3e88d47ac48175639b8b6cb Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Fri, 19 Jul 2024 08:43:23 -0700 Subject: [PATCH 344/529] chore(rethinkdb): QueryMap: One-shot (#10005) Signed-off-by: Matt Krick --- packages/server/__tests__/common.ts | 8 +++-- packages/server/database/rethinkDriver.ts | 4 --- packages/server/graphql/CompiledQueryCache.ts | 11 ++++-- packages/server/graphql/DocumentCache.ts | 11 ++++-- .../1721348544381_QueryMapOneShot.ts | 36 +++++++++++++++++++ scripts/toolboxSrc/preDeploy.ts | 13 +++---- scripts/toolboxSrc/primeIntegrations.ts | 2 -- 7 files changed, 63 insertions(+), 22 deletions(-) create mode 100644 packages/server/postgres/migrations/1721348544381_QueryMapOneShot.ts diff --git a/packages/server/__tests__/common.ts b/packages/server/__tests__/common.ts index 71e00b8a71a..587c206b289 100644 --- a/packages/server/__tests__/common.ts +++ b/packages/server/__tests__/common.ts @@ -49,14 +49,18 @@ const persistFunction = (text: string) => { } const persistQuery = async (query: string) => { - const r = await getRethink() + const pg = getKysely() const id = persistFunction(query.trim()) const record = { id, query, createdAt: new Date() } - await r.table('QueryMap').insert(record, {conflict: 'replace'}).run() + await pg + .insertInto('QueryMap') + .values(record) + .onConflict((oc) => oc.doNothing()) + .execute() return id } diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index 80a8e5817e6..a8aa3ddd709 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -119,10 +119,6 @@ export type RethinkSchema = { type: PushInvitation index: 'userId' } - QueryMap: { - type: any - index: '' - } MeetingTemplate: { type: MeetingTemplate index: 'teamId' | 'orgId' diff --git a/packages/server/graphql/CompiledQueryCache.ts b/packages/server/graphql/CompiledQueryCache.ts index b1ed411c16f..e041dda189c 100644 --- a/packages/server/graphql/CompiledQueryCache.ts +++ b/packages/server/graphql/CompiledQueryCache.ts @@ -1,7 +1,7 @@ import tracer from 'dd-trace' import {GraphQLSchema, parse} from 'graphql' import {CompiledQuery} from 'graphql-jit' -import getRethink from '../database/rethinkDriver' +import getKysely from '../postgres/getKysely' import {MutationResolvers, QueryResolvers, Resolver} from './public/resolverTypes' import {tracedCompileQuery} from './traceGraphQL' @@ -42,8 +42,13 @@ export default class CompiledQueryCache { async fromID(docId: string, schema: GraphQLSchema) { const compiledQuery = this.store[docId] if (compiledQuery) return compiledQuery - const r = await getRethink() - let queryString = await r.table('QueryMap').get(docId)('query').default(null).run() + const pg = getKysely() + const queryStringRes = await pg + .selectFrom('QueryMap') + .select('query') + .where('id', '=', docId) + .executeTakeFirst() + let queryString = queryStringRes?.query if (!queryString && !__PRODUCTION__) { // try/catch block required for building the toolbox try { diff --git a/packages/server/graphql/DocumentCache.ts b/packages/server/graphql/DocumentCache.ts index d0a6d9fab6d..dab84015bbf 100644 --- a/packages/server/graphql/DocumentCache.ts +++ b/packages/server/graphql/DocumentCache.ts @@ -5,7 +5,7 @@ */ import {DocumentNode, parse} from 'graphql' -import getRethink from '../database/rethinkDriver' +import getKysely from '../postgres/getKysely' export default class DocumentCache { store = {} as {[docId: string]: DocumentNode} @@ -16,8 +16,13 @@ export default class DocumentCache { // looks up query string for a persisted query, parses into an AST, caches and returns it let document = this.store[docId] if (!document) { - const r = await getRethink() - let queryString = await r.table('QueryMap').get(docId)('query').default(null).run() + const pg = getKysely() + const queryStringRes = await pg + .selectFrom('QueryMap') + .select('query') + .where('id', '=', docId) + .executeTakeFirst() + let queryString = queryStringRes?.query if (!queryString && !__PRODUCTION__) { // In development, use the frequently changing queryMap to look up persisted queries by hash const queryMap = require('../../../queryMap.json') diff --git a/packages/server/postgres/migrations/1721348544381_QueryMapOneShot.ts b/packages/server/postgres/migrations/1721348544381_QueryMapOneShot.ts new file mode 100644 index 00000000000..865cf28604f --- /dev/null +++ b/packages/server/postgres/migrations/1721348544381_QueryMapOneShot.ts @@ -0,0 +1,36 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {Client} from 'pg' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' +import getPgConfig from '../getPgConfig' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql` + CREATE TABLE IF NOT EXISTS "QueryMap" ( + "id" VARCHAR(24) PRIMARY KEY, + "query" TEXT NOT NULL, + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW() + ) +`.execute(pg) + const queries = await r + .table('QueryMap') + .filter((row) => row('createdAt').default(null).ne(null)) + .run() + await pg.insertInto('QueryMap').values(queries).execute() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE "QueryMap"; + ` /* Do undo magic */) + await client.end() +} diff --git a/scripts/toolboxSrc/preDeploy.ts b/scripts/toolboxSrc/preDeploy.ts index acf6d96c89c..68f34ee0430 100644 --- a/scripts/toolboxSrc/preDeploy.ts +++ b/scripts/toolboxSrc/preDeploy.ts @@ -1,7 +1,7 @@ import dotenv from 'dotenv' import dotenvExpand from 'dotenv-expand' +import getKysely from 'parabol-server/postgres/getKysely' import path from 'path' -import getRethink from '../../packages/server/database/rethinkDriver' import queryMap from '../../queryMap.json' import getProjectRoot from '../webpack/utils/getProjectRoot' import {applyEnvVarsToClientAssets} from './applyEnvVarsToClientAssets' @@ -21,13 +21,9 @@ const storePersistedQueries = async () => { createdAt: now })) - const r = await getRethink() - const res = await r.table('QueryMap').insert(records, {conflict: 'replace'}).run() - // without this sleep RethinkDB closes the connection before the query completes. It doesn't make sense! - await new Promise((resolve) => setTimeout(resolve, 50)) - await r.getPoolMaster()?.drain() - - console.log(`🔗 QueryMap Persistence Complete: ${res.inserted} records added`) + const pg = getKysely() + const res = await pg.insertInto('QueryMap').values(records).onConflict((oc) => oc.doNothing()).returning('id').execute() + console.log(`🔗 QueryMap Persistence Complete: ${res.length} records added`) } const preDeploy = async () => { @@ -41,6 +37,7 @@ const preDeploy = async () => { // The we can prime the DB & CDN await Promise.all([storePersistedQueries(), primeIntegrations(), pushToCDN()]) + await getKysely().destroy() console.log(`🚀 Predeploy Complete`) process.exit() } diff --git a/scripts/toolboxSrc/primeIntegrations.ts b/scripts/toolboxSrc/primeIntegrations.ts index e441c1a59ae..12b272f8384 100644 --- a/scripts/toolboxSrc/primeIntegrations.ts +++ b/scripts/toolboxSrc/primeIntegrations.ts @@ -44,9 +44,7 @@ const upsertGlobalIntegrationProvidersFromEnv = async () => { const primeIntegrations = async () => { console.log('⛓️ Prime Integrationgs Started') - const pg = getPg() await upsertGlobalIntegrationProvidersFromEnv() - await pg.end() console.log('⛓️ Prime Integrations Complete') } From 892cbd654d030a6884a4c50cecf6fb5244e153c8 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Fri, 19 Jul 2024 08:52:15 -0700 Subject: [PATCH 345/529] chore: fix broken build mig file (#10006) Signed-off-by: Matt Krick --- .../server/postgres/migrations/1721348544381_QueryMapOneShot.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/postgres/migrations/1721348544381_QueryMapOneShot.ts b/packages/server/postgres/migrations/1721348544381_QueryMapOneShot.ts index 865cf28604f..d34461815b4 100644 --- a/packages/server/postgres/migrations/1721348544381_QueryMapOneShot.ts +++ b/packages/server/postgres/migrations/1721348544381_QueryMapOneShot.ts @@ -23,6 +23,7 @@ export async function up() { .table('QueryMap') .filter((row) => row('createdAt').default(null).ne(null)) .run() + if (queries.length === 0) return await pg.insertInto('QueryMap').values(queries).execute() } From efad77ae93088f84a009b2a50a1ba0ac88223ff8 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Fri, 19 Jul 2024 09:03:31 -0700 Subject: [PATCH 346/529] chore(release): release v7.38.11 (#9998) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 16 ++++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 28 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4d7295d26c4..444fcf166a0 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.38.10" + ".": "7.38.11" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b617a3f406c..523db907f02 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,22 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.38.11](https://github.com/ParabolInc/parabol/compare/v7.38.10...v7.38.11) (2024-07-19) + + +### Fixed + +* Filipino checkin greeting ([#9997](https://github.com/ParabolInc/parabol/issues/9997)) ([5c45379](https://github.com/ParabolInc/parabol/commit/5c453794fa3ebb6f1948a2f6c8c7f70be2a0d009)) + + +### Changed + +* fix broken build mig file ([#10006](https://github.com/ParabolInc/parabol/issues/10006)) ([892cbd6](https://github.com/ParabolInc/parabol/commit/892cbd654d030a6884a4c50cecf6fb5244e153c8)) +* move some integrations to SDL pattern ([#10000](https://github.com/ParabolInc/parabol/issues/10000)) ([6d01097](https://github.com/ParabolInc/parabol/commit/6d01097ef68bbbe33eea1faf1367d7ad6120f1db)) +* **rethinkdb:** QueryMap: One-shot ([#10005](https://github.com/ParabolInc/parabol/issues/10005)) ([28553e4](https://github.com/ParabolInc/parabol/commit/28553e49c64206fde3e88d47ac48175639b8b6cb)) +* **rethinkdb:** TaskHistory: One-shot ([#10004](https://github.com/ParabolInc/parabol/issues/10004)) ([7100a23](https://github.com/ParabolInc/parabol/commit/7100a231fc6bbc750398daec03d882e5afc7a57c)) +* **rethinkdb:** TeamMember: Phase 1 ([#9979](https://github.com/ParabolInc/parabol/issues/9979)) ([b0c2cf2](https://github.com/ParabolInc/parabol/commit/b0c2cf2aa40aa08c8de24bfdcb2a9a150178271e)) + ## [7.38.10](https://github.com/ParabolInc/parabol/compare/v7.38.9...v7.38.10) (2024-07-17) diff --git a/package.json b/package.json index d9eb8c2c99d..dcd82b5d9e7 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.10", + "version": "7.38.11", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index f6ecbc6abc2..729ca876e99 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.38.10", + "version": "7.38.11", "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.10" + "parabol-server": "7.38.11" } } diff --git a/packages/client/package.json b/packages/client/package.json index 3b8b0fa106c..b968a7a3998 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.10", + "version": "7.38.11", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index a69d556fe61..b80958ac24f 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.38.10", + "version": "7.38.11", "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 051d70fcf8e..0d125435860 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.38.10", + "version": "7.38.11", "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.10", - "parabol-server": "7.38.10", + "parabol-client": "7.38.11", + "parabol-server": "7.38.11", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index ba52f6301c5..bb4a3bf0cc5 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.10", + "version": "7.38.11", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 8207d84ee74..bef4d94ca22 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.10", + "version": "7.38.11", "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.10", + "parabol-client": "7.38.11", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From e6a3c7d12b0f4e7a3b7caa4fe62ab81764c9f577 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Mon, 22 Jul 2024 10:27:05 +0200 Subject: [PATCH 347/529] chore: Reduce Azure DevOps scope (#9999) --- packages/client/utils/AzureDevOpsClientManager.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/client/utils/AzureDevOpsClientManager.ts b/packages/client/utils/AzureDevOpsClientManager.ts index 09dc2e510bf..84f3956a6e3 100644 --- a/packages/client/utils/AzureDevOpsClientManager.ts +++ b/packages/client/utils/AzureDevOpsClientManager.ts @@ -41,7 +41,8 @@ class AzureDevOpsClientManager { const verifier = AzureDevOpsClientManager.generateVerifier() const code = await AzureDevOpsClientManager.generateCodeChallenge(verifier) const redirect = makeHref('/auth/ado2') - const scope = '499b84ac-1321-427f-aa17-267ca6975798/.default offline_access' + const scope = + '499b84ac-1321-427f-aa17-267ca6975798/vso.project 499b84ac-1321-427f-aa17-267ca6975798/vso.work_write offline_access' const url = `https://login.microsoftonline.com/${tenantId}/oauth2/v2.0/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirect}&response_mode=query&scope=${scope}&state=${providerState}&code_challenge=${code}&code_challenge_method=S256` // Open synchronously because of Safari From 23c8048eb88916d5f2021dc0e830fd3b953452e6 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Mon, 22 Jul 2024 11:44:47 +0200 Subject: [PATCH 348/529] chore: Fix test (#10013) --- packages/server/__tests__/disableAnonymity.test.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/server/__tests__/disableAnonymity.test.ts b/packages/server/__tests__/disableAnonymity.test.ts index 1887c3357a0..2fe17015cbc 100644 --- a/packages/server/__tests__/disableAnonymity.test.ts +++ b/packages/server/__tests__/disableAnonymity.test.ts @@ -130,6 +130,10 @@ const addReflection = async (meetingId: string, promptId: string, authToken: str return reflection } +afterAll(async () => { + await closePg() +}) + test('By default all reflections are anonymous', async () => { const {userId, authToken} = await signUp() const teamId = (await getUserTeams(userId))[0].id @@ -182,7 +186,6 @@ test('Super user can always read creatorId of a reflection', async () => { const pg = getPg() await pg.query(`UPDATE "User" SET rol='su' WHERE id='${userId}'`) - await closePg() const login = await sendPublic({ query: ` From 8a6659a2373a101a970b2fb2a1004c0e21c85f65 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Mon, 22 Jul 2024 12:54:32 +0200 Subject: [PATCH 349/529] feat: GCal event series for Standup (#9959) --- .../graphql/public/mutations/startTeamPrompt.ts | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/packages/server/graphql/public/mutations/startTeamPrompt.ts b/packages/server/graphql/public/mutations/startTeamPrompt.ts index 393e802e1b7..322ee844986 100644 --- a/packages/server/graphql/public/mutations/startTeamPrompt.ts +++ b/packages/server/graphql/public/mutations/startTeamPrompt.ts @@ -1,5 +1,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import updateTeamByTeamId from '../../../postgres/queries/updateTeamByTeamId' import RedisLockQueue from '../../../utils/RedisLockQueue' import {analytics} from '../../../utils/analytics/analytics' @@ -61,22 +62,31 @@ const startTeamPrompt: MutationResolvers['startTeamPrompt'] = async ( ]) const {id: meetingId} = meeting - if (rrule) { - const meetingSeries = await startNewMeetingSeries(meeting, rrule, name) + const meetingSeries = rrule && (await startNewMeetingSeries(meeting, rrule, name)) + if (meetingSeries) { // meeting was modified if a new meeting series was created dataLoader.get('newMeetings').clear(meetingId) analytics.recurrenceStarted(viewer, meetingSeries) } IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting) - const {error} = await createGcalEvent({ + const {error, gcalSeriesId} = await createGcalEvent({ name: eventName, gcalInput, meetingId, teamId, viewerId, + rrule, dataLoader }) + if (meetingSeries && gcalSeriesId) { + const pg = getKysely() + await pg + .updateTable('MeetingSeries') + .set({gcalSeriesId}) + .where('id', '=', meetingSeries.id) + .execute() + } const data = {teamId, meetingId: meetingId, hasGcalError: !!error?.message} publish(SubscriptionChannel.TEAM, teamId, 'StartTeamPromptSuccess', data, subOptions) return data From b816727460620637f49027a1b32e47d3a2676152 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Mon, 22 Jul 2024 08:51:35 -0700 Subject: [PATCH 350/529] chore: upgrade from gpt-3.5-turbo to gpt-4o-mini (#10002) --- packages/server/utils/OpenAIServerManager.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/utils/OpenAIServerManager.ts b/packages/server/utils/OpenAIServerManager.ts index b39615807af..25ae8526109 100644 --- a/packages/server/utils/OpenAIServerManager.ts +++ b/packages/server/utils/OpenAIServerManager.ts @@ -51,7 +51,7 @@ class OpenAIServerManager { """` try { const response = await this.openAIApi.chat.completions.create({ - model: 'gpt-3.5-turbo', + model: 'gpt-4o-mini', messages: [ { role: 'user', @@ -86,7 +86,7 @@ class OpenAIServerManager { """` try { const response = await this.openAIApi.chat.completions.create({ - model: 'gpt-3.5-turbo', + model: 'gpt-4o-mini', messages: [ { role: 'user', @@ -136,7 +136,7 @@ class OpenAIServerManager { .join('\n')}` try { const response = await this.openAIApi.chat.completions.create({ - model: 'gpt-3.5-turbo', + model: 'gpt-4o-mini', messages: [ { role: 'user', @@ -208,7 +208,7 @@ class OpenAIServerManager { try { const response = await this.openAIApi.chat.completions.create({ - model: 'gpt-3.5-turbo-0125', + model: 'gpt-4o-mini', messages: [ { role: 'user', From 12799719081e53fdf0976005dabf851fc2908c42 Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 23 Jul 2024 17:56:29 +0200 Subject: [PATCH 351/529] chore: Move more integration GraphQL types to SDL (#10015) --- codegen.json | 4 +- .../JiraScopingSearchFilterMenu.tsx | 4 +- .../mutations/addTeamMemberIntegrationAuth.ts | 189 ---------- .../removeTeamMemberIntegrationAuth.ts | 50 --- .../mutations/addTeamMemberIntegrationAuth.ts | 149 ++++++++ .../removeTeamMemberIntegrationAuth.ts | 34 ++ .../public/typeDefs/JiraRemoteProject.graphql | 30 ++ .../typeDefs/JiraServerRemoteProject.graphql | 21 ++ .../public/typeDefs/RepoIntegration.graphql | 7 + .../TeamMemberIntegrationAuth.graphql | 44 +++ .../TeamMemberIntegrationAuthOAuth1.graphql | 44 +++ .../TeamMemberIntegrationAuthOAuth2.graphql | 54 +++ .../TeamMemberIntegrationAuthWebhook.graphql | 44 +++ .../graphql/public/typeDefs/_legacy.graphql | 349 ------------------ .../addTeamMemberIntegrationAuth.graphql | 51 +++ .../removeTeamMemberIntegrationAuth.graphql | 50 +++ .../AddTeamMemberIntegrationAuthSuccess.ts | 23 ++ .../graphql/public/types/JiraRemoteProject.ts | 33 ++ .../RemoveTeamMemberIntegrationAuthSuccess.ts | 20 + .../types/TeamMemberIntegrationAuthOAuth1.ts | 20 + .../types/TeamMemberIntegrationAuthOAuth2.ts | 14 + .../types/TeamMemberIntegrationAuthWebhook.ts | 14 + packages/server/graphql/rootMutation.ts | 4 - .../AddTeamMemberIntegrationAuthPayload.ts | 47 --- .../server/graphql/types/JiraRemoteProject.ts | 86 ----- .../RemoveTeamMemberIntegrationAuthPayload.ts | 34 -- .../types/TeamMemberIntegrationAuth.ts | 62 ---- .../types/TeamMemberIntegrationAuthOAuth1.ts | 31 -- .../types/TeamMemberIntegrationAuthOAuth2.ts | 33 -- .../types/TeamMemberIntegrationAuthWebhook.ts | 27 -- 30 files changed, 657 insertions(+), 915 deletions(-) delete mode 100644 packages/server/graphql/mutations/addTeamMemberIntegrationAuth.ts delete mode 100644 packages/server/graphql/mutations/removeTeamMemberIntegrationAuth.ts create mode 100644 packages/server/graphql/public/mutations/addTeamMemberIntegrationAuth.ts create mode 100644 packages/server/graphql/public/mutations/removeTeamMemberIntegrationAuth.ts create mode 100644 packages/server/graphql/public/typeDefs/JiraRemoteProject.graphql create mode 100644 packages/server/graphql/public/typeDefs/JiraServerRemoteProject.graphql create mode 100644 packages/server/graphql/public/typeDefs/RepoIntegration.graphql create mode 100644 packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuth.graphql create mode 100644 packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthOAuth1.graphql create mode 100644 packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthOAuth2.graphql create mode 100644 packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthWebhook.graphql create mode 100644 packages/server/graphql/public/typeDefs/addTeamMemberIntegrationAuth.graphql create mode 100644 packages/server/graphql/public/typeDefs/removeTeamMemberIntegrationAuth.graphql create mode 100644 packages/server/graphql/public/types/AddTeamMemberIntegrationAuthSuccess.ts create mode 100644 packages/server/graphql/public/types/JiraRemoteProject.ts create mode 100644 packages/server/graphql/public/types/RemoveTeamMemberIntegrationAuthSuccess.ts create mode 100644 packages/server/graphql/public/types/TeamMemberIntegrationAuthOAuth1.ts create mode 100644 packages/server/graphql/public/types/TeamMemberIntegrationAuthOAuth2.ts create mode 100644 packages/server/graphql/public/types/TeamMemberIntegrationAuthWebhook.ts delete mode 100644 packages/server/graphql/types/AddTeamMemberIntegrationAuthPayload.ts delete mode 100644 packages/server/graphql/types/JiraRemoteProject.ts delete mode 100644 packages/server/graphql/types/RemoveTeamMemberIntegrationAuthPayload.ts delete mode 100644 packages/server/graphql/types/TeamMemberIntegrationAuth.ts delete mode 100644 packages/server/graphql/types/TeamMemberIntegrationAuthOAuth1.ts delete mode 100644 packages/server/graphql/types/TeamMemberIntegrationAuthOAuth2.ts delete mode 100644 packages/server/graphql/types/TeamMemberIntegrationAuthWebhook.ts diff --git a/codegen.json b/codegen.json index 8b189ff16ad..99f5a4b9baf 100644 --- a/codegen.json +++ b/codegen.json @@ -64,6 +64,7 @@ "AddPokerTemplateSuccess": "./types/AddPokerTemplateSuccess#AddPokerTemplateSuccessSource", "AddReactjiToReactableSuccess": "./types/AddReactjiToReactableSuccess#AddReactjiToReactableSuccessSource", "AddReflectTemplateSuccess": "./types/AddReflectTemplateSuccess#AddReflectTemplateSuccessSource", + "AddTeamMemberIntegrationAuthSuccess": "./types/AddTeamMemberIntegrationAuthPayload#AddTeamMemberIntegrationAuthSuccessSource", "AddTranscriptionBotSuccess": "./types/AddTranscriptionBotSuccess#AddTranscriptionBotSuccessSource", "AddedNotification": "./types/AddedNotification#AddedNotificationSource", "AgendaItem": "../../database/types/AgendaItem#default as AgendaItemDB", @@ -92,7 +93,7 @@ "JiraServerIssue": "./types/JiraServerIssue#JiraServerIssueSource", "JiraServerRemoteProject": "../../dataloader/jiraServerLoaders#JiraServerProject", "JiraIssue": "./types/JiraIssue#JiraIssueSource", - "JiraRemoteProject": "../types/JiraRemoteProject#JiraRemoteProjectSource", + "JiraRemoteProject": "./types/JiraRemoteProject#JiraRemoteProjectSource", "MeetingSeries": "../../postgres/types/MeetingSeries#MeetingSeries", "MeetingTemplate": "../../database/types/MeetingTemplate#default", "NewMeeting": "../../postgres/types/Meeting#AnyMeeting", @@ -122,6 +123,7 @@ "RemoveApprovedOrganizationDomainsSuccess": "./types/RemoveApprovedOrganizationDomainsSuccess#RemoveApprovedOrganizationDomainsSuccessSource", "RemoveIntegrationSearchQuerySuccess": "./types/RemoveIntegrationSearchQuerySuccess#RemoveIntegrationSearchQuerySuccessSource", "RemoveTeamMemberPayload": "./types/RemoveTeamMemberPayload#RemoveTeamMemberPayloadSource", + "RemoveTeamMemberIntegrationAuthSuccess": "./types/RemoveTeamMemberIntegrationAuthPayload#RemoveTeamMemberIntegrationAuthSuccessSource", "RequestToJoinDomainSuccess": "./types/RequestToJoinDomainSuccess#RequestToJoinDomainSuccessSource", "ResetReflectionGroupsSuccess": "./types/ResetReflectionGroupsSuccess#ResetReflectionGroupsSuccessSource", "RetroReflection": "./types/RetroReflection#RetroReflectionSource", diff --git a/packages/client/components/JiraScopingSearchFilterMenu.tsx b/packages/client/components/JiraScopingSearchFilterMenu.tsx index 648bbc71a6d..5210003cb20 100644 --- a/packages/client/components/JiraScopingSearchFilterMenu.tsx +++ b/packages/client/components/JiraScopingSearchFilterMenu.tsx @@ -51,7 +51,7 @@ type JiraSearchQuery = { type Project = { id: string name: string - avatar: string + avatar: string | null } interface Props { @@ -157,7 +157,7 @@ const JiraScopingSearchFilterMenu = (props: Props) => { active={projectKeyFilters.includes(globalProjectKey)} disabled={isJQL} /> - + } diff --git a/packages/server/graphql/mutations/addTeamMemberIntegrationAuth.ts b/packages/server/graphql/mutations/addTeamMemberIntegrationAuth.ts deleted file mode 100644 index 52a20bdd1ca..00000000000 --- a/packages/server/graphql/mutations/addTeamMemberIntegrationAuth.ts +++ /dev/null @@ -1,189 +0,0 @@ -import {GraphQLID, GraphQLNonNull} from 'graphql' -import IntegrationProviderId from '~/shared/gqlIds/IntegrationProviderId' -import GcalOAuth2Manager from '../../integrations/gcal/GcalOAuth2Manager' -import GitLabOAuth2Manager from '../../integrations/gitlab/GitLabOAuth2Manager' -import JiraServerOAuth1Manager, { - OAuth1Auth -} from '../../integrations/jiraServer/JiraServerOAuth1Manager' -import {IntegrationProviderAzureDevOps} from '../../postgres/queries/getIntegrationProvidersByIds' -import upsertTeamMemberIntegrationAuth from '../../postgres/queries/upsertTeamMemberIntegrationAuth' -import AzureDevOpsServerManager from '../../utils/AzureDevOpsServerManager' -import {analytics} from '../../utils/analytics/analytics' -import {getUserId, isTeamMember} from '../../utils/authorization' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import updateRepoIntegrationsCacheByPerms from '../queries/helpers/updateRepoIntegrationsCacheByPerms' -import AddTeamMemberIntegrationAuthPayload from '../types/AddTeamMemberIntegrationAuthPayload' -import GraphQLURLType from '../types/GraphQLURLType' - -interface OAuth2Auth { - accessToken: string - refreshToken: string - scopes: string - expiresAt?: Date | null -} - -const addTeamMemberIntegrationAuth = { - name: 'AddTeamMemberIntegrationAuth', - type: new GraphQLNonNull(AddTeamMemberIntegrationAuthPayload), - description: 'Add an integration authorization for a specific team member', - args: { - providerId: { - type: new GraphQLNonNull(GraphQLID) - }, - teamId: { - type: new GraphQLNonNull(GraphQLID) - }, - oauthCodeOrPat: { - type: GraphQLID, - description: 'The OAuth2 code or personal access token. Null for webhook auth' - }, - oauthVerifier: { - type: GraphQLID, - description: 'OAuth1 token verifier' - }, - redirectUri: { - type: GraphQLURLType, - description: 'The URL the OAuth2 token will be sent to. Null for webhook auth' - } - }, - resolve: async ( - _source: unknown, - { - providerId, - oauthCodeOrPat, - oauthVerifier, - teamId, - redirectUri - }: { - providerId: string - oauthCodeOrPat: string | null - oauthVerifier: string | null - teamId: string - redirectUri: string | null - }, - context: GQLContext - ) => { - const {authToken, dataLoader} = context - const viewerId = getUserId(authToken) - - //AUTH - if (!isTeamMember(authToken, teamId)) { - return standardError(new Error('Attempted teamId spoof'), {userId: viewerId}) - } - - const providerDbId = IntegrationProviderId.split(providerId) - const [integrationProvider, viewer] = await Promise.all([ - dataLoader.get('integrationProviders').load(providerDbId), - dataLoader.get('users').loadNonNull(viewerId) - ]) - if (!integrationProvider) { - return standardError( - new Error(`Unable to find appropriate integration provider for providerId ${providerId}`), - { - userId: viewerId - } - ) - } - - // VALIDATION - const {authStrategy, service, scope} = integrationProvider - if (scope === 'team') { - if (teamId !== integrationProvider.teamId) { - return {error: {message: 'teamId mismatch'}} - } - } else if (scope === 'org' && teamId !== integrationProvider.teamId) { - const [providerTeam, authTeam] = await Promise.all([ - dataLoader.get('teams').loadNonNull(integrationProvider.teamId), - dataLoader.get('teams').loadNonNull(teamId) - ]) - if (providerTeam.orgId !== authTeam.orgId) { - return {error: {message: 'provider not available for this team'}} - } - } - - let tokenMetadata: OAuth2Auth | OAuth1Auth | Error | undefined = undefined - if (authStrategy === 'oauth2') { - if (!oauthCodeOrPat || !redirectUri) - return {error: {message: 'Missing OAuth2 code or redirect URI'}} - if (service === 'gitlab') { - const {clientId, clientSecret, serverBaseUrl} = integrationProvider - const manager = new GitLabOAuth2Manager(clientId, clientSecret, serverBaseUrl) - const authRes = await manager.authorize(oauthCodeOrPat, redirectUri) - if ('expiresIn' in authRes) { - const {expiresIn, ...metadata} = authRes - const buffer = 30 - const expiresAtTimestamp = new Date().getTime() + (expiresIn - buffer) * 1000 - const expiresAt = new Date(expiresAtTimestamp) - tokenMetadata = { - expiresAt, - ...metadata - } - } else { - tokenMetadata = authRes - } - } - if (service === 'azureDevOps') { - if (!oauthVerifier) { - return { - error: {message: 'Missing OAuth2 Verifier required for Azure DevOps authentication'} - } - } - const manager = new AzureDevOpsServerManager( - null, - integrationProvider as IntegrationProviderAzureDevOps - ) - tokenMetadata = (await manager.init(oauthCodeOrPat, oauthVerifier)) as OAuth2Auth | Error - } - if (service === 'gcal') { - const {clientId, clientSecret, serverBaseUrl} = integrationProvider - const manager = new GcalOAuth2Manager(clientId, clientSecret, serverBaseUrl) - const authRes = await manager.authorize(oauthCodeOrPat, redirectUri) - - if ('expiresIn' in authRes) { - const {expiresIn, ...metadata} = authRes - const expiresAtTimestamp = new Date().getTime() + (expiresIn - 30) * 1000 - const expiresAt = new Date(expiresAtTimestamp) - tokenMetadata = { - expiresAt, - ...metadata - } - } else { - tokenMetadata = authRes - } - } - } - if (authStrategy === 'oauth1') { - if (!oauthCodeOrPat || !oauthVerifier) - return {error: {message: 'Missing OAuth1 token or verifier'}} - if (service === 'jiraServer') { - const {serverBaseUrl, consumerKey, consumerSecret} = integrationProvider - const manager = new JiraServerOAuth1Manager(serverBaseUrl, consumerKey, consumerSecret) - tokenMetadata = await manager.accessToken(oauthCodeOrPat, oauthVerifier) - } - } - - if (tokenMetadata instanceof Error) { - return standardError(tokenMetadata, { - userId: viewerId - }) - } - - // RESOLUTION - await upsertTeamMemberIntegrationAuth({ - ...tokenMetadata, - providerId: providerDbId, - service, - teamId, - userId: viewerId - }) - updateRepoIntegrationsCacheByPerms(dataLoader, viewerId, teamId, true) - - analytics.integrationAdded(viewer, teamId, service) - - const data = {userId: viewerId, teamId, service} - return data - } -} - -export default addTeamMemberIntegrationAuth diff --git a/packages/server/graphql/mutations/removeTeamMemberIntegrationAuth.ts b/packages/server/graphql/mutations/removeTeamMemberIntegrationAuth.ts deleted file mode 100644 index 232e0690ebf..00000000000 --- a/packages/server/graphql/mutations/removeTeamMemberIntegrationAuth.ts +++ /dev/null @@ -1,50 +0,0 @@ -import {GraphQLID, GraphQLNonNull} from 'graphql' -import {IntegrationProviderServiceEnum as TIntegrationProviderServiceEnum} from '../../postgres/queries/generated/getIntegrationProvidersByIdsQuery' -import removeTeamMemberIntegrationAuthQuery from '../../postgres/queries/removeTeamMemberIntegrationAuth' -import {analytics} from '../../utils/analytics/analytics' -import {getUserId, isTeamMember} from '../../utils/authorization' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import updateRepoIntegrationsCacheByPerms from '../queries/helpers/updateRepoIntegrationsCacheByPerms' -import IntegrationProviderServiceEnum from '../types/IntegrationProviderServiceEnum' -import RemoveTeamMemberIntegrationAuthPayload from '../types/RemoveTeamMemberIntegrationAuthPayload' - -const removeTeamMemberIntegrationAuth = { - type: GraphQLNonNull(RemoveTeamMemberIntegrationAuthPayload), - description: 'Remove the integrated auth for a given team member', - args: { - service: { - type: GraphQLNonNull(IntegrationProviderServiceEnum), - description: 'The Integration Provider service name related to the token' - }, - teamId: { - type: GraphQLNonNull(GraphQLID), - description: 'The team id related to the token' - } - }, - resolve: async ( - _source: unknown, - {service, teamId}: {service: TIntegrationProviderServiceEnum; teamId: string}, - context: GQLContext - ) => { - const {authToken, dataLoader} = context - const viewerId = getUserId(authToken) - - // AUTH - if (!isTeamMember(authToken, teamId)) - return standardError(new Error('permission denied; must be team member')) - - // RESOLUTION - const [viewer] = await Promise.all([ - dataLoader.get('users').loadNonNull(viewerId), - removeTeamMemberIntegrationAuthQuery(service, teamId, viewerId) - ]) - updateRepoIntegrationsCacheByPerms(dataLoader, viewerId, teamId, false) - analytics.integrationRemoved(viewer, teamId, service) - - const data = {userId: viewerId, teamId} - return data - } -} - -export default removeTeamMemberIntegrationAuth diff --git a/packages/server/graphql/public/mutations/addTeamMemberIntegrationAuth.ts b/packages/server/graphql/public/mutations/addTeamMemberIntegrationAuth.ts new file mode 100644 index 00000000000..215a819001d --- /dev/null +++ b/packages/server/graphql/public/mutations/addTeamMemberIntegrationAuth.ts @@ -0,0 +1,149 @@ +import IntegrationProviderId from '~/shared/gqlIds/IntegrationProviderId' +import GcalOAuth2Manager from '../../../integrations/gcal/GcalOAuth2Manager' +import GitLabOAuth2Manager from '../../../integrations/gitlab/GitLabOAuth2Manager' +import JiraServerOAuth1Manager, { + OAuth1Auth +} from '../../../integrations/jiraServer/JiraServerOAuth1Manager' +import {IntegrationProviderAzureDevOps} from '../../../postgres/queries/getIntegrationProvidersByIds' +import upsertTeamMemberIntegrationAuth from '../../../postgres/queries/upsertTeamMemberIntegrationAuth' +import AzureDevOpsServerManager from '../../../utils/AzureDevOpsServerManager' +import {analytics} from '../../../utils/analytics/analytics' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import standardError from '../../../utils/standardError' +import updateRepoIntegrationsCacheByPerms from '../../queries/helpers/updateRepoIntegrationsCacheByPerms' +import {MutationResolvers} from '../resolverTypes' + +interface OAuth2Auth { + accessToken: string + refreshToken: string + scopes: string + expiresAt?: Date | null +} + +const addTeamMemberIntegrationAuth: MutationResolvers['addTeamMemberIntegrationAuth'] = async ( + _source, + {providerId, oauthCodeOrPat, oauthVerifier, teamId, redirectUri}, + context +) => { + const {authToken, dataLoader} = context + const viewerId = getUserId(authToken) + + //AUTH + if (!isTeamMember(authToken, teamId)) { + return standardError(new Error('Attempted teamId spoof'), {userId: viewerId}) + } + + const providerDbId = IntegrationProviderId.split(providerId) + const [integrationProvider, viewer] = await Promise.all([ + dataLoader.get('integrationProviders').load(providerDbId), + dataLoader.get('users').loadNonNull(viewerId) + ]) + if (!integrationProvider) { + return standardError( + new Error(`Unable to find appropriate integration provider for providerId ${providerId}`), + { + userId: viewerId + } + ) + } + + // VALIDATION + const {authStrategy, service, scope} = integrationProvider + if (scope === 'team') { + if (teamId !== integrationProvider.teamId) { + return {error: {message: 'teamId mismatch'}} + } + } else if (scope === 'org' && teamId !== integrationProvider.teamId) { + const [providerTeam, authTeam] = await Promise.all([ + dataLoader.get('teams').loadNonNull(integrationProvider.teamId), + dataLoader.get('teams').loadNonNull(teamId) + ]) + if (providerTeam.orgId !== authTeam.orgId) { + return {error: {message: 'provider not available for this team'}} + } + } + + let tokenMetadata: OAuth2Auth | OAuth1Auth | Error | undefined = undefined + if (authStrategy === 'oauth2') { + if (!oauthCodeOrPat || !redirectUri) + return {error: {message: 'Missing OAuth2 code or redirect URI'}} + if (service === 'gitlab') { + const {clientId, clientSecret, serverBaseUrl} = integrationProvider + const manager = new GitLabOAuth2Manager(clientId, clientSecret, serverBaseUrl) + const authRes = await manager.authorize(oauthCodeOrPat, redirectUri) + if ('expiresIn' in authRes) { + const {expiresIn, ...metadata} = authRes + const buffer = 30 + const expiresAtTimestamp = new Date().getTime() + (expiresIn - buffer) * 1000 + const expiresAt = new Date(expiresAtTimestamp) + tokenMetadata = { + expiresAt, + ...metadata + } + } else { + tokenMetadata = authRes + } + } + if (service === 'azureDevOps') { + if (!oauthVerifier) { + return { + error: {message: 'Missing OAuth2 Verifier required for Azure DevOps authentication'} + } + } + const manager = new AzureDevOpsServerManager( + null, + integrationProvider as IntegrationProviderAzureDevOps + ) + tokenMetadata = (await manager.init(oauthCodeOrPat, oauthVerifier)) as OAuth2Auth | Error + } + if (service === 'gcal') { + const {clientId, clientSecret, serverBaseUrl} = integrationProvider + const manager = new GcalOAuth2Manager(clientId, clientSecret, serverBaseUrl) + const authRes = await manager.authorize(oauthCodeOrPat, redirectUri) + + if ('expiresIn' in authRes) { + const {expiresIn, ...metadata} = authRes + const expiresAtTimestamp = new Date().getTime() + (expiresIn - 30) * 1000 + const expiresAt = new Date(expiresAtTimestamp) + tokenMetadata = { + expiresAt, + ...metadata + } + } else { + tokenMetadata = authRes + } + } + } + if (authStrategy === 'oauth1') { + if (!oauthCodeOrPat || !oauthVerifier) + return {error: {message: 'Missing OAuth1 token or verifier'}} + if (service === 'jiraServer') { + const {serverBaseUrl, consumerKey, consumerSecret} = integrationProvider + const manager = new JiraServerOAuth1Manager(serverBaseUrl, consumerKey, consumerSecret) + tokenMetadata = await manager.accessToken(oauthCodeOrPat, oauthVerifier) + } + } + + if (tokenMetadata instanceof Error) { + return standardError(tokenMetadata, { + userId: viewerId + }) + } + + // RESOLUTION + await upsertTeamMemberIntegrationAuth({ + ...tokenMetadata, + providerId: providerDbId, + service, + teamId, + userId: viewerId + }) + updateRepoIntegrationsCacheByPerms(dataLoader, viewerId, teamId, true) + + analytics.integrationAdded(viewer, teamId, service) + + const data = {userId: viewerId, teamId, service} + return data +} + +export default addTeamMemberIntegrationAuth diff --git a/packages/server/graphql/public/mutations/removeTeamMemberIntegrationAuth.ts b/packages/server/graphql/public/mutations/removeTeamMemberIntegrationAuth.ts new file mode 100644 index 00000000000..084df24e53f --- /dev/null +++ b/packages/server/graphql/public/mutations/removeTeamMemberIntegrationAuth.ts @@ -0,0 +1,34 @@ +import {IntegrationProviderServiceEnum} from '../../../postgres/queries/generated/removeTeamMemberIntegrationAuthQuery' +import removeTeamMemberIntegrationAuthQuery from '../../../postgres/queries/removeTeamMemberIntegrationAuth' +import {analytics} from '../../../utils/analytics/analytics' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import standardError from '../../../utils/standardError' +import updateRepoIntegrationsCacheByPerms from '../../queries/helpers/updateRepoIntegrationsCacheByPerms' +import {MutationResolvers} from '../resolverTypes' + +const removeTeamMemberIntegrationAuth: MutationResolvers['removeTeamMemberIntegrationAuth'] = + async (_source, {service, teamId}, context) => { + const {authToken, dataLoader} = context + const viewerId = getUserId(authToken) + + // AUTH + if (!isTeamMember(authToken, teamId)) + return standardError(new Error('permission denied; must be team member')) + + // RESOLUTION + const [viewer] = await Promise.all([ + dataLoader.get('users').loadNonNull(viewerId), + removeTeamMemberIntegrationAuthQuery( + service as IntegrationProviderServiceEnum, + teamId, + viewerId + ) + ]) + updateRepoIntegrationsCacheByPerms(dataLoader, viewerId, teamId, false) + analytics.integrationRemoved(viewer, teamId, service) + + const data = {userId: viewerId, teamId} + return data + } + +export default removeTeamMemberIntegrationAuth diff --git a/packages/server/graphql/public/typeDefs/JiraRemoteProject.graphql b/packages/server/graphql/public/typeDefs/JiraRemoteProject.graphql new file mode 100644 index 00000000000..5e8130658cf --- /dev/null +++ b/packages/server/graphql/public/typeDefs/JiraRemoteProject.graphql @@ -0,0 +1,30 @@ +""" +A project fetched from Jira in real time +""" +type JiraRemoteProject implements RepoIntegration { + id: ID! + service: IntegrationProviderServiceEnum! + + """ + The parabol teamId this issue was fetched for + """ + teamId: ID! + + """ + The parabol userId this issue was fetched for + """ + userId: ID! + self: ID! + + """ + The cloud ID that the project lives on. Does not exist on the Jira object! + """ + cloudId: ID! + key: String! + name: String! + avatar: String + avatarUrls: JiraRemoteAvatarUrls! + projectCategory: JiraRemoteProjectCategory! + simplified: Boolean! + style: String! +} diff --git a/packages/server/graphql/public/typeDefs/JiraServerRemoteProject.graphql b/packages/server/graphql/public/typeDefs/JiraServerRemoteProject.graphql new file mode 100644 index 00000000000..794faad6d95 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/JiraServerRemoteProject.graphql @@ -0,0 +1,21 @@ +""" +A project fetched from Jira in real time +""" +type JiraServerRemoteProject implements RepoIntegration { + id: ID! + service: IntegrationProviderServiceEnum! + + """ + The parabol teamId this issue was fetched for + """ + teamId: ID! + + """ + The parabol userId this issue was fetched for + """ + userId: ID! + name: String! + avatar: String! + avatarUrls: JiraRemoteAvatarUrls! + projectCategory: JiraRemoteProjectCategory! +} diff --git a/packages/server/graphql/public/typeDefs/RepoIntegration.graphql b/packages/server/graphql/public/typeDefs/RepoIntegration.graphql new file mode 100644 index 00000000000..e23cba398e8 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/RepoIntegration.graphql @@ -0,0 +1,7 @@ +""" +The suggested repos and projects a user can integrate with +""" +interface RepoIntegration { + id: ID! + service: IntegrationProviderServiceEnum! +} diff --git a/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuth.graphql b/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuth.graphql new file mode 100644 index 00000000000..3051e354cce --- /dev/null +++ b/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuth.graphql @@ -0,0 +1,44 @@ +""" +The auth credentials for a token, specific to a team member +""" +interface TeamMemberIntegrationAuth { + """ + The token's unique identifier + """ + id: ID! + + """ + The team that the token is linked to + """ + teamId: ID! + + """ + The timestamp the token was created + """ + createdAt: DateTime! + + """ + The timestamp the token was updated at + """ + updatedAt: DateTime! + + """ + The GQL GUID of the DB providerId foreign key + """ + providerId: ID! + + """ + The service this token is associated with, denormalized from the provider + """ + service: IntegrationProviderServiceEnum! + + """ + true if the token configuration should be used + """ + isActive: Boolean! + + """ + The provider to connect to + """ + provider: IntegrationProvider! +} diff --git a/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthOAuth1.graphql b/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthOAuth1.graphql new file mode 100644 index 00000000000..7ba07aa9a12 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthOAuth1.graphql @@ -0,0 +1,44 @@ +""" +An integration token that connects via OAuth1 +""" +type TeamMemberIntegrationAuthOAuth1 implements TeamMemberIntegrationAuth { + """ + The token's unique identifier + """ + id: ID! + + """ + The team that the token is linked to + """ + teamId: ID! + + """ + The timestamp the token was created + """ + createdAt: DateTime! + + """ + The timestamp the token was updated at + """ + updatedAt: DateTime! + + """ + The GQL GUID of the DB providerId foreign key + """ + providerId: ID! + + """ + The service this token is associated with, denormalized from the provider + """ + service: IntegrationProviderServiceEnum! + + """ + true if the token configuration should be used + """ + isActive: Boolean! + + """ + The provider strategy this token connects to + """ + provider: IntegrationProviderOAuth1! +} diff --git a/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthOAuth2.graphql b/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthOAuth2.graphql new file mode 100644 index 00000000000..d672e3ab9a8 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthOAuth2.graphql @@ -0,0 +1,54 @@ +""" +An integration token that connects via OAuth2 +""" +type TeamMemberIntegrationAuthOAuth2 implements TeamMemberIntegrationAuth { + """ + The token's unique identifier + """ + id: ID! + + """ + The team that the token is linked to + """ + teamId: ID! + + """ + The timestamp the token was created + """ + createdAt: DateTime! + + """ + The timestamp the token was updated at + """ + updatedAt: DateTime! + + """ + The GQL GUID of the DB providerId foreign key + """ + providerId: ID! + + """ + The service this token is associated with, denormalized from the provider + """ + service: IntegrationProviderServiceEnum! + + """ + true if the token configuration should be used + """ + isActive: Boolean! + + """ + The provider strategy this token connects to + """ + provider: IntegrationProviderOAuth2! + + """ + The token used to connect to the provider + """ + accessToken: ID! + + """ + The scopes allowed on the provider + """ + scopes: String! +} diff --git a/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthWebhook.graphql b/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthWebhook.graphql new file mode 100644 index 00000000000..8d9d8b98a08 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/TeamMemberIntegrationAuthWebhook.graphql @@ -0,0 +1,44 @@ +""" +An integration authorization that connects via Webhook auth strategy +""" +type TeamMemberIntegrationAuthWebhook implements TeamMemberIntegrationAuth { + """ + The token's unique identifier + """ + id: ID! + + """ + The team that the token is linked to + """ + teamId: ID! + + """ + The timestamp the token was created + """ + createdAt: DateTime! + + """ + The timestamp the token was updated at + """ + updatedAt: DateTime! + + """ + The GQL GUID of the DB providerId foreign key + """ + providerId: ID! + + """ + The service this token is associated with, denormalized from the provider + """ + service: IntegrationProviderServiceEnum! + + """ + true if the token configuration should be used + """ + isActive: Boolean! + + """ + The provider strategy this token connects to + """ + provider: IntegrationProviderWebhook! +} diff --git a/packages/server/graphql/public/typeDefs/_legacy.graphql b/packages/server/graphql/public/typeDefs/_legacy.graphql index 8034caa107f..a1fab0ffb5b 100644 --- a/packages/server/graphql/public/typeDefs/_legacy.graphql +++ b/packages/server/graphql/public/typeDefs/_legacy.graphql @@ -838,14 +838,6 @@ type TeamMember { userId: ID! } -""" -The suggested repos and projects a user can integrate with -""" -interface RepoIntegration { - id: ID! - service: IntegrationProviderServiceEnum! -} - """ The right drawer types available on the team dashboard """ @@ -860,37 +852,6 @@ interface TaskIntegration { id: ID! } -""" -A project fetched from Jira in real time -""" -type JiraRemoteProject implements RepoIntegration { - id: ID! - service: IntegrationProviderServiceEnum! - - """ - The parabol teamId this issue was fetched for - """ - teamId: ID! - - """ - The parabol userId this issue was fetched for - """ - userId: ID! - self: ID! - - """ - The cloud ID that the project lives on. Does not exist on the Jira object! - """ - cloudId: ID! - key: String! - name: String! - avatar: String! - avatarUrls: JiraRemoteAvatarUrls! - projectCategory: JiraRemoteProjectCategory! - simplified: Boolean! - style: String! -} - """ The URLs for avatars. NOTE: If they are custom, an Authorization header is required! """ @@ -953,218 +914,6 @@ type JiraSearchQuery { lastUsedAt: DateTime! } -""" -An integration token that connects via OAuth1 -""" -type TeamMemberIntegrationAuthOAuth1 implements TeamMemberIntegrationAuth { - """ - The token's unique identifier - """ - id: ID! - - """ - The team that the token is linked to - """ - teamId: ID! - - """ - The timestamp the token was created - """ - createdAt: DateTime! - - """ - The timestamp the token was updated at - """ - updatedAt: DateTime! - - """ - The GQL GUID of the DB providerId foreign key - """ - providerId: ID! - - """ - The service this token is associated with, denormalized from the provider - """ - service: IntegrationProviderServiceEnum! - - """ - true if the token configuration should be used - """ - isActive: Boolean! - - """ - The provider strategy this token connects to - """ - provider: IntegrationProviderOAuth1! -} - -""" -The auth credentials for a token, specific to a team member -""" -interface TeamMemberIntegrationAuth { - """ - The token's unique identifier - """ - id: ID! - - """ - The team that the token is linked to - """ - teamId: ID! - - """ - The timestamp the token was created - """ - createdAt: DateTime! - - """ - The timestamp the token was updated at - """ - updatedAt: DateTime! - - """ - The GQL GUID of the DB providerId foreign key - """ - providerId: ID! - - """ - The service this token is associated with, denormalized from the provider - """ - service: IntegrationProviderServiceEnum! - - """ - true if the token configuration should be used - """ - isActive: Boolean! - - """ - The provider to connect to - """ - provider: IntegrationProvider! -} - -""" -A project fetched from Jira in real time -""" -type JiraServerRemoteProject implements RepoIntegration { - id: ID! - service: IntegrationProviderServiceEnum! - - """ - The parabol teamId this issue was fetched for - """ - teamId: ID! - - """ - The parabol userId this issue was fetched for - """ - userId: ID! - name: String! - avatar: String! - avatarUrls: JiraRemoteAvatarUrls! - projectCategory: JiraRemoteProjectCategory! -} - -""" -An integration token that connects via OAuth2 -""" -type TeamMemberIntegrationAuthOAuth2 implements TeamMemberIntegrationAuth { - """ - The token's unique identifier - """ - id: ID! - - """ - The team that the token is linked to - """ - teamId: ID! - - """ - The timestamp the token was created - """ - createdAt: DateTime! - - """ - The timestamp the token was updated at - """ - updatedAt: DateTime! - - """ - The GQL GUID of the DB providerId foreign key - """ - providerId: ID! - - """ - The service this token is associated with, denormalized from the provider - """ - service: IntegrationProviderServiceEnum! - - """ - true if the token configuration should be used - """ - isActive: Boolean! - - """ - The provider strategy this token connects to - """ - provider: IntegrationProviderOAuth2! - - """ - The token used to connect to the provider - """ - accessToken: ID! - - """ - The scopes allowed on the provider - """ - scopes: String! -} - -""" -An integration authorization that connects via Webhook auth strategy -""" -type TeamMemberIntegrationAuthWebhook implements TeamMemberIntegrationAuth { - """ - The token's unique identifier - """ - id: ID! - - """ - The team that the token is linked to - """ - teamId: ID! - - """ - The timestamp the token was created - """ - createdAt: DateTime! - - """ - The timestamp the token was updated at - """ - updatedAt: DateTime! - - """ - The GQL GUID of the DB providerId foreign key - """ - providerId: ID! - - """ - The service this token is associated with, denormalized from the provider - """ - service: IntegrationProviderServiceEnum! - - """ - true if the token configuration should be used - """ - isActive: Boolean! - - """ - The provider strategy this token connects to - """ - provider: IntegrationProviderWebhook! -} - """ an event trigger and slack channel to receive it """ @@ -5934,29 +5683,6 @@ type Mutation { newPoll: CreatePollInput! ): CreatePollPayload! - """ - Add an integration authorization for a specific team member - """ - addTeamMemberIntegrationAuth( - providerId: ID! - teamId: ID! - - """ - The OAuth2 code or personal access token. Null for webhook auth - """ - oauthCodeOrPat: ID - - """ - OAuth1 token verifier - """ - oauthVerifier: ID - - """ - The URL the OAuth2 token will be sent to. Null for webhook auth - """ - redirectUri: URL - ): AddTeamMemberIntegrationAuthPayload! - """ Adds a new Integration Provider configuration """ @@ -5987,20 +5713,6 @@ type Mutation { providerId: ID! ): RemoveIntegrationProviderPayload! - """ - Remove the integrated auth for a given team member - """ - removeTeamMemberIntegrationAuth( - """ - The Integration Provider service name related to the token - """ - service: IntegrationProviderServiceEnum! - - """ - The team id related to the token - """ - teamId: ID! - ): RemoveTeamMemberIntegrationAuthPayload! } type AddAgendaItemPayload { @@ -8132,33 +7844,6 @@ input PollOptionInput { title: String! } -""" -Return object for AddTeamMemberIntegrationAuthPayload -""" -union AddTeamMemberIntegrationAuthPayload = ErrorPayload | AddTeamMemberIntegrationAuthSuccess - -type AddTeamMemberIntegrationAuthSuccess { - """ - The auth that was just added - """ - integrationAuth: TeamMemberIntegrationAuth! - - """ - The service this provider is associated with - """ - service: IntegrationProviderServiceEnum! - - """ - The team member with the updated auth - """ - teamMember: TeamMember! - - """ - The user who updated TeamMemberIntegrationAuth object - """ - user: User! -} - """ Return object for AddIntegrationProviderPayload """ @@ -8327,40 +8012,6 @@ input UpdateIntegrationProviderInput { oAuth2ProviderMetadataInput: IntegrationProviderMetadataInputOAuth2 } -""" -Return object for RemoveIntegrationProviderPayload -""" -union RemoveIntegrationProviderPayload = ErrorPayload | RemoveIntegrationProviderSuccess - -type RemoveIntegrationProviderSuccess { - """ - The team member with the updated auth - """ - teamMember: TeamMember! - - """ - The user who updated TeamMemberIntegrationAuth object - """ - user: User! -} - -""" -Return object for RemoveTeamMemberIntegrationAuthPayload -""" -union RemoveTeamMemberIntegrationAuthPayload = ErrorPayload | RemoveTeamMemberIntegrationAuthSuccess - -type RemoveTeamMemberIntegrationAuthSuccess { - """ - The team member with the updated auth - """ - teamMember: TeamMember! - - """ - The user who updated TeamMemberIntegrationAuth object - """ - user: User! -} - type AddReactjiToReflectionSuccess { """ the reflection with the updated list of reactjis diff --git a/packages/server/graphql/public/typeDefs/addTeamMemberIntegrationAuth.graphql b/packages/server/graphql/public/typeDefs/addTeamMemberIntegrationAuth.graphql new file mode 100644 index 00000000000..f121abe6032 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/addTeamMemberIntegrationAuth.graphql @@ -0,0 +1,51 @@ +extend type Mutation { + """ + Add an integration authorization for a specific team member + """ + addTeamMemberIntegrationAuth( + providerId: ID! + teamId: ID! + + """ + The OAuth2 code or personal access token. Null for webhook auth + """ + oauthCodeOrPat: ID + + """ + OAuth1 token verifier + """ + oauthVerifier: ID + + """ + The URL the OAuth2 token will be sent to. Null for webhook auth + """ + redirectUri: URL + ): AddTeamMemberIntegrationAuthPayload! +} + +""" +Return object for AddTeamMemberIntegrationAuthPayload +""" +union AddTeamMemberIntegrationAuthPayload = ErrorPayload | AddTeamMemberIntegrationAuthSuccess + +type AddTeamMemberIntegrationAuthSuccess { + """ + The auth that was just added + """ + integrationAuth: TeamMemberIntegrationAuth! + + """ + The service this provider is associated with + """ + service: IntegrationProviderServiceEnum! + + """ + The team member with the updated auth + """ + teamMember: TeamMember! + + """ + The user who updated TeamMemberIntegrationAuth object + """ + user: User! +} diff --git a/packages/server/graphql/public/typeDefs/removeTeamMemberIntegrationAuth.graphql b/packages/server/graphql/public/typeDefs/removeTeamMemberIntegrationAuth.graphql new file mode 100644 index 00000000000..17b0548d259 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/removeTeamMemberIntegrationAuth.graphql @@ -0,0 +1,50 @@ +extend type Mutation { + """ + Remove the integrated auth for a given team member + """ + removeTeamMemberIntegrationAuth( + """ + The Integration Provider service name related to the token + """ + service: IntegrationProviderServiceEnum! + + """ + The team id related to the token + """ + teamId: ID! + ): RemoveTeamMemberIntegrationAuthPayload! +} + +""" +Return object for RemoveIntegrationProviderPayload +""" +union RemoveIntegrationProviderPayload = ErrorPayload | RemoveIntegrationProviderSuccess + +type RemoveIntegrationProviderSuccess { + """ + The team member with the updated auth + """ + teamMember: TeamMember! + + """ + The user who updated TeamMemberIntegrationAuth object + """ + user: User! +} + +""" +Return object for RemoveTeamMemberIntegrationAuthPayload +""" +union RemoveTeamMemberIntegrationAuthPayload = ErrorPayload | RemoveTeamMemberIntegrationAuthSuccess + +type RemoveTeamMemberIntegrationAuthSuccess { + """ + The team member with the updated auth + """ + teamMember: TeamMember! + + """ + The user who updated TeamMemberIntegrationAuth object + """ + user: User! +} diff --git a/packages/server/graphql/public/types/AddTeamMemberIntegrationAuthSuccess.ts b/packages/server/graphql/public/types/AddTeamMemberIntegrationAuthSuccess.ts new file mode 100644 index 00000000000..24a623a2e02 --- /dev/null +++ b/packages/server/graphql/public/types/AddTeamMemberIntegrationAuthSuccess.ts @@ -0,0 +1,23 @@ +import toTeamMemberId from '../../../../client/utils/relay/toTeamMemberId' +import {AddTeamMemberIntegrationAuthSuccessResolvers} from '../resolverTypes' + +export type AddTeamMemberIntegrationAuthSuccessSource = { + service: string + teamId: string + userId: string +} + +const AddTeamMemberIntegrationAuthSuccess: AddTeamMemberIntegrationAuthSuccessResolvers = { + integrationAuth: async ({service, teamId, userId}, _args, {dataLoader}) => { + return (await dataLoader.get('teamMemberIntegrationAuths').load({service, teamId, userId}))! + }, + teamMember: ({teamId, userId}, _args, {dataLoader}) => { + const teamMemberId = toTeamMemberId(teamId, userId) + return dataLoader.get('teamMembers').load(teamMemberId) + }, + user: async ({userId}, _args, {dataLoader}) => { + return dataLoader.get('users').loadNonNull(userId) + } +} + +export default AddTeamMemberIntegrationAuthSuccess diff --git a/packages/server/graphql/public/types/JiraRemoteProject.ts b/packages/server/graphql/public/types/JiraRemoteProject.ts new file mode 100644 index 00000000000..4cf82277e81 --- /dev/null +++ b/packages/server/graphql/public/types/JiraRemoteProject.ts @@ -0,0 +1,33 @@ +import JiraProjectId from 'parabol-client/shared/gqlIds/JiraProjectId' +import AtlassianServerManager, {JiraProject} from '../../../utils/AtlassianServerManager' +import { + createImageUrlHash, + createParabolImageUrl, + downloadAndCacheImage +} from '../../../utils/atlassian/jiraImages' +import {JiraRemoteProjectResolvers} from '../resolverTypes' + +export type JiraRemoteProjectSource = JiraProject & { + service: 'jira' + cloudId: string + teamId: string + userId: string +} + +const JiraRemoteProject: JiraRemoteProjectResolvers = { + __isTypeOf: ({service}) => service === 'jira', + id: ({cloudId, key}) => JiraProjectId.join(cloudId, key), + service: () => 'jira', + avatar: async ({avatarUrls, teamId, userId}, _args: unknown, {dataLoader}) => { + const url = avatarUrls['48x48'] + const auth = await dataLoader.get('freshAtlassianAuth').load({teamId, userId}) + if (!auth) return null + const {accessToken} = auth + const manager = new AtlassianServerManager(accessToken) + const avatarUrlHash = createImageUrlHash(url) + await downloadAndCacheImage(manager, avatarUrlHash, url) + return createParabolImageUrl(avatarUrlHash) + } +} + +export default JiraRemoteProject diff --git a/packages/server/graphql/public/types/RemoveTeamMemberIntegrationAuthSuccess.ts b/packages/server/graphql/public/types/RemoveTeamMemberIntegrationAuthSuccess.ts new file mode 100644 index 00000000000..e722f61c18c --- /dev/null +++ b/packages/server/graphql/public/types/RemoveTeamMemberIntegrationAuthSuccess.ts @@ -0,0 +1,20 @@ +import toTeamMemberId from '../../../../client/utils/relay/toTeamMemberId' +import {RemoveTeamMemberIntegrationAuthSuccessResolvers} from '../resolverTypes' + +export type RemoveTeamMemberIntegrationAuthSuccessSource = { + service: string + teamId: string + userId: string +} + +const RemoveTeamMemberIntegrationAuthSuccess: RemoveTeamMemberIntegrationAuthSuccessResolvers = { + teamMember: ({teamId, userId}, _args, {dataLoader}) => { + const teamMemberId = toTeamMemberId(teamId, userId) + return dataLoader.get('teamMembers').load(teamMemberId) + }, + user: async ({userId}, _args, {dataLoader}) => { + return dataLoader.get('users').loadNonNull(userId) + } +} + +export default RemoveTeamMemberIntegrationAuthSuccess diff --git a/packages/server/graphql/public/types/TeamMemberIntegrationAuthOAuth1.ts b/packages/server/graphql/public/types/TeamMemberIntegrationAuthOAuth1.ts new file mode 100644 index 00000000000..81216f24b99 --- /dev/null +++ b/packages/server/graphql/public/types/TeamMemberIntegrationAuthOAuth1.ts @@ -0,0 +1,20 @@ +import IntegrationProviderId from '../../../../client/shared/gqlIds/IntegrationProviderId' +import TeamMemberIntegrationAuthId from '../../../../client/shared/gqlIds/TeamMemberIntegrationAuthId' +import {TeamMemberIntegrationAuthOAuth1Resolvers} from '../resolverTypes' + +const TeamMemberIntegrationAuthOAuth1: TeamMemberIntegrationAuthOAuth1Resolvers = { + __isTypeOf: ({ + accessToken, + accessTokenSecret + }: { + accessToken: string | undefined | null + accessTokenSecret: string | undefined | null + }) => !!(accessToken && accessTokenSecret), + id: ({service, teamId, userId}) => TeamMemberIntegrationAuthId.join(service, teamId, userId), + providerId: ({providerId}) => IntegrationProviderId.join(providerId), + provider: async ({providerId}, _args, {dataLoader}) => { + return dataLoader.get('integrationProviders').loadNonNull(providerId) + } +} + +export default TeamMemberIntegrationAuthOAuth1 diff --git a/packages/server/graphql/public/types/TeamMemberIntegrationAuthOAuth2.ts b/packages/server/graphql/public/types/TeamMemberIntegrationAuthOAuth2.ts new file mode 100644 index 00000000000..079c958ce9f --- /dev/null +++ b/packages/server/graphql/public/types/TeamMemberIntegrationAuthOAuth2.ts @@ -0,0 +1,14 @@ +import IntegrationProviderId from '../../../../client/shared/gqlIds/IntegrationProviderId' +import TeamMemberIntegrationAuthId from '../../../../client/shared/gqlIds/TeamMemberIntegrationAuthId' +import {TeamMemberIntegrationAuthOAuth2Resolvers} from '../resolverTypes' + +const TeamMemberIntegrationAuthOAuth2: TeamMemberIntegrationAuthOAuth2Resolvers = { + __isTypeOf: ({accessToken, refreshToken, scopes}) => !!(accessToken && refreshToken && scopes), + id: ({service, teamId, userId}) => TeamMemberIntegrationAuthId.join(service, teamId, userId), + providerId: ({providerId}) => IntegrationProviderId.join(providerId), + provider: async ({providerId}, _args, {dataLoader}) => { + return dataLoader.get('integrationProviders').loadNonNull(providerId) + } +} + +export default TeamMemberIntegrationAuthOAuth2 diff --git a/packages/server/graphql/public/types/TeamMemberIntegrationAuthWebhook.ts b/packages/server/graphql/public/types/TeamMemberIntegrationAuthWebhook.ts new file mode 100644 index 00000000000..33248cc8a5a --- /dev/null +++ b/packages/server/graphql/public/types/TeamMemberIntegrationAuthWebhook.ts @@ -0,0 +1,14 @@ +import IntegrationProviderId from '../../../../client/shared/gqlIds/IntegrationProviderId' +import TeamMemberIntegrationAuthId from '../../../../client/shared/gqlIds/TeamMemberIntegrationAuthId' +import {TeamMemberIntegrationAuthWebhookResolvers} from '../resolverTypes' + +const TeamMemberIntegrationAuthWebhook: TeamMemberIntegrationAuthWebhookResolvers = { + __isTypeOf: ({accessToken, refreshToken, scopes}) => !accessToken || !refreshToken || !scopes, + id: ({service, teamId, userId}) => TeamMemberIntegrationAuthId.join(service, teamId, userId), + providerId: ({providerId}) => IntegrationProviderId.join(providerId), + provider: async ({providerId}, _args, {dataLoader}) => { + return dataLoader.get('integrationProviders').loadNonNull(providerId) + } +} + +export default TeamMemberIntegrationAuthWebhook diff --git a/packages/server/graphql/rootMutation.ts b/packages/server/graphql/rootMutation.ts index 608dad1c1ec..afda6e84e7e 100644 --- a/packages/server/graphql/rootMutation.ts +++ b/packages/server/graphql/rootMutation.ts @@ -12,7 +12,6 @@ import addPokerTemplateScaleValue from './mutations/addPokerTemplateScaleValue' import addReflectTemplatePrompt from './mutations/addReflectTemplatePrompt' import addSlackAuth from './mutations/addSlackAuth' import addTeam from './mutations/addTeam' -import addTeamMemberIntegrationAuth from './mutations/addTeamMemberIntegrationAuth' import archiveOrganization from './mutations/archiveOrganization' import archiveTeam from './mutations/archiveTeam' import archiveTimelineEvent from './mutations/archiveTimelineEvent' @@ -79,7 +78,6 @@ import removeReflectTemplatePrompt from './mutations/removeReflectTemplatePrompt import removeReflection from './mutations/removeReflection' import removeSlackAuth from './mutations/removeSlackAuth' import removeTeamMember from './mutations/removeTeamMember' -import removeTeamMemberIntegrationAuth from './mutations/removeTeamMemberIntegrationAuth' import renameMeeting from './mutations/renameMeeting' import renameMeetingTemplate from './mutations/renameMeetingTemplate' import renamePokerTemplateDimension from './mutations/renamePokerTemplateDimension' @@ -236,11 +234,9 @@ export default new GraphQLObjectType({ toggleTeamDrawer, updateGitHubDimensionField, createPoll, - addTeamMemberIntegrationAuth, addIntegrationProvider, updateIntegrationProvider, removeIntegrationProvider, - removeTeamMemberIntegrationAuth, endTeamPrompt, updateAzureDevOpsDimensionField }) as any diff --git a/packages/server/graphql/types/AddTeamMemberIntegrationAuthPayload.ts b/packages/server/graphql/types/AddTeamMemberIntegrationAuthPayload.ts deleted file mode 100644 index 49055213f36..00000000000 --- a/packages/server/graphql/types/AddTeamMemberIntegrationAuthPayload.ts +++ /dev/null @@ -1,47 +0,0 @@ -import {GraphQLNonNull, GraphQLObjectType} from 'graphql' -import toTeamMemberId from '../../../client/utils/relay/toTeamMemberId' -import {GQLContext} from '../graphql' -import IntegrationProviderServiceEnum from './IntegrationProviderServiceEnum' -import TeamMember from './TeamMember' -import TeamMemberIntegrationAuth from './TeamMemberIntegrationAuth' -import User from './User' -import makeMutationPayload from './makeMutationPayload' - -export const AddTeamMemberIntegrationAuthSuccess = new GraphQLObjectType({ - name: 'AddTeamMemberIntegrationAuthSuccess', - fields: () => ({ - integrationAuth: { - type: new GraphQLNonNull(TeamMemberIntegrationAuth), - description: 'The auth that was just added', - resolve: ({service, teamId, userId}, _args, {dataLoader}) => { - return dataLoader.get('teamMemberIntegrationAuths').load({service, teamId, userId}) - } - }, - service: { - type: new GraphQLNonNull(IntegrationProviderServiceEnum), - description: 'The service this provider is associated with' - }, - teamMember: { - type: new GraphQLNonNull(TeamMember), - description: 'The team member with the updated auth', - resolve: ({teamId, userId}, _args, {dataLoader}) => { - const teamMemberId = toTeamMemberId(teamId, userId) - return dataLoader.get('teamMembers').load(teamMemberId) - } - }, - user: { - type: new GraphQLNonNull(User), - description: 'The user who updated TeamMemberIntegrationAuth object', - resolve: async ({userId}, _args, {dataLoader}) => { - return dataLoader.get('users').load(userId) - } - } - }) -}) - -const AddTeamMemberIntegrationAuthPayload = makeMutationPayload( - 'AddTeamMemberIntegrationAuthPayload', - AddTeamMemberIntegrationAuthSuccess -) - -export default AddTeamMemberIntegrationAuthPayload diff --git a/packages/server/graphql/types/JiraRemoteProject.ts b/packages/server/graphql/types/JiraRemoteProject.ts deleted file mode 100644 index 5bd90a6a384..00000000000 --- a/packages/server/graphql/types/JiraRemoteProject.ts +++ /dev/null @@ -1,86 +0,0 @@ -import {GraphQLBoolean, GraphQLID, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql' -import JiraProjectId from 'parabol-client/shared/gqlIds/JiraProjectId' -import AtlassianServerManager, {JiraProject} from '../../utils/AtlassianServerManager' -import { - createImageUrlHash, - createParabolImageUrl, - downloadAndCacheImage -} from '../../utils/atlassian/jiraImages' -import {GQLContext} from '../graphql' -import IntegrationProviderServiceEnum from './IntegrationProviderServiceEnum' -import JiraRemoteAvatarUrls from './JiraRemoteAvatarUrls' -import JiraRemoteProjectCategory from './JiraRemoteProjectCategory' -import RepoIntegration, {repoIntegrationFields} from './RepoIntegration' - -export type JiraRemoteProjectSource = JiraProject & { - service: 'jira' - cloudId: string - teamId: string - userId: string -} - -const JiraRemoteProject = new GraphQLObjectType({ - name: 'JiraRemoteProject', - description: 'A project fetched from Jira in real time', - interfaces: () => [RepoIntegration], - isTypeOf: ({service}) => service === 'jira', - fields: () => ({ - ...repoIntegrationFields(), - id: { - type: new GraphQLNonNull(GraphQLID), - resolve: ({cloudId, key}) => JiraProjectId.join(cloudId, key) - }, - service: { - type: new GraphQLNonNull(IntegrationProviderServiceEnum), - resolve: () => 'jira' - }, - teamId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The parabol teamId this issue was fetched for' - }, - userId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The parabol userId this issue was fetched for' - }, - self: { - type: new GraphQLNonNull(GraphQLID) - }, - cloudId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The cloud ID that the project lives on. Does not exist on the Jira object!' - }, - key: { - type: new GraphQLNonNull(GraphQLString) - }, - name: { - type: new GraphQLNonNull(GraphQLString) - }, - avatar: { - type: new GraphQLNonNull(GraphQLString), - resolve: async ({avatarUrls, teamId, userId}, _args: unknown, {dataLoader}) => { - const url = avatarUrls['48x48'] - const auth = await dataLoader.get('freshAtlassianAuth').load({teamId, userId}) - if (!auth) return null - const {accessToken} = auth - const manager = new AtlassianServerManager(accessToken) - const avatarUrlHash = createImageUrlHash(url) - await downloadAndCacheImage(manager, avatarUrlHash, url) - return createParabolImageUrl(avatarUrlHash) - } - }, - avatarUrls: { - type: new GraphQLNonNull(JiraRemoteAvatarUrls) - }, - projectCategory: { - type: new GraphQLNonNull(JiraRemoteProjectCategory) - }, - simplified: { - type: new GraphQLNonNull(GraphQLBoolean) - }, - style: { - type: new GraphQLNonNull(GraphQLString) - } - }) -}) - -export default JiraRemoteProject diff --git a/packages/server/graphql/types/RemoveTeamMemberIntegrationAuthPayload.ts b/packages/server/graphql/types/RemoveTeamMemberIntegrationAuthPayload.ts deleted file mode 100644 index ec145084377..00000000000 --- a/packages/server/graphql/types/RemoveTeamMemberIntegrationAuthPayload.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {GraphQLNonNull, GraphQLObjectType} from 'graphql' -import TeamMemberId from '../../../client/shared/gqlIds/TeamMemberId' -import {GQLContext} from '../graphql' -import TeamMember from './TeamMember' -import User from './User' -import makeMutationPayload from './makeMutationPayload' - -export const RemoveTeamMemberIntegrationAuthSuccess = new GraphQLObjectType({ - name: 'RemoveTeamMemberIntegrationAuthSuccess', - fields: () => ({ - teamMember: { - type: new GraphQLNonNull(TeamMember), - description: 'The team member with the updated auth', - resolve: ({teamId, userId}, _args, {dataLoader}) => { - const teamMemberId = TeamMemberId.join(teamId, userId) - return dataLoader.get('teamMembers').load(teamMemberId) - } - }, - user: { - type: new GraphQLNonNull(User), - description: 'The user who updated TeamMemberIntegrationAuth object', - resolve: async ({userId}, _args, {dataLoader}) => { - return dataLoader.get('users').load(userId) - } - } - }) -}) - -const RemoveTeamMemberIntegrationAuthPayload = makeMutationPayload( - 'RemoveTeamMemberIntegrationAuthPayload', - RemoveTeamMemberIntegrationAuthSuccess -) - -export default RemoveTeamMemberIntegrationAuthPayload diff --git a/packages/server/graphql/types/TeamMemberIntegrationAuth.ts b/packages/server/graphql/types/TeamMemberIntegrationAuth.ts deleted file mode 100644 index f7de7d29924..00000000000 --- a/packages/server/graphql/types/TeamMemberIntegrationAuth.ts +++ /dev/null @@ -1,62 +0,0 @@ -import {GraphQLBoolean, GraphQLID, GraphQLInterfaceType, GraphQLNonNull} from 'graphql' -import IntegrationProviderId from 'parabol-client/shared/gqlIds/IntegrationProviderId' -import TeamMemberIntegrationAuthId from 'parabol-client/shared/gqlIds/TeamMemberIntegrationAuthId' -import {IGetTeamMemberIntegrationAuthQueryResult} from '../../postgres/queries/generated/getTeamMemberIntegrationAuthQuery' -import {GQLContext} from '../graphql' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import IntegrationProvider from './IntegrationProvider' -import IntegrationProviderServiceEnum from './IntegrationProviderServiceEnum' - -export const teamMemberIntegrationAuthFields = () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: "The token's unique identifier", - resolve: ({service, teamId, userId}: IGetTeamMemberIntegrationAuthQueryResult) => - TeamMemberIntegrationAuthId.join(service, teamId, userId) - }, - teamId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The team that the token is linked to' - }, - createdAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the token was created' - }, - updatedAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the token was updated at' - }, - providerId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The GQL GUID of the DB providerId foreign key', - resolve: ({providerId}: IGetTeamMemberIntegrationAuthQueryResult) => - IntegrationProviderId.join(providerId) - }, - service: { - type: new GraphQLNonNull(IntegrationProviderServiceEnum), - description: 'The service this token is associated with, denormalized from the provider' - }, - isActive: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'true if the token configuration should be used' - }, - provider: { - description: 'The provider to connect to', - type: new GraphQLNonNull(IntegrationProvider), - resolve: async ( - {providerId}: IGetTeamMemberIntegrationAuthQueryResult, - _args: unknown, - {dataLoader}: GQLContext - ) => { - return dataLoader.get('integrationProviders').load(providerId) - } - } -}) - -const TeamMemberIntegrationAuth = new GraphQLInterfaceType({ - name: 'TeamMemberIntegrationAuth', - description: 'The auth credentials for a token, specific to a team member', - fields: teamMemberIntegrationAuthFields -}) - -export default TeamMemberIntegrationAuth diff --git a/packages/server/graphql/types/TeamMemberIntegrationAuthOAuth1.ts b/packages/server/graphql/types/TeamMemberIntegrationAuthOAuth1.ts deleted file mode 100644 index 0af05551a55..00000000000 --- a/packages/server/graphql/types/TeamMemberIntegrationAuthOAuth1.ts +++ /dev/null @@ -1,31 +0,0 @@ -import {GraphQLNonNull, GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import IntegrationProviderOAuth1 from './IntegrationProviderOAuth1' -import TeamMemberIntegrationAuth, { - teamMemberIntegrationAuthFields -} from './TeamMemberIntegrationAuth' - -const TeamMemberIntegrationAuthOAuth1 = new GraphQLObjectType({ - name: 'TeamMemberIntegrationAuthOAuth1', - description: 'An integration token that connects via OAuth1', - interfaces: () => [TeamMemberIntegrationAuth], - isTypeOf: ({ - accessToken, - accessTokenSecret - }: { - accessToken: string | undefined | null - accessTokenSecret: string | undefined | null - }) => !!(accessToken && accessTokenSecret), - fields: () => ({ - ...teamMemberIntegrationAuthFields(), - provider: { - description: 'The provider strategy this token connects to', - type: new GraphQLNonNull(IntegrationProviderOAuth1), - resolve: async ({providerId}: {providerId: number}, _args, {dataLoader}) => { - return dataLoader.get('integrationProviders').load(providerId) - } - } - }) -}) - -export default TeamMemberIntegrationAuthOAuth1 diff --git a/packages/server/graphql/types/TeamMemberIntegrationAuthOAuth2.ts b/packages/server/graphql/types/TeamMemberIntegrationAuthOAuth2.ts deleted file mode 100644 index 2155a1d57c0..00000000000 --- a/packages/server/graphql/types/TeamMemberIntegrationAuthOAuth2.ts +++ /dev/null @@ -1,33 +0,0 @@ -import {GraphQLID, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql' -import {GQLContext} from '../graphql' -import IntegrationProviderOAuth2 from './IntegrationProviderOAuth2' -import TeamMemberIntegrationAuth, { - teamMemberIntegrationAuthFields -} from './TeamMemberIntegrationAuth' - -const TeamMemberIntegrationAuthOAuth2 = new GraphQLObjectType({ - name: 'TeamMemberIntegrationAuthOAuth2', - description: 'An integration token that connects via OAuth2', - interfaces: () => [TeamMemberIntegrationAuth], - isTypeOf: ({accessToken, refreshToken, scopes}) => accessToken && refreshToken && scopes, - fields: () => ({ - ...teamMemberIntegrationAuthFields(), - accessToken: { - type: new GraphQLNonNull(GraphQLID), - description: 'The token used to connect to the provider' - }, - scopes: { - type: new GraphQLNonNull(GraphQLString), - description: 'The scopes allowed on the provider' - }, - provider: { - description: 'The provider strategy this token connects to', - type: new GraphQLNonNull(IntegrationProviderOAuth2), - resolve: async ({providerId}, _args, {dataLoader}) => { - return dataLoader.get('integrationProviders').load(providerId) - } - } - }) -}) - -export default TeamMemberIntegrationAuthOAuth2 diff --git a/packages/server/graphql/types/TeamMemberIntegrationAuthWebhook.ts b/packages/server/graphql/types/TeamMemberIntegrationAuthWebhook.ts deleted file mode 100644 index f2d59c75115..00000000000 --- a/packages/server/graphql/types/TeamMemberIntegrationAuthWebhook.ts +++ /dev/null @@ -1,27 +0,0 @@ -import {GraphQLNonNull, GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import IntegrationProviderWebhook from './IntegrationProviderWebhook' -import TeamMemberIntegrationAuth, { - teamMemberIntegrationAuthFields -} from './TeamMemberIntegrationAuth' - -const TeamMemberIntegrationAuthWebhook = new GraphQLObjectType({ - name: 'TeamMemberIntegrationAuthWebhook', - description: 'An integration authorization that connects via Webhook auth strategy', - interfaces: () => [TeamMemberIntegrationAuth], - // negating the duck typing of OAuth2 feels bad, man - // make sure if we add another provider type we add that here, too - isTypeOf: ({accessToken, refreshToken, scopes}) => !accessToken || !refreshToken || !scopes, - fields: () => ({ - ...teamMemberIntegrationAuthFields(), - provider: { - description: 'The provider strategy this token connects to', - type: new GraphQLNonNull(IntegrationProviderWebhook), - resolve: async ({providerId}, _args, {dataLoader}) => { - return dataLoader.get('integrationProviders').load(providerId) - } - } - }) -}) - -export default TeamMemberIntegrationAuthWebhook From eceb7af96b44f832dd31b43df48ed050c8b01343 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 11:36:10 -0700 Subject: [PATCH 352/529] chore(release): release v7.39.0 (#10010) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 15 +++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 27 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 444fcf166a0..6054f981be4 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.38.11" + ".": "7.39.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 523db907f02..2a7739ff7b5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.39.0](https://github.com/ParabolInc/parabol/compare/v7.38.11...v7.39.0) (2024-07-23) + + +### Added + +* GCal event series for Standup ([#9959](https://github.com/ParabolInc/parabol/issues/9959)) ([8a6659a](https://github.com/ParabolInc/parabol/commit/8a6659a2373a101a970b2fb2a1004c0e21c85f65)) + + +### Changed + +* Fix test ([#10013](https://github.com/ParabolInc/parabol/issues/10013)) ([23c8048](https://github.com/ParabolInc/parabol/commit/23c8048eb88916d5f2021dc0e830fd3b953452e6)) +* Move more integration GraphQL types to SDL ([#10015](https://github.com/ParabolInc/parabol/issues/10015)) ([1279971](https://github.com/ParabolInc/parabol/commit/12799719081e53fdf0976005dabf851fc2908c42)) +* Reduce Azure DevOps scope ([#9999](https://github.com/ParabolInc/parabol/issues/9999)) ([e6a3c7d](https://github.com/ParabolInc/parabol/commit/e6a3c7d12b0f4e7a3b7caa4fe62ab81764c9f577)) +* upgrade from gpt-3.5-turbo to gpt-4o-mini ([#10002](https://github.com/ParabolInc/parabol/issues/10002)) ([b816727](https://github.com/ParabolInc/parabol/commit/b816727460620637f49027a1b32e47d3a2676152)) + ## [7.38.11](https://github.com/ParabolInc/parabol/compare/v7.38.10...v7.38.11) (2024-07-19) diff --git a/package.json b/package.json index dcd82b5d9e7..7c1052a333a 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.11", + "version": "7.39.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 729ca876e99..c71da5ef114 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.38.11", + "version": "7.39.0", "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.11" + "parabol-server": "7.39.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index b968a7a3998..1a7a040a775 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.11", + "version": "7.39.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index b80958ac24f..812dc94a797 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.38.11", + "version": "7.39.0", "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 0d125435860..71a1e1dea06 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.38.11", + "version": "7.39.0", "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.11", - "parabol-server": "7.38.11", + "parabol-client": "7.39.0", + "parabol-server": "7.39.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index bb4a3bf0cc5..a0cb0594b08 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.11", + "version": "7.39.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index bef4d94ca22..ec51fbe3381 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.11", + "version": "7.39.0", "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.11", + "parabol-client": "7.39.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 90de32f702849a92be28dff42188445947a54325 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 23 Jul 2024 16:26:54 -0700 Subject: [PATCH 353/529] chore(rethinkdb): TeamMember: Phase 2 (#9993) Signed-off-by: Matt Krick --- packages/server/__tests__/globalTeardown.ts | 1 + .../__tests__/isOrgVerified.test.ts | 1 + .../__tests__/usersCustomRedisQueries.test.ts | 3 - .../mutations/checkRethinkPgEquality.ts | 36 +++-- .../1721356124871_TeamMember-phase2.ts | 144 ++++++++++++++++++ packages/server/postgres/utils/checkEqBase.ts | 4 +- .../postgres/utils/rethinkEqualityFns.ts | 5 + .../isRequestToJoinDomainAllowed.test.ts | 1 + 8 files changed, 176 insertions(+), 19 deletions(-) create mode 100644 packages/server/postgres/migrations/1721356124871_TeamMember-phase2.ts diff --git a/packages/server/__tests__/globalTeardown.ts b/packages/server/__tests__/globalTeardown.ts index b5a3cd1633f..03744b6ffa4 100644 --- a/packages/server/__tests__/globalTeardown.ts +++ b/packages/server/__tests__/globalTeardown.ts @@ -6,6 +6,7 @@ async function teardown() { const r = await getRethink() await r.getPoolMaster()?.drain() await getKysely().destroy() + console.log('global teardown destroy') await getRedis().quit() } diff --git a/packages/server/dataloader/__tests__/isOrgVerified.test.ts b/packages/server/dataloader/__tests__/isOrgVerified.test.ts index ed37f906642..2c6957b83f4 100644 --- a/packages/server/dataloader/__tests__/isOrgVerified.test.ts +++ b/packages/server/dataloader/__tests__/isOrgVerified.test.ts @@ -68,6 +68,7 @@ afterEach(async () => { afterAll(async () => { await getKysely().destroy() + console.log('org verified destroy') }) test('Founder is billing lead', async () => { diff --git a/packages/server/dataloader/__tests__/usersCustomRedisQueries.test.ts b/packages/server/dataloader/__tests__/usersCustomRedisQueries.test.ts index 50fcc7fa77f..3d1dbb8c44e 100644 --- a/packages/server/dataloader/__tests__/usersCustomRedisQueries.test.ts +++ b/packages/server/dataloader/__tests__/usersCustomRedisQueries.test.ts @@ -5,10 +5,7 @@ import isValid from '../../graphql/isValid' import getPg from '../../postgres/getPg' afterAll(async () => { - const pg = getPg() const dataloader = getDataLoader() - - await pg.end() dataloader.dispose(true) // TODO shutdown redis to properly end test }) diff --git a/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts b/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts index 47c8215ed43..927cf3685fe 100644 --- a/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts +++ b/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts @@ -2,7 +2,13 @@ import getRethink from '../../../database/rethinkDriver' import getFileStoreManager from '../../../fileStorage/getFileStoreManager' import getKysely from '../../../postgres/getKysely' import {checkRowCount, checkTableEq} from '../../../postgres/utils/checkEqBase' -import {compareRValUndefinedAsNull, defaultEqFn} from '../../../postgres/utils/rethinkEqualityFns' +import { + compareDateAlmostEqual, + compareRValUndefinedAsFalse, + compareRValUndefinedAsNull, + compareRValUndefinedAsNullAndTruncateRVal, + defaultEqFn +} from '../../../postgres/utils/rethinkEqualityFns' import {MutationResolvers} from '../resolverTypes' const handleResult = async ( @@ -27,35 +33,37 @@ const checkRethinkPgEquality: MutationResolvers['checkRethinkPgEquality'] = asyn ) => { const r = await getRethink() - if (tableName === 'OrganizationUser') { + if (tableName === 'TeamMember') { const rowCountResult = await checkRowCount(tableName) const rethinkQuery = (joinedAt: Date, id: string | number) => { return r - .table('OrganizationUser' as any) + .table('TeamMember' as any) .between([joinedAt, id], [r.maxval, r.maxval], { - index: 'joinedAtId', + index: 'updatedAtId', leftBound: 'open', rightBound: 'closed' }) - .orderBy({index: 'joinedAtId'}) as any + .orderBy({index: 'updatedAtId'}) as any } const pgQuery = async (ids: string[]) => { - return getKysely().selectFrom('OrganizationUser').selectAll().where('id', 'in', ids).execute() + return getKysely().selectFrom('TeamMember').selectAll().where('id', 'in', ids).execute() } const errors = await checkTableEq( rethinkQuery, pgQuery, { id: defaultEqFn, - suggestedTier: compareRValUndefinedAsNull, - inactive: defaultEqFn, - joinedAt: defaultEqFn, - orgId: defaultEqFn, - removedAt: defaultEqFn, - role: compareRValUndefinedAsNull, + isNotRemoved: compareRValUndefinedAsFalse, + isLead: compareRValUndefinedAsFalse, + isSpectatingPoker: compareRValUndefinedAsFalse, + email: defaultEqFn, + openDrawer: compareRValUndefinedAsNull, + picture: defaultEqFn, + preferredName: compareRValUndefinedAsNullAndTruncateRVal(100), + teamId: defaultEqFn, userId: defaultEqFn, - tier: defaultEqFn, - trialStartDate: compareRValUndefinedAsNull + createdAt: compareDateAlmostEqual, + updatedAt: compareDateAlmostEqual }, maxErrors ) diff --git a/packages/server/postgres/migrations/1721356124871_TeamMember-phase2.ts b/packages/server/postgres/migrations/1721356124871_TeamMember-phase2.ts new file mode 100644 index 00000000000..f88c3c6a97d --- /dev/null +++ b/packages/server/postgres/migrations/1721356124871_TeamMember-phase2.ts @@ -0,0 +1,144 @@ +import {Kysely, PostgresDialect} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + // add a dummy date for nulls + const parabolEpoch = new Date('2016-06-01') + await r + .table('TeamMember') + .update((row) => ({ + updatedAt: row('updatedAt').default(parabolEpoch), + createdAt: row('createdAt').default(parabolEpoch) + })) + .run() + const strDates = await r + .table('TeamMember') + .filter((row) => row('updatedAt').typeOf().eq('STRING')) + .pluck('updatedAt', 'id', 'createdAt') + .run() + const dateDates = strDates.map((d) => ({ + id: d.id, + updatedAt: new Date(d.updatedAt), + createdAt: new Date(d.createdAt) + })) + // some dates are + await r(dateDates) + .forEach((row: any) => { + return r + .table('TeamMember') + .get(row('id')) + .update({updatedAt: row('updatedAt')}) + }) + .run() + + try { + console.log('Adding index') + await r + .table('TeamMember') + .indexCreate('updatedAtId', (row: any) => [row('updatedAt'), row('id')]) + .run() + await r.table('TeamMember').indexWait().run() + } catch { + // index already exists + } + + await console.log('Adding index complete') + const MAX_PG_PARAMS = 65545 + const PG_COLS = [ + 'id', + 'isNotRemoved', + 'isLead', + 'isSpectatingPoker', + 'email', + 'openDrawer', + 'picture', + 'preferredName', + 'teamId', + 'userId', + 'createdAt', + 'updatedAt' + ] as const + type TeamMember = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + + let curUpdatedAt = r.minval + let curId = r.minval + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, String(curUpdatedAt), String(curId)) + const rawRowsToInsert = (await r + .table('TeamMember') + .between([curUpdatedAt, curId], [r.maxval, r.maxval], { + index: 'updatedAtId', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'updatedAtId'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as TeamMember[] + + const rowsToInsert = rawRowsToInsert.map((row) => { + const {preferredName, picture, ...rest} = row as any + return { + ...rest, + preferredName: preferredName.slice(0, 100), + picture: picture.slice(0, 2056) + } + }) + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curUpdatedAt = lastRow.updatedAt + curId = lastRow.id + try { + await pg + .insertInto('TeamMember') + .values(rowsToInsert) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + await Promise.all( + rowsToInsert.map(async (row) => { + try { + await pg + .insertInto('TeamMember') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_userId' || e.constraint === 'fk_teamId') { + console.log(`Skipping ${row.id} because it has no user/team`) + return + } + console.log(e, row) + } + }) + ) + } + } +} + +export async function down() { + await connectRethinkDB() + try { + await r.table('TeamMember').indexDrop('updatedAtId').run() + } catch { + // index already dropped + } + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await pg.deleteFrom('TeamMember').execute() +} diff --git a/packages/server/postgres/utils/checkEqBase.ts b/packages/server/postgres/utils/checkEqBase.ts index bbe31117b3f..175d20a57a5 100644 --- a/packages/server/postgres/utils/checkEqBase.ts +++ b/packages/server/postgres/utils/checkEqBase.ts @@ -33,7 +33,7 @@ export const checkRowCount = async (tableName: string) => { } export async function checkTableEq( - rethinkQuery: (joinedAt: Date, id: string | number) => RSelection, + rethinkQuery: (updatedAt: Date, id: string | number) => RSelection, pgQuery: (ids: string[]) => Promise, equalityMap: Record boolean>, maxErrors: number | null | undefined @@ -51,7 +51,7 @@ export async function checkTableEq( .run()) as RethinkDoc[] if (rethinkRows.length === 0) break const lastRow = rethinkRows[rethinkRows.length - 1]! - curUpdatedDate = lastRow.joinedAt + curUpdatedDate = lastRow.updatedAt curId = lastRow.id const ids = rethinkRows.map((t) => t.id) const pgRows = (await pgQuery(ids)) ?? [] diff --git a/packages/server/postgres/utils/rethinkEqualityFns.ts b/packages/server/postgres/utils/rethinkEqualityFns.ts index 3d63e8381df..201afa28e9c 100644 --- a/packages/server/postgres/utils/rethinkEqualityFns.ts +++ b/packages/server/postgres/utils/rethinkEqualityFns.ts @@ -22,6 +22,11 @@ export const compareRealNumber = (rVal: unknown, pgVal: unknown) => { return answer } +export const compareRValUndefinedAs = + (as: string | number | boolean | null | undefined) => (rVal: unknown, pgVal: unknown) => { + const normalizedRVal = rVal === undefined ? as : rVal + return defaultEqFn(normalizedRVal, pgVal) + } export const compareRValUndefinedAsNull = (rVal: unknown, pgVal: unknown) => { const normalizedRVal = rVal === undefined ? null : rVal return defaultEqFn(normalizedRVal, pgVal) diff --git a/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts b/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts index 52b67b82600..dbe8589d6d8 100644 --- a/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts +++ b/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts @@ -72,6 +72,7 @@ afterEach(async () => { afterAll(async () => { await getKysely().destroy() getRedis().quit() + console.log('request to join destroy') }) test('Only the biggest org with verified emails qualify', async () => { From dbbf588069f49d870349c77dc59a5d79202964ef Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 23 Jul 2024 16:32:47 -0700 Subject: [PATCH 354/529] chore(release): release v7.39.1 (#10020) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 6054f981be4..af120f4b997 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.39.0" + ".": "7.39.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a7739ff7b5..2d2ff9b9d19 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.39.1](https://github.com/ParabolInc/parabol/compare/v7.39.0...v7.39.1) (2024-07-23) + + +### Changed + +* **rethinkdb:** TeamMember: Phase 2 ([#9993](https://github.com/ParabolInc/parabol/issues/9993)) ([90de32f](https://github.com/ParabolInc/parabol/commit/90de32f702849a92be28dff42188445947a54325)) + ## [7.39.0](https://github.com/ParabolInc/parabol/compare/v7.38.11...v7.39.0) (2024-07-23) diff --git a/package.json b/package.json index 7c1052a333a..72f16a26c6c 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.39.0", + "version": "7.39.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index c71da5ef114..07db583627b 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.39.0", + "version": "7.39.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.39.0" + "parabol-server": "7.39.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index 1a7a040a775..f73ad247867 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.39.0", + "version": "7.39.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 812dc94a797..b09be582a0e 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.39.0", + "version": "7.39.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 71a1e1dea06..8794d4de07f 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.39.0", + "version": "7.39.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.39.0", - "parabol-server": "7.39.0", + "parabol-client": "7.39.1", + "parabol-server": "7.39.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index a0cb0594b08..ef1d9c5acc4 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.39.0", + "version": "7.39.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index ec51fbe3381..34ca2fd235b 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.39.0", + "version": "7.39.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.39.0", + "parabol-client": "7.39.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 0bb8ead2adc52f64ad30ef57891791e1b3dd4ac1 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 24 Jul 2024 10:49:21 -0700 Subject: [PATCH 355/529] fix: bump pm2 version (#10027) Signed-off-by: Matt Krick --- package.json | 2 +- yarn.lock | 573 +++++++++++++++++---------------------------------- 2 files changed, 193 insertions(+), 382 deletions(-) diff --git a/package.json b/package.json index 72f16a26c6c..3e6a45467c2 100644 --- a/package.json +++ b/package.json @@ -111,7 +111,7 @@ "minimist": "^1.2.5", "node-loader": "^2.0.0", "pg-promise": "^11.2.0", - "pm2": "^5.3.1", + "pm2": "^5.4.2", "postcss": "^8.4.21", "postcss-loader": "^7.0.2", "prettier": "^3.2.5", diff --git a/yarn.lock b/yarn.lock index 774e599c991..0e96a64ad3b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5451,36 +5451,6 @@ dependencies: "@octokit/openapi-types" "^13.10.0" -"@opencensus/core@0.0.9": - version "0.0.9" - resolved "https://registry.yarnpkg.com/@opencensus/core/-/core-0.0.9.tgz#b16f775435ee309433e4126af194d37313fc93b3" - integrity sha512-31Q4VWtbzXpVUd2m9JS6HEaPjlKvNMOiF7lWKNmXF84yUcgfAFL5re7/hjDmdyQbOp32oGc+RFV78jXIldVz6Q== - dependencies: - continuation-local-storage "^3.2.1" - log-driver "^1.2.7" - semver "^5.5.0" - shimmer "^1.2.0" - uuid "^3.2.1" - -"@opencensus/core@^0.0.8": - version "0.0.8" - resolved "https://registry.yarnpkg.com/@opencensus/core/-/core-0.0.8.tgz#df01f200c2d2fbfe14dae129a1a86fb87286db92" - integrity sha512-yUFT59SFhGMYQgX0PhoTR0LBff2BEhPrD9io1jWfF/VDbakRfs6Pq60rjv0Z7iaTav5gQlttJCX2+VPxFWCuoQ== - dependencies: - continuation-local-storage "^3.2.1" - log-driver "^1.2.7" - semver "^5.5.0" - shimmer "^1.2.0" - uuid "^3.2.1" - -"@opencensus/propagation-b3@0.0.8": - version "0.0.8" - resolved "https://registry.yarnpkg.com/@opencensus/propagation-b3/-/propagation-b3-0.0.8.tgz#0751e6fd75f09400d9d3c419001e9e15a0df68e9" - integrity sha512-PffXX2AL8Sh0VHQ52jJC4u3T0H6wDK6N/4bg7xh4ngMYOIi13aR1kzVvX1sVDBgfGwDOkMbl4c54Xm3tlPx/+A== - dependencies: - "@opencensus/core" "^0.0.8" - uuid "^3.2.1" - "@opentelemetry/api@^1.0.0": version "1.4.1" resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.4.1.tgz#ff22eb2e5d476fbc2450a196e40dd243cc20c28f" @@ -5635,9 +5605,9 @@ fsevents "2.3.2" "@pm2/agent@~2.0.0": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@pm2/agent/-/agent-2.0.1.tgz#0edffc54cd8ee2b12f90136264e7880f3f78c79d" - integrity sha512-QKHMm6yexcvdDfcNE7PL9D6uEjoQPGRi+8dh+rc4Hwtbpsbh5IAvZbz3BVGjcd4HaX6pt2xGpOohG7/Y2L4QLw== + version "2.0.4" + resolved "https://registry.yarnpkg.com/@pm2/agent/-/agent-2.0.4.tgz#a6699a6c57741492129776eb5a7abc15e50672cb" + integrity sha512-n7WYvvTJhHLS2oBb1PjOtgLpMhgImOq8sXkPBw6smeg9LJBWZjiEgPKOpR8mn9UJZsB5P3W4V/MyvNnp31LKeA== dependencies: async "~3.2.0" chalk "~3.0.0" @@ -5649,22 +5619,20 @@ nssocket "0.6.0" pm2-axon "~4.0.1" pm2-axon-rpc "~0.7.0" - proxy-agent "~5.0.0" - semver "~7.2.0" - ws "~7.4.0" + proxy-agent "~6.3.0" + semver "~7.5.0" + ws "~7.5.10" -"@pm2/io@~5.0.0": - version "5.0.0" - resolved "https://registry.yarnpkg.com/@pm2/io/-/io-5.0.0.tgz#623cbcaf6fe39375f20ac2e75497477a1b1ec5c5" - integrity sha512-3rToDVJaRoob5Lq8+7Q2TZFruoEkdORxwzFpZaqF4bmH6Bkd7kAbdPrI/z8X6k1Meq5rTtScM7MmDgppH6aLlw== +"@pm2/io@~6.0.1": + version "6.0.1" + resolved "https://registry.yarnpkg.com/@pm2/io/-/io-6.0.1.tgz#300fefa485317f26e3bc348da061ca395a52149a" + integrity sha512-KiA+shC6sULQAr9mGZ1pg+6KVW9MF8NpG99x26Lf/082/Qy8qsTCtnJy+HQReW1A9Rdf0C/404cz0RZGZro+IA== dependencies: - "@opencensus/core" "0.0.9" - "@opencensus/propagation-b3" "0.0.8" async "~2.6.1" debug "~4.3.1" eventemitter2 "^6.3.1" require-in-the-middle "^5.0.0" - semver "6.3.0" + semver "~7.5.4" shimmer "^1.2.0" signal-exit "^3.0.3" tslib "1.9.3" @@ -8810,7 +8778,7 @@ acorn-walk@^7.0.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== -acorn-walk@^8.0.0, acorn-walk@^8.0.2, acorn-walk@^8.2.0: +acorn-walk@^8.0.0, acorn-walk@^8.0.2: version "8.2.0" resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== @@ -8825,7 +8793,7 @@ acorn@^7.0.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4, acorn@^8.1.0, acorn@^8.7.0, acorn@^8.7.1, acorn@^8.8.0, acorn@^8.8.2, acorn@^8.9.0: +acorn@^8.0.4, acorn@^8.1.0, acorn@^8.7.1, acorn@^8.8.0, acorn@^8.8.2, acorn@^8.9.0: version "8.10.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== @@ -8850,7 +8818,7 @@ addressparser@1.0.1: resolved "https://registry.yarnpkg.com/addressparser/-/addressparser-1.0.1.tgz#47afbe1a2a9262191db6838e4fd1d39b40821746" integrity sha1-R6++GiqSYhkdtoOOT9HTm0CCF0Y= -agent-base@6, agent-base@^6.0.0, agent-base@^6.0.2: +agent-base@6, agent-base@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ== @@ -8935,14 +8903,14 @@ ajv@^8.0.0, ajv@^8.10.0, ajv@^8.12.0, ajv@^8.6.0, ajv@^8.8.0, ajv@^8.9.0: amp-message@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/amp-message/-/amp-message-0.1.2.tgz#a78f1c98995087ad36192a41298e4db49e3dfc45" - integrity sha1-p48cmJlQh602GSpBKY5NtJ49/EU= + integrity sha512-JqutcFwoU1+jhv7ArgW38bqrE+LQdcRv4NxNw0mp0JHQyB6tXesWRjtYKlDgHRY2o3JE5UTaBGUK8kSWUdxWUg== dependencies: amp "0.3.1" amp@0.3.1, amp@~0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/amp/-/amp-0.3.1.tgz#6adf8d58a74f361e82c1fa8d389c079e139fc47d" - integrity sha1-at+NWKdPNh6CwfqNOJwHnhOfxH0= + integrity sha512-OwIuC4yZaRogHKiuU5WlMR5Xk/jAcpPtawWL05Gj8Lvm2F6mwoJt4O/bHI+DHwG79vWd+8OFYM4/BzYqyRd3qw== analytics-node@^6.0.0: version "6.2.0" @@ -8959,9 +8927,9 @@ analytics-node@^6.0.0: uuid "^8.3.2" ansi-colors@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== ansi-escapes@^4.2.1, ansi-escapes@^4.3.0: version "4.3.2" @@ -9014,7 +8982,7 @@ any-promise@^1.0.0: resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= -anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.2: +anymatch@^3.0.0, anymatch@^3.0.3: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== @@ -9022,6 +8990,14 @@ anymatch@^3.0.0, anymatch@^3.0.3, anymatch@~3.1.2: normalize-path "^3.0.0" picomatch "^2.0.4" +anymatch@~3.1.2: + version "3.1.3" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e" + integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + api@^5.0.7: version "5.0.8" resolved "https://registry.yarnpkg.com/api/-/api-5.0.8.tgz#a58e70f9efaf68318206521cae5f659e87035f4c" @@ -9228,7 +9204,7 @@ ast-types@0.15.2: dependencies: tslib "^2.0.1" -ast-types@^0.13.2, ast-types@^0.13.4: +ast-types@^0.13.4: version "0.13.4" resolved "https://registry.yarnpkg.com/ast-types/-/ast-types-0.13.4.tgz#ee0d77b343263965ecc3fb62da16e7222b2b6782" integrity sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w== @@ -9240,14 +9216,6 @@ astral-regex@^2.0.0: resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" integrity sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ== -async-listener@^0.6.0: - version "0.6.10" - resolved "https://registry.yarnpkg.com/async-listener/-/async-listener-0.6.10.tgz#a7c97abe570ba602d782273c0de60a51e3e17cbc" - integrity sha512-gpuo6xOyF4D5DE5WvyqZdPA3NGhiT6Qf07l7DCB0wwDEsLvDIbCr6j9S5aj5Ch96dLace5tXVzWBZkxU/c5ohw== - dependencies: - semver "^5.3.0" - shimmer "^1.1.0" - async-retry@1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/async-retry/-/async-retry-1.3.1.tgz#139f31f8ddce50c0870b0ba558a6079684aaed55" @@ -9262,7 +9230,12 @@ async@^2.6.3, async@~2.6.1: dependencies: lodash "^4.17.14" -async@^3.2.0, async@^3.2.3, async@~3.2.0: +async@^3.2.0, async@~3.2.0: + version "3.2.5" + resolved "https://registry.yarnpkg.com/async/-/async-3.2.5.tgz#ebd52a8fdaf7a2289a24df399f8d8485c8a46b66" + integrity sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg== + +async@^3.2.3: version "3.2.3" resolved "https://registry.yarnpkg.com/async/-/async-3.2.3.tgz#ac53dafd3f4720ee9e8a160628f18ea91df196c9" integrity sha512-spZRyzKL5l5BZQrr/6m/SqFdBN0q3OCI0f9rjfBzCMBIP4p75P620rR3gTmaksNOhmzgdxcaxdNfMy6anrbM0g== @@ -9611,9 +9584,9 @@ bin-links@^3.0.0: write-file-atomic "^4.0.0" binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + version "2.3.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.3.0.tgz#f6e14a97858d327252200242d4ccfe522c445522" + integrity sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw== bl@^4.0.3, bl@^4.1.0: version "4.1.0" @@ -9627,12 +9600,12 @@ bl@^4.0.3, bl@^4.1.0: blessed@0.1.81: version "0.1.81" resolved "https://registry.yarnpkg.com/blessed/-/blessed-0.1.81.tgz#f962d687ec2c369570ae71af843256e6d0ca1129" - integrity sha1-+WLWh+wsNpVwrnGvhDJW5tDKESk= + integrity sha512-LoF5gae+hlmfORcG1M5+5XZi4LBmvlXTzwJWzUlPryN/SJdSflZvROM2TwkT0GMpq7oqT48NRd4GS7BiVBc5OQ== bodec@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/bodec/-/bodec-0.1.0.tgz#bc851555430f23c9f7650a75ef64c6a94c3418cc" - integrity sha1-vIUVVUMPI8n3ZQp172TGqUw0GMw= + integrity sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ== body-parser@1.20.2: version "1.20.2" @@ -9685,13 +9658,20 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" +braces@~3.0.2: + version "3.0.3" + resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" + integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== + dependencies: + fill-range "^7.1.1" + browserslist@^4.14.5, browserslist@^4.21.1, browserslist@^4.21.4, browserslist@^4.21.9: version "4.21.10" resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.10.tgz#dbbac576628c13d3b2231332cb2ec5a46e015bb0" @@ -10058,7 +10038,7 @@ charenc@0.0.2: charm@~0.1.1: version "0.1.2" resolved "https://registry.yarnpkg.com/charm/-/charm-0.1.2.tgz#06c21eed1a1b06aeb67553cdc53e23274bac2296" - integrity sha1-BsIe7RobBq62dVPNxT4jJ0usIpY= + integrity sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ== chart.js@^3.8.0: version "3.8.0" @@ -10114,22 +10094,7 @@ chokidar@^3.3.1, chokidar@^3.4.0, chokidar@^3.5.1: optionalDependencies: fsevents "~2.3.2" -chokidar@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -chokidar@^3.6.0: +chokidar@^3.5.3, chokidar@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== @@ -10531,7 +10496,7 @@ compute-lcm@^1.1.2: concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" - integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= + integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg== concat-stream@^2.0.0: version "2.0.0" @@ -10602,14 +10567,6 @@ content-type@~1.0.5: resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== -continuation-local-storage@^3.2.1: - version "3.2.1" - resolved "https://registry.yarnpkg.com/continuation-local-storage/-/continuation-local-storage-3.2.1.tgz#11f613f74e914fe9b34c92ad2d28fe6ae1db7ffb" - integrity sha512-jx44cconVqkCEEyLSKWwkvUXwO561jXMa3LPjTPsm5QR22PA0/mhe33FT4Xb5y74JDvt/Cq+5lm8S8rskLv9ZA== - dependencies: - async-listener "^0.6.0" - emitter-listener "^1.1.1" - conventional-changelog-angular@^5.0.12: version "5.0.13" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-5.0.13.tgz#896885d63b914a70d4934b59d2fe7bde1832b28c" @@ -10954,7 +10911,7 @@ csstype@^3.0.2, csstype@^3.1.0: culvert@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/culvert/-/culvert-0.1.2.tgz#9502f5f0154a2d5a22a023e79f71cc936fa6ef6f" - integrity sha1-lQL18BVKLVoioCPnn3HMk2+m728= + integrity sha512-yi1x3EAWKjQTreYWeSd98431AV+IEE0qoDyOoaHJ7KJ21gv6HtBXHVLX74opVSGqcR8/AbjJBHAHpcOy2bj5Gg== d@1, d@^1.0.1: version "1.0.1" @@ -10981,11 +10938,6 @@ dashdash@^1.12.0: dependencies: assert-plus "^1.0.0" -data-uri-to-buffer@3: - version "3.0.1" - resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-3.0.1.tgz#594b8973938c5bc2c33046535785341abc4f3636" - integrity sha512-WboRycPNsVw3B3TL559F7kuBUM4d8CgMEvk6xEJlOp7OBPjt6G7z8WMWlD2rOFZLk6OYfFIUGsCOWzcQH9K2og== - data-uri-to-buffer@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/data-uri-to-buffer/-/data-uri-to-buffer-6.0.1.tgz#540bd4c8753a25ee129035aebdedf63b078703c7" @@ -11036,11 +10988,16 @@ dateformat@^3.0.0: resolved "https://registry.yarnpkg.com/dateformat/-/dateformat-3.0.3.tgz#a6e37499a4d9a9cf85ef5872044d62901c9889ae" integrity sha512-jyCETtSl3VMZMWeRo7iY1FL19ges1t55hMo5yaam4Jrsm5EPL89UQkoQRyiI+Yf4k8r2ZpdngkV8hr1lIdjb3Q== -dayjs@^1.11.3, dayjs@~1.11.5: +dayjs@^1.11.3: version "1.11.9" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.9.tgz#9ca491933fadd0a60a2c19f6c237c03517d71d1a" integrity sha512-QvzAURSbQ0pKdIye2txOzNaHmxtUBXerpY0FJsFXUMKbIZeFm5ht1LS/jFsrncjnmtv8HsG0W2g6c0zUjZWmpA== +dayjs@~1.11.5: + version "1.11.12" + resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.11.12.tgz#5245226cc7f40a15bf52e0b99fd2a04669ccac1d" + integrity sha512-Rt2g+nTbLlDWZTwwrIXjy9MeiZmSDI375FvZs72ngxx8PDC6YXOeR3q5LAuPzjZQxhiWdRKac7RKV+YyQYfYIg== + dayjs@~1.8.24: version "1.8.36" resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.36.tgz#be36e248467afabf8f5a86bae0de0cdceecced50" @@ -11100,10 +11057,10 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.2.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4, debug@~4.3.1: - version "4.3.4" - resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" - integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== +debug@4, debug@^4.1.1, debug@^4.3.1, debug@~4.3.1: + version "4.3.5" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.5.tgz#e83444eceb9fedd4a1da56d671ae2446a01a6e1e" + integrity sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg== dependencies: ms "2.1.2" @@ -11114,6 +11071,13 @@ debug@^3.2.6: dependencies: ms "^2.1.1" +debug@^4.1.0, debug@^4.2.0, debug@^4.3.2, debug@^4.3.3, debug@^4.3.4: + version "4.3.4" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" + integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== + dependencies: + ms "2.1.2" + debuglog@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/debuglog/-/debuglog-1.0.1.tgz#aa24ffb9ac3df9a2351837cfb2d279360cd78492" @@ -11166,7 +11130,7 @@ deep-extend@0.6.0, deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== -deep-is@^0.1.3, deep-is@~0.1.3: +deep-is@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831" integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ== @@ -11230,16 +11194,6 @@ defined@^1.0.0: resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.1.tgz#c0b9db27bfaffd95d6f61399419b893df0f91ebf" integrity sha512-hsBd2qSVCRE+5PmNdHt1uzyrFu5d3RwmFDKzyNZMFq/EwDNJF7Ee5+D5oEKF0hU6LhtoUF1macFvOe4AskQC1Q== -degenerator@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-3.0.1.tgz#7ef78ec0c8577a544477308ddf1d2d6e88d51f5b" - integrity sha512-LFsIFEeLPlKvAKXu7j3ssIG6RT0TbI7/GhsqrI0DnHASEQjXQ0LUSYcjJteGgRGmZbl1TnMSxpNQIAiJ7Du5TQ== - dependencies: - ast-types "^0.13.2" - escodegen "^1.8.1" - esprima "^4.0.0" - vm2 "^3.9.3" - degenerator@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/degenerator/-/degenerator-5.0.1.tgz#9403bf297c6dad9a1ece409b37db27954f91f2f5" @@ -11665,13 +11619,6 @@ email-addresses@^3.0.1: resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-3.1.0.tgz#cabf7e085cbdb63008a70319a74e6136188812fb" integrity sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg== -emitter-listener@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/emitter-listener/-/emitter-listener-1.1.2.tgz#56b140e8f6992375b3d7cb2cab1cc7432d9632e8" - integrity sha512-Bt1sBAGFHY9DKY+4/2cV6izcKJUf5T7/gkdmkxzX/qv9CcGH8xSwVRW5mtX03SWJtRTWSOpzCuWN9rBFYZepZQ== - dependencies: - shimmer "^1.2.0" - emittery@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.13.1.tgz#c04b8c3457490e0847ae51fced3af52d338e3dad" @@ -11895,18 +11842,6 @@ escape-string-regexp@^4.0.0: resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== -escodegen@^1.8.1: - version "1.14.3" - resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503" - integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw== - dependencies: - esprima "^4.0.1" - estraverse "^4.2.0" - esutils "^2.0.2" - optionator "^0.8.1" - optionalDependencies: - source-map "~0.6.1" - escodegen@^2.0.0, escodegen@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17" @@ -12063,7 +11998,7 @@ esrecurse@^4.3.0: dependencies: estraverse "^5.2.0" -estraverse@^4.1.1, estraverse@^4.2.0: +estraverse@^4.1.1: version "4.3.0" resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d" integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw== @@ -12109,17 +12044,17 @@ eventemitter-asyncresource@^1.0.0: eventemitter2@5.0.1, eventemitter2@~5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-5.0.1.tgz#6197a095d5fb6b57e8942f6fd7eaad63a09c9452" - integrity sha1-YZegldX7a1folC9v1+qtY6CclFI= + integrity sha512-5EM1GHXycJBS6mauYAbVKT1cVs7POKWb2NXD4Vyt8dDqeZa7LaDK1/sjtL+Zb0lzTpSNil4596Dyu97hz37QLg== eventemitter2@^6.3.1: - version "6.4.5" - resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.5.tgz#97380f758ae24ac15df8353e0cc27f8b95644655" - integrity sha512-bXE7Dyc1i6oQElDG0jMRZJrRAn9QR2xyyFGmBdZleNmyQX0FqGYmhZIrIrpPfm/w//LTo4tVQGOGQcGCb5q9uw== + version "6.4.9" + resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-6.4.9.tgz#41f2750781b4230ed58827bc119d293471ecb125" + integrity sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg== eventemitter2@~0.4.14: version "0.4.14" resolved "https://registry.yarnpkg.com/eventemitter2/-/eventemitter2-0.4.14.tgz#8f61b75cde012b2e9eb284d4545583b5643b61ab" - integrity sha1-j2G3XN4BKy6esoTUVFWDtWQ7Yas= + integrity sha512-K7J4xq5xAD5jHsGM5ReWXRTFa3JRGofHiMcVgQ8PRwgWxzjHpMWCIzsmyf60+mh8KLsqYPcjUMa0AC4hd6lPyQ== eventemitter3@^4.0.0, eventemitter3@^4.0.4: version "4.0.7" @@ -12340,7 +12275,7 @@ fast-json-stringify@^5.7.0: json-schema-ref-resolver "^1.0.1" rfdc "^1.2.0" -fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6: +fast-levenshtein@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= @@ -12435,7 +12370,7 @@ fbjs@^0.8.15, fbjs@^3.0.0, fbjs@^3.0.2, fbjs@^3.0.4: fclone@1.0.11, fclone@~1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/fclone/-/fclone-1.0.11.tgz#10e85da38bfea7fc599341c296ee1d77266ee640" - integrity sha1-EOhdo4v+p/xZk0HClu4ddyZu5kA= + integrity sha512-GDqVQezKzRABdeqflsgMr7ktzgF9CyS+p2oe0jJqUY6izSSbhPIQJDpoU4PtGcD7VPM9xh/dVrTu6z1nwgmEGw== fetch-har@^8.1.5: version "8.1.5" @@ -12470,11 +12405,6 @@ file-loader@6.2.0: loader-utils "^2.0.0" schema-utils "^3.0.0" -file-uri-to-path@2: - version "2.0.0" - resolved "https://registry.yarnpkg.com/file-uri-to-path/-/file-uri-to-path-2.0.0.tgz#7b415aeba227d575851e0a5b0c640d7656403fba" - integrity sha512-hjPFI8oE/2iQPVe4gbrJ73Pp+Xfub2+WI2LlXDbsaJBwT5wuMh35WNWVYYTpnz895shtwfyutMFLFywpQAFdLg== - filelist@^1.0.1: version "1.0.3" resolved "https://registry.yarnpkg.com/filelist/-/filelist-1.0.3.tgz#448607750376484932f67ef1b9ff07386b036c83" @@ -12482,10 +12412,10 @@ filelist@^1.0.1: dependencies: minimatch "^5.0.1" -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== +fill-range@^7.0.1, fill-range@^7.1.1: + version "7.1.1" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.1.1.tgz#44265d3cac07e3ea7dc247516380643754a05292" + integrity sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg== dependencies: to-regex-range "^5.0.1" @@ -12744,27 +12674,19 @@ fs-readdir-recursive@^1.1.0: fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" - integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= + integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw== -fsevents@2.3.2, fsevents@^2.3.2, fsevents@~2.3.2: +fsevents@2.3.2, fsevents@^2.3.2: version "2.3.2" resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== -ftp@^0.3.10: - version "0.3.10" - resolved "https://registry.yarnpkg.com/ftp/-/ftp-0.3.10.tgz#9197d861ad8142f3e63d5a83bfe4c59f7330885d" - integrity sha1-kZfYYa2BQvPmPVqDv+TFn3MwiF0= - dependencies: - readable-stream "1.1.x" - xregexp "2.0.0" - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== +fsevents@~2.3.2: + version "2.3.3" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== -function-bind@^1.1.2: +function-bind@^1.1.1, function-bind@^1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.2.tgz#2c02d864d97f3ea6c8830c464cbd11ab6eab7a1c" integrity sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA== @@ -12920,18 +12842,6 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" -get-uri@3: - version "3.0.2" - resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-3.0.2.tgz#f0ef1356faabc70e1f9404fa3b66b2ba9bfc725c" - integrity sha512-+5s0SJbGoyiJTZZ2JTpFPLMPSch72KEqGOTvQsBqg0RBWvwhWUSYZFAtz3TPW0GXJuLBJPts1E241iHg+VRfhg== - dependencies: - "@tootallnate/once" "1" - data-uri-to-buffer "3" - debug "4" - file-uri-to-path "2" - fs-extra "^8.1.0" - ftp "^0.3.10" - get-uri@^6.0.1: version "6.0.2" resolved "https://registry.yarnpkg.com/get-uri/-/get-uri-6.0.2.tgz#e019521646f4a8ff6d291fbaea2c46da204bb75b" @@ -12963,7 +12873,7 @@ git-diff@^2.0.6: git-node-fs@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/git-node-fs/-/git-node-fs-1.0.0.tgz#49b215e242ebe43aa4c7561bbba499521752080f" - integrity sha1-SbIV4kLr5Dqkx1Ybu6SZUhdSCA8= + integrity sha512-bLQypt14llVXBg0S0u8q8HmU7g9p3ysH+NvVlae5vILuUvs759665HvmR5+wb04KjHyjFcDRxdYb4kyNnluMUQ== git-raw-commits@^2.0.8: version "2.0.11" @@ -12995,7 +12905,7 @@ git-semver-tags@^4.1.1: git-sha1@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/git-sha1/-/git-sha1-0.1.2.tgz#599ac192b71875825e13a445f3a6e05118c2f745" - integrity sha1-WZrBkrcYdYJeE6RF86bgURjC90U= + integrity sha512-2e/nZezdVlyCopOCYHeW0onkbZg7xP1Ad6pndPy1rCygeRykefUS6r7oA5cJRGEFvseiaz5a/qUHFVX1dd6Isg== git-up@^6.0.0: version "6.0.0" @@ -13082,7 +12992,7 @@ glob@^10.3.7: minipass "^7.0.4" path-scurry "^1.10.2" -glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: +glob@^7.0.0, glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q== @@ -13438,11 +13348,9 @@ has-unicode@^2.0.0, has-unicode@^2.0.1: integrity sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ== has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" + version "1.0.4" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.4.tgz#2eb2860e000011dae4f1406a86fe80e530fb2ec6" + integrity sha512-qdSAmqLF6209RFj4VVItywPMbm3vWylknmB3nvNiUIs72xAimcM8nVYxYr7ncvZq5qzk9MKIZR8ijqD/1QuYjQ== hasown@^2.0.0: version "2.0.0" @@ -13451,6 +13359,13 @@ hasown@^2.0.0: dependencies: function-bind "^1.1.2" +hasown@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/hasown/-/hasown-2.0.2.tgz#003eaf91be7adc372e84ec59dc37252cedb80003" + integrity sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ== + dependencies: + function-bind "^1.1.2" + hdr-histogram-js@^2.0.1: version "2.0.3" resolved "https://registry.yarnpkg.com/hdr-histogram-js/-/hdr-histogram-js-2.0.3.tgz#0b860534655722b6e3f3e7dca7b78867cf43dcb5" @@ -13643,7 +13558,7 @@ http-parser-js@>=0.5.1: resolved "https://registry.yarnpkg.com/http-parser-js/-/http-parser-js-0.5.5.tgz#d7c30d5d3c90d865b4a2e870181f9d6f22ac7ac5" integrity sha512-x+JVEkO2PoM8qqpbPbOL3cqHPwerep7OwzK7Ay+sMQjKzaKCqWvjoXm5tqMP9tXWWTnTzAjIhXg+J99XYuPhPA== -http-proxy-agent@^4.0.0, http-proxy-agent@^4.0.1: +http-proxy-agent@^4.0.1: version "4.0.1" resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a" integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg== @@ -13702,7 +13617,7 @@ http2-client@^1.2.5: resolved "https://registry.yarnpkg.com/http2-client/-/http2-client-1.3.5.tgz#20c9dc909e3cc98284dd20af2432c524086df181" integrity sha512-EC2utToWl4RKfs5zd36Mxq7nzHHBuomZboI0yYL6Y0RmBgT7Sgkq4rQ0ezFTYoIsSs7Tm9SJe+o2FcAg6GBhGA== -https-proxy-agent@5, https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: +https-proxy-agent@^5.0.0, https-proxy-agent@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6" integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA== @@ -13895,12 +13810,12 @@ infer-owner@^1.0.4: inflight@^1.0.4: version "1.0.6" resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" - integrity sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk= + integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA== dependencies: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.1, inherits@~2.0.3: +inherits@2, inherits@2.0.4, inherits@^2.0.1, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -14018,7 +13933,7 @@ ioredis@^5.2.3: redis-parser "^3.0.0" standard-as-callback "^2.1.0" -ip@^1.1.5, ip@^1.1.8: +ip@^1.1.8: version "1.1.9" resolved "https://registry.yarnpkg.com/ip/-/ip-1.1.9.tgz#8dfbcc99a754d07f425310b86a99546b1151e396" integrity sha512-cyRxvOEpNHNtchU3Ln9KC/auJgup87llfQpQ+t5ghoC/UhL16SWzbueiCsdTnWmqAWl7LadfuwhlqmtOaqMHdQ== @@ -14107,13 +14022,20 @@ is-core-module@^2.13.0: dependencies: hasown "^2.0.0" -is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.1, is-core-module@^2.9.0: +is-core-module@^2.2.0, is-core-module@^2.5.0, is-core-module@^2.8.1: version "2.11.0" resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144" integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw== dependencies: has "^1.0.3" +is-core-module@^2.9.0: + version "2.15.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.15.0.tgz#71c72ec5442ace7e76b306e9d48db361f22699ea" + integrity sha512-Dd+Lb2/zvk9SKy1TGCt1wFJFo/MWBPMX5x7KcvLajWTGuomczdQX61PvY5yK6SVACwpoexWo81IfFyoKY2QnTA== + dependencies: + hasown "^2.0.2" + is-date-object@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" @@ -14146,7 +14068,7 @@ is-extendable@^1.0.0: is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== is-fullwidth-code-point@^1.0.0: version "1.0.0" @@ -14425,7 +14347,7 @@ is-wsl@^3.1.0: isarray@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" - integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== isarray@~1.0.0: version "1.0.0" @@ -14945,7 +14867,7 @@ jose@^4.11.4: js-git@^0.7.8: version "0.7.8" resolved "https://registry.yarnpkg.com/js-git/-/js-git-0.7.8.tgz#52fa655ab61877d6f1079efc6534b554f31e5444" - integrity sha1-UvplWrYYd9bxB578ZTS1VPMeVEQ= + integrity sha512-+E5ZH/HeRnoc/LW0AmAyhU+mNcWBzAKE+30+IDMLSLbbK+Tdt02AdkOKq9u15rlJsDEGFqtgckc8ZM59LhhiUA== dependencies: bodec "^0.1.0" culvert "^0.1.2" @@ -14965,7 +14887,7 @@ js-yaml@3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@4.1.0, js-yaml@^4.0.0, js-yaml@^4.1.0: +js-yaml@4.1.0, js-yaml@^4.0.0, js-yaml@^4.1.0, js-yaml@~4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -15141,7 +15063,7 @@ json-stringify-nice@^1.1.4: json-stringify-safe@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" - integrity sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus= + integrity sha512-ZClg6AaYvamvYEE82d3Iyd3vSSIjQ+odgjaTzRuO3s7toCdFKczob2i0zCh7JE8kWn17yvAWhUVxvqGwUalsRA== json-to-ast@^2.0.3: version "2.1.0" @@ -15181,7 +15103,7 @@ jsonc-parser@3.2.0: jsonfile@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb" - integrity sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss= + integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg== optionalDependencies: graceful-fs "^4.1.6" @@ -15344,7 +15266,7 @@ launch-editor@^2.6.1: lazy@~1.0.11: version "1.0.11" resolved "https://registry.yarnpkg.com/lazy/-/lazy-1.0.11.tgz#daa068206282542c088288e975c297c1ae77b690" - integrity sha1-2qBoIGKCVCwIgojpdcKXwa53tpA= + integrity sha512-Y+CjUfLmIpoUCCRl0ub4smrYtGGr5AOa2AKOaWelGHOGz33X/Y/KizefGqbkwfz44+cnq/+9habclf8vOmu2LA== lerna@^6.4.1: version "6.4.1" @@ -15390,14 +15312,6 @@ levn@^0.4.1: prelude-ls "^1.2.1" type-check "~0.4.0" -levn@~0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" - integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4= - dependencies: - prelude-ls "~1.1.2" - type-check "~0.3.2" - libbase64@0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/libbase64/-/libbase64-0.1.0.tgz#62351a839563ac5ff5bd26f12f60e9830bb751e6" @@ -15751,11 +15665,6 @@ lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17 resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-driver@^1.2.7: - version "1.2.7" - resolved "https://registry.yarnpkg.com/log-driver/-/log-driver-1.2.7.tgz#63b95021f0702fedfa2c9bb0a24e7797d71871d8" - integrity sha512-U7KCmLdqsGHBLeWqYlFA0V0Sl6P08EE1ZrmA9cxjUE0WVqT9qnyVDPz1kzpFEP0jdJuFnasWIfSd7fsaNXkpbg== - log-symbols@^4.0.0, log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" @@ -16358,7 +16267,7 @@ modify-values@^1.0.0: module-details-from-path@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" - integrity sha1-EUyUlnPiqKNenTV4hSeqN7Z52is= + integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== moment@^2.29.3: version "2.29.4" @@ -16510,7 +16419,7 @@ nest-graphql-endpoint@mattkrick/nest-graphql-endpoint#add-options: tslib "~2.3.1" typescript "^4.2.4" -netmask@^2.0.1, netmask@^2.0.2: +netmask@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/netmask/-/netmask-2.0.2.tgz#8b01a07644065d536383835823bc52004ebac5e7" integrity sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg== @@ -16892,7 +16801,7 @@ npmlog@^6.0.0, npmlog@^6.0.2: nssocket@0.6.0: version "0.6.0" resolved "https://registry.yarnpkg.com/nssocket/-/nssocket-0.6.0.tgz#59f96f6ff321566f33c70f7dbeeecdfdc07154fa" - integrity sha1-Wflvb/MhVm8zxw99vu7N/cBxVPo= + integrity sha512-a9GSOIql5IqgWJR3F/JXG4KpJTA3Z53Cj0MeMvGpglytB1nxE4PdFNC0jINe27CS7cGivoynwc054EzCcT3M3w== dependencies: eventemitter2 "~0.4.14" lazy "~1.0.11" @@ -17152,7 +17061,7 @@ on-headers@~1.0.2: once@^1.3.0, once@^1.3.1, once@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" - integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E= + integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w== dependencies: wrappy "1" @@ -17236,18 +17145,6 @@ opentracing@>=0.12.1: resolved "https://registry.yarnpkg.com/opentracing/-/opentracing-0.14.5.tgz#891fa92cd90a24e64f99bc964370227310926c85" integrity sha512-XLKtEfHxqrWyF1fzxznsv78w3csW41ucHnjiKnfzZLD5FN8UBDZZL1i4q0FR29zjxXhm+2Hop+5Vr/b8tKIvEg== -optionator@^0.8.1: - version "0.8.3" - resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" - integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== - dependencies: - deep-is "~0.1.3" - fast-levenshtein "~2.0.6" - levn "~0.3.0" - prelude-ls "~1.1.2" - type-check "~0.3.2" - word-wrap "~1.2.3" - optionator@^0.9.3: version "0.9.3" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64" @@ -17429,21 +17326,6 @@ p-waterfall@^2.1.1: dependencies: p-reduce "^2.0.0" -pac-proxy-agent@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-5.0.0.tgz#b718f76475a6a5415c2efbe256c1c971c84f635e" - integrity sha512-CcFG3ZtnxO8McDigozwE3AqAw15zDvGH+OjXO4kzf7IkEKkQ4gxQ+3sdF50WmhQ4P/bVusXcqNE2S3XrNURwzQ== - dependencies: - "@tootallnate/once" "1" - agent-base "6" - debug "4" - get-uri "3" - http-proxy-agent "^4.0.1" - https-proxy-agent "5" - pac-resolver "^5.0.0" - raw-body "^2.2.0" - socks-proxy-agent "5" - pac-proxy-agent@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/pac-proxy-agent/-/pac-proxy-agent-7.0.1.tgz#6b9ddc002ec3ff0ba5fdf4a8a21d363bcc612d75" @@ -17458,15 +17340,6 @@ pac-proxy-agent@^7.0.1: pac-resolver "^7.0.0" socks-proxy-agent "^8.0.2" -pac-resolver@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-5.0.0.tgz#1d717a127b3d7a9407a16d6e1b012b13b9ba8dc0" - integrity sha512-H+/A6KitiHNNW+bxBKREk2MCGSxljfqRX76NjummWEYIat7ldVXRU3dhRIE3iXZ0nvGBk6smv3nntxKkzRL8NA== - dependencies: - degenerator "^3.0.1" - ip "^1.1.5" - netmask "^2.0.1" - pac-resolver@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/pac-resolver/-/pac-resolver-7.0.0.tgz#79376f1ca26baf245b96b34c339d79bff25e900c" @@ -17511,7 +17384,7 @@ pacote@^13.0.3, pacote@^13.6.1: pako@^0.2.5: version "0.2.9" resolved "https://registry.yarnpkg.com/pako/-/pako-0.2.9.tgz#f3f7522f4ef782348da8161bad9ecfd51bf83a75" - integrity sha1-8/dSL073gjSNqBYbrZ7P1Rv4OnU= + integrity sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA== pako@^1.0.10, pako@^1.0.3, pako@~1.0.2: version "1.0.11" @@ -17646,7 +17519,7 @@ path-extra@^1.0.2: path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" - integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18= + integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg== path-is-inside@^1.0.2: version "1.0.2" @@ -17809,9 +17682,9 @@ pidusage@^2.0.21: safe-buffer "^5.2.1" pidusage@~3.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-3.0.0.tgz#69108079724c9afdd958644b920bc40bac964044" - integrity sha512-8VJLToXhj+RYZGNVw8oxc7dS54iCQXUJ+MDFHezQ/fwF5B8W4OWodAMboc1wb08S/4LiHwAmkT4ohf/d3YPPsw== + version "3.0.2" + resolved "https://registry.yarnpkg.com/pidusage/-/pidusage-3.0.2.tgz#6faa5402b2530b3af2cf93d13bcf202889724a53" + integrity sha512-g0VU+y08pKw5M8EZ2rIGiEBaB8wrQMjYGFfW2QVIfyT8V+fq8YFLkvlz4bz5ljvFDJYNFCWT3PWqcRr2FKO81w== dependencies: safe-buffer "^5.2.1" @@ -17917,7 +17790,7 @@ pm2-deploy@~1.0.2: pm2-multimeter@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/pm2-multimeter/-/pm2-multimeter-0.1.2.tgz#1a1e55153d41a05534cea23cfe860abaa0eb4ace" - integrity sha1-Gh5VFT1BoFU0zqI8/oYKuqDrSs4= + integrity sha512-S+wT6XfyKfd7SJIBqRgOctGxaBzUOmVQzTAS+cg04TsEUObJVreha7lvCfX8zzGVr871XwCSnHUU7DQQ5xEsfA== dependencies: charm "~0.1.1" @@ -17932,13 +17805,13 @@ pm2-sysmonit@^1.2.8: systeminformation "^5.7" tx2 "~1.0.4" -pm2@^5.3.1: - version "5.3.1" - resolved "https://registry.yarnpkg.com/pm2/-/pm2-5.3.1.tgz#f4c1b1199aac2988e9079ca4f127adaa1a5d18ce" - integrity sha512-DLVQHpSR1EegaTaRH3KbRXxpPVaqYwAp3uHSCtCsS++LSErvk07WSxuUnntFblBRqNU/w2KQyqs12mSq5wurkg== +pm2@^5.4.2: + version "5.4.2" + resolved "https://registry.yarnpkg.com/pm2/-/pm2-5.4.2.tgz#34a50044cf772c5528d68e2713f84383ebb2e09b" + integrity sha512-ynVpBwZampRH3YWLwRepZpQ7X3MvpwLIaqIdFEeBYEhaXbHmEx2KqOdxGV4T54wvKBhH3LixvU1j1bK4/sq7Tw== dependencies: "@pm2/agent" "~2.0.0" - "@pm2/io" "~5.0.0" + "@pm2/io" "~6.0.1" "@pm2/js-api" "~0.8.0" "@pm2/pm2-version-check" latest async "~3.2.0" @@ -17953,6 +17826,7 @@ pm2@^5.3.1: enquirer "2.3.6" eventemitter2 "5.0.1" fclone "1.0.11" + js-yaml "~4.1.0" mkdirp "1.0.4" needle "2.4.0" pidusage "~3.0" @@ -17965,7 +17839,6 @@ pm2@^5.3.1: source-map-support "0.5.21" sprintf-js "1.1.2" vizion "~2.2.1" - yamljs "0.3.0" optionalDependencies: pm2-sysmonit "^1.2.8" @@ -18119,11 +17992,6 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prelude-ls@~1.1.2: - version "1.1.2" - resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" - integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ= - prettier-plugin-organize-imports@^3.2.4: version "3.2.4" resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz#77967f69d335e9c8e6e5d224074609309c62845e" @@ -18209,7 +18077,7 @@ promise@^7.1.1: promptly@^2: version "2.2.0" resolved "https://registry.yarnpkg.com/promptly/-/promptly-2.2.0.tgz#2a13fa063688a2a5983b161fff0108a07d26fc74" - integrity sha1-KhP6BjaIoqWYOxYf/wEIoH0m/HQ= + integrity sha512-aC9j+BZsRSSzEsXBNBwDnAxujdx19HycZoKgRgzWnS8eOHg1asuf9heuLprfbe739zY3IdUQx+Egv6Jn135WHA== dependencies: read "^1.0.4" @@ -18471,21 +18339,21 @@ proxy-agent@^6.4.0: proxy-from-env "^1.1.0" socks-proxy-agent "^8.0.2" -proxy-agent@~5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-5.0.0.tgz#d31405c10d6e8431fde96cba7a0c027ce01d633b" - integrity sha512-gkH7BkvLVkSfX9Dk27W6TyNOWWZWRilRfk1XxGNWOYJ2TuedAv1yFpCaU9QSBmBe716XOTNpYNOzhysyw8xn7g== +proxy-agent@~6.3.0: + version "6.3.1" + resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-6.3.1.tgz#40e7b230552cf44fd23ffaf7c59024b692612687" + integrity sha512-Rb5RVBy1iyqOtNl15Cw/llpeLH8bsb37gM1FUfKQ+Wck6xHlbAhWGUFiTRHtkjqGTA5pSHz6+0hrPW/oECihPQ== dependencies: - agent-base "^6.0.0" - debug "4" - http-proxy-agent "^4.0.0" - https-proxy-agent "^5.0.0" - lru-cache "^5.1.1" - pac-proxy-agent "^5.0.0" - proxy-from-env "^1.0.0" - socks-proxy-agent "^5.0.0" + agent-base "^7.0.2" + debug "^4.3.4" + http-proxy-agent "^7.0.0" + https-proxy-agent "^7.0.2" + lru-cache "^7.14.1" + pac-proxy-agent "^7.0.1" + proxy-from-env "^1.1.0" + socks-proxy-agent "^8.0.2" -proxy-from-env@^1.0.0, proxy-from-env@^1.1.0: +proxy-from-env@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== @@ -18622,16 +18490,6 @@ raw-body@2.5.2: iconv-lite "0.4.24" unpipe "1.0.0" -raw-body@^2.2.0: - version "2.5.1" - resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.1.tgz#fe1b1628b181b700215e5fd42389f98b71392857" - integrity sha512-qqJBtEyVgS0ZmPGdCFPWJ3FreoqvG4MVQln/kCgF7Olq95IbOp0/BWyMwbdtn4VTvkM8Y7khCQ2Xgk/tcrCXig== - dependencies: - bytes "3.1.2" - http-errors "2.0.0" - iconv-lite "0.4.24" - unpipe "1.0.0" - raw-loader@^4.0.2: version "4.0.2" resolved "https://registry.yarnpkg.com/raw-loader/-/raw-loader-4.0.2.tgz#1aac6b7d1ad1501e66efdac1522c73e59a584eb6" @@ -18985,16 +18843,6 @@ read@1, read@^1.0.4, read@^1.0.7: dependencies: mute-stream "~0.0.4" -readable-stream@1.1.x: - version "1.1.14" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.1.14.tgz#7cf4c54ef648e3813084c636dd2079e166c081d9" - integrity sha1-fPTFTvZI44EwhMY23SB54WbAgdk= - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.1" - isarray "0.0.1" - string_decoder "~0.10.x" - readable-stream@3, readable-stream@^3.0.0, readable-stream@^3.0.2, readable-stream@^3.0.6, readable-stream@^3.1.1, readable-stream@^3.4.0, readable-stream@^3.6.0: version "3.6.0" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-3.6.0.tgz#337bbda3adc0706bd3e024426a286d4b4b2c9198" @@ -19270,13 +19118,13 @@ require-from-string@^2.0.2: integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== require-in-the-middle@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-5.1.0.tgz#b768f800377b47526d026bbf5a7f727f16eb412f" - integrity sha512-M2rLKVupQfJ5lf9OvqFGIT+9iVLnTmjgbOmpil12hiSQNn5zJTKGPoIisETNjfK+09vP3rpm1zJajmErpr2sEQ== + version "5.2.0" + resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-5.2.0.tgz#4b71e3cc7f59977100af9beb76bf2d056a5a6de2" + integrity sha512-efCx3b+0Z69/LGJmm9Yvi4cqEdxnoGnxYxGxBghkkTTFeXRtTCmmhO0AnAfHz59k957uTSuy8WaHqOs8wbYUWg== dependencies: debug "^4.1.1" module-details-from-path "^1.0.3" - resolve "^1.12.0" + resolve "^1.22.1" require-main-filename@^2.0.0: version "2.0.0" @@ -19332,7 +19180,7 @@ resolve.exports@^2.0.0: resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-2.0.2.tgz#f8c934b8e6a13f539e38b7098e2e36134f01e800" integrity sha512-X2UW6Nw3n/aMgDVy+0rSqgHlv39WZAlZrXCdnbyEiKm17DSqHX4MmQMaST3FbeWR5FTuRcUwYAziZajji0Y7mg== -resolve@^1.0.0, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.9.0: +resolve@^1.0.0, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.22.1, resolve@^1.9.0: version "1.22.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== @@ -19341,7 +19189,7 @@ resolve@^1.0.0, resolve@^1.1.7, resolve@^1.10.0, resolve@^1.12.0, resolve@^1.14. path-parse "^1.0.7" supports-preserve-symlinks-flag "^1.0.0" -resolve@^1.1.6: +resolve@^1.1.6, resolve@^1.12.0: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -19552,11 +19400,16 @@ sanitizer@^0.1.3: resolved "https://registry.yarnpkg.com/sanitizer/-/sanitizer-0.1.3.tgz#d4f0af7475d9a7baf2a9e5a611718baa178a39e1" integrity sha1-1PCvdHXZp7ryqeWmEXGLqheKOeE= -sax@>=0.6.0, sax@^1.2.4: +sax@>=0.6.0: version "1.3.0" resolved "https://registry.yarnpkg.com/sax/-/sax-1.3.0.tgz#a5dbe77db3be05c9d1ee7785dbd3ea9de51593d0" integrity sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA== +sax@^1.2.4: + version "1.4.1" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.4.1.tgz#44cc8988377f126304d3b3fc1010c733b929ef0f" + integrity sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg== + saxes@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/saxes/-/saxes-6.0.0.tgz#fe5b4a4768df4f14a201b1ba6a65c1f3d9988cc5" @@ -19624,16 +19477,11 @@ semver-compare@^1.0.0: resolved "https://registry.yarnpkg.com/semver-compare/-/semver-compare-1.0.0.tgz#0dee216a1c941ab37e9efb1788f6afc5ff5537fc" integrity sha1-De4hahyUGrN+nvsXiPavxf9VN/w= -"semver@2 || 3 || 4 || 5", semver@^5.3.0, semver@^5.5.0, semver@^5.6.0, semver@^5.7.1: +"semver@2 || 3 || 4 || 5", semver@^5.6.0, semver@^5.7.1: version "5.7.2" resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.2.tgz#48d55db737c3287cd4835e17fa13feace1c41ef8" integrity sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g== -semver@6.3.0: - version "6.3.0" - resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d" - integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw== - semver@7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e" @@ -19646,7 +19494,7 @@ semver@7.3.4: dependencies: lru-cache "^6.0.0" -semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.2, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4: +semver@7.x, semver@^7.0.0, semver@^7.1.1, semver@^7.3.2, semver@^7.3.4, semver@^7.3.5, semver@^7.3.7, semver@^7.3.8, semver@^7.5.3, semver@^7.5.4, semver@~7.5.0, semver@~7.5.4: version "7.5.4" resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e" integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA== @@ -19658,10 +19506,10 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@~7.2.0: - version "7.2.3" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.2.3.tgz#3641217233c6382173c76bf2c7ecd1e1c16b0d8a" - integrity sha512-utbW9Z7ZxVvwiIWkdOMLOR9G/NFXh2aRucghkVrEMJWuC++r3lCkBC3LwqBinyHzGMAJxY5tn6VakZGHObq5ig== +semver@^7.2: + version "7.6.3" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" + integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== send@0.18.0: version "0.18.0" @@ -19820,7 +19668,7 @@ shelljs@^0.8.1: interpret "^1.0.0" rechoir "^0.6.2" -shimmer@^1.1.0, shimmer@^1.2.0: +shimmer@^1.2.0: version "1.2.1" resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== @@ -20000,15 +19848,6 @@ sockjs@^0.3.24: uuid "^8.3.2" websocket-driver "^0.7.4" -socks-proxy-agent@5, socks-proxy-agent@^5.0.0: - version "5.0.1" - resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-5.0.1.tgz#032fb583048a29ebffec2e6a73fca0761f48177e" - integrity sha512-vZdmnjb9a2Tz6WEQVIurybSwElwPxMZaIc7PzqbJTrezcKNznv6giT7J7tZDZ1BojVaa1jvO/UiUdhDVB0ACoQ== - dependencies: - agent-base "^6.0.2" - debug "4" - socks "^2.3.3" - socks-proxy-agent@^6.0.0: version "6.2.1" resolved "https://registry.yarnpkg.com/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz#2687a31f9d7185e38d530bef1944fe1f1496d6ce" @@ -20036,7 +19875,7 @@ socks-proxy-agent@^8.0.2: debug "^4.3.4" socks "^2.7.1" -socks@^2.3.3, socks@^2.6.2, socks@^2.7.1: +socks@^2.6.2, socks@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/socks/-/socks-2.7.1.tgz#d8e651247178fde79c0663043e07240196857d55" integrity sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ== @@ -20204,7 +20043,7 @@ sprintf-js@1.1.2: sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" - integrity sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw= + integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g== ssh2-streams@0.4.10: version "0.4.10" @@ -20430,11 +20269,6 @@ string_decoder@^1.1.1: dependencies: safe-buffer "~5.2.0" -string_decoder@~0.10.x: - version "0.10.31" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-0.10.31.tgz#62e203bc41766c6c28c9fc84301dab1c5310fa94" - integrity sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ= - string_decoder@~1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" @@ -20654,9 +20488,9 @@ symbol-tree@^3.2.4: integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw== systeminformation@^5.7: - version "5.21.11" - resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.21.11.tgz#c1a0b2f0f338903bb7bc1759db9fcd34b7312360" - integrity sha512-dIJEGoP5W7k4JJGje/b+inJrOL5hV9LPsUi5ndBvJydI80CVEcu2DZYgt6prdRErDi2SA4SqYd/WMR4b+u34mA== + version "5.22.11" + resolved "https://registry.yarnpkg.com/systeminformation/-/systeminformation-5.22.11.tgz#42be7b650ce0a8b940c06219a6647f6ab3f7a349" + integrity sha512-aLws5yi4KCHTb0BVvbodQY5bY8eW4asMRDTxTW46hqw9lGjACX6TlLdJrkdoHYRB0qs+MekqEq1zG7WDnWE8Ug== tailwind-merge@^1.13.2: version "1.13.2" @@ -21230,11 +21064,16 @@ tslib@^1.10.0, tslib@^1.11.1, tslib@^1.9.0, tslib@^1.9.3: resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg== -tslib@^2.0.0, tslib@^2.0.1, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1, "tslib@^2.4.1 || ^1.9.3", tslib@^2.5.0, tslib@^2.6.2: +tslib@^2.0.0, tslib@^2.0.3, tslib@^2.1.0, tslib@^2.2.0, tslib@^2.3.0, tslib@^2.3.1, tslib@^2.4.0, tslib@^2.4.1, "tslib@^2.4.1 || ^1.9.3", tslib@^2.5.0, tslib@^2.6.2: version "2.6.2" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.2.tgz#703ac29425e7b37cd6fd456e92404d46d1f3e4ae" integrity sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q== +tslib@^2.0.1: + version "2.6.3" + resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" + integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== + tslib@~2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.2.0.tgz#fb2c475977e35e241311ede2693cee1ec6698f5c" @@ -21260,7 +21099,7 @@ tunnel-agent@^0.6.0: tv4@^1.3.0: version "1.3.0" resolved "https://registry.yarnpkg.com/tv4/-/tv4-1.3.0.tgz#d020c846fadd50c855abb25ebaecc68fc10f7963" - integrity sha1-0CDIRvrdUMhVq7JeuuzGj8EPeWM= + integrity sha512-afizzfpJgvPr+eDkREK4MxJ/+r8nEEHcmitwgnPUqpaP+FpwQyadnxNoSACbgc/b1LsZYtODGoPiFxQrgJgjvw== tweetnacl@^0.14.3, tweetnacl@~0.14.0: version "0.14.5" @@ -21286,13 +21125,6 @@ type-check@^0.4.0, type-check@~0.4.0: dependencies: prelude-ls "^1.2.1" -type-check@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" - integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I= - dependencies: - prelude-ls "~1.1.2" - type-detect@4.0.8, type-detect@^4.0.0, type-detect@^4.0.5: version "4.0.8" resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c" @@ -21660,7 +21492,7 @@ utils-merge@1.0.1: resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA== -uuid@^3.2.1, uuid@^3.3.2: +uuid@^3.3.2: version "3.4.0" resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee" integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A== @@ -21788,14 +21620,6 @@ vizion@~2.2.1: ini "^1.3.5" js-git "^0.7.8" -vm2@^3.9.3: - version "3.9.18" - resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.18.tgz#d919848bee191a0410c5cc1c5aac58adfd03ce9a" - integrity sha512-iM7PchOElv6Uv6Q+0Hq7dcgDtWWT6SizYqVcvol+1WQc+E9HlgTCnPozbQNSP3yDV9oXHQOEQu530w2q/BCVZg== - dependencies: - acorn "^8.7.0" - acorn-walk "^8.2.0" - vscode-apollo-relay@^1.5.0: version "1.5.2" resolved "https://registry.yarnpkg.com/vscode-apollo-relay/-/vscode-apollo-relay-1.5.2.tgz#0893f56ba11debf85391aefa9241e8afd356c012" @@ -22133,11 +21957,6 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== -word-wrap@~1.2.3: - version "1.2.4" - resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.4.tgz#cb4b50ec9aca570abd1f52f33cd45b6c61739a9f" - integrity sha512-2V81OA4ugVo5pRo46hAoD2ivUJx8jXmWXfUkY4KFNw0hEptvN0QfH3K4nHiwzGeKl5rFKedV48QVoqYavy4YpA== - wordwrap@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" @@ -22342,7 +22161,7 @@ wrap-ansi@^8.1.0: wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" - integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= + integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ== write-file-atomic@^2.3.0, write-file-atomic@^2.4.2: version "2.4.3" @@ -22404,7 +22223,7 @@ write-pkg@^4.0.0: type-fest "^0.4.1" write-json-file "^3.2.0" -ws@7.4.6, ws@~7.4.0: +ws@7.4.6: version "7.4.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== @@ -22414,7 +22233,12 @@ ws@8.13.0, ws@^8.12.0, ws@^8.13.0, ws@^8.9.0: resolved "https://registry.yarnpkg.com/ws/-/ws-8.13.0.tgz#9a9fb92f93cf41512a0735c8f4dd09b8a1211cd0" integrity sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA== -ws@^7.0.0, ws@^7.3.1: +ws@^7.0.0, ws@~7.5.10: + version "7.5.10" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.10.tgz#58b5c20dc281633f6c19113f39b349bd8bd558d9" + integrity sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ== + +ws@^7.3.1: version "7.5.6" resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b" integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA== @@ -22472,11 +22296,6 @@ xpath@0.0.32, xpath@^0.0.32: resolved "https://registry.yarnpkg.com/xpath/-/xpath-0.0.32.tgz#1b73d3351af736e17ec078d6da4b8175405c48af" integrity sha512-rxMJhSIoiO8vXcWvSifKqhvV96GjiD5wYb8/QHdoRyQvraTpp4IEv944nhGausZZ3u7dhQXteZuZbaqfpB7uYw== -xregexp@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/xregexp/-/xregexp-2.0.0.tgz#52a63e56ca0b84a7f3a5f3d61872f126ad7a5943" - integrity sha1-UqY+VsoLhKfzpfPWGHLxJq16WUM= - xtend@^4.0.0, xtend@^4.0.2, xtend@~4.0.1: version "4.0.2" resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" @@ -22517,14 +22336,6 @@ yaml@^2.3.1: resolved "https://registry.yarnpkg.com/yaml/-/yaml-2.3.1.tgz#02fe0975d23cd441242aa7204e09fc28ac2ac33b" integrity sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ== -yamljs@0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/yamljs/-/yamljs-0.3.0.tgz#dc060bf267447b39f7304e9b2bfbe8b5a7ddb03b" - integrity sha512-C/FsVVhht4iPQYXOInoxUM/1ELSf9EsgKH34FofQOp6hwCPrW4vG4w5++TED3xRUo8gD7l0P1J1dLlDYzODsTQ== - dependencies: - argparse "^1.0.7" - glob "^7.0.5" - yamux-js@0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/yamux-js/-/yamux-js-0.1.2.tgz#a157e4922f8f0393725955c352b418f16259fd48" From 73a5881709f9345767c2a233cbe28716b6c3b8e1 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 24 Jul 2024 12:06:52 -0700 Subject: [PATCH 356/529] chore(rethinkdb): TeamMember: Phase 3 (#10003) Signed-off-by: Matt Krick --- codegen.json | 4 +- packages/server/database/rethinkDriver.ts | 5 - packages/server/database/types/TeamMember.ts | 55 ----- .../dataloader/foreignKeyLoaderMakers.ts | 17 ++ .../dataloader/primaryKeyLoaderMakers.ts | 4 + .../rethinkForeignKeyLoaderMakers.ts | 28 --- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../server/graphql/mutations/archiveTeam.ts | 10 +- .../mutations/helpers/createTeamAndLeader.ts | 5 +- .../mutations/helpers/removeTeamMember.ts | 12 +- .../helpers/sendPokerMeetingRevoteEvent.ts | 2 +- .../server/graphql/mutations/joinMeeting.ts | 4 +- .../graphql/mutations/promoteToTeamLead.ts | 10 - .../graphql/mutations/removeTeamMember.ts | 8 +- .../graphql/mutations/setPokerSpectate.ts | 7 +- .../graphql/mutations/startSprintPoker.ts | 2 +- .../graphql/mutations/toggleTeamDrawer.ts | 15 -- .../private/mutations/changeEmailDomain.ts | 7 - .../private/mutations/hardDeleteUser.ts | 1 - .../graphql/private/mutations/updateEmail.ts | 27 +-- .../graphql/private/typeDefs/_legacy.graphql | 17 -- .../mutations/acceptRequestToJoinDomain.ts | 4 +- .../public/mutations/updateUserProfile.ts | 36 +-- .../public/mutations/uploadUserImage.ts | 31 +-- .../RepoIntegrationQueryPayload.graphql | 16 ++ .../public/typeDefs/TeamMember.graphql | 133 ++++++++++ .../graphql/public/typeDefs/_legacy.graphql | 17 -- .../types/AcceptTeamInvitationPayload.ts | 2 +- .../AddTeamMemberIntegrationAuthSuccess.ts | 2 +- .../server/graphql/public/types/AgendaItem.ts | 2 +- .../server/graphql/public/types/Company.ts | 2 +- .../public/types/JiraServerIntegration.ts | 2 +- .../server/graphql/public/types/NewMeeting.ts | 2 +- .../public/types/NotifyTaskInvolves.ts | 2 +- .../RemoveTeamMemberIntegrationAuthSuccess.ts | 2 +- .../public/types/RemoveTeamMemberPayload.ts | 2 +- packages/server/graphql/public/types/Team.ts | 2 +- .../server/graphql/public/types/TeamMember.ts | 101 ++++++++ packages/server/graphql/queries/tasks.ts | 3 +- packages/server/graphql/resolvers.ts | 2 +- packages/server/graphql/types/Team.ts | 2 +- packages/server/graphql/types/TeamMember.ts | 228 +----------------- packages/server/postgres/types/index.d.ts | 5 +- .../safeMutations/acceptTeamInvitation.ts | 2 - .../safeMutations/insertNewTeamMember.ts | 43 ---- packages/server/utils/authorization.ts | 16 -- 46 files changed, 339 insertions(+), 561 deletions(-) delete mode 100644 packages/server/database/types/TeamMember.ts create mode 100644 packages/server/graphql/public/typeDefs/RepoIntegrationQueryPayload.graphql create mode 100644 packages/server/graphql/public/typeDefs/TeamMember.graphql delete mode 100644 packages/server/safeMutations/insertNewTeamMember.ts diff --git a/codegen.json b/codegen.json index 99f5a4b9baf..14c1806ab5f 100644 --- a/codegen.json +++ b/codegen.json @@ -111,7 +111,7 @@ "NotifyTaskInvolves": "../../database/types/NotificationTaskInvolves#default", "NotifyTeamArchived": "../../database/types/NotificationTeamArchived#default", "Organization": "./types/Organization#OrganizationSource", - "OrganizationUser": "../../postgres/types/index#OrganizationUser", + "OrganizationUser": "../../postgres/types/index#OrganizationUser as OrganizationUserDB", "PokerMeeting": "../../database/types/MeetingPoker#default as MeetingPoker", "PokerMeetingMember": "../../database/types/MeetingPokerMeetingMember#default as PokerMeetingMemberDB", "PokerTemplate": "../../database/types/PokerTemplate#default as PokerTemplateDB", @@ -145,7 +145,7 @@ "TeamHealthPhase": "./types/TeamHealthPhase#TeamHealthPhaseSource", "TeamHealthStage": "./types/TeamHealthStage#TeamHealthStageSource", "TeamInvitation": "../../database/types/TeamInvitation#default", - "TeamMember": "../../database/types/TeamMember#default as TeamMemberDB", + "TeamMember": "../../postgres/types/index#TeamMember as TeamMember", "TeamMemberIntegrationAuthWebhook": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", "TeamMemberIntegrationAuthOAuth1": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", "TeamMemberIntegrationAuthOAuth2": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index a8aa3ddd709..713d255a085 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -2,7 +2,6 @@ import {MasterPool, r} from 'rethinkdb-ts' import SlackAuth from '../database/types/SlackAuth' import SlackNotification from '../database/types/SlackNotification' import TeamInvitation from '../database/types/TeamInvitation' -import TeamMember from '../database/types/TeamMember' import {AnyMeeting, AnyMeetingSettings, AnyMeetingTeamMember} from '../postgres/types/Meeting' import {ScheduledJobUnion} from '../types/custom' import getRethinkConfig from './getRethinkConfig' @@ -156,10 +155,6 @@ export type RethinkSchema = { type: TeamInvitation index: 'email' | 'teamId' | 'token' } - TeamMember: { - type: TeamMember - index: 'teamId' | 'userId' - } TemplateDimension: { type: TemplateDimension index: 'teamId' | 'templateId' | 'scaleId' diff --git a/packages/server/database/types/TeamMember.ts b/packages/server/database/types/TeamMember.ts deleted file mode 100644 index 8ca3defbeb9..00000000000 --- a/packages/server/database/types/TeamMember.ts +++ /dev/null @@ -1,55 +0,0 @@ -import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' -import {TeamDrawer} from './../../../client/__generated__/ToggleTeamDrawerMutation.graphql' - -interface Input { - isNotRemoved?: boolean - isLead?: boolean - isSpectatingPoker?: boolean - email: string - openDrawer?: TeamDrawer - picture: string - preferredName: string - teamId: string - userId: string - updatedAt?: Date -} - -export default class TeamMember { - id: string - isNotRemoved: boolean - isLead: boolean - isSpectatingPoker: boolean - email: string - openDrawer: TeamDrawer | null - picture: string - preferredName: string - teamId: string - userId: string - createdAt: Date - updatedAt: Date - constructor(input: Input) { - const { - teamId, - email, - isLead, - isNotRemoved, - openDrawer, - picture, - preferredName, - userId, - isSpectatingPoker - } = input - this.id = toTeamMemberId(teamId, userId) - this.teamId = teamId - this.email = email - this.openDrawer = openDrawer || null - this.isLead = isLead || false - this.isSpectatingPoker = isSpectatingPoker || false - this.isNotRemoved = isNotRemoved === undefined ? true : isNotRemoved - this.picture = picture - this.preferredName = preferredName - this.userId = userId - this.createdAt = new Date() - this.updatedAt = new Date() - } -} diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 2f6cb7daf09..67ba8f37cd8 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -6,6 +6,23 @@ export const teamsByOrgIds = foreignKeyLoaderMaker('teams', 'orgId', (orgIds) => selectTeams().where('orgId', 'in', orgIds).where('isArchived', '=', false).execute() ) +export const teamMembersByTeamId = foreignKeyLoaderMaker('teamMembers', 'teamId', (teamIds) => + getKysely() + .selectFrom('TeamMember') + .selectAll() + .where('teamId', 'in', teamIds) + .where('isNotRemoved', '=', true) + .execute() +) + +export const teamMembersByUserId = foreignKeyLoaderMaker('teamMembers', 'userId', (userIds) => + getKysely() + .selectFrom('TeamMember') + .selectAll() + .where('userId', 'in', userIds) + .where('isNotRemoved', '=', true) + .execute() +) export const discussionsByMeetingId = foreignKeyLoaderMaker( 'discussions', 'meetingId', diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 5f199227b88..10b7d3c3530 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -146,3 +146,7 @@ export const saml = primaryKeyLoaderMaker((ids: readonly string[]) => { export const organizationUsers = primaryKeyLoaderMaker((ids: readonly string[]) => { return getKysely().selectFrom('OrganizationUser').selectAll().where('id', 'in', ids).execute() }) + +export const teamMembers = primaryKeyLoaderMaker((ids: readonly string[]) => { + return getKysely().selectFrom('TeamMember').selectAll().where('id', 'in', ids).execute() +}) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index 913c7dcc87f..1fbc779c4cf 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -221,31 +221,3 @@ export const teamInvitationsByTeamId = new RethinkForeignKeyLoaderMaker( .run() } ) - -export const teamMembersByTeamId = new RethinkForeignKeyLoaderMaker( - 'teamMembers', - 'teamId', - async (teamIds) => { - // tasksByUserId is expensive since we have to look up each team to check the team archive status - const r = await getRethink() - return r - .table('TeamMember') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter({isNotRemoved: true}) - .run() - } -) - -export const teamMembersByUserId = new RethinkForeignKeyLoaderMaker( - 'teamMembers', - 'userId', - async (userIds) => { - // tasksByUserId is expensive since we have to look up each team to check the team archive status - const r = await getRethink() - return r - .table('TeamMember') - .getAll(r.args(userIds), {index: 'userId'}) - .filter({isNotRemoved: true}) - .run() - } -) diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index ef78ee05b50..8c7f70d9be0 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -18,6 +18,5 @@ export const slackAuths = new RethinkPrimaryKeyLoaderMaker('SlackAuth') export const slackNotifications = new RethinkPrimaryKeyLoaderMaker('SlackNotification') export const suggestedActions = new RethinkPrimaryKeyLoaderMaker('SuggestedAction') export const tasks = new RethinkPrimaryKeyLoaderMaker('Task') -export const teamMembers = new RethinkPrimaryKeyLoaderMaker('TeamMember') export const teamInvitations = new RethinkPrimaryKeyLoaderMaker('TeamInvitation') export const templateDimensions = new RethinkPrimaryKeyLoaderMaker('TemplateDimension') diff --git a/packages/server/graphql/mutations/archiveTeam.ts b/packages/server/graphql/mutations/archiveTeam.ts index aaa5ec888f6..6b447ab44c1 100644 --- a/packages/server/graphql/mutations/archiveTeam.ts +++ b/packages/server/graphql/mutations/archiveTeam.ts @@ -1,12 +1,13 @@ import {GraphQLID, GraphQLNonNull, GraphQLObjectType} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import TeamMemberId from '../../../client/shared/gqlIds/TeamMemberId' import {maybeRemoveRestrictions} from '../../billing/helpers/teamLimitsCheck' import getRethink from '../../database/rethinkDriver' import NotificationTeamArchived from '../../database/types/NotificationTeamArchived' import removeMeetingTemplatesForTeam from '../../postgres/queries/removeMeetingTemplatesForTeam' import safeArchiveTeam from '../../safeMutations/safeArchiveTeam' import {analytics} from '../../utils/analytics/analytics' -import {getUserId, isSuperUser, isTeamLead, isUserOrgAdmin} from '../../utils/authorization' +import {getUserId, isSuperUser, isUserOrgAdmin} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' @@ -35,13 +36,14 @@ export default { // AUTH const viewerId = getUserId(authToken) - const [teamLead, viewer, teamToArchive] = await Promise.all([ - isTeamLead(viewerId, teamId, dataLoader), + const [teamMember, viewer, teamToArchive] = await Promise.all([ + dataLoader.get('teamMembers').load(TeamMemberId.join(teamId, viewerId)), dataLoader.get('users').loadNonNull(viewerId), dataLoader.get('teams').loadNonNull(teamId) ]) + const isTeamLead = teamMember?.isLead const isOrgAdmin = await isUserOrgAdmin(viewerId, teamToArchive.orgId, dataLoader) - if (!teamLead && !isSuperUser(authToken) && !isOrgAdmin) { + if (!isTeamLead && !isSuperUser(authToken) && !isOrgAdmin) { return standardError(new Error('Not team lead or org admin'), {userId: viewerId}) } diff --git a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts index b6a3f3c650a..92ca62973de 100644 --- a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts +++ b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts @@ -9,7 +9,6 @@ import TimelineEventCreatedTeam from '../../../database/types/TimelineEventCreat import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' import getKysely from '../../../postgres/getKysely' import IUser from '../../../postgres/types/IUser' -import insertNewTeamMember from '../../../safeMutations/insertNewTeamMember' interface ValidNewTeam { id: string @@ -68,9 +67,7 @@ export default async function createTeamAndLeader( .values(timelineEvent) .execute(), // add meeting settings - r.table('MeetingSettings').insert(meetingSettings).run(), - // denormalize common fields to team member - insertNewTeamMember(user, teamId, dataLoader) + r.table('MeetingSettings').insert(meetingSettings).run() ]) dataLoader.clearAll(['teams', 'users', 'teamMembers', 'timelineEvents', 'meetingSettings']) } diff --git a/packages/server/graphql/mutations/helpers/removeTeamMember.ts b/packages/server/graphql/mutations/helpers/removeTeamMember.ts index eba334bec0c..d9b92314e63 100644 --- a/packages/server/graphql/mutations/helpers/removeTeamMember.ts +++ b/packages/server/graphql/mutations/helpers/removeTeamMember.ts @@ -47,18 +47,12 @@ const removeTeamMember = async ( r.table('Task').getAll(teamId, {index: 'teamId'}).delete() ]) } else if (isLead) { + // assign new leader, remove old leader flag await pg .updateTable('TeamMember') .set(({not}) => ({isLead: not('isLead')})) .where('id', 'in', [teamMemberId, teamLeader.id]) .execute() - // assign new leader, remove old leader flag - await r({ - newTeamLead: r.table('TeamMember').get(teamLeader.id).update({ - isLead: true - }), - oldTeamLead: r.table('TeamMember').get(teamMemberId).update({isLead: false}) - }).run() } await pg @@ -68,10 +62,6 @@ const removeTeamMember = async ( .execute() // assign active tasks to the team lead const {integratedTasksToArchive, reassignedTasks} = await r({ - teamMember: r.table('TeamMember').get(teamMemberId).update({ - isNotRemoved: false, - updatedAt: now - }), integratedTasksToArchive: r .table('Task') .getAll(userId, {index: 'userId'}) diff --git a/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts b/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts index 2f736ae3770..9db1d61ab85 100644 --- a/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts +++ b/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts @@ -1,6 +1,6 @@ import Meeting from '../../../database/types/Meeting' import MeetingMember from '../../../database/types/MeetingMember' -import TeamMember from '../../../database/types/TeamMember' +import {TeamMember} from '../../../postgres/types' import {analytics} from '../../../utils/analytics/analytics' import {DataLoaderWorker} from '../../graphql' diff --git a/packages/server/graphql/mutations/joinMeeting.ts b/packages/server/graphql/mutations/joinMeeting.ts index 6528c0e37db..7be33d39e2f 100644 --- a/packages/server/graphql/mutations/joinMeeting.ts +++ b/packages/server/graphql/mutations/joinMeeting.ts @@ -10,11 +10,11 @@ import Meeting from '../../database/types/Meeting' import MeetingRetrospective from '../../database/types/MeetingRetrospective' import PokerMeetingMember from '../../database/types/PokerMeetingMember' import RetroMeetingMember from '../../database/types/RetroMeetingMember' -import TeamMember from '../../database/types/TeamMember' import TeamPromptMeetingMember from '../../database/types/TeamPromptMeetingMember' import TeamPromptResponseStage from '../../database/types/TeamPromptResponseStage' import UpdatesStage from '../../database/types/UpdatesStage' import insertDiscussions from '../../postgres/queries/insertDiscussions' +import {TeamMember} from '../../postgres/types' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' @@ -82,7 +82,7 @@ const joinMeeting = { return {error: {message: 'Not on the team'}} } const teamMemberId = toTeamMemberId(teamId, viewerId) - const teamMember = await dataLoader.get('teamMembers').load(teamMemberId) + const teamMember = await dataLoader.get('teamMembers').loadNonNull(teamMemberId) const meetingMember = createMeetingMember(meeting, teamMember) const {errors} = await r.table('MeetingMember').insert(meetingMember).run() // if this is called concurrently, only 1 will be error free diff --git a/packages/server/graphql/mutations/promoteToTeamLead.ts b/packages/server/graphql/mutations/promoteToTeamLead.ts index 68f9513bc3d..83cd3f9b4ca 100644 --- a/packages/server/graphql/mutations/promoteToTeamLead.ts +++ b/packages/server/graphql/mutations/promoteToTeamLead.ts @@ -1,7 +1,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import TeamMemberId from '../../../client/shared/gqlIds/TeamMemberId' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {getUserId, isSuperUser, isUserOrgAdmin} from '../../utils/authorization' import publish from '../../utils/publish' @@ -27,7 +26,6 @@ export default { {teamId, userId}: {teamId: string; userId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { - const r = await getRethink() const pg = getKysely() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -61,14 +59,6 @@ export default { .where('id', 'in', [oldLeadTeamMemberId, promoteeOnTeam.id]) .execute() dataLoader.clearAll('teamMembers') - await r({ - teamLead: r.table('TeamMember').get(oldLeadTeamMemberId).update({ - isLead: false - }), - promotee: r.table('TeamMember').get(promoteeOnTeam.id).update({ - isLead: true - }) - }).run() const data = {teamId, oldLeaderId: oldLeadTeamMemberId, newLeaderId: promoteeOnTeam.id} publish(SubscriptionChannel.TEAM, teamId, 'PromoteToTeamLeadPayload', data, subOptions) diff --git a/packages/server/graphql/mutations/removeTeamMember.ts b/packages/server/graphql/mutations/removeTeamMember.ts index f235589a70a..1e0f4feda90 100644 --- a/packages/server/graphql/mutations/removeTeamMember.ts +++ b/packages/server/graphql/mutations/removeTeamMember.ts @@ -1,7 +1,8 @@ import {GraphQLID, GraphQLNonNull, GraphQLObjectType} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import fromTeamMemberId from 'parabol-client/utils/relay/fromTeamMemberId' -import {getUserId, isTeamLead, isUserOrgAdmin} from '../../utils/authorization' +import TeamMemberId from '../../../client/shared/gqlIds/TeamMemberId' +import {getUserId, isUserOrgAdmin} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' @@ -31,10 +32,11 @@ export default { const viewerId = getUserId(authToken) const {userId, teamId} = fromTeamMemberId(teamMemberId) const team = await dataLoader.get('teams').loadNonNull(teamId) - const [isOrgAdmin, isViewerTeamLead] = await Promise.all([ + const [isOrgAdmin, teamMember] = await Promise.all([ isUserOrgAdmin(viewerId, team.orgId, dataLoader), - isTeamLead(viewerId, teamId, dataLoader) + dataLoader.get('teamMembers').loadNonNull(TeamMemberId.join(teamId, viewerId)) ]) + const isViewerTeamLead = teamMember?.isLead const isSelf = viewerId === userId if (!isSelf) { if (!isOrgAdmin && !isViewerTeamLead) { diff --git a/packages/server/graphql/mutations/setPokerSpectate.ts b/packages/server/graphql/mutations/setPokerSpectate.ts index 7f007738d8d..61b7fbf9868 100644 --- a/packages/server/graphql/mutations/setPokerSpectate.ts +++ b/packages/server/graphql/mutations/setPokerSpectate.ts @@ -32,7 +32,6 @@ const setPokerSpectate = { const r = await getRethink() const pg = getKysely() const viewerId = getUserId(authToken) - const now = new Date() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -68,11 +67,7 @@ const setPokerSpectate = { .where('id', '=', teamMemberId) .execute() await r({ - meetingMember: r.table('MeetingMember').get(meetingMemberId).update({isSpectating}), - teamMember: r - .table('TeamMember') - .get(teamMemberId) - .update({isSpectatingPoker: isSpectating, updatedAt: now}) + meetingMember: r.table('MeetingMember').get(meetingMemberId).update({isSpectating}) }).run() dataLoader.clearAll('teamMembers') // mutate the dataLoader cache diff --git a/packages/server/graphql/mutations/startSprintPoker.ts b/packages/server/graphql/mutations/startSprintPoker.ts index b25cc5364de..bfeb3c72af1 100644 --- a/packages/server/graphql/mutations/startSprintPoker.ts +++ b/packages/server/graphql/mutations/startSprintPoker.ts @@ -163,7 +163,7 @@ export default { } const teamMemberId = toTeamMemberId(teamId, viewerId) - const teamMember = await dataLoader.get('teamMembers').load(teamMemberId) + const teamMember = await dataLoader.get('teamMembers').loadNonNull(teamMemberId) const {isSpectatingPoker} = teamMember const updates = { lastMeetingType: meetingType diff --git a/packages/server/graphql/mutations/toggleTeamDrawer.ts b/packages/server/graphql/mutations/toggleTeamDrawer.ts index aa9ae06abb2..5d922a8d718 100644 --- a/packages/server/graphql/mutations/toggleTeamDrawer.ts +++ b/packages/server/graphql/mutations/toggleTeamDrawer.ts @@ -1,6 +1,4 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' -import getRethink from '../../database/rethinkDriver' -import {RValue} from '../../database/stricterR' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import standardError from '../../utils/standardError' @@ -27,7 +25,6 @@ const toggleTeamDrawer = { {teamId, teamDrawerType}: {teamId: string; teamDrawerType: TeamDrawer | null}, {authToken}: GQLContext ) => { - const r = await getRethink() const pg = getKysely() const viewerId = getUserId(authToken) @@ -47,18 +44,6 @@ const toggleTeamDrawer = { })) .where('id', '=', viewerTeamMemberId) .execute() - await r - .table('TeamMember') - .get(viewerTeamMemberId) - .update((teamMember: RValue) => ({ - openDrawer: r.branch( - teamMember('openDrawer').default(null).eq(teamDrawerType), - null, - teamDrawerType - ) - })) - .run() - return {teamMemberId: viewerTeamMemberId} } } diff --git a/packages/server/graphql/private/mutations/changeEmailDomain.ts b/packages/server/graphql/private/mutations/changeEmailDomain.ts index 0365503d134..fc0a3c384e9 100644 --- a/packages/server/graphql/private/mutations/changeEmailDomain.ts +++ b/packages/server/graphql/private/mutations/changeEmailDomain.ts @@ -82,13 +82,6 @@ const changeEmailDomain: MutationResolvers['changeEmailDomain'] = async ( .where('id', 'in', userIdsToUpdate) .returning('id') .execute(), - r - .table('TeamMember') - .filter((row: RDatum) => row('email').match(`@${normalizedOldDomain}$`)) - .update((row: RDatum) => ({ - email: row('email').split('@').nth(0).add(`@${normalizedNewDomain}`) - })) - .run(), r .table('Invoice') .filter((row: RDatum) => diff --git a/packages/server/graphql/private/mutations/hardDeleteUser.ts b/packages/server/graphql/private/mutations/hardDeleteUser.ts index 7bc41619d2b..9037ff5a653 100644 --- a/packages/server/graphql/private/mutations/hardDeleteUser.ts +++ b/packages/server/graphql/private/mutations/hardDeleteUser.ts @@ -87,7 +87,6 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( .filter((row: RValue) => row('createdBy').eq(userIdToDelete)) .update({createdBy: null}) .run(), - teamMember: r.table('TeamMember').getAll(userIdToDelete, {index: 'userId'}).delete(), meetingMember: r.table('MeetingMember').getAll(userIdToDelete, {index: 'userId'}).delete(), notification: r.table('Notification').getAll(userIdToDelete, {index: 'userId'}).delete(), suggestedAction: r.table('SuggestedAction').getAll(userIdToDelete, {index: 'userId'}).delete(), diff --git a/packages/server/graphql/private/mutations/updateEmail.ts b/packages/server/graphql/private/mutations/updateEmail.ts index e06fe860c56..bd1f0766da9 100644 --- a/packages/server/graphql/private/mutations/updateEmail.ts +++ b/packages/server/graphql/private/mutations/updateEmail.ts @@ -1,10 +1,8 @@ -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {getUserByEmail} from '../../../postgres/queries/getUsersByEmails' import {MutationResolvers} from '../resolverTypes' const updateEmail: MutationResolvers['updateEmail'] = async (_source, {oldEmail, newEmail}) => { - const r = await getRethink() const pg = getKysely() // VALIDATION @@ -19,23 +17,14 @@ const updateEmail: MutationResolvers['updateEmail'] = async (_source, {oldEmail, // RESOLUTION const {id: userId} = user - await Promise.all([ - pg - .with('TeamMemberUpdate', (qc) => - qc.updateTable('TeamMember').set({email: newEmail}).where('userId', '=', userId) - ) - .updateTable('User') - .set({email: newEmail}) - .where('id', '=', userId) - .execute(), - r - .table('TeamMember') - .getAll(userId, {index: 'userId'}) - .update({ - email: newEmail - }) - .run() - ]) + await pg + .with('TeamMemberUpdate', (qc) => + qc.updateTable('TeamMember').set({email: newEmail}).where('userId', '=', userId) + ) + .updateTable('User') + .set({email: newEmail}) + .where('id', '=', userId) + .execute() return true } diff --git a/packages/server/graphql/private/typeDefs/_legacy.graphql b/packages/server/graphql/private/typeDefs/_legacy.graphql index d702b048134..48da89dfeff 100644 --- a/packages/server/graphql/private/typeDefs/_legacy.graphql +++ b/packages/server/graphql/private/typeDefs/_legacy.graphql @@ -989,23 +989,6 @@ enum MeetingTypeEnum { teamPrompt } -""" -The details associated with the possible repo and project integrations -""" -type RepoIntegrationQueryPayload { - error: StandardMutationError - - """ - true if the items returned are a subset of all the possible integration, else false (all possible integrations) - """ - hasMore: Boolean! - - """ - All the integrations that are likely to be integrated - """ - items: [RepoIntegration!] -} - """ The scope of a shareable item """ diff --git a/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts b/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts index 33a5ada82b6..e67a1728c9f 100644 --- a/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts +++ b/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts @@ -6,7 +6,6 @@ import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' import adjustUserCount from '../../../billing/helpers/adjustUserCount' import getKysely from '../../../postgres/getKysely' import {getUserById} from '../../../postgres/queries/getUsersByIds' -import insertNewTeamMember from '../../../safeMutations/insertNewTeamMember' import {Logger} from '../../../utils/Logger' import RedisLock from '../../../utils/RedisLock' import {getUserId} from '../../../utils/authorization' @@ -108,8 +107,7 @@ const acceptRequestToJoinDomain: MutationResolvers['acceptRequestToJoinDomain'] openDrawer: 'manageTeam' }) .onConflict((oc) => oc.column('id').doUpdateSet({isNotRemoved: true})) - .execute(), - insertNewTeamMember(user, teamId, dataLoader) + .execute() ]) if (!organizationUser) { diff --git a/packages/server/graphql/public/mutations/updateUserProfile.ts b/packages/server/graphql/public/mutations/updateUserProfile.ts index 72816963520..759e6761ffa 100644 --- a/packages/server/graphql/public/mutations/updateUserProfile.ts +++ b/packages/server/graphql/public/mutations/updateUserProfile.ts @@ -1,7 +1,5 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import linkify from 'parabol-client/utils/linkify' -import getRethink from '../../../database/rethinkDriver' -import TeamMember from '../../../database/types/TeamMember' import getKysely from '../../../postgres/getKysely' import {analytics} from '../../../utils/analytics/analytics' import {getUserId, isAuthenticated} from '../../../utils/authorization' @@ -14,7 +12,6 @@ const updateUserProfile: MutationResolvers['updateUserProfile'] = async ( {updatedUser}, {authToken, dataLoader, socketId: mutatorId} ) => { - const r = await getRethink() const pg = getKysely() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -47,28 +44,17 @@ const updateUserProfile: MutationResolvers['updateUserProfile'] = async ( // RESOLUTION // propagate denormalized changes to TeamMember - const updateObj = { - preferredName: normalizedPreferredName - } - await Promise.all([ - pg - .with('TeamMemberUpdate', (qc) => - qc - .updateTable('TeamMember') - .set({preferredName: normalizedPreferredName}) - .where('userId', '=', userId) - ) - .updateTable('User') - .set({preferredName: normalizedPreferredName}) - .where('id', '=', userId) - .execute(), - r - .table('TeamMember') - .getAll(userId, {index: 'userId'}) - .update(updateObj, {returnChanges: true})('changes')('new_val') - .default([]) - .run() as unknown as TeamMember[] - ]) + await pg + .with('TeamMemberUpdate', (qc) => + qc + .updateTable('TeamMember') + .set({preferredName: normalizedPreferredName}) + .where('userId', '=', userId) + ) + .updateTable('User') + .set({preferredName: normalizedPreferredName}) + .where('id', '=', userId) + .execute() dataLoader.clearAll(['users', 'teamMembers']) const [user, teamMembers] = await Promise.all([ diff --git a/packages/server/graphql/public/mutations/uploadUserImage.ts b/packages/server/graphql/public/mutations/uploadUserImage.ts index 394f3053066..9208187d1f7 100644 --- a/packages/server/graphql/public/mutations/uploadUserImage.ts +++ b/packages/server/graphql/public/mutations/uploadUserImage.ts @@ -1,21 +1,18 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -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 updateUser from '../../../postgres/queries/updateUser' import {getUserId, isAuthenticated} from '../../../utils/authorization' import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' -import {MutationResolvers, TeamMember} from '../resolverTypes' +import {MutationResolvers} from '../resolverTypes' const uploadUserImage: MutationResolvers['uploadUserImage'] = async ( _, {file}, {authToken, dataLoader, socketId: mutatorId} ) => { - const r = await getRethink() const pg = getKysely() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -34,24 +31,14 @@ const uploadUserImage: MutationResolvers['uploadUserImage'] = async ( const manager = getFileStoreManager() const publicLocation = await manager.putUserAvatar(normalBuffer, userId, normalExt) - await Promise.all([ - pg - .with('TeamMemberUpdate', (qc) => - qc.updateTable('TeamMember').set({picture: publicLocation}).where('userId', '=', userId) - ) - .updateTable('User') - .set({picture: publicLocation}) - .where('id', '=', userId) - .execute(), - r - .table('TeamMember') - .getAll(userId, {index: 'userId'}) - .update({picture: publicLocation}, {returnChanges: true})('changes')('new_val') - .default([]) - .run() as unknown as TeamMember[], - - updateUser({picture: publicLocation}, userId) - ]) + await pg + .with('TeamMemberUpdate', (qc) => + qc.updateTable('TeamMember').set({picture: publicLocation}).where('userId', '=', userId) + ) + .updateTable('User') + .set({picture: publicLocation}) + .where('id', '=', userId) + .execute() dataLoader.clearAll(['users', 'teamMembers']) const teamMembers = await dataLoader.get('teamMembersByUserId').load(userId) const teamIds = teamMembers.map(({teamId}) => teamId) diff --git a/packages/server/graphql/public/typeDefs/RepoIntegrationQueryPayload.graphql b/packages/server/graphql/public/typeDefs/RepoIntegrationQueryPayload.graphql new file mode 100644 index 00000000000..9f8c653ee58 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/RepoIntegrationQueryPayload.graphql @@ -0,0 +1,16 @@ +""" +The details associated with the possible repo and project integrations +""" +type RepoIntegrationQueryPayload { + error: StandardMutationError + + """ + true if the items returned are a subset of all the possible integration, else false (all possible integrations) + """ + hasMore: Boolean + + """ + All the integrations that are likely to be integrated + """ + items: [RepoIntegration!] +} diff --git a/packages/server/graphql/public/typeDefs/TeamMember.graphql b/packages/server/graphql/public/typeDefs/TeamMember.graphql new file mode 100644 index 00000000000..d442dd77214 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/TeamMember.graphql @@ -0,0 +1,133 @@ +""" +A member of a team +""" +type TeamMember { + """ + An ID for the teamMember. userId::teamId + """ + id: ID! + + """ + The datetime the team member was created + """ + createdAt: DateTime! + + """ + true if the user is a part of the team, false if they no longer are + """ + isNotRemoved: Boolean + + """ + Is user a team lead? + """ + isLead: Boolean! + + """ + true if the user prefers to not vote during a poker meeting + """ + isSpectatingPoker: Boolean! + + """ + the type of drawer that is open in the team dash. Null if the drawer is closed + """ + openDrawer: TeamDrawer + + """ + The user email + """ + email: Email! + + """ + true if this team member belongs to the user that queried it + """ + isSelf: Boolean! + + """ + The integrations that the team member has authorized. accessible by all + """ + integrations: TeamMemberIntegrations! + + """ + The meeting specifics for the meeting the team member is currently in + """ + meetingMember(meetingId: ID!): MeetingMember + + """ + The name of the assignee + """ + preferredName: String! + + """ + The integrations that the team has previously used + """ + prevUsedRepoIntegrations( + """ + the number of repo integrations to return + """ + first: Int! + after: DateTime + ): RepoIntegrationQueryPayload! + + """ + The integrations that the user would probably like to use + """ + repoIntegrations( + """ + the number of repo integrations to return + """ + first: Int! + after: DateTime + + """ + true if we should fetch from the network, false if we should use the cache + """ + networkOnly: Boolean! + ): RepoIntegrationQueryPayload! + + """ + Tasks owned by the team member + """ + tasks( + first: Int + + """ + the datetime cursor + """ + after: DateTime + ): TaskConnection + + """ + The team this team member belongs to + """ + team: Team + + """ + foreign key to Team table + """ + teamId: ID! + + """ + The user for the team member + """ + user: User! + + """ + foreign key to User table + """ + userId: ID! + + """ + All the integrations that the user could possibly use + """ + allAvailableRepoIntegrations: [RepoIntegration!]! + + """ + Is user an admin of the team's org? + """ + isOrgAdmin: Boolean! + + """ + url of user’s profile picture + """ + picture: URL! +} diff --git a/packages/server/graphql/public/typeDefs/_legacy.graphql b/packages/server/graphql/public/typeDefs/_legacy.graphql index a1fab0ffb5b..6550de57f6d 100644 --- a/packages/server/graphql/public/typeDefs/_legacy.graphql +++ b/packages/server/graphql/public/typeDefs/_legacy.graphql @@ -994,23 +994,6 @@ enum MeetingTypeEnum { teamPrompt } -""" -The details associated with the possible repo and project integrations -""" -type RepoIntegrationQueryPayload { - error: StandardMutationError - - """ - true if the items returned are a subset of all the possible integration, else false (all possible integrations) - """ - hasMore: Boolean! - - """ - All the integrations that are likely to be integrated - """ - items: [RepoIntegration!] -} - """ An invitation and expiration """ diff --git a/packages/server/graphql/public/types/AcceptTeamInvitationPayload.ts b/packages/server/graphql/public/types/AcceptTeamInvitationPayload.ts index 2babf18a1d4..1590a4b281b 100644 --- a/packages/server/graphql/public/types/AcceptTeamInvitationPayload.ts +++ b/packages/server/graphql/public/types/AcceptTeamInvitationPayload.ts @@ -19,7 +19,7 @@ const AcceptTeamInvitationPayload: AcceptTeamInvitationPayloadResolvers = { return teamId ? dataLoader.get('teams').loadNonNull(teamId) : null }, teamMember: async ({teamMemberId}, _args, {dataLoader}: GQLContext) => { - return teamMemberId ? dataLoader.get('teamMembers').load(teamMemberId) : null + return teamMemberId ? dataLoader.get('teamMembers').loadNonNull(teamMemberId) : null }, meeting: async ({meetingId}, _args, {dataLoader, authToken}) => { if (!meetingId) { diff --git a/packages/server/graphql/public/types/AddTeamMemberIntegrationAuthSuccess.ts b/packages/server/graphql/public/types/AddTeamMemberIntegrationAuthSuccess.ts index 24a623a2e02..5e0c14bd641 100644 --- a/packages/server/graphql/public/types/AddTeamMemberIntegrationAuthSuccess.ts +++ b/packages/server/graphql/public/types/AddTeamMemberIntegrationAuthSuccess.ts @@ -13,7 +13,7 @@ const AddTeamMemberIntegrationAuthSuccess: AddTeamMemberIntegrationAuthSuccessRe }, teamMember: ({teamId, userId}, _args, {dataLoader}) => { const teamMemberId = toTeamMemberId(teamId, userId) - return dataLoader.get('teamMembers').load(teamMemberId) + return dataLoader.get('teamMembers').loadNonNull(teamMemberId) }, user: async ({userId}, _args, {dataLoader}) => { return dataLoader.get('users').loadNonNull(userId) diff --git a/packages/server/graphql/public/types/AgendaItem.ts b/packages/server/graphql/public/types/AgendaItem.ts index 685ad8f290b..d924bba0d1a 100644 --- a/packages/server/graphql/public/types/AgendaItem.ts +++ b/packages/server/graphql/public/types/AgendaItem.ts @@ -3,7 +3,7 @@ import {AgendaItemResolvers} from '../resolverTypes' const AgendaItem: AgendaItemResolvers = { isActive: ({isActive}) => !!isActive, teamMember: async ({teamMemberId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('teamMembers').load(teamMemberId) + return dataLoader.get('teamMembers').loadNonNull(teamMemberId) } } diff --git a/packages/server/graphql/public/types/Company.ts b/packages/server/graphql/public/types/Company.ts index 97ee27b50ec..d04004afed0 100644 --- a/packages/server/graphql/public/types/Company.ts +++ b/packages/server/graphql/public/types/Company.ts @@ -1,7 +1,7 @@ import getRethink from '../../../database/rethinkDriver' import {RDatum, RValue} from '../../../database/stricterR' import AuthToken from '../../../database/types/AuthToken' -import TeamMember from '../../../database/types/TeamMember' +import {TeamMember} from '../../../postgres/types' import {getUserId} from '../../../utils/authorization' import errorFilter from '../../errorFilter' import {DataLoaderWorker} from '../../graphql' diff --git a/packages/server/graphql/public/types/JiraServerIntegration.ts b/packages/server/graphql/public/types/JiraServerIntegration.ts index 029a57cbcfb..a4690b2d070 100644 --- a/packages/server/graphql/public/types/JiraServerIntegration.ts +++ b/packages/server/graphql/public/types/JiraServerIntegration.ts @@ -1,9 +1,9 @@ import IntegrationProviderId from '~/shared/gqlIds/IntegrationProviderId' import IntegrationRepoId from '~/shared/gqlIds/IntegrationRepoId' -import TeamMember from '../../../database/types/TeamMember' import JiraServerRestManager from '../../../integrations/jiraServer/JiraServerRestManager' import {IntegrationProviderJiraServer} from '../../../postgres/queries/getIntegrationProvidersByIds' import getLatestIntegrationSearchQueries from '../../../postgres/queries/getLatestIntegrationSearchQueries' +import {TeamMember} from '../../../postgres/types' import {getUserId} from '../../../utils/authorization' import standardError from '../../../utils/standardError' import {JiraServerIntegrationResolvers} from '../resolverTypes' diff --git a/packages/server/graphql/public/types/NewMeeting.ts b/packages/server/graphql/public/types/NewMeeting.ts index 030f8b42d06..d75bc19bceb 100644 --- a/packages/server/graphql/public/types/NewMeeting.ts +++ b/packages/server/graphql/public/types/NewMeeting.ts @@ -20,7 +20,7 @@ const NewMeeting: NewMeetingResolvers = { }, facilitator: ({facilitatorUserId, teamId}, _args, {dataLoader}) => { const teamMemberId = toTeamMemberId(teamId, facilitatorUserId) - return dataLoader.get('teamMembers').load(teamMemberId) + return dataLoader.get('teamMembers').loadNonNull(teamMemberId) }, locked: async ({endedAt, teamId}, _args, {authToken, dataLoader}) => { const viewerId = getUserId(authToken) diff --git a/packages/server/graphql/public/types/NotifyTaskInvolves.ts b/packages/server/graphql/public/types/NotifyTaskInvolves.ts index 3cbc20a861e..a5513dba45e 100644 --- a/packages/server/graphql/public/types/NotifyTaskInvolves.ts +++ b/packages/server/graphql/public/types/NotifyTaskInvolves.ts @@ -15,7 +15,7 @@ const NotifyTaskInvolves: NotifyTaskInvolvesResolvers = { }, changeAuthor: ({changeAuthorId}, _args, {dataLoader}) => { - return dataLoader.get('teamMembers').load(changeAuthorId) + return dataLoader.get('teamMembers').loadNonNull(changeAuthorId) }, team: ({teamId}, _args, {dataLoader}) => { diff --git a/packages/server/graphql/public/types/RemoveTeamMemberIntegrationAuthSuccess.ts b/packages/server/graphql/public/types/RemoveTeamMemberIntegrationAuthSuccess.ts index e722f61c18c..87263f1a50b 100644 --- a/packages/server/graphql/public/types/RemoveTeamMemberIntegrationAuthSuccess.ts +++ b/packages/server/graphql/public/types/RemoveTeamMemberIntegrationAuthSuccess.ts @@ -10,7 +10,7 @@ export type RemoveTeamMemberIntegrationAuthSuccessSource = { const RemoveTeamMemberIntegrationAuthSuccess: RemoveTeamMemberIntegrationAuthSuccessResolvers = { teamMember: ({teamId, userId}, _args, {dataLoader}) => { const teamMemberId = toTeamMemberId(teamId, userId) - return dataLoader.get('teamMembers').load(teamMemberId) + return dataLoader.get('teamMembers').loadNonNull(teamMemberId) }, user: async ({userId}, _args, {dataLoader}) => { return dataLoader.get('users').loadNonNull(userId) diff --git a/packages/server/graphql/public/types/RemoveTeamMemberPayload.ts b/packages/server/graphql/public/types/RemoveTeamMemberPayload.ts index 6a4b13f5920..dedf7714255 100644 --- a/packages/server/graphql/public/types/RemoveTeamMemberPayload.ts +++ b/packages/server/graphql/public/types/RemoveTeamMemberPayload.ts @@ -15,7 +15,7 @@ export type RemoveTeamMemberPayloadSource = { const RemoveTeamMemberPayload: RemoveTeamMemberPayloadResolvers = { teamMember: async ({teamMemberId}, _args, {dataLoader}: GQLContext) => { - return dataLoader.get('teamMembers').load(teamMemberId) + return dataLoader.get('teamMembers').loadNonNull(teamMemberId) }, team: async ({teamId}, _args, {dataLoader}) => { return dataLoader.get('teams').loadNonNull(teamId) diff --git a/packages/server/graphql/public/types/Team.ts b/packages/server/graphql/public/types/Team.ts index 4c13a4adc0f..d28e7a23ae5 100644 --- a/packages/server/graphql/public/types/Team.ts +++ b/packages/server/graphql/public/types/Team.ts @@ -37,7 +37,7 @@ const Team: TeamResolvers = { const viewerId = getUserId(authToken) if (!viewerId) return null const teamMemberId = toTeamMemberId(teamId, viewerId) - const teamMember = await dataLoader.get('teamMembers').load(teamMemberId) + const teamMember = await dataLoader.get('teamMembers').loadNonNull(teamMemberId) return teamMember }, isViewerOnTeam: async ({id: teamId}, _args, {authToken}) => isTeamMember(authToken, teamId), diff --git a/packages/server/graphql/public/types/TeamMember.ts b/packages/server/graphql/public/types/TeamMember.ts index 0e3f6007670..477914e7a21 100644 --- a/packages/server/graphql/public/types/TeamMember.ts +++ b/packages/server/graphql/public/types/TeamMember.ts @@ -1,3 +1,15 @@ +import ms from 'ms' +import isTaskPrivate from 'parabol-client/utils/isTaskPrivate' +import MeetingMemberId from '../../../../client/shared/gqlIds/MeetingMemberId' +import {getUserId} from '../../../utils/authorization' +import getAllRepoIntegrationsRedisKey from '../../../utils/getAllRepoIntegrationsRedisKey' +import getRedis from '../../../utils/getRedis' +import standardError from '../../../utils/standardError' +import connectionFromTasks from '../../queries/helpers/connectionFromTasks' +import fetchAllRepoIntegrations from '../../queries/helpers/fetchAllRepoIntegrations' +import getAllCachedRepoIntegrations from '../../queries/helpers/getAllCachedRepoIntegrations' +import getPrevUsedRepoIntegrations from '../../queries/helpers/getPrevUsedRepoIntegrations' +import {default as sortRepoIntegrations} from '../../queries/helpers/sortRepoIntegrations' import {TeamMemberResolvers} from '../resolverTypes' const TeamMember: TeamMemberResolvers = { @@ -8,8 +20,97 @@ const TeamMember: TeamMemberResolvers = { .load({userId, orgId: team.orgId}) return organizationUser?.role === 'ORG_ADMIN' }, + picture: async ({picture}, _args, {dataLoader}) => { return dataLoader.get('fileStoreAsset').load(picture) + }, + + isSelf: (source, _args, {authToken}) => { + const userId = getUserId(authToken) + return source.userId === userId + }, + + integrations: ({teamId, userId}) => { + return {teamId, userId} + }, + + meetingMember: async ({userId}, {meetingId}, {dataLoader}) => { + const meetingMemberId = MeetingMemberId.join(meetingId, userId) + return meetingId ? dataLoader.get('meetingMembers').load(meetingMemberId) : undefined + }, + + prevUsedRepoIntegrations: async ({teamId, userId}, {first}, context) => { + const {authToken, dataLoader} = context + const viewerId = getUserId(authToken) + if (userId !== viewerId) { + const user = await dataLoader.get('users').loadNonNull(userId) + const {tms} = user + const onTeam = authToken.tms.find((teamId) => tms.includes(teamId)) + if (!onTeam) { + return standardError(new Error('Not on same team as user'), {userId: viewerId}) + } + } + const prevUsedRepoIntegrations = await getPrevUsedRepoIntegrations(teamId) + if (!prevUsedRepoIntegrations) return {hasMore: false, items: []} + if (prevUsedRepoIntegrations.length > first) { + return {hasMore: true, items: prevUsedRepoIntegrations.slice(0, first)} + } else { + return {hasMore: false, items: prevUsedRepoIntegrations} + } + }, + + repoIntegrations: async ({teamId, userId}, {first, networkOnly}, context, info) => { + const {authToken, dataLoader} = context + const viewerId = getUserId(authToken) + if (userId !== viewerId) { + const user = await dataLoader.get('users').loadNonNull(userId) + const {tms} = user + const onTeam = authToken.tms.find((teamId) => tms.includes(teamId)) + if (!onTeam) { + return standardError(new Error('Not on same team as user'), {userId: viewerId}) + } + } + const [allCachedRepoIntegrations, prevUsedRepoIntegrations] = await Promise.all([ + getAllCachedRepoIntegrations(teamId, viewerId), + getPrevUsedRepoIntegrations(teamId) + ]) + const ignoreCache = networkOnly || !allCachedRepoIntegrations?.length + const allRepoIntegrations = ignoreCache + ? await fetchAllRepoIntegrations(teamId, userId, context, info) + : allCachedRepoIntegrations + if (ignoreCache) { + // create a new cache with newly fetched allRepoIntegrations + const redis = getRedis() + const allRepoIntegrationsKey = getAllRepoIntegrationsRedisKey(teamId, viewerId) + redis.set(allRepoIntegrationsKey, JSON.stringify(allRepoIntegrations), 'PX', ms('90d')) + } + const sortedRepoIntegrations = await sortRepoIntegrations( + allRepoIntegrations, + prevUsedRepoIntegrations + ) + if (sortedRepoIntegrations.length > first) { + return {hasMore: true, items: sortedRepoIntegrations.slice(0, first)} + } else { + return {hasMore: false, items: sortedRepoIntegrations} + } + }, + + tasks: async ({teamId, userId}, _args, {dataLoader}) => { + const allTasks = await dataLoader.get('tasksByTeamId').load(teamId) + const publicTasksForUserId = allTasks.filter((task) => { + if (task.userId !== userId) return false + if (isTaskPrivate(task.tags)) return false + return true + }) + return connectionFromTasks(publicTasksForUserId) + }, + + team: ({teamId}, _args, {dataLoader}) => { + return dataLoader.get('teams').loadNonNull(teamId) + }, + + user: ({userId}, _args, {dataLoader}) => { + return dataLoader.get('users').loadNonNull(userId) } } diff --git a/packages/server/graphql/queries/tasks.ts b/packages/server/graphql/queries/tasks.ts index ca5fd0cc4cd..ce47533ff7f 100644 --- a/packages/server/graphql/queries/tasks.ts +++ b/packages/server/graphql/queries/tasks.ts @@ -8,7 +8,6 @@ import { } from 'graphql' import isTaskPrivate from 'parabol-client/utils/isTaskPrivate' import Task from '../../database/types/Task' -import TeamMember from '../../database/types/TeamMember' import {getUserId} from '../../utils/authorization' import standardError from '../../utils/standardError' import errorFilter from '../errorFilter' @@ -32,7 +31,7 @@ const getValidUserIds = async ( ).filter(errorFilter) const teamMembersOnValidTeams = teamMembersByUserIds .flat() - .filter((teamMember: TeamMember) => validTeamIds.includes(teamMember.teamId)) + .filter((teamMember) => validTeamIds.includes(teamMember.teamId)) const teamMemberUserIds = new Set( teamMembersOnValidTeams.map(({userId}: {userId: string}) => userId) ) diff --git a/packages/server/graphql/resolvers.ts b/packages/server/graphql/resolvers.ts index 708f54a04db..1dac9a9f6fc 100644 --- a/packages/server/graphql/resolvers.ts +++ b/packages/server/graphql/resolvers.ts @@ -6,9 +6,9 @@ import GenericMeetingStage from '../database/types/GenericMeetingStage' import Meeting from '../database/types/Meeting' import Organization from '../database/types/Organization' import Task from '../database/types/Task' -import TeamMember from '../database/types/TeamMember' import User from '../database/types/User' import {Loaders} from '../dataloader/RootDataLoader' +import {TeamMember} from '../postgres/types' import {AnyMeeting} from '../postgres/types/Meeting' import {getUserId, isSuperUser, isUserBillingLeader} from '../utils/authorization' import {GQLContext} from './graphql' diff --git a/packages/server/graphql/types/Team.ts b/packages/server/graphql/types/Team.ts index f1c2ec71be7..a950e9412bd 100644 --- a/packages/server/graphql/types/Team.ts +++ b/packages/server/graphql/types/Team.ts @@ -153,7 +153,7 @@ const Team: GraphQLObjectType = new GraphQLObjectType({ if (!isTeamMember(authToken, teamId)) return false const viewerId = getUserId(authToken) const teamMemberId = toTeamMemberId(teamId, viewerId) - const teamMember = await dataLoader.get('teamMembers').load(teamMemberId) + const teamMember = await dataLoader.get('teamMembers').loadNonNull(teamMemberId) return !!teamMember.isLead } }, diff --git a/packages/server/graphql/types/TeamMember.ts b/packages/server/graphql/types/TeamMember.ts index eec91d60db0..97948c7b0ec 100644 --- a/packages/server/graphql/types/TeamMember.ts +++ b/packages/server/graphql/types/TeamMember.ts @@ -1,233 +1,9 @@ -import { - GraphQLBoolean, - GraphQLID, - GraphQLInt, - GraphQLNonNull, - GraphQLObjectType, - GraphQLString -} from 'graphql' -import ms from 'ms' -import isTaskPrivate from 'parabol-client/utils/isTaskPrivate' -import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' -import {getUserId} from '../../utils/authorization' -import getAllRepoIntegrationsRedisKey from '../../utils/getAllRepoIntegrationsRedisKey' -import getRedis from '../../utils/getRedis' -import standardError from '../../utils/standardError' +import {GraphQLObjectType} from 'graphql' import {GQLContext} from '../graphql' -import connectionFromTasks from '../queries/helpers/connectionFromTasks' -import fetchAllRepoIntegrations from '../queries/helpers/fetchAllRepoIntegrations' -import getAllCachedRepoIntegrations from '../queries/helpers/getAllCachedRepoIntegrations' -import getPrevUsedRepoIntegrations from '../queries/helpers/getPrevUsedRepoIntegrations' -import {default as sortRepoIntegrations} from '../queries/helpers/sortRepoIntegrations' -import {resolveTeam} from '../resolvers' -import GraphQLEmailType from './GraphQLEmailType' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import RepoIntegrationQueryPayload from './RepoIntegrationQueryPayload' -import {TaskConnection} from './Task' -import Team from './Team' -import TeamDrawerEnum from './TeamDrawerEnum' -import TeamMemberIntegrations from './TeamMemberIntegrations' -import User from './User' const TeamMember = new GraphQLObjectType({ name: 'TeamMember', - description: 'A member of a team', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'An ID for the teamMember. userId::teamId' - }, - createdAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The datetime the team member was created' - }, - isNotRemoved: { - type: GraphQLBoolean, - description: 'true if the user is a part of the team, false if they no longer are' - }, - isLead: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'Is user a team lead?', - resolve: ({isLead}) => !!isLead - }, - isSpectatingPoker: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'true if the user prefers to not vote during a poker meeting', - resolve: ({isSpectatingPoker}) => !!isSpectatingPoker - }, - openDrawer: { - type: TeamDrawerEnum, - description: 'the type of drawer that is open in the team dash. Null if the drawer is closed' - }, - /* denormalized from User */ - email: { - type: new GraphQLNonNull(GraphQLEmailType), - description: 'The user email' - }, - isSelf: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'true if this team member belongs to the user that queried it', - resolve: (source, _args: unknown, {authToken}) => { - const userId = getUserId(authToken) - return source.userId === userId - } - }, - integrations: { - type: new GraphQLNonNull(TeamMemberIntegrations), - description: 'The integrations that the team member has authorized. accessible by all', - resolve: ({teamId, userId}) => { - return {teamId, userId} - } - }, - meetingMember: { - type: require('./MeetingMember').default, - description: 'The meeting specifics for the meeting the team member is currently in', - args: { - meetingId: { - type: new GraphQLNonNull(GraphQLID) - } - }, - resolve: async ({userId}, {meetingId}, {dataLoader}) => { - const meetingMemberId = toTeamMemberId(meetingId, userId) - return meetingId ? dataLoader.get('meetingMembers').load(meetingMemberId) : undefined - } - }, - preferredName: { - type: new GraphQLNonNull(GraphQLString), - description: 'The name of the assignee' - }, - prevUsedRepoIntegrations: { - description: 'The integrations that the team has previously used', - type: new GraphQLNonNull(RepoIntegrationQueryPayload), - args: { - first: { - type: new GraphQLNonNull(GraphQLInt), - description: 'the number of repo integrations to return' - }, - after: { - type: GraphQLISO8601Type - } - }, - resolve: async ({teamId, userId}: {teamId: string; userId: string}, {first}, context) => { - const {authToken, dataLoader} = context - const viewerId = getUserId(authToken) - if (userId !== viewerId) { - const user = await dataLoader.get('users').loadNonNull(userId) - const {tms} = user - const onTeam = authToken.tms.find((teamId) => tms.includes(teamId)) - if (!onTeam) { - return standardError(new Error('Not on same team as user'), {userId: viewerId}) - } - } - const prevUsedRepoIntegrations = await getPrevUsedRepoIntegrations(teamId) - if (!prevUsedRepoIntegrations) return [] - if (prevUsedRepoIntegrations.length > first) { - return {hasMore: true, items: prevUsedRepoIntegrations.slice(0, first)} - } else { - return {hasMore: false, items: prevUsedRepoIntegrations} - } - } - }, - repoIntegrations: { - description: 'The integrations that the user would probably like to use', - type: new GraphQLNonNull(RepoIntegrationQueryPayload), - args: { - first: { - type: new GraphQLNonNull(GraphQLInt), - description: 'the number of repo integrations to return' - }, - after: { - type: GraphQLISO8601Type - }, - networkOnly: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'true if we should fetch from the network, false if we should use the cache' - } - }, - resolve: async ( - {teamId, userId}: {teamId: string; userId: string}, - {first, networkOnly}, - context, - info - ) => { - const {authToken, dataLoader} = context - const viewerId = getUserId(authToken) - if (userId !== viewerId) { - const user = await dataLoader.get('users').loadNonNull(userId) - const {tms} = user - const onTeam = authToken.tms.find((teamId) => tms.includes(teamId)) - if (!onTeam) { - return standardError(new Error('Not on same team as user'), {userId: viewerId}) - } - } - const [allCachedRepoIntegrations, prevUsedRepoIntegrations] = await Promise.all([ - getAllCachedRepoIntegrations(teamId, viewerId), - getPrevUsedRepoIntegrations(teamId) - ]) - const ignoreCache = networkOnly || !allCachedRepoIntegrations?.length - const allRepoIntegrations = ignoreCache - ? await fetchAllRepoIntegrations(teamId, userId, context, info) - : allCachedRepoIntegrations - if (ignoreCache) { - // create a new cache with newly fetched allRepoIntegrations - const redis = getRedis() - const allRepoIntegrationsKey = getAllRepoIntegrationsRedisKey(teamId, viewerId) - redis.set(allRepoIntegrationsKey, JSON.stringify(allRepoIntegrations), 'PX', ms('90d')) - } - const sortedRepoIntegrations = await sortRepoIntegrations( - allRepoIntegrations, - prevUsedRepoIntegrations - ) - if (sortedRepoIntegrations.length > first) { - return {hasMore: true, items: sortedRepoIntegrations.slice(0, first)} - } else { - return {hasMore: false, items: sortedRepoIntegrations} - } - } - }, - tasks: { - type: TaskConnection, - description: 'Tasks owned by the team member', - args: { - first: { - type: GraphQLInt - }, - after: { - type: GraphQLISO8601Type, - description: 'the datetime cursor' - } - }, - resolve: async ({teamId, userId}, _args: unknown, {dataLoader}) => { - const allTasks = await dataLoader.get('tasksByTeamId').load(teamId) - const publicTasksForUserId = allTasks.filter((task) => { - if (task.userId !== userId) return false - if (isTaskPrivate(task.tags)) return false - return true - }) - return connectionFromTasks(publicTasksForUserId) - } - }, - team: { - type: Team, - description: 'The team this team member belongs to', - resolve: resolveTeam - }, - teamId: { - type: new GraphQLNonNull(GraphQLID), - description: 'foreign key to Team table' - }, - user: { - type: new GraphQLNonNull(User), - description: 'The user for the team member', - resolve({userId}, _args: unknown, {dataLoader}) { - return dataLoader.get('users').load(userId) - } - }, - userId: { - type: new GraphQLNonNull(GraphQLID), - description: 'foreign key to User table' - } - }) + fields: {} }) export default TeamMember diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index cdc9adef2ad..558bc238192 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -1,3 +1,6 @@ import {Selectable} from 'kysely' -import {OrganizationUser as OrganizationUserPG} from '../pg.d' +import {OrganizationUser as OrganizationUserPG, TeamMember as TeamMemberPG} from '../pg.d' + export type OrganizationUser = Selectable + +export type TeamMember = Selectable diff --git a/packages/server/safeMutations/acceptTeamInvitation.ts b/packages/server/safeMutations/acceptTeamInvitation.ts index 0be315f8a1f..587a4b68fdf 100644 --- a/packages/server/safeMutations/acceptTeamInvitation.ts +++ b/packages/server/safeMutations/acceptTeamInvitation.ts @@ -11,7 +11,6 @@ import {TeamSource} from '../graphql/public/types/Team' import getKysely from '../postgres/getKysely' import {Logger} from '../utils/Logger' import setUserTierForUserIds from '../utils/setUserTierForUserIds' -import insertNewTeamMember from './insertNewTeamMember' const handleFirstAcceptedInvitation = async ( team: TeamSource, @@ -108,7 +107,6 @@ const acceptTeamInvitation = async ( }) .onConflict((oc) => oc.column('id').doUpdateSet({isNotRemoved: true})) .execute(), - insertNewTeamMember(user, teamId, dataLoader), r .table('TeamInvitation') .getAll(teamId, {index: 'teamId'}) diff --git a/packages/server/safeMutations/insertNewTeamMember.ts b/packages/server/safeMutations/insertNewTeamMember.ts deleted file mode 100644 index 6a63ba6f0e1..00000000000 --- a/packages/server/safeMutations/insertNewTeamMember.ts +++ /dev/null @@ -1,43 +0,0 @@ -import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' -import getRethink from '../database/rethinkDriver' -import TeamMember from '../database/types/TeamMember' -import {DataLoaderInstance} from '../dataloader/RootDataLoader' -import IUser from '../postgres/types/IUser' - -const insertNewTeamMember = async (user: IUser, teamId: string, dataLoader: DataLoaderInstance) => { - const r = await getRethink() - const now = new Date() - const {id: userId} = user - const teamMemberId = toTeamMemberId(teamId, userId) - const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) - const existingTeamMember = teamMembers.find((tm) => tm.userId === userId) - const teamMemberCount = teamMembers.length - - if (!user) { - throw new Error('User does not exist') - } - if (existingTeamMember) { - existingTeamMember.isNotRemoved = true - existingTeamMember.updatedAt = now - await r.table('TeamMember').get(teamMemberId).replace(existingTeamMember).run() - dataLoader.clearAll('teamMembers') - return existingTeamMember - } - - const {picture, preferredName, email} = user - const isLead = teamMemberCount === 0 - const teamMember = new TeamMember({ - teamId, - userId, - picture, - preferredName, - email, - isLead, - openDrawer: 'manageTeam' - }) - - await r.table('TeamMember').insert(teamMember).run() - return teamMember -} - -export default insertNewTeamMember diff --git a/packages/server/utils/authorization.ts b/packages/server/utils/authorization.ts index d082ce15e78..c1aeeb41ac7 100644 --- a/packages/server/utils/authorization.ts +++ b/packages/server/utils/authorization.ts @@ -1,5 +1,3 @@ -import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' -import getRethink from '../database/rethinkDriver' import AuthToken from '../database/types/AuthToken' import {DataLoaderWorker} from '../graphql/graphql' import {OrganizationUser} from '../postgres/types' @@ -22,20 +20,6 @@ export const isTeamMember = (authToken: AuthToken, teamId: string) => { return Array.isArray(tms) && tms.includes(teamId) } -export const isTeamLead = async (userId: string, teamId: string, dataLoader: DataLoaderWorker) => { - const r = await getRethink() - const teamMemberId = toTeamMemberId(teamId, userId) - if (await r.table('TeamMember').get(teamMemberId)('isLead').default(false).run()) { - return true - } - - const team = await dataLoader.get('teams').loadNonNull(teamId) - const organizationUser = await dataLoader - .get('organizationUsersByUserIdOrgId') - .load({userId, orgId: team.orgId}) - return organizationUser?.role === 'ORG_ADMIN' -} - interface Options { clearCache?: boolean } From 1e6c0b49254b1911f9730f81366349d2f9562a58 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 24 Jul 2024 12:14:15 -0700 Subject: [PATCH 357/529] chore(release): release v7.39.2 (#10028) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index af120f4b997..a999d60cc0b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.39.1" + ".": "7.39.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d2ff9b9d19..09b9fa1731c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.39.2](https://github.com/ParabolInc/parabol/compare/v7.39.1...v7.39.2) (2024-07-24) + + +### Fixed + +* bump pm2 version ([#10027](https://github.com/ParabolInc/parabol/issues/10027)) ([0bb8ead](https://github.com/ParabolInc/parabol/commit/0bb8ead2adc52f64ad30ef57891791e1b3dd4ac1)) + + +### Changed + +* **rethinkdb:** TeamMember: Phase 3 ([#10003](https://github.com/ParabolInc/parabol/issues/10003)) ([73a5881](https://github.com/ParabolInc/parabol/commit/73a5881709f9345767c2a233cbe28716b6c3b8e1)) + ## [7.39.1](https://github.com/ParabolInc/parabol/compare/v7.39.0...v7.39.1) (2024-07-23) diff --git a/package.json b/package.json index 3e6a45467c2..7351040d65d 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.39.1", + "version": "7.39.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 07db583627b..8852d449624 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.39.1", + "version": "7.39.2", "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.39.1" + "parabol-server": "7.39.2" } } diff --git a/packages/client/package.json b/packages/client/package.json index f73ad247867..5ece805bce9 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.39.1", + "version": "7.39.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index b09be582a0e..e78210071f3 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.39.1", + "version": "7.39.2", "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 8794d4de07f..0738c1b2524 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.39.1", + "version": "7.39.2", "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.39.1", - "parabol-server": "7.39.1", + "parabol-client": "7.39.2", + "parabol-server": "7.39.2", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index ef1d9c5acc4..2626544317a 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.39.1", + "version": "7.39.2", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 34ca2fd235b..e8ed5217f59 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.39.1", + "version": "7.39.2", "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.39.1", + "parabol-client": "7.39.2", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 0c6c8e79dfbbac93362b3db640b008e32ba701b3 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 24 Jul 2024 17:10:16 -0700 Subject: [PATCH 358/529] chore(rethinkdb): TemplateScale: One-shot (#10021) Signed-off-by: Matt Krick --- codegen.json | 3 + .../components/SelectScaleDropdown.tsx | 12 +- .../AddPokerTemplateScaleMutation.ts | 1 + .../RemovePokerTemplateScaleMutation.ts | 6 +- .../handlers/handleAddPokerTemplateScale.ts | 4 +- packages/client/utils/dndNoise.ts | 1 + packages/embedder/indexing/meetingTemplate.ts | 2 +- packages/server/database/rethinkDriver.ts | 10 -- .../server/database/types/TemplateScale.ts | 48 ------ .../server/dataloader/customRedisQueries.ts | 16 -- .../dataloader/foreignKeyLoaderMakers.ts | 8 + .../dataloader/primaryKeyLoaderMakers.ts | 5 + .../rethinkForeignKeyLoaderMakers.ts | 14 -- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../mutations/addPokerTemplateDimension.ts | 9 +- .../mutations/addPokerTemplateScale.ts | 124 ++++++++++----- .../mutations/addPokerTemplateScaleValue.ts | 70 ++++---- .../mutations/helpers/validateScaleValue.ts | 9 +- .../mutations/movePokerTemplateScaleValue.ts | 45 +++--- .../mutations/removePokerTemplateScale.ts | 20 ++- .../removePokerTemplateScaleValue.ts | 31 ++-- .../mutations/renamePokerTemplateScale.ts | 37 ++--- .../graphql/mutations/startSprintPoker.ts | 23 +-- .../updatePokerTemplateDimensionScale.ts | 2 +- .../updatePokerTemplateScaleValue.ts | 54 ++----- .../graphql/public/types/TemplateScale.ts | 31 ++++ .../graphql/public/types/TemplateScaleRef.ts | 14 ++ .../public/types/TemplateScaleValue.ts | 16 ++ packages/server/graphql/types/Team.ts | 13 +- .../server/graphql/types/TemplateScale.ts | 86 +--------- .../server/graphql/types/TemplateScaleRef.ts | 34 +--- .../graphql/types/TemplateScaleValue.ts | 34 ---- .../1614030642692_refs-for-meetings.ts | 5 +- ...698265600000_addPrioritizationTemplates.ts | 3 +- .../migrations/1721405703862_TemplateScale.ts | 149 ++++++++++++++++++ .../queries/src/insertTemplateRefQuery.sql | 11 -- .../src/insertTemplateScaleRefQuery.sql | 11 -- packages/server/postgres/select.ts | 18 +++ packages/server/postgres/types/index.d.ts | 20 ++- packages/server/utils/analytics/analytics.ts | 4 +- packages/server/utils/sortOrder.ts | 67 ++++++++ 41 files changed, 569 insertions(+), 502 deletions(-) delete mode 100644 packages/server/database/types/TemplateScale.ts create mode 100644 packages/server/graphql/public/types/TemplateScale.ts create mode 100644 packages/server/graphql/public/types/TemplateScaleRef.ts create mode 100644 packages/server/graphql/public/types/TemplateScaleValue.ts delete mode 100644 packages/server/graphql/types/TemplateScaleValue.ts create mode 100644 packages/server/postgres/migrations/1721405703862_TemplateScale.ts delete mode 100644 packages/server/postgres/queries/src/insertTemplateRefQuery.sql delete mode 100644 packages/server/postgres/queries/src/insertTemplateScaleRefQuery.sql create mode 100644 packages/server/postgres/select.ts create mode 100644 packages/server/utils/sortOrder.ts diff --git a/codegen.json b/codegen.json index 14c1806ab5f..00dd488587c 100644 --- a/codegen.json +++ b/codegen.json @@ -111,6 +111,9 @@ "NotifyTaskInvolves": "../../database/types/NotificationTaskInvolves#default", "NotifyTeamArchived": "../../database/types/NotificationTeamArchived#default", "Organization": "./types/Organization#OrganizationSource", + "TemplateScaleValue": "./types/TemplateScaleValue#TemplateScaleValueSource as TemplateScaleValueSourceDB", + "TemplateScale": "../../postgres/types/index#TemplateScale as TemplateScaleDB", + "TemplateScaleRef": "../../postgres/types/index#TemplateScaleRef as TemplateScaleRefDB", "OrganizationUser": "../../postgres/types/index#OrganizationUser as OrganizationUserDB", "PokerMeeting": "../../database/types/MeetingPoker#default as MeetingPoker", "PokerMeetingMember": "../../database/types/MeetingPokerMeetingMember#default as PokerMeetingMemberDB", diff --git a/packages/client/modules/meeting/components/SelectScaleDropdown.tsx b/packages/client/modules/meeting/components/SelectScaleDropdown.tsx index 3d7e91864de..b557015c6e2 100644 --- a/packages/client/modules/meeting/components/SelectScaleDropdown.tsx +++ b/packages/client/modules/meeting/components/SelectScaleDropdown.tsx @@ -59,6 +59,7 @@ const SelectScaleDropdown = (props: Props) => { scales { id isStarter + name ...ScaleDropdownMenuItem_scale } } @@ -70,8 +71,11 @@ const SelectScaleDropdown = (props: Props) => { const {selectedScale, team} = dimension const {id: seletedScaleId} = selectedScale const {id: teamId, scales} = team + const sortedScales = scales.toSorted((a, b) => { + return a.isStarter !== b.isStarter ? (a.isStarter ? 1 : -1) : a.name.localeCompare(b.name) + }) const defaultActiveIdx = useMemo( - () => scales.findIndex(({id}) => id === seletedScaleId), + () => sortedScales.findIndex(({id}) => id === seletedScaleId), [dimension] ) @@ -98,17 +102,17 @@ const SelectScaleDropdown = (props: Props) => { {...menuProps} defaultActiveIdx={defaultActiveIdx} > - {scales.map((scale) => ( + {sortedScales.map((scale) => ( ))} - {scales.length < Threshold.MAX_POKER_TEMPLATE_SCALES && ( + {sortedScales.length < Threshold.MAX_POKER_TEMPLATE_SCALES && ( { - const scale = await dataLoader.get('templateScales').load(scaleId) + const scale = await dataLoader.get('templateScales').loadNonNull(scaleId) const scaleValues = scale.values.map(({label}) => label).join(', ') return `${name}\n${description ?? ''}\n${scale.name}\n${scaleValues}` }) diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index 713d255a085..0e47dcf664c 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -13,7 +13,6 @@ import FailedAuthRequest from './types/FailedAuthRequest' import Invoice from './types/Invoice' import InvoiceItemHook from './types/InvoiceItemHook' import MassInvitation from './types/MassInvitation' -import MeetingTemplate from './types/MeetingTemplate' import NotificationKickedOut from './types/NotificationKickedOut' import NotificationMeetingStageTimeLimitEnd from './types/NotificationMeetingStageTimeLimitEnd' import NotificationMentioned from './types/NotificationMentioned' @@ -32,7 +31,6 @@ import SuggestedActionInviteYourTeam from './types/SuggestedActionInviteYourTeam import SuggestedActionTryTheDemo from './types/SuggestedActionTryTheDemo' import Task from './types/Task' import TemplateDimension from './types/TemplateDimension' -import TemplateScale from './types/TemplateScale' export type RethinkSchema = { AgendaItem: { @@ -118,10 +116,6 @@ export type RethinkSchema = { type: PushInvitation index: 'userId' } - MeetingTemplate: { - type: MeetingTemplate - index: 'teamId' | 'orgId' - } ScheduledJob: { type: ScheduledJobUnion index: 'runAt' | 'type' @@ -159,10 +153,6 @@ export type RethinkSchema = { type: TemplateDimension index: 'teamId' | 'templateId' | 'scaleId' } - TemplateScale: { - type: TemplateScale - index: 'teamId' - } } export type DBType = { diff --git a/packages/server/database/types/TemplateScale.ts b/packages/server/database/types/TemplateScale.ts deleted file mode 100644 index edbf61b3df5..00000000000 --- a/packages/server/database/types/TemplateScale.ts +++ /dev/null @@ -1,48 +0,0 @@ -import {PALETTE} from '../../../client/styles/paletteV3' -import {PokerCards} from '../../../client/types/constEnums' -import generateUID from '../../generateUID' -import TemplateScaleValue from './TemplateScaleValue' - -export interface TemplateScaleInput { - teamId: string - sortOrder: number - name: string - values?: TemplateScaleValue[] - parentScaleId?: string - isStarter?: boolean - removedAt?: Date -} - -const questionMarkCard = new TemplateScaleValue({ - color: PALETTE.FUSCIA_400, - label: PokerCards.QUESTION_CARD as string -}) -const passCard = new TemplateScaleValue({ - color: PALETTE.GRAPE_500, - label: PokerCards.PASS_CARD as string -}) - -export default class TemplateScale { - id: string - createdAt = new Date() - name: string - sortOrder: number - values: TemplateScaleValue[] - teamId: string - updatedAt = new Date() - parentScaleId?: string - isStarter?: boolean - removedAt?: Date - - constructor(input: TemplateScaleInput) { - const {name, sortOrder, values, teamId, parentScaleId, isStarter, removedAt} = input - this.id = generateUID() - this.sortOrder = sortOrder - this.name = name - this.values = values || [questionMarkCard, passCard] - this.teamId = teamId - this.parentScaleId = parentScaleId - this.isStarter = isStarter - this.removedAt = removedAt - } -} diff --git a/packages/server/dataloader/customRedisQueries.ts b/packages/server/dataloader/customRedisQueries.ts index a461202ca5a..8ce3cd85ac7 100644 --- a/packages/server/dataloader/customRedisQueries.ts +++ b/packages/server/dataloader/customRedisQueries.ts @@ -50,22 +50,6 @@ const customRedisQueries = { ) return publicTemplatesByType - }, - starterScales: async (teamIds: string[]) => { - const r = await getRethink() - - const starterScales = await Promise.all( - teamIds.map((teamId) => { - return r - .table('TemplateScale') - .getAll(teamId, {index: 'teamId'}) - .filter({isStarter: true}) - .filter((row: RDatum) => row('removedAt').default(null).eq(null)) - .run() - }) - ) - - return starterScales } } as const diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 67ba8f37cd8..58579aceec5 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -1,4 +1,5 @@ import getKysely from '../postgres/getKysely' +import {selectTemplateScale} from '../postgres/select' import {foreignKeyLoaderMaker} from './foreignKeyLoaderMaker' import {selectOrganizations, selectRetroReflections, selectTeams} from './primaryKeyLoaderMakers' @@ -124,3 +125,10 @@ export const organizationUsersByOrgId = foreignKeyLoaderMaker( .execute() } ) + +export const scalesByTeamId = foreignKeyLoaderMaker('templateScales', 'teamId', async (teamIds) => { + return selectTemplateScale() + .where('teamId', 'in', teamIds) + .orderBy(['isStarter', 'name']) + .execute() +}) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 10b7d3c3530..0f9c8a053f9 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -7,6 +7,7 @@ import {getTeamPromptResponsesByIds} from '../postgres/queries/getTeamPromptResp import getTemplateRefsByIds from '../postgres/queries/getTemplateRefsByIds' import getTemplateScaleRefsByIds from '../postgres/queries/getTemplateScaleRefsByIds' import {getUsersByIds} from '../postgres/queries/getUsersByIds' +import {selectTemplateScale} from '../postgres/select' import {primaryKeyLoaderMaker} from './primaryKeyLoaderMaker' export const users = primaryKeyLoaderMaker(getUsersByIds) @@ -150,3 +151,7 @@ export const organizationUsers = primaryKeyLoaderMaker((ids: readonly string[]) export const teamMembers = primaryKeyLoaderMaker((ids: readonly string[]) => { return getKysely().selectFrom('TeamMember').selectAll().where('id', 'in', ids).execute() }) + +export const templateScales = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectTemplateScale().where('TemplateScale.id', 'in', ids).execute() +}) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index 1fbc779c4cf..bdd7d47e595 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -116,20 +116,6 @@ export const meetingMembersByUserId = new RethinkForeignKeyLoaderMaker( } ) -export const scalesByTeamId = new RethinkForeignKeyLoaderMaker( - 'templateScales', - 'teamId', - async (teamIds) => { - const r = await getRethink() - return r - .table('TemplateScale') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((row: RDatum) => row('removedAt').default(null).eq(null)) - .orderBy('sortOrder') - .run() - } -) - export const templateDimensionsByTemplateId = new RethinkForeignKeyLoaderMaker( 'templateDimensions', 'templateId', diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index 8c7f70d9be0..cf77db7ba68 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -13,7 +13,6 @@ export const meetingMembers = new RethinkPrimaryKeyLoaderMaker('MeetingMember') export const newMeetings = new RethinkPrimaryKeyLoaderMaker('NewMeeting') export const newFeatures = new RethinkPrimaryKeyLoaderMaker('NewFeature') export const notifications = new RethinkPrimaryKeyLoaderMaker('Notification') -export const templateScales = new RethinkPrimaryKeyLoaderMaker('TemplateScale') export const slackAuths = new RethinkPrimaryKeyLoaderMaker('SlackAuth') export const slackNotifications = new RethinkPrimaryKeyLoaderMaker('SlackNotification') export const suggestedActions = new RethinkPrimaryKeyLoaderMaker('SuggestedAction') diff --git a/packages/server/graphql/mutations/addPokerTemplateDimension.ts b/packages/server/graphql/mutations/addPokerTemplateDimension.ts index ff1d5a1e2ef..41fed4f6b99 100644 --- a/packages/server/graphql/mutations/addPokerTemplateDimension.ts +++ b/packages/server/graphql/mutations/addPokerTemplateDimension.ts @@ -54,13 +54,8 @@ const addPokerTemplateDimension = { // RESOLUTION const sortOrder = Math.max(0, ...activeDimensions.map((dimension) => dimension.sortOrder)) + 1 + dndNoise() - - const availableScales = await r - .table('TemplateScale') - .filter({teamId}) - .filter((row: RDatum) => row('removedAt').default(null).eq(null)) - .orderBy(r.desc('updatedAt')) - .run() + const rawAvailableScales = await dataLoader.get('scalesByTeamId').load(teamId) + const availableScales = rawAvailableScales.sort((a, b) => (a.updatedAt > b.updatedAt ? -1 : 1)) const defaultScaleId = availableScales.length > 0 ? availableScales.map((teamScale) => teamScale.id)[0] diff --git a/packages/server/graphql/mutations/addPokerTemplateScale.ts b/packages/server/graphql/mutations/addPokerTemplateScale.ts index fdb38242b46..f4c7f69dc91 100644 --- a/packages/server/graphql/mutations/addPokerTemplateScale.ts +++ b/packages/server/graphql/mutations/addPokerTemplateScale.ts @@ -1,12 +1,12 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' -import {SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' -import dndNoise from 'parabol-client/utils/dndNoise' -import getRethink from '../../database/rethinkDriver' -import {RDatum} from '../../database/stricterR' -import TemplateScale from '../../database/types/TemplateScale' +import {PokerCards, SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' +import {PALETTE} from '../../../client/styles/paletteV3' +import generateUID from '../../generateUID' +import getKysely from '../../postgres/getKysely' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' +import {positionAfter} from '../../utils/sortOrder' import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' import AddPokerTemplateScalePayload from '../types/AddPokerTemplateScalePayload' @@ -27,7 +27,7 @@ const addPokerTemplateScale = { {parentScaleId, teamId}: {parentScaleId?: string | null; teamId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { - const r = await getRethink() + const pg = getKysely() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const viewerId = getUserId(authToken) @@ -39,11 +39,7 @@ const addPokerTemplateScale = { // VALIDATION const [activeScales, viewer] = await Promise.all([ - r - .table('TemplateScale') - .getAll(teamId, {index: 'teamId'}) - .filter((row: RDatum) => row('removedAt').default(null).eq(null)) - .run(), + dataLoader.get('scalesByTeamId').load(teamId), dataLoader.get('users').loadNonNull(viewerId) ]) if (activeScales.length >= Threshold.MAX_POKER_TEMPLATE_SCALES) { @@ -51,12 +47,11 @@ const addPokerTemplateScale = { } // RESOLUTION - let newScale - const sortOrder = Math.max(0, ...activeScales.map((scale) => scale.sortOrder)) + 1 + dndNoise() + let newScaleId: string | undefined + let newScaleName: string + if (parentScaleId) { - const parentScale = (await dataLoader - .get('templateScales') - .load(parentScaleId)) as TemplateScale + const parentScale = await dataLoader.get('templateScales').load(parentScaleId) if (!parentScale) { return standardError(new Error('Parent scale not found'), {userId: viewerId}) } @@ -67,34 +62,79 @@ const addPokerTemplateScale = { } const {name} = parentScale const copyName = `${name} Copy` - const existingCopyCount = await r - .table('TemplateScale') - .getAll(teamId, {index: 'teamId'}) - .filter((row: RDatum) => row('removedAt').default(null).eq(null)) - .filter((row: RDatum) => row('name').match(`^${copyName}`) as any) - .count() - .run() - const newName = existingCopyCount === 0 ? copyName : `${copyName} #${existingCopyCount + 1}` - newScale = new TemplateScale({ - sortOrder, - name: newName, - teamId, - parentScaleId, - values: parentScale.values - }) + const re = new RegExp(`^${copyName}`) + const existingCopyCount = activeScales.filter(({name}) => name.match(re)).length + newScaleName = existingCopyCount === 0 ? copyName : `${copyName} #${existingCopyCount + 1}` + const res = await pg + .with('TemplateScaleInsert', (qc) => + qc + .insertInto('TemplateScale') + .values({ + id: generateUID(), + name: newScaleName, + teamId, + parentScaleId + }) + .returning(['id', 'parentScaleId']) + ) + .insertInto('TemplateScaleValue') + .columns(['templateScaleId', 'sortOrder', 'color', 'label']) + .expression(({selectFrom}) => + selectFrom('TemplateScaleValue') + .innerJoin( + 'TemplateScaleInsert', + 'TemplateScaleValue.templateScaleId', + 'TemplateScaleInsert.parentScaleId' + ) + .select(({ref}) => [ + ref('TemplateScaleInsert.id').as('templateScaleId'), + ref('TemplateScaleValue.sortOrder').as('sortOrder'), + ref('TemplateScaleValue.color').as('color'), + ref('TemplateScaleValue.label').as('label') + ]) + ) + .returning('TemplateScaleValue.templateScaleId') + .executeTakeFirstOrThrow() + newScaleId = res.templateScaleId } else { - newScale = new TemplateScale({ - sortOrder, - name: `*New Scale #${activeScales.length + 1}`, - teamId - }) + newScaleName = `*New Scale #${activeScales.length + 1}` + const res = await pg + .with('TemplateScaleInsert', (qc) => + qc + .insertInto('TemplateScale') + .values({ + id: generateUID(), + name: newScaleName, + teamId + }) + .returning('id as templateScaleId') + ) + .insertInto('TemplateScaleValue') + .values(({selectFrom}) => [ + { + templateScaleId: selectFrom('TemplateScaleInsert').select('templateScaleId'), + color: PALETTE.FUSCIA_400, + label: PokerCards.QUESTION_CARD as string, + sortOrder: positionAfter('') + }, + { + templateScaleId: selectFrom('TemplateScaleInsert').select('templateScaleId'), + color: PALETTE.GRAPE_500, + label: PokerCards.PASS_CARD as string, + sortOrder: positionAfter(positionAfter('')) + } + ]) + .returning('templateScaleId') + .executeTakeFirstOrThrow() + newScaleId = res.templateScaleId } - - await r.table('TemplateScale').insert(newScale).run() - - const scaleId = newScale.id - const data = {scaleId} - analytics.scaleMetrics(viewer, newScale, parentScaleId ? 'Scale Cloned' : 'Scale Created') + dataLoader.clearAll('templateScales') + const data = {scaleId: newScaleId} + analytics.scaleMetrics( + viewer, + {id: newScaleId, name: newScaleName, teamId}, + parentScaleId ? 'Scale Cloned' : 'Scale Created' + ) publish(SubscriptionChannel.TEAM, teamId, 'AddPokerTemplateScalePayload', data, subOptions) return data } diff --git a/packages/server/graphql/mutations/addPokerTemplateScaleValue.ts b/packages/server/graphql/mutations/addPokerTemplateScaleValue.ts index 3470e9418dc..2d379081848 100644 --- a/packages/server/graphql/mutations/addPokerTemplateScaleValue.ts +++ b/packages/server/graphql/mutations/addPokerTemplateScaleValue.ts @@ -1,20 +1,15 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' -import {RDatum} from '../../database/stricterR' -import TemplateScale from '../../database/types/TemplateScale' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' +import {getSortOrder} from '../../utils/sortOrder' import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' import AddPokerTemplateScaleValuePayload from '../types/AddPokerTemplateScaleValuePayload' import AddTemplateScaleInput, {AddTemplateScaleInputType} from '../types/AddTemplateScaleInput' -import { - validateColorValue, - validateScaleLabel, - validateScaleLabelValueUniqueness -} from './helpers/validateScaleValue' +import {validateColorValue, validateScaleLabel} from './helpers/validateScaleValue' const addPokerTemplateScaleValue = { description: 'Add a new scale value for a scale in a poker template', @@ -40,7 +35,7 @@ const addPokerTemplateScaleValue = { const viewerId = getUserId(authToken) // AUTH - const existingScale = await r.table('TemplateScale').get(scaleId).run() + const existingScale = await dataLoader.get('templateScales').load(scaleId) if (!existingScale || existingScale.removedAt) { return standardError(new Error('Did not find an active scale'), {userId: viewerId}) } @@ -56,48 +51,39 @@ const addPokerTemplateScaleValue = { if (!validateScaleLabel(label)) { return standardError(new Error('Invalid scale label'), {userId: viewerId}) } - - const updatedScale = await r - .table('TemplateScale') - .get(scaleId) - .update( - (row: RDatum) => ({ - // Append at the end of the sub-array (minus ? and Pass) - values: row('values').insertAt(row('values').count().sub(2), scaleValue), - updatedAt: now - }), - {returnChanges: true} - )('changes')(0)('new_val') - .default(null) - .run() - - if (updatedScale && !validateScaleLabelValueUniqueness(updatedScale.values)) { - // updated values and/or labels are not unique, rolling back - await r - .table('TemplateScale') - .get(scaleId) - .update({ - values: existingScale.values, - updatedAt: existingScale.updatedAt + const {values} = existingScale + const endCardIdx = values.findIndex(({label}) => ['?', 'Pass'].includes(label)) + const sortOrder = getSortOrder(values, endCardIdx + 1, endCardIdx) + try { + await pg + .insertInto('TemplateScaleValue') + .values({ + templateScaleId: scaleId, + color, + label, + sortOrder }) - .run() - return standardError(new Error('Scale labels and/or numerical values are not unique'), { - userId: viewerId - }) + .execute() + } catch (e) { + if ((e as any).constraint === 'TemplateScaleValue_templateScaleId_label_key') { + return {error: {message: 'Scale labels and/or numerical values are not unique'}} + } + return {error: {message: 'Could not add scale value'}} } - + dataLoader.clearAll('templateScales') // mark all templates using this scale as updated const updatedDimensions = await r .table('TemplateDimension') .getAll(scaleId, {index: 'scaleId'}) .run() const updatedTemplateIds = updatedDimensions.map(({templateId}) => templateId) - await pg - .updateTable('MeetingTemplate') - .set({updatedAt: now}) - .where('id', 'in', updatedTemplateIds) - .execute() - + if (updatedTemplateIds.length) { + await pg + .updateTable('MeetingTemplate') + .set({updatedAt: now}) + .where('id', 'in', updatedTemplateIds) + .execute() + } const data = {scaleId} publish( SubscriptionChannel.TEAM, diff --git a/packages/server/graphql/mutations/helpers/validateScaleValue.ts b/packages/server/graphql/mutations/helpers/validateScaleValue.ts index 908c19fb3d1..60fc93e91ff 100644 --- a/packages/server/graphql/mutations/helpers/validateScaleValue.ts +++ b/packages/server/graphql/mutations/helpers/validateScaleValue.ts @@ -1,7 +1,6 @@ import toArray from 'lodash.toarray' import palettePickerOptions from '../../../../client/styles/palettePickerOptions' import {Threshold} from '../../../../client/types/constEnums' -import TemplateScaleValue from '../../../database/types/TemplateScaleValue' const validateColorValue = (color: string) => { const validHexes = palettePickerOptions.map(({hex}) => hex) @@ -14,10 +13,4 @@ const validateScaleLabel = (label: string) => { return 0 < labelArr.length && labelArr.length <= Threshold.POKER_SCALE_VALUE_MAX_LENGTH } -const validateScaleLabelValueUniqueness = (scaleValues: TemplateScaleValue[]) => { - const labelList = scaleValues.map((scaleValue) => scaleValue.label) - - return new Set(labelList).size === labelList.length -} - -export {validateColorValue, validateScaleLabel, validateScaleLabelValueUniqueness} +export {validateColorValue, validateScaleLabel} diff --git a/packages/server/graphql/mutations/movePokerTemplateScaleValue.ts b/packages/server/graphql/mutations/movePokerTemplateScaleValue.ts index 6da4d67829d..d445953c1ed 100644 --- a/packages/server/graphql/mutations/movePokerTemplateScaleValue.ts +++ b/packages/server/graphql/mutations/movePokerTemplateScaleValue.ts @@ -1,10 +1,10 @@ import {GraphQLID, GraphQLInt, GraphQLNonNull, GraphQLString} 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 {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' +import {getSortOrder} from '../../utils/sortOrder' import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' import MovePokerTemplateScaleValuePayload from '../types/MovePokerTemplateScaleValuePayload' @@ -36,7 +36,7 @@ const movePokerTemplateScaleValue = { const now = new Date() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} - const scale = await r.table('TemplateScale').get(scaleId).run() + const scale = await dataLoader.get('templateScales').load(scaleId) //AUTH if (!scale || scale.removedAt) { @@ -47,39 +47,38 @@ const movePokerTemplateScaleValue = { } // VALIDATION - if (index < 0 || index >= scale.values.length - 2) { - return standardError(new Error('Invalid index to move to'), {userId: viewerId}) - } - const scaleValueIndex = scale.values.findIndex((scaleValue) => scaleValue.label === label) - if (scaleValueIndex === -1) { + const itemIdx = scale.values.findIndex((scaleValue) => scaleValue.label === label) + if (itemIdx === -1) { return standardError(new Error('Did not find an existing scale value to move'), { userId: viewerId }) } + if (index < 0 || index >= scale.values.length - 2) { + return standardError(new Error('Invalid index to move to'), {userId: viewerId}) + } // RESOLUTION - await r - .table('TemplateScale') - .get(scaleId) - .update((row: RValue) => ({ - values: row('values') - .deleteAt(scaleValueIndex) - .insertAt(index, scale.values[scaleValueIndex]), - updatedAt: now - })) - .run() + const sortOrder = getSortOrder(scale.values, itemIdx, index) + await pg + .updateTable('TemplateScaleValue') + .set({sortOrder}) + .where('templateScaleId', '=', scale.id) + .where('label', '=', label) + .execute() + dataLoader.clearAll('templateScales') // mark all templates using this scale as updated const updatedDimensions = await r .table('TemplateDimension') .getAll(scaleId, {index: 'scaleId'}) .run() const updatedTemplateIds = updatedDimensions.map(({templateId}) => templateId) - await pg - .updateTable('MeetingTemplate') - .set({updatedAt: now}) - .where('id', 'in', updatedTemplateIds) - .execute() - + if (updatedTemplateIds.length) { + await pg + .updateTable('MeetingTemplate') + .set({updatedAt: now}) + .where('id', 'in', updatedTemplateIds) + .execute() + } const data = {scaleId} publish( SubscriptionChannel.TEAM, diff --git a/packages/server/graphql/mutations/removePokerTemplateScale.ts b/packages/server/graphql/mutations/removePokerTemplateScale.ts index b56255592f4..fd2b48a35f4 100644 --- a/packages/server/graphql/mutations/removePokerTemplateScale.ts +++ b/packages/server/graphql/mutations/removePokerTemplateScale.ts @@ -1,4 +1,5 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' +import {sql} from 'kysely' import {SprintPokerDefaults, SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' @@ -40,7 +41,11 @@ const removePokerTemplateScale = { } // RESOLUTION - await r.table('TemplateScale').get(scaleId).update({removedAt: now, updatedAt: now}).run() + await pg + .updateTable('TemplateScale') + .set({removedAt: sql`CURRENT_TIMESTAMP`}) + .where('id', '=', scaleId) + .execute() const nextDefaultScaleId = SprintPokerDefaults.DEFAULT_SCALE_ID const dimensions = await r @@ -60,12 +65,13 @@ const removePokerTemplateScale = { .run() // mark templates as updated const updatedTemplateIds = dimensions.map(({templateId}: any) => templateId) - await pg - .updateTable('MeetingTemplate') - .set({updatedAt: now}) - .where('id', 'in', updatedTemplateIds) - .execute() - + if (updatedTemplateIds.length) { + await pg + .updateTable('MeetingTemplate') + .set({updatedAt: now}) + .where('id', 'in', updatedTemplateIds) + .execute() + } const data = {scaleId, dimensions} publish(SubscriptionChannel.TEAM, teamId, 'RemovePokerTemplateScalePayload', data, subOptions) return data diff --git a/packages/server/graphql/mutations/removePokerTemplateScaleValue.ts b/packages/server/graphql/mutations/removePokerTemplateScaleValue.ts index 9caecbfeab5..739ed5ce0ea 100644 --- a/packages/server/graphql/mutations/removePokerTemplateScaleValue.ts +++ b/packages/server/graphql/mutations/removePokerTemplateScaleValue.ts @@ -1,7 +1,6 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} 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 {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -33,7 +32,7 @@ const removePokerTemplateScaleValue = { const viewerId = getUserId(authToken) // AUTH - const scale = await r.table('TemplateScale').get(scaleId).run() + const scale = await dataLoader.get('templateScales').load(scaleId) if (!scale || scale.removedAt) { return standardError(new Error('Did not find an active scale'), {userId: viewerId}) } @@ -51,27 +50,25 @@ const removePokerTemplateScaleValue = { } // RESOLUTION - await r - .table('TemplateScale') - .get(scaleId) - .update((row: RValue) => ({ - values: row('values').deleteAt(row('values').offsetsOf(oldScaleValue).nth(0)), - updatedAt: now - })) - .run() - + await pg + .deleteFrom('TemplateScaleValue') + .where('templateScaleId', '=', scaleId) + .where('label', '=', label) + .execute() + dataLoader.clearAll('templateScales') // mark all templates using this scale as updated const updatedDimensions = await r .table('TemplateDimension') .getAll(scaleId, {index: 'scaleId'}) .run() const updatedTemplateIds = updatedDimensions.map(({templateId}) => templateId) - await pg - .updateTable('MeetingTemplate') - .set({updatedAt: now}) - .where('id', 'in', updatedTemplateIds) - .execute() - + if (updatedTemplateIds.length) { + await pg + .updateTable('MeetingTemplate') + .set({updatedAt: now}) + .where('id', 'in', updatedTemplateIds) + .execute() + } const data = {scaleId} publish( SubscriptionChannel.TEAM, diff --git a/packages/server/graphql/mutations/renamePokerTemplateScale.ts b/packages/server/graphql/mutations/renamePokerTemplateScale.ts index f9b42c50778..6d2ce4eea06 100644 --- a/packages/server/graphql/mutations/renamePokerTemplateScale.ts +++ b/packages/server/graphql/mutations/renamePokerTemplateScale.ts @@ -1,7 +1,6 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' -import {RDatum} from '../../database/stricterR' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -30,7 +29,7 @@ const renamePokerTemplateScale = { const now = new Date() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} - const scale = await r.table('TemplateScale').get(scaleId).run() + const scale = await dataLoader.get('templateScales').load(scaleId) const viewerId = getUserId(authToken) // AUTH @@ -46,37 +45,31 @@ const renamePokerTemplateScale = { const trimmedName = name.trim().slice(0, 50) const normalizedName = trimmedName || 'Unnamed Scale' - const allScales = await r - .table('TemplateScale') - .getAll(teamId, {index: 'teamId'}) - .filter((row: RDatum) => row('removedAt').default(null).eq(null)) - .run() + const allScales = await dataLoader.get('scalesByTeamId').load(teamId) if (allScales.find((scale) => scale.name === normalizedName)) { return standardError(new Error('Duplicate name scale'), {userId: viewerId}) } // RESOLUTION - await r - .table('TemplateScale') - .get(scaleId) - .update({ - name: normalizedName, - updatedAt: now - }) - .run() - + await pg + .updateTable('TemplateScale') + .set({name: normalizedName}) + .where('id', '=', scaleId) + .execute() + dataLoader.clearAll('templateScales') // mark all templates using this scale as updated const updatedDimensions = await r .table('TemplateDimension') .getAll(scaleId, {index: 'scaleId'}) .run() const updatedTemplateIds = updatedDimensions.map(({templateId}) => templateId) - await pg - .updateTable('MeetingTemplate') - .set({updatedAt: now}) - .where('id', 'in', updatedTemplateIds) - .execute() - + if (updatedTemplateIds.length) { + await pg + .updateTable('MeetingTemplate') + .set({updatedAt: now}) + .where('id', 'in', updatedTemplateIds) + .execute() + } const data = {scaleId} publish(SubscriptionChannel.TEAM, teamId, 'RenamePokerTemplateScalePayload', data, subOptions) return data diff --git a/packages/server/graphql/mutations/startSprintPoker.ts b/packages/server/graphql/mutations/startSprintPoker.ts index bfeb3c72af1..3b7b7766c32 100644 --- a/packages/server/graphql/mutations/startSprintPoker.ts +++ b/packages/server/graphql/mutations/startSprintPoker.ts @@ -6,9 +6,7 @@ import MeetingPoker from '../../database/types/MeetingPoker' import MeetingSettingsPoker from '../../database/types/MeetingSettingsPoker' import PokerMeetingMember from '../../database/types/PokerMeetingMember' import generateUID from '../../generateUID' -import getPg from '../../postgres/getPg' -import {insertTemplateRefQuery} from '../../postgres/queries/generated/insertTemplateRefQuery' -import {insertTemplateScaleRefQuery} from '../../postgres/queries/generated/insertTemplateScaleRefQuery' +import getKysely from '../../postgres/getKysely' import updateMeetingTemplateLastUsedAt from '../../postgres/queries/updateMeetingTemplateLastUsedAt' import updateTeamByTeamId from '../../postgres/queries/updateTeamByTeamId' import {MeetingTypeEnum} from '../../postgres/types/Meeting' @@ -27,7 +25,7 @@ import isStartMeetingLocked from './helpers/isStartMeetingLocked' import {IntegrationNotifier} from './helpers/notifications/IntegrationNotifier' const freezeTemplateAsRef = async (templateId: string, dataLoader: DataLoaderWorker) => { - const pg = getPg() + const pg = getKysely() const [template, dimensions] = await Promise.all([ dataLoader.get('meetingTemplates').loadNonNull(templateId), dataLoader.get('templateDimensionsByTemplateId').load(templateId) @@ -39,7 +37,7 @@ const freezeTemplateAsRef = async (templateId: string, dataLoader: DataLoaderWor isValid ) const templateScales = uniqueScales.map(({name, values}) => { - const scale = {name, values} + const scale = {name, values: values.map(({color, label}) => ({color, label}))} const {id, str} = getHashAndJSON(scale) return {id, scale: str} }) @@ -59,10 +57,17 @@ const freezeTemplateAsRef = async (templateId: string, dataLoader: DataLoaderWor } const {id: templateRefId, str: templateRefStr} = getHashAndJSON(templateRef) const ref = {id: templateRefId, template: templateRefStr} - await Promise.all([ - insertTemplateScaleRefQuery.run({templateScales}, pg), - insertTemplateRefQuery.run({ref}, pg) - ]) + await pg + .with('TemplateScaleRefUpsert', (qc) => + qc + .insertInto('TemplateScaleRef') + .values(templateScales) + .onConflict((oc) => oc.doNothing()) + ) + .insertInto('TemplateRef') + .values(ref) + .onConflict((oc) => oc.doNothing()) + .execute() return templateRefId } diff --git a/packages/server/graphql/mutations/updatePokerTemplateDimensionScale.ts b/packages/server/graphql/mutations/updatePokerTemplateDimensionScale.ts index 479eba4abfb..af6c5dc86d3 100644 --- a/packages/server/graphql/mutations/updatePokerTemplateDimensionScale.ts +++ b/packages/server/graphql/mutations/updatePokerTemplateDimensionScale.ts @@ -42,7 +42,7 @@ const updatePokerTemplateDimensionScale = { } // VALIDATION - const scale = await r.table('TemplateScale').get(scaleId).run() + const scale = await dataLoader.get('templateScales').load(scaleId) if (!scale || scale.removedAt || (!scale.isStarter && scale.teamId !== teamId)) { return standardError(new Error('Scale not found'), {userId: viewerId}) } diff --git a/packages/server/graphql/mutations/updatePokerTemplateScaleValue.ts b/packages/server/graphql/mutations/updatePokerTemplateScaleValue.ts index 64ce07cab26..b19dbd65478 100644 --- a/packages/server/graphql/mutations/updatePokerTemplateScaleValue.ts +++ b/packages/server/graphql/mutations/updatePokerTemplateScaleValue.ts @@ -2,7 +2,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import isSpecialPokerLabel from 'parabol-client/utils/isSpecialPokerLabel' import getRethink from '../../database/rethinkDriver' -import {RValue} from '../../database/stricterR' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -10,11 +9,7 @@ import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' import TemplateScaleInput, {TemplateScaleInputType} from '../types/TemplateScaleInput' import UpdatePokerTemplateScaleValuePayload from '../types/UpdatePokerTemplateScaleValuePayload' -import { - validateColorValue, - validateScaleLabel, - validateScaleLabelValueUniqueness -} from './helpers/validateScaleValue' +import {validateColorValue, validateScaleLabel} from './helpers/validateScaleValue' const updatePokerTemplateScaleValue = { description: 'Update the label, numerical value or color of a scale value in a scale', @@ -51,7 +46,7 @@ const updatePokerTemplateScaleValue = { const viewerId = getUserId(authToken) // AUTH - const existingScale = await r.table('TemplateScale').get(scaleId).run() + const existingScale = await dataLoader.get('templateScales').load(scaleId) if (!existingScale || existingScale.removedAt) { return standardError(new Error('Did not find an active scale'), {userId: viewerId}) } @@ -83,31 +78,13 @@ const updatePokerTemplateScaleValue = { if (!isSpecialPokerLabel(label) && !validateScaleLabel(label)) { return standardError(new Error('Invalid scale label'), {userId: viewerId}) } - - await r - .table('TemplateScale') - .get(scaleId) - .update((row: RValue) => ({ - values: row('values').changeAt(oldScaleValueIndex, newScaleValue), - updatedAt: now - })) - .run() - const updatedScale = await r.table('TemplateScale').get(scaleId).run() - - if (!validateScaleLabelValueUniqueness(updatedScale.values)) { - // updated values or labels are not unique, rolling back - await r - .table('TemplateScale') - .get(scaleId) - .update({ - values: existingScale.values, - updatedAt: existingScale.updatedAt - }) - .run() - return standardError(new Error('Scale labels and/or numerical values are not unique'), { - userId: viewerId - }) - } + await pg + .updateTable('TemplateScaleValue') + .set({label, color}) + .where('templateScaleId', '=', scaleId) + .where('label', '=', oldScaleLabel) + .execute() + dataLoader.clearAll('templateScales') // mark all templates using this scale as updated const updatedDimensions = await r @@ -115,12 +92,13 @@ const updatePokerTemplateScaleValue = { .getAll(scaleId, {index: 'scaleId'}) .run() const updatedTemplateIds = updatedDimensions.map(({templateId}) => templateId) - await pg - .updateTable('MeetingTemplate') - .set({updatedAt: now}) - .where('id', 'in', updatedTemplateIds) - .execute() - + if (updatedTemplateIds.length) { + await pg + .updateTable('MeetingTemplate') + .set({updatedAt: now}) + .where('id', 'in', updatedTemplateIds) + .execute() + } const data = {scaleId} publish( SubscriptionChannel.TEAM, diff --git a/packages/server/graphql/public/types/TemplateScale.ts b/packages/server/graphql/public/types/TemplateScale.ts new file mode 100644 index 00000000000..44b3e15b292 --- /dev/null +++ b/packages/server/graphql/public/types/TemplateScale.ts @@ -0,0 +1,31 @@ +import getRethink from '../../../database/rethinkDriver' +import {RDatum} from '../../../database/stricterR' +import {TemplateScaleResolvers} from '../resolverTypes' + +const TemplateScale: TemplateScaleResolvers = { + isActive: ({removedAt}) => !removedAt, + team: ({teamId}, _args, {dataLoader}) => { + return dataLoader.get('teams').loadNonNull(teamId) + }, + + dimensions: async ({id: scaleId, teamId}) => { + const r = await getRethink() + return r + .table('TemplateDimension') + .getAll(teamId, {index: 'teamId'}) + .filter((row: RDatum) => + row('removedAt').default(null).eq(null).and(row('scaleId').eq(scaleId)) + ) + .run() + }, + + values: ({id, values}) => { + return values.map((value, index) => ({ + ...value, + scaleId: id, + sortOrder: index + })) + } +} + +export default TemplateScale diff --git a/packages/server/graphql/public/types/TemplateScaleRef.ts b/packages/server/graphql/public/types/TemplateScaleRef.ts new file mode 100644 index 00000000000..97280282825 --- /dev/null +++ b/packages/server/graphql/public/types/TemplateScaleRef.ts @@ -0,0 +1,14 @@ +import {TemplateScaleRefResolvers} from '../resolverTypes' + +const TemplateScaleRef: TemplateScaleRefResolvers = { + values: ({id, values}) => { + return values.map(({color, label}, index) => ({ + color, + label, + scaleId: id, + sortOrder: index + })) + } +} + +export default TemplateScaleRef diff --git a/packages/server/graphql/public/types/TemplateScaleValue.ts b/packages/server/graphql/public/types/TemplateScaleValue.ts new file mode 100644 index 00000000000..4e3ac6836f0 --- /dev/null +++ b/packages/server/graphql/public/types/TemplateScaleValue.ts @@ -0,0 +1,16 @@ +import {TemplateScaleValueResolvers} from '../resolverTypes' + +export type TemplateScaleValueSource = { + color: string + label: string + sortOrder: number + scaleId: string +} + +const TemplateScaleValue: TemplateScaleValueResolvers = { + id: ({scaleId, label}) => { + return `${scaleId}:${label}` + } +} + +export default TemplateScaleValue diff --git a/packages/server/graphql/types/Team.ts b/packages/server/graphql/types/Team.ts index a950e9412bd..f6bf14bb400 100644 --- a/packages/server/graphql/types/Team.ts +++ b/packages/server/graphql/types/Team.ts @@ -13,9 +13,9 @@ import getRethink from '../../database/rethinkDriver' import MassInvitationDB from '../../database/types/MassInvitation' import Task from '../../database/types/Task' import ITeam from '../../database/types/Team' -import db from '../../db' import {getUserId, isSuperUser, isTeamMember, isUserBillingLeader} from '../../utils/authorization' import standardError from '../../utils/standardError' +import isValid from '../isValid' import connectionFromTasks from '../queries/helpers/connectionFromTasks' import {GQLContext} from './../graphql' import AgendaItem from './AgendaItem' @@ -204,13 +204,10 @@ const Team: GraphQLObjectType = new GraphQLObjectType({ type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(TemplateScale))), description: 'The list of scales this team can use', resolve: async ({id: teamId}: {id: string}, {}, {dataLoader}: GQLContext) => { - const activeTeamScales = await dataLoader.get('scalesByTeamId').load(teamId) - const publicScales = await db.read('starterScales', 'aGhostTeam') - const activeScales = [...activeTeamScales, ...publicScales] - const uniqueScales = activeScales.filter( - (scale, index) => index === activeScales.findIndex((obj) => obj.id === scale.id) - ) - return uniqueScales + const availableScales = await dataLoader + .get('scalesByTeamId') + .loadMany([teamId, 'aGhostTeam']) + return availableScales.filter(isValid).flat() } }, activeMeetings: { diff --git a/packages/server/graphql/types/TemplateScale.ts b/packages/server/graphql/types/TemplateScale.ts index 0f42590aba3..66859e8a47c 100644 --- a/packages/server/graphql/types/TemplateScale.ts +++ b/packages/server/graphql/types/TemplateScale.ts @@ -1,88 +1,8 @@ -import { - GraphQLBoolean, - GraphQLID, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLString -} from 'graphql' -import getRethink from '../../database/rethinkDriver' -import {RDatum} from '../../database/stricterR' -import TemplateScaleDB from '../../database/types/TemplateScale' -import {GQLContext} from '../graphql' -import {resolveTeam} from '../resolvers' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import Team from './Team' -import TemplateDimension from './TemplateDimension' -import TemplateScaleValue from './TemplateScaleValue' +import {GraphQLObjectType} from 'graphql' -const TemplateScale = new GraphQLObjectType({ +const TemplateScale = new GraphQLObjectType({ name: 'TemplateScale', - description: 'A team-specific template scale.', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'shortid' - }, - createdAt: { - type: new GraphQLNonNull(GraphQLISO8601Type) - }, - isActive: { - type: new GraphQLNonNull(GraphQLBoolean), - resolve: ({removedAt}) => !removedAt, - description: 'true if the scale is currently used by the team, else false' - }, - isStarter: { - type: new GraphQLNonNull(GraphQLBoolean), - resolve: ({isStarter}) => !!isStarter, - description: 'True if this is a starter/default scale; false otherwise' - }, - removedAt: { - type: GraphQLISO8601Type, - description: 'The datetime that the scale was removed. Null if it has not been removed.' - }, - teamId: { - type: new GraphQLNonNull(GraphQLID), - description: 'foreign key. use the team field' - }, - team: { - type: new GraphQLNonNull(Team), - description: 'The team that owns this template scale', - resolve: resolveTeam - }, - updatedAt: { - type: new GraphQLNonNull(GraphQLISO8601Type) - }, - name: { - type: new GraphQLNonNull(GraphQLString), - description: 'The title of the scale used in the template' - }, - dimensions: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(TemplateDimension))), - description: 'The dimensions currently using this scale', - resolve: async ({id: scaleId, teamId}) => { - const r = await getRethink() - return r - .table('TemplateDimension') - .getAll(teamId, {index: 'teamId'}) - .filter((row: RDatum) => - row('removedAt').default(null).eq(null).and(row('scaleId').eq(scaleId)) - ) - .run() - } - }, - values: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(TemplateScaleValue))), - description: 'The values used in this scale', - resolve: ({id, values}) => { - return values.map((value, index) => ({ - ...value, - scaleId: id, - sortOrder: index - })) - } - } - }) + fields: {} }) export default TemplateScale diff --git a/packages/server/graphql/types/TemplateScaleRef.ts b/packages/server/graphql/types/TemplateScaleRef.ts index 8d706c98625..78ab520da42 100644 --- a/packages/server/graphql/types/TemplateScaleRef.ts +++ b/packages/server/graphql/types/TemplateScaleRef.ts @@ -1,36 +1,8 @@ -import {GraphQLID, GraphQLList, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql' -import {GQLContext} from '../graphql' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import TemplateScaleValue from './TemplateScaleValue' +import {GraphQLObjectType} from 'graphql' -const TemplateScaleRef = new GraphQLObjectType({ +const TemplateScaleRef = new GraphQLObjectType({ name: 'TemplateScaleRef', - description: 'An immutable version of TemplateScale to be shared across all users', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'md5 hash' - }, - createdAt: { - type: new GraphQLNonNull(GraphQLISO8601Type) - }, - name: { - type: new GraphQLNonNull(GraphQLString), - description: 'The title of the scale used in the template' - }, - values: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(TemplateScaleValue))), - description: 'The values used in this scale', - resolve: ({id, values}) => { - return values.map(({color, label}: {color: string; label: string}, index: number) => ({ - color, - label, - scaleId: id, - sortOrder: index - })) - } - } - }) + fields: {} }) export default TemplateScaleRef diff --git a/packages/server/graphql/types/TemplateScaleValue.ts b/packages/server/graphql/types/TemplateScaleValue.ts deleted file mode 100644 index e9dfc193629..00000000000 --- a/packages/server/graphql/types/TemplateScaleValue.ts +++ /dev/null @@ -1,34 +0,0 @@ -import {GraphQLID, GraphQLInt, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql' -import {GQLContext} from '../graphql' - -const TemplateScaleValue = new GraphQLObjectType({ - name: 'TemplateScaleValue', - description: 'A value for a scale.', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - resolve: ({scaleId, label}) => { - return `${scaleId}:${label}` - } - }, - scaleId: { - description: 'The id of the scale this value belongs to', - type: new GraphQLNonNull(GraphQLID) - }, - color: { - description: 'The color used to visually group a scale value', - type: new GraphQLNonNull(GraphQLString) - }, - label: { - description: 'The label for this value, e.g., XS, M, L', - type: new GraphQLNonNull(GraphQLString) - }, - - sortOrder: { - type: new GraphQLNonNull(GraphQLInt), - description: 'the order of the scale value in this scale' - } - }) -}) - -export default TemplateScaleValue diff --git a/packages/server/postgres/migrations/1614030642692_refs-for-meetings.ts b/packages/server/postgres/migrations/1614030642692_refs-for-meetings.ts index 152c5c99793..b024cd537d8 100644 --- a/packages/server/postgres/migrations/1614030642692_refs-for-meetings.ts +++ b/packages/server/postgres/migrations/1614030642692_refs-for-meetings.ts @@ -8,7 +8,6 @@ import {RValue, r} from 'rethinkdb-ts' import {parse} from 'url' import MeetingPoker from '../../database/types/MeetingPoker' import TemplateDimension from '../../database/types/TemplateDimension' -import TemplateScale from '../../database/types/TemplateScale' import {insertTemplateRefQuery, insertTemplateScaleRefQuery} from '../generatedMigrationHelpers' import getPgConfig from '../getPgConfig' @@ -58,11 +57,11 @@ export async function up(): Promise { .run()) as { id: string name: string - dimensions: (TemplateDimension & {scale: TemplateScale})[] + dimensions: (TemplateDimension & {scale: any})[] }[] const uniqueScaleIdSet = new Set() - const uniqueScales = [] as TemplateScale[] + const uniqueScales = [] as any[] dimensionsByTemplateId.forEach((group) => { const {dimensions} = group dimensions.forEach((dimension) => { diff --git a/packages/server/postgres/migrations/1698265600000_addPrioritizationTemplates.ts b/packages/server/postgres/migrations/1698265600000_addPrioritizationTemplates.ts index a73875ad16c..49bf054d3bd 100644 --- a/packages/server/postgres/migrations/1698265600000_addPrioritizationTemplates.ts +++ b/packages/server/postgres/migrations/1698265600000_addPrioritizationTemplates.ts @@ -3,7 +3,6 @@ import {Client} from 'pg' import {r} from 'rethinkdb-ts' import connectRethinkDB from '../../database/connectRethinkDB' import TemplateDimension from '../../database/types/TemplateDimension' -import TemplateScale from '../../database/types/TemplateScale' import getPgConfig from '../getPgConfig' import getPgp from '../getPgp' @@ -46,7 +45,7 @@ const getTemplateIllustrationUrl = (filename: string) => { throw new Error('Mssing Env: FILE_STORE_PROVIDER') } -const MOSCOW_SCALE_CONFIG: TemplateScale = { +const MOSCOW_SCALE_CONFIG = { createdAt, id: 'moscowScale', isStarter: true, diff --git a/packages/server/postgres/migrations/1721405703862_TemplateScale.ts b/packages/server/postgres/migrations/1721405703862_TemplateScale.ts new file mode 100644 index 00000000000..7926e454beb --- /dev/null +++ b/packages/server/postgres/migrations/1721405703862_TemplateScale.ts @@ -0,0 +1,149 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {Client} from 'pg' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' +import getPgConfig from '../getPgConfig' + +const START_CHAR_CODE = 32 +const END_CHAR_CODE = 126 + +export function positionAfter(pos: string) { + for (let i = pos.length - 1; i >= 0; i--) { + const curCharCode = pos.charCodeAt(i) + if (curCharCode < END_CHAR_CODE) { + return pos.substr(0, i) + String.fromCharCode(curCharCode + 1) + } + } + return pos + String.fromCharCode(START_CHAR_CODE + 1) +} + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS "TemplateScale" ( + "id" VARCHAR(100) PRIMARY KEY, + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "name" VARCHAR(50) NOT NULL, + "teamId" VARCHAR(100) NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "parentScaleId" VARCHAR(100), + "isStarter" BOOLEAN NOT NULL DEFAULT FALSE, + "removedAt" TIMESTAMP WITH TIME ZONE, + CONSTRAINT "fk_teamId" + FOREIGN KEY("teamId") + REFERENCES "Team"("id") + ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS "idx_TemplateScale_teamId" ON "TemplateScale"("teamId") WHERE "removedAt" IS NULL; + DROP TRIGGER IF EXISTS "update_TemplateScale_updatedAt" ON "TemplateScale"; + CREATE TRIGGER "update_TemplateScale_updatedAt" BEFORE UPDATE ON "TemplateScale" FOR EACH ROW EXECUTE PROCEDURE "set_updatedAt"(); + + CREATE TABLE IF NOT EXISTS "TemplateScaleValue" ( + "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + "templateScaleId" VARCHAR(100) NOT NULL, + "sortOrder" VARCHAR(64) NOT NULL COLLATE "C", + "color" VARCHAR(9) NOT NULL, + "label" VARCHAR(18) NOT NULL, + UNIQUE ("templateScaleId","label"), + CONSTRAINT "fk_templateScaleId" + FOREIGN KEY("templateScaleId") + REFERENCES "TemplateScale"("id") + ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS "idx_TemplateScaleValue_templateScaleId" ON "TemplateScaleValue"("templateScaleId"); + CREATE OR REPLACE FUNCTION "set_TemplateScale_updatedAt"() + RETURNS TRIGGER AS $t$ + BEGIN + -- Update the updatedAt column in TemplateScale + UPDATE "TemplateScale" + SET "updatedAt" = CURRENT_TIMESTAMP + WHERE id = NEW."templateScaleId"; + RETURN NEW; + END; + $t$ LANGUAGE plpgsql; + CREATE TRIGGER "update_TemplateScale_updatedAt_from_TemplateScaleValue" + AFTER INSERT OR UPDATE OR DELETE ON "TemplateScaleValue" + FOR EACH ROW + EXECUTE FUNCTION "set_TemplateScale_updatedAt"(); + END $$; +`.execute(pg) + + const rTemplateScales = await r.table('TemplateScale').coerceTo('array').run() + const templateScaleValues = [] as { + templateScaleId: string + sortOrder: string + color: string + label: string + }[] + const templateScales = rTemplateScales.map((templateScale) => { + const {id, createdAt, name, teamId, updatedAt, parentScaleId, isStarter, removedAt, values} = + templateScale + let curSortOrder = '' + const templateVals = values?.map((value) => { + const sortOrder = positionAfter(curSortOrder) + curSortOrder = sortOrder + return { + templateScaleId: id, + sortOrder, + color: value.color, + label: value.label.slice(0, 8) + } + }) + templateScaleValues.push(...templateVals) + return {id, createdAt, name, teamId, updatedAt, parentScaleId, isStarter, removedAt} + }) + + const chunk = (arr: any[], size: number) => + Array.from({length: Math.ceil(arr.length / size)}, (_, i) => + arr.slice(i * size, i * size + size) + ) + + const valueChunks = chunk(templateScaleValues, 10000) + + await pg.insertInto('TemplateScale').values(templateScales).execute() + await Promise.all( + valueChunks.map(async (chunk) => { + try { + return await pg.insertInto('TemplateScaleValue').values(chunk).execute() + } catch (e) { + await Promise.all( + chunk.map(async (row) => { + try { + await pg + .insertInto('TemplateScaleValue') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + console.log(e, row) + } + }) + ) + } + }) + ) + + await pg.schema + .alterTable('TemplateScale') + .addForeignKeyConstraint('fk_parentScaleId', ['parentScaleId'], 'TemplateScale', ['id']) + .onDelete('set null') + .execute() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE IF EXISTS "TemplateScaleValue"; + DROP TABLE IF EXISTS "TemplateScale"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/queries/src/insertTemplateRefQuery.sql b/packages/server/postgres/queries/src/insertTemplateRefQuery.sql deleted file mode 100644 index f0da3dc65ca..00000000000 --- a/packages/server/postgres/queries/src/insertTemplateRefQuery.sql +++ /dev/null @@ -1,11 +0,0 @@ -/* - @name insertTemplateRefQuery - @param ref -> (id, template) -*/ -INSERT INTO "TemplateRef" ( - "id", - "template" -) -VALUES :ref -ON CONFLICT (id) -DO NOTHING; diff --git a/packages/server/postgres/queries/src/insertTemplateScaleRefQuery.sql b/packages/server/postgres/queries/src/insertTemplateScaleRefQuery.sql deleted file mode 100644 index 68e8aca0ab5..00000000000 --- a/packages/server/postgres/queries/src/insertTemplateScaleRefQuery.sql +++ /dev/null @@ -1,11 +0,0 @@ -/* - @name insertTemplateScaleRefQuery - @param templateScales -> ((id, scale)...) -*/ -INSERT INTO "TemplateScaleRef" ( - "id", - "scale" -) -VALUES :templateScales -ON CONFLICT (id) -DO NOTHING; diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts new file mode 100644 index 00000000000..59af2b66533 --- /dev/null +++ b/packages/server/postgres/select.ts @@ -0,0 +1,18 @@ +import {sql} from 'kysely' +import getKysely from './getKysely' + +export const selectTemplateScale = () => { + return getKysely() + .selectFrom('TemplateScale') + .where('removedAt', 'is', null) + .leftJoin('TemplateScaleValue', 'TemplateScale.id', 'TemplateScaleValue.templateScaleId') + .groupBy('TemplateScale.id') + .selectAll('TemplateScale') + .select(() => [ + sql< + {color: string; label: string; sortOrder: string}[] + >`json_agg(json_build_object('color', "TemplateScaleValue".color, 'label', "TemplateScaleValue".label, 'sortOrder', "TemplateScaleValue"."sortOrder") ORDER BY "TemplateScaleValue"."sortOrder")`.as( + 'values' + ) + ]) +} diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index 558bc238192..ea5fcd37de1 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -1,6 +1,22 @@ -import {Selectable} from 'kysely' -import {OrganizationUser as OrganizationUserPG, TeamMember as TeamMemberPG} from '../pg.d' +import {SelectQueryBuilder, Selectable} from 'kysely' +import { + OrganizationUser as OrganizationUserPG, + TeamMember as TeamMemberPG, + TemplateScaleRef as TemplateScaleRefPG +} from '../pg.d' +import {selectTemplateScale} from '../select' + +type ExtractTypeFromQueryBuilderSelect any> = + ReturnType extends SelectQueryBuilder ? X : never export type OrganizationUser = Selectable export type TeamMember = Selectable + +export type TemplateScale = ExtractTypeFromQueryBuilderSelect + +// TODO refactor getTemplateScaleRefsByIds to kysely +export type TemplateScaleRef = Selectable & { + name: string + values: {color: string; label: string}[] +} diff --git a/packages/server/utils/analytics/analytics.ts b/packages/server/utils/analytics/analytics.ts index 741e73ab6ab..61f672d9931 100644 --- a/packages/server/utils/analytics/analytics.ts +++ b/packages/server/utils/analytics/analytics.ts @@ -8,12 +8,12 @@ import MeetingTemplate from '../../database/types/MeetingTemplate' import {Reactable, ReactableEnum} from '../../database/types/Reactable' import {SlackNotificationEventEnum} from '../../database/types/SlackNotification' import {TaskServiceEnum} from '../../database/types/Task' -import TemplateScale from '../../database/types/TemplateScale' import {DataLoaderWorker} from '../../graphql/graphql' import {ModifyType} from '../../graphql/public/resolverTypes' import {IntegrationProviderServiceEnumType} from '../../graphql/types/IntegrationProviderServiceEnum' import {UpgradeCTALocationEnumType} from '../../graphql/types/UpgradeCTALocationEnum' import {TeamPromptResponse} from '../../postgres/queries/getTeamPromptResponsesByIds' +import {TemplateScale} from '../../postgres/types' import {MeetingTypeEnum} from '../../postgres/types/Meeting' import {MeetingSeries} from '../../postgres/types/MeetingSeries' import {AmplitudeAnalytics} from './amplitude/AmplitudeAnalytics' @@ -413,7 +413,7 @@ class Analytics { scaleMetrics = ( user: AnalyticsUser, - scale: TemplateScale, + scale: Pick, eventName: 'Scale Created' | 'Scale Cloned' ) => { this.track(user, eventName, { diff --git a/packages/server/utils/sortOrder.ts b/packages/server/utils/sortOrder.ts new file mode 100644 index 00000000000..aa03d336ded --- /dev/null +++ b/packages/server/utils/sortOrder.ts @@ -0,0 +1,67 @@ +// https://www.figma.com/blog/realtime-editing-of-ordered-sequences/#fractional-indexing/ +// https://steve.dignam.xyz/2020/03/31/practical-ordering/ +// Creates a sortOrder that is a string of characters that can be compared lexicographically +// This is beneficial because each change results in 1 update to the DB & there is no rebalancing necessary +// WARNING: the lexicographical sort assumes a C collation (i.e. compare strings via ASCII code) +// CloudSQL assumes a utf-8 (byte-code) comparision by default, so make sure the column is collated correctly! + +const START_CHAR_CODE = 32 +const END_CHAR_CODE = 126 + +export function positionBefore(pos: string) { + for (let i = pos.length - 1; i >= 0; i--) { + const curCharCode = pos.charCodeAt(i) + if (curCharCode > START_CHAR_CODE + 1) { + return pos.substr(0, i) + String.fromCharCode(curCharCode - 1) + } + } + return ( + pos.substr(0, pos.length - 1) + + String.fromCharCode(START_CHAR_CODE) + + String.fromCharCode(END_CHAR_CODE) + ) +} +export function positionAfter(pos: string) { + for (let i = pos.length - 1; i >= 0; i--) { + const curCharCode = pos.charCodeAt(i) + if (curCharCode < END_CHAR_CODE) { + return pos.substr(0, i) + String.fromCharCode(curCharCode + 1) + } + } + return pos + String.fromCharCode(START_CHAR_CODE + 1) +} + +function avg(a: number, b: number) { + return Math.trunc((a + b) / 2) +} + +function positionBetween(firstPos: string, secondPos: string) { + let flag = false + let position = '' + const maxLength = Math.max(firstPos.length, secondPos.length) + for (let i = 0; i < maxLength; i++) { + const lower = i < firstPos.length ? firstPos.charCodeAt(i) : START_CHAR_CODE + const upper = i < secondPos.length && !flag ? secondPos.charCodeAt(i) : END_CHAR_CODE + if (lower === upper) { + position += String.fromCharCode(lower) + } else if (upper - lower > 1) { + position += String.fromCharCode(avg(lower, upper)) + flag = false + break + } else { + position += String.fromCharCode(lower) + flag = true + } + } + if (!flag) return position + return position + String.fromCharCode(avg(START_CHAR_CODE, END_CHAR_CODE)) +} + +export function getSortOrder(arr: {sortOrder: string}[], fromIdx: number, toIdx: number) { + const secondPosIdx = fromIdx < toIdx ? toIdx + 1 : toIdx + const firstPos = arr[secondPosIdx - 1]?.sortOrder + const secondPos = arr[secondPosIdx]?.sortOrder + if (firstPos && secondPos) return positionBetween(firstPos, secondPos) + if (secondPos) return positionBefore(secondPos) + return positionAfter(firstPos || '') +} From 88a3539e6f75a140e2ba4e1e15b23d71c865f945 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 24 Jul 2024 17:21:59 -0700 Subject: [PATCH 359/529] chore(release): release v7.39.3 (#10031) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a999d60cc0b..2f8093d94f6 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.39.2" + ".": "7.39.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 09b9fa1731c..6654a361cd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.39.3](https://github.com/ParabolInc/parabol/compare/v7.39.2...v7.39.3) (2024-07-25) + + +### Changed + +* **rethinkdb:** TemplateScale: One-shot ([#10021](https://github.com/ParabolInc/parabol/issues/10021)) ([0c6c8e7](https://github.com/ParabolInc/parabol/commit/0c6c8e79dfbbac93362b3db640b008e32ba701b3)) + ## [7.39.2](https://github.com/ParabolInc/parabol/compare/v7.39.1...v7.39.2) (2024-07-24) diff --git a/package.json b/package.json index 7351040d65d..94dd7ffa721 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.39.2", + "version": "7.39.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 8852d449624..a48ced587a5 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.39.2", + "version": "7.39.3", "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.39.2" + "parabol-server": "7.39.3" } } diff --git a/packages/client/package.json b/packages/client/package.json index 5ece805bce9..00da4eecb33 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.39.2", + "version": "7.39.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index e78210071f3..a5258421eac 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.39.2", + "version": "7.39.3", "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 0738c1b2524..1623ef6af34 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.39.2", + "version": "7.39.3", "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.39.2", - "parabol-server": "7.39.2", + "parabol-client": "7.39.3", + "parabol-server": "7.39.3", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 2626544317a..95a50e4e3af 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.39.2", + "version": "7.39.3", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index e8ed5217f59..f9156cd8334 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.39.2", + "version": "7.39.3", "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.39.2", + "parabol-client": "7.39.3", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From fa52c4638629fcf9c5c3453798d1320b9d8da2dd Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Thu, 25 Jul 2024 12:46:37 +0200 Subject: [PATCH 360/529] fix: Disable SAML on downgrade to starter (#10026) --- .../graphql/mutations/helpers/resolveDowngradeToStarter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts b/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts index 8e44110824c..86d5fcc0a4b 100644 --- a/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts +++ b/packages/server/graphql/mutations/helpers/resolveDowngradeToStarter.ts @@ -38,7 +38,7 @@ const resolveDowngradeToStarter = async ( .execute(), pg .updateTable('SAML') - .set({metadata: null, lastUpdatedBy: user.id}) + .set({metadata: null, url: null, lastUpdatedBy: user.id}) .where('orgId', '=', orgId) .execute(), updateTeamByOrgId( From 2dd490ac75578f73d7a7b3e1f8ccfb1cd2164884 Mon Sep 17 00:00:00 2001 From: Terry Acker Date: Thu, 25 Jul 2024 10:36:38 -0500 Subject: [PATCH 361/529] feat: nav updates (#9973) --- .../components/DashNavList/DashNavList.tsx | 97 ++++++++---------- .../DashNavList/DashNavListTeams.tsx | 6 +- .../components/DashNavList/DashNavMenu.tsx | 98 ------------------- .../components/Dashboard/DashSidebar.tsx | 19 ++-- .../components/Dashboard/LeftDashNavItem.tsx | 10 +- .../Dashboard/MobileDashSidebar.tsx | 27 +++-- .../components/SideBarStartMeetingButton.tsx | 4 +- .../components/Organization/OrgNav.tsx | 18 ++-- 8 files changed, 83 insertions(+), 196 deletions(-) delete mode 100644 packages/client/components/DashNavList/DashNavMenu.tsx diff --git a/packages/client/components/DashNavList/DashNavList.tsx b/packages/client/components/DashNavList/DashNavList.tsx index 33a61b06965..bda7d56a650 100644 --- a/packages/client/components/DashNavList/DashNavList.tsx +++ b/packages/client/components/DashNavList/DashNavList.tsx @@ -1,17 +1,14 @@ import styled from '@emotion/styled' +import {ManageAccounts} from '@mui/icons-material' import graphql from 'babel-plugin-relay/macro' import React from 'react' import {useFragment} from 'react-relay' -import {PALETTE} from '~/styles/paletteV3' import {DashNavList_organization$key} from '../../__generated__/DashNavList_organization.graphql' -import {TierEnum} from '../../__generated__/InvoiceHeader_invoice.graphql' -import useBreakpoint from '../../hooks/useBreakpoint' -import {Breakpoint} from '../../types/constEnums' -import {upperFirst} from '../../utils/upperFirst' -import LeftDashNavItem from '../Dashboard/LeftDashNavItem' -import BaseTag from '../Tag/BaseTag' +import {TierEnum} from '../../__generated__/OrganizationSubscription.graphql' +import {Tooltip} from '../../ui/Tooltip/Tooltip' +import {TooltipContent} from '../../ui/Tooltip/TooltipContent' +import {TooltipTrigger} from '../../ui/Tooltip/TooltipTrigger' import DashNavListTeams from './DashNavListTeams' -import DashNavMenu from './DashNavMenu' const EmptyTeams = styled('div')({ fontSize: 16, @@ -20,22 +17,13 @@ const EmptyTeams = styled('div')({ textAlign: 'center' }) -const StyledLeftDashNavItem = styled(LeftDashNavItem)<{isViewerOnTeam: boolean}>( - ({isViewerOnTeam}) => ({ - color: isViewerOnTeam ? PALETTE.SLATE_700 : PALETTE.SLATE_600, - borderRadius: 44, - paddingLeft: 15 - }) -) - -const Tag = styled(BaseTag)<{tier: TierEnum | null}>(({tier}) => ({ - backgroundColor: - tier === 'enterprise' ? PALETTE.SKY_500 : tier === 'team' ? PALETTE.GOLD_300 : PALETTE.JADE_400, - color: tier === 'team' ? PALETTE.GRAPE_700 : PALETTE.WHITE -})) +const StyledIcon = styled(ManageAccounts)({ + height: 18, + width: 18 +}) interface Props { - organizationsRef: DashNavList_organization$key | null + organizationsRef: DashNavList_organization$key onClick?: () => void } @@ -45,7 +33,6 @@ const DashNavList = (props: Props) => { graphql` fragment DashNavList_organization on Organization @relay(plural: true) { ...DashNavListTeams_organization - ...DashNavMenu_organization id name tier @@ -56,44 +43,42 @@ const DashNavList = (props: Props) => { `, organizationsRef ) - const isDesktop = useBreakpoint(Breakpoint.SIDEBAR_LEFT) - const teams = organizations?.flatMap((org) => org.viewerTeams) + + const TierEnumValues: TierEnum[] = ['enterprise', 'team', 'starter'] + + const sortedOrgs = organizations.toSorted((a, b) => { + const aTier = TierEnumValues.indexOf(a.tier) + const bTier = TierEnumValues.indexOf(b.tier) + return aTier < bTier ? -1 : aTier > bTier ? 1 : a.name.localeCompare(b.name) + }) + + const teams = organizations.flatMap((org) => org.viewerTeams) if (teams?.length === 0) { - return It appears you are not a member of any team! + return {'It appears you are not a member of any team!'} } return ( -
- {organizations?.map((org) => ( -
-
0 ? `border-b border-solid border-slate-300 p-2` : 'p-2' - } - > -
-
- - {org.name} - -
- {upperFirst(org.tier)} -
-
-
- {isDesktop ? ( - - ) : ( - - )} +
+ {sortedOrgs.map((org) => ( +
+
+ + {org.name} + + + + + + + + + {'Settings & Members'} + +
diff --git a/packages/client/components/DashNavList/DashNavListTeams.tsx b/packages/client/components/DashNavList/DashNavListTeams.tsx index 3c9e22099bb..560567cdb79 100644 --- a/packages/client/components/DashNavList/DashNavListTeams.tsx +++ b/packages/client/components/DashNavList/DashNavListTeams.tsx @@ -12,7 +12,7 @@ const StyledLeftDashNavItem = styled(LeftDashNavItem)<{isPublicTeams?: boolean}> ({isPublicTeams}) => ({ color: isPublicTeams ? PALETTE.SLATE_600 : PALETTE.SLATE_700, borderRadius: 44, - paddingLeft: 15 + paddingLeft: 16 }) ) @@ -61,7 +61,7 @@ const DashNavListTeams = (props: Props) => { if (!viewerTeams.length) return null return ( -
+
{viewerTeams.map((team) => { return ( { })} {publicTeamsCount > 0 && ( ( - ({isViewerOnTeam}) => ({ - color: isViewerOnTeam ? PALETTE.SLATE_700 : PALETTE.SLATE_600, - borderRadius: 44, - paddingLeft: 15 - }) -) - -type Props = { - organizationRef: DashNavMenu_organization$key -} - -const DashNavMenu = (props: Props) => { - const {organizationRef} = props - const history = useHistory() - const org = useFragment( - graphql` - fragment DashNavMenu_organization on Organization { - id - tier - } - `, - organizationRef - ) - const {id: orgId, tier} = org - const menuItems = [ - { - label: ( - <> - Plans & Billing{' '} - {tier === 'starter' && ( - <> - • Upgrade - - )} - - ), - href: `/me/organizations/${orgId}/billing` - }, - { - label: 'Teams', - href: `/me/organizations/${orgId}/teams` - }, - { - label: 'Members', - href: `/me/organizations/${orgId}/members` - }, - { - label: 'Organization Settings', - href: `/me/organizations/${orgId}/settings` - }, - { - label: 'Authentication', - href: `/me/organizations/${orgId}/authentication` - } - ] - - const handleMenuItemClick = (href: string) => { - history.push(href) - } - - return ( - - -
- } - > - - {menuItems.map((item) => ( - handleMenuItemClick(item.href)}> - {item.label} - - ))} - - - ) -} - -export default DashNavMenu diff --git a/packages/client/components/Dashboard/DashSidebar.tsx b/packages/client/components/Dashboard/DashSidebar.tsx index e919517f9e4..fb7ee5afdd9 100644 --- a/packages/client/components/Dashboard/DashSidebar.tsx +++ b/packages/client/components/Dashboard/DashSidebar.tsx @@ -4,7 +4,6 @@ import React from 'react' import {useFragment} from 'react-relay' import {useRouteMatch} from 'react-router' import {DashSidebar_viewer$key} from '../../__generated__/DashSidebar_viewer.graphql' -import {PALETTE} from '../../styles/paletteV3' import {NavSidebar} from '../../types/constEnums' import { AUTHENTICATION_PAGE, @@ -40,7 +39,7 @@ const NavMain = styled('div')({ const NavItem = styled(LeftDashNavItem)({ borderRadius: 44, - paddingLeft: 15 + paddingLeft: 16 }) const NavList = styled(DashNavList)({ @@ -56,14 +55,6 @@ const Wrapper = styled('div')({ flexDirection: 'column' }) -const OrgName = styled('div')({ - color: PALETTE.SLATE_600, - fontWeight: 600, - fontSize: 12, - lineHeight: '16px', - padding: '8px 16px' -}) - interface Props { isOpen: boolean viewerRef: DashSidebar_viewer$key | null @@ -107,7 +98,11 @@ const DashSidebar = (props: Props) => { label={'Organizations'} exact /> - {name} +
+ + {name} + +
{
@@ -78,7 +90,12 @@ const WholeMeetingSummaryResult = (props: Props) => { - + diff --git a/packages/client/package.json b/packages/client/package.json index 6be831cb4d2..6fa20806ca7 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -116,6 +116,7 @@ "json2csv": "5.0.7", "jwt-decode": "^2.1.0", "linkify-it": "^2.0.3", + "marked": "^13.0.3", "mousetrap": "^1.6.3", "ms": "^2.0.0", "react": "^17.0.2", diff --git a/packages/client/styles/theme/global.css b/packages/client/styles/theme/global.css index 8bfbfb9f72e..b1a76203c76 100644 --- a/packages/client/styles/theme/global.css +++ b/packages/client/styles/theme/global.css @@ -65,7 +65,7 @@ 2) prevent a horizontal scrollbar from causing a vertical scrollbar due to the 100vh */ #root { - @apply w-full h-screen p-0 m-0 bg-slate-200; + @apply m-0 h-screen w-full bg-slate-200 p-0; } *, @@ -187,3 +187,8 @@ .draft-codeblock { @apply m-0 rounded-[1px] border-l-2 border-solid border-l-slate-500 bg-slate-200 py-0 px-[8px] font-mono font-[13px] leading-normal; } + +.summary-link-style a { + @apply text-sky-500; + text-decoration: underline; +} diff --git a/yarn.lock b/yarn.lock index 3a072fdc2ea..267fb2c403b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -16916,6 +16916,11 @@ markdown-it@^13.0.1: mdurl "^1.0.1" uc.micro "^1.0.5" +marked@^13.0.3: + version "13.0.3" + resolved "https://registry.yarnpkg.com/marked/-/marked-13.0.3.tgz#5c5b4a5d0198060c7c9bc6ef9420a7fed30f822d" + integrity sha512-rqRix3/TWzE9rIoFGIn8JmsVfhiuC8VIQ8IdX5TfzmeBucdY05/0UlzKaw0eVtpcN/OdVFpBk7CjKGo9iHJ/zA== + marked@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/marked/-/marked-4.3.0.tgz#796362821b019f734054582038b116481b456cf3" From 40d8c8c15b9804a5ee2ef49649893cdc5dfdd665 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 12 Aug 2024 12:07:04 -0700 Subject: [PATCH 401/529] chore(rethinkdb): MeetingSettings: Phase 1 (#10088) Signed-off-by: Matt Krick --- packages/server/database/rethinkDriver.ts | 23 ------ .../server/dataloader/customLoaderMakers.ts | 35 +++++++-- .../dataloader/primaryKeyLoaderMakers.ts | 5 ++ .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../mutations/helpers/createTeamAndLeader.ts | 6 ++ .../graphql/mutations/removePokerTemplate.ts | 15 ++-- .../mutations/removeReflectTemplate.ts | 16 ++-- .../graphql/mutations/selectTemplate.ts | 9 ++- .../graphql/mutations/updateRetroMaxVotes.ts | 10 +++ .../public/mutations/setMeetingSettings.ts | 49 +++++++++++-- .../public/mutations/startRetrospective.ts | 10 ++- .../helpers/resolveSelectedTemplate.ts | 7 ++ .../1723061869934_MeetingSettings-phase1.ts | 73 +++++++++++++++++++ packages/server/postgres/select.ts | 39 ++++++++++ packages/server/postgres/types/index.d.ts | 3 + packages/server/utils/analytics/analytics.ts | 2 +- 16 files changed, 252 insertions(+), 51 deletions(-) create mode 100644 packages/server/postgres/migrations/1723061869934_MeetingSettings-phase1.ts diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index 1b61df93624..03dc30c72c3 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -3,13 +3,10 @@ import SlackAuth from '../database/types/SlackAuth' import SlackNotification from '../database/types/SlackNotification' import TeamInvitation from '../database/types/TeamInvitation' import {AnyMeeting, AnyMeetingSettings, AnyMeetingTeamMember} from '../postgres/types/Meeting' -import {ScheduledJobUnion} from '../types/custom' import getRethinkConfig from './getRethinkConfig' import {R} from './stricterR' import AgendaItem from './types/AgendaItem' -import AtlassianAuth from './types/AtlassianAuth' import Comment from './types/Comment' -import FailedAuthRequest from './types/FailedAuthRequest' import MassInvitation from './types/MassInvitation' import NotificationKickedOut from './types/NotificationKickedOut' import NotificationMeetingStageTimeLimitEnd from './types/NotificationMeetingStageTimeLimitEnd' @@ -31,10 +28,6 @@ export type RethinkSchema = { type: AgendaItem index: 'teamId' | 'meetingId' } - AtlassianAuth: { - type: AtlassianAuth - index: 'atlassianUserId' | 'userId' | 'teamId' - } Comment: { type: Comment index: 'discussionId' @@ -43,18 +36,6 @@ export type RethinkSchema = { type: RetrospectivePrompt index: 'teamId' | 'templateId' } - EmailVerification: { - type: any - index: 'email' | 'token' - } - FailedAuthRequest: { - type: FailedAuthRequest - index: 'email' | 'ip' - } - GQLRequest: { - type: any - index: 'id' - } MassInvitation: { type: MassInvitation index: 'teamMemberId' @@ -102,10 +83,6 @@ export type RethinkSchema = { type: PushInvitation index: 'userId' } - ScheduledJob: { - type: ScheduledJobUnion - index: 'runAt' | 'type' - } SlackAuth: { type: SlackAuth index: 'teamId' | 'userId' diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 38e013297ef..6dd43e5141e 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -2,7 +2,7 @@ import DataLoader from 'dataloader' import tracer from 'dd-trace' import {Selectable, SqlBool, sql} from 'kysely' import {PARABOL_AI_USER_ID} from '../../client/utils/constants' -import getRethink, {RethinkSchema} from '../database/rethinkDriver' +import getRethink from '../database/rethinkDriver' import {RDatum} from '../database/stricterR' import MeetingSettingsTeamPrompt from '../database/types/MeetingSettingsTeamPrompt' import MeetingTemplate from '../database/types/MeetingTemplate' @@ -26,8 +26,8 @@ import getLatestTaskEstimates from '../postgres/queries/getLatestTaskEstimates' import getMeetingTaskEstimates, { MeetingTaskEstimatesResult } from '../postgres/queries/getMeetingTaskEstimates' -import {selectTeams} from '../postgres/select' -import {OrganizationUser, Team} from '../postgres/types' +import {selectMeetingSettings, selectTeams} from '../postgres/select' +import {MeetingSettings, OrganizationUser, Team} from '../postgres/types' import {AnyMeeting, MeetingTypeEnum} from '../postgres/types/Meeting' import {Logger} from '../utils/Logger' import getRedis from '../utils/getRedis' @@ -288,7 +288,7 @@ export const githubDimensionFieldMaps = (parent: RootDataLoader) => { export const meetingSettingsByType = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { dependsOn('meetingSettings') - return new DataLoader( + return new DataLoader( async (keys) => { const r = await getRethink() const types = {} as Record @@ -313,7 +313,7 @@ export const meetingSettingsByType = (parent: RootDataLoader, dependsOn: Registe const {teamId, meetingType} = key // until we decide the final shape of the team prompt settings, let's return a temporary hardcoded value if (meetingType === 'teamPrompt') { - return new MeetingSettingsTeamPrompt({teamId}) + return new MeetingSettingsTeamPrompt({teamId}) as any } return docs.find((doc) => doc.teamId === teamId && doc.meetingType === meetingType)! }) @@ -325,6 +325,31 @@ export const meetingSettingsByType = (parent: RootDataLoader, dependsOn: Registe ) } +export const _PGmeetingSettingsByType = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('meetingSettings') + return new DataLoader( + async (keys) => { + const res = await selectMeetingSettings() + .where(({eb, refTuple, tuple}) => + eb( + refTuple('teamId', 'meetingType'), + 'in', + keys.map((key) => tuple(key.teamId, key.meetingType)) + ) + ) + .execute() + return keys.map( + (key) => + res.find((doc) => doc.teamId === key.teamId && doc.meetingType === key.meetingType)! + ) + }, + { + ...parent.dataLoaderOptions, + cacheKeyFn: (key) => `${key.teamId}:${key.meetingType}` + } + ) +} + export const organizationApprovedDomainsByOrgId = (parent: RootDataLoader) => { return new DataLoader( async (orgIds) => { diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 155e7f34489..a5e46c9e45b 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -6,6 +6,7 @@ import getMeetingTemplatesByIds from '../postgres/queries/getMeetingTemplatesByI import getTemplateRefsByIds from '../postgres/queries/getTemplateRefsByIds' import {getUsersByIds} from '../postgres/queries/getUsersByIds' import { + selectMeetingSettings, selectOrganizations, selectRetroReflections, selectSuggestedAction, @@ -85,3 +86,7 @@ export const templateDimensions = primaryKeyLoaderMaker((ids: readonly string[]) export const suggestedActions = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectSuggestedAction().where('id', 'in', ids).execute() }) + +export const _PGmeetingSettings = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectMeetingSettings().where('id', 'in', ids).execute() +}) diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index 5d015a2042a..a0fa13be86a 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -4,7 +4,6 @@ import RethinkPrimaryKeyLoaderMaker from './RethinkPrimaryKeyLoaderMaker' * all rethink dataloader types which also must exist in {@link rethinkDriver/RethinkSchema} */ export const agendaItems = new RethinkPrimaryKeyLoaderMaker('AgendaItem') -export const atlassianAuths = new RethinkPrimaryKeyLoaderMaker('AtlassianAuth') export const comments = new RethinkPrimaryKeyLoaderMaker('Comment') export const reflectPrompts = new RethinkPrimaryKeyLoaderMaker('ReflectPrompt') export const massInvitations = new RethinkPrimaryKeyLoaderMaker('MassInvitation') diff --git a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts index f9c9bdf6d73..c7e672d1c69 100644 --- a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts +++ b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts @@ -30,6 +30,7 @@ export default async function createTeamAndLeader( const organization = await dataLoader.get('organizations').loadNonNull(orgId) const {tier, trialStartDate} = organization const verifiedTeam = new Team({...newTeam, createdBy: userId, tier, trialStartDate}) + const meetingSettings = [ new MeetingSettingsRetrospective({teamId}), new MeetingSettingsAction({teamId}), @@ -77,6 +78,11 @@ export default async function createTeamAndLeader( .values(suggestedAction) .onConflict((oc) => oc.columns(['userId', 'type']).doNothing()) ) + .with('MeetingSettingsInsert', (qc) => + qc + .insertInto('MeetingSettings') + .values(meetingSettings.map((s) => ({...s, jiraSearchQueries: null}))) + ) .insertInto('TimelineEvent') .values(timelineEvent) .execute(), diff --git a/packages/server/graphql/mutations/removePokerTemplate.ts b/packages/server/graphql/mutations/removePokerTemplate.ts index 660b5622633..7d1b9f399a8 100644 --- a/packages/server/graphql/mutations/removePokerTemplate.ts +++ b/packages/server/graphql/mutations/removePokerTemplate.ts @@ -42,12 +42,9 @@ const removePokerTemplate = { const {teamId} = template const [templates, settings] = await Promise.all([ dataLoader.get('meetingTemplatesByType').load({meetingType: 'poker', teamId}), - r - .table('MeetingSettings') - .getAll(teamId, {index: 'teamId'}) - .filter({meetingType: 'poker'}) - .nth(0) - .run() as unknown as MeetingSettingsPoker + dataLoader + .get('meetingSettingsByType') + .load({meetingType: 'poker', teamId}) as any as MeetingSettingsPoker ]) // RESOLUTION @@ -66,6 +63,11 @@ const removePokerTemplate = { if (settings.selectedTemplateId === templateId) { const nextTemplate = templates.find((template) => template.id !== templateId) const nextTemplateId = nextTemplate?.id ?? SprintPokerDefaults.DEFAULT_TEMPLATE_ID + await getKysely() + .updateTable('MeetingSettings') + .set({selectedTemplateId: nextTemplateId}) + .where('id', '=', settingsId) + .execute() await r .table('MeetingSettings') .get(settingsId) @@ -73,6 +75,7 @@ const removePokerTemplate = { selectedTemplateId: nextTemplateId }) .run() + dataLoader.clearAll('meetingSettings') } const data = {templateId, settingsId} diff --git a/packages/server/graphql/mutations/removeReflectTemplate.ts b/packages/server/graphql/mutations/removeReflectTemplate.ts index 2a7f9bc9896..251f0f631a2 100644 --- a/packages/server/graphql/mutations/removeReflectTemplate.ts +++ b/packages/server/graphql/mutations/removeReflectTemplate.ts @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import MeetingSettingsRetrospective from '../../database/types/MeetingSettingsRetrospective' +import getKysely from '../../postgres/getKysely' import removeMeetingTemplate from '../../postgres/queries/removeMeetingTemplate' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -41,12 +42,9 @@ const removeReflectTemplate = { const {teamId} = template const [templates, settings] = await Promise.all([ dataLoader.get('meetingTemplatesByType').load({meetingType: 'retrospective', teamId}), - r - .table('MeetingSettings') - .getAll(teamId, {index: 'teamId'}) - .filter({meetingType: 'retrospective'}) - .nth(0) - .run() as unknown as MeetingSettingsRetrospective + dataLoader + .get('meetingSettingsByType') + .load({meetingType: 'retrospective', teamId}) as any as MeetingSettingsRetrospective ]) // RESOLUTION @@ -69,6 +67,11 @@ const removeReflectTemplate = { if (settings.selectedTemplateId === templateId) { const nextTemplate = templates.find((template) => template.id !== templateId) const nextTemplateId = nextTemplate?.id ?? 'workingStuckTemplate' + await getKysely() + .updateTable('MeetingSettings') + .set({selectedTemplateId: nextTemplateId}) + .where('id', '=', settingsId) + .execute() await r .table('MeetingSettings') .get(settingsId) @@ -76,6 +79,7 @@ const removeReflectTemplate = { selectedTemplateId: nextTemplateId }) .run() + dataLoader.clearAll('meetingSettings') } const data = {templateId, settingsId} diff --git a/packages/server/graphql/mutations/selectTemplate.ts b/packages/server/graphql/mutations/selectTemplate.ts index 5e0855e6912..861f9a37545 100644 --- a/packages/server/graphql/mutations/selectTemplate.ts +++ b/packages/server/graphql/mutations/selectTemplate.ts @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import MeetingTemplate from '../../database/types/MeetingTemplate' +import getKysely from '../../postgres/getKysely' import {Logger} from '../../utils/Logger' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -67,7 +68,13 @@ const selectTemplate = { )('changes')(0)('old_val')('id') .default(null) .run() - + await getKysely() + .updateTable('MeetingSettings') + .set({selectedTemplateId}) + .where('teamId', '=', teamId) + .where('meetingType', '=', template.type) + .returning('id') + .executeTakeFirst() // No need to check if a non-null 'meetingSettingsId' was returned - the Activity Library client // will always attempt to update the template, even if it's already selected, and we don't need // to return a 'meetingSettingsId' if no updates took place. diff --git a/packages/server/graphql/mutations/updateRetroMaxVotes.ts b/packages/server/graphql/mutations/updateRetroMaxVotes.ts index 3ec7c8d1f2c..e687a9b9bb8 100644 --- a/packages/server/graphql/mutations/updateRetroMaxVotes.ts +++ b/packages/server/graphql/mutations/updateRetroMaxVotes.ts @@ -5,6 +5,7 @@ import mode from 'parabol-client/utils/mode' import getRethink from '../../database/rethinkDriver' import {RValue} from '../../database/stricterR' import MeetingRetrospective from '../../database/types/MeetingRetrospective' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -136,6 +137,15 @@ const updateRetroMaxVotes = { // RESOLUTION await Promise.all([ + getKysely() + .updateTable('MeetingSettings') + .set({ + totalVotes, + maxVotesPerGroup + }) + .where('teamId', '=', teamId) + .where('meetingType', '=', 'retrospective') + .execute(), r .table('MeetingSettings') .getAll(teamId, {index: 'teamId'}) diff --git a/packages/server/graphql/public/mutations/setMeetingSettings.ts b/packages/server/graphql/public/mutations/setMeetingSettings.ts index 0b0cc44a899..92c339d1b41 100644 --- a/packages/server/graphql/public/mutations/setMeetingSettings.ts +++ b/packages/server/graphql/public/mutations/setMeetingSettings.ts @@ -2,11 +2,16 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {isNotNull} from 'parabol-client/utils/predicates' import getRethink from '../../../database/rethinkDriver' import {RValue} from '../../../database/stricterR' -import {analytics, MeetingSettings} from '../../../utils/analytics/analytics' +import getKysely from '../../../postgres/getKysely' +import {MeetingSettings} from '../../../postgres/types' +import { + analytics, + MeetingSettings as MeetingSettingsAnalytics +} from '../../../utils/analytics/analytics' import {getUserId} from '../../../utils/authorization' import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' -import {MutationResolvers} from '../resolverTypes' +import {MutationResolvers, NewMeetingPhaseTypeEnum} from '../resolverTypes' const setMeetingSettings: MutationResolvers['setMeetingSettings'] = async ( _source, @@ -19,13 +24,13 @@ const setMeetingSettings: MutationResolvers['setMeetingSettings'] = async ( // AUTH const viewerId = getUserId(authToken) - const settings = await r.table('MeetingSettings').get(settingsId).run() + const settings = (await dataLoader.get('meetingSettings').load(settingsId)) as MeetingSettings if (!settings) { return standardError(new Error('Settings not found'), {userId: viewerId}) } // RESOLUTION - const {teamId, meetingType} = settings + const {teamId, meetingType, phaseTypes} = settings const [team, viewer] = await Promise.all([ dataLoader.get('teams').loadNonNull(teamId), dataLoader.get('users').loadNonNull(viewerId) @@ -34,7 +39,27 @@ const setMeetingSettings: MutationResolvers['setMeetingSettings'] = async ( const {featureFlags} = organization const hasTranscriptFlag = featureFlags?.includes('zoomTranscription') - const meetingSettings = {} as MeetingSettings + const meetingSettings = {} as MeetingSettingsAnalytics + const firstPhases: NewMeetingPhaseTypeEnum[] = [] + if (checkinEnabled || (checkinEnabled !== false && phaseTypes.includes('checkin'))) { + firstPhases.push('checkin') + } + if (teamHealthEnabled || (teamHealthEnabled !== false && phaseTypes.includes('TEAM_HEALTH'))) { + firstPhases.push('TEAM_HEALTH') + } + const nextSettings = { + phaseTypes: [ + ...firstPhases, + ...phaseTypes.filter((phase) => phase !== 'checkin' && phase !== 'TEAM_HEALTH') + ], + disableAnonymity: isNotNull(disableAnonymity) ? disableAnonymity : settings.disableAnonymity, + videoMeetingURL: hasTranscriptFlag + ? isNotNull(videoMeetingURL) + ? videoMeetingURL + : settings.videoMeetingURL + : null + } + await r .table('MeetingSettings') .get(settingsId) @@ -74,8 +99,20 @@ const setMeetingSettings: MutationResolvers['setMeetingSettings'] = async ( }) .run() + await getKysely() + .updateTable('MeetingSettings') + .set(nextSettings) + .where('id', '=', settings.id) + .execute() + dataLoader.clearAll('meetingSettings') + const data = {settingsId} - analytics.meetingSettingsChanged(viewer, teamId, meetingType, meetingSettings) + analytics.meetingSettingsChanged(viewer, teamId, meetingType, { + disableAnonymity: nextSettings.disableAnonymity, + videoMeetingURL: nextSettings.videoMeetingURL, + hasIcebreaker: nextSettings.phaseTypes.includes('checkin'), + hasTeamHealth: nextSettings.phaseTypes.includes('TEAM_HEALTH') + }) publish(SubscriptionChannel.TEAM, teamId, 'SetMeetingSettingsPayload', data, subOptions) return data } diff --git a/packages/server/graphql/public/mutations/startRetrospective.ts b/packages/server/graphql/public/mutations/startRetrospective.ts index b4c7ba3ddad..81b5a500f0d 100644 --- a/packages/server/graphql/public/mutations/startRetrospective.ts +++ b/packages/server/graphql/public/mutations/startRetrospective.ts @@ -24,6 +24,7 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( {authToken, socketId: mutatorId, dataLoader} ) => { const r = await getRethink() + const pg = getKysely() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const DUPLICATE_THRESHOLD = 3000 @@ -113,7 +114,13 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( .update({ videoMeetingURL: null }) - .run() + .run(), + videoMeetingURL && + pg + .updateTable('MeetingSettings') + .set({videoMeetingURL: null}) + .where('id', '=', meetingSettingsId) + .execute() ]) if (meetingSeries) { // meeting was modified if a new meeting series was created @@ -133,7 +140,6 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( dataLoader }) if (meetingSeries && gcalSeriesId) { - const pg = getKysely() await pg .updateTable('MeetingSeries') .set({gcalSeriesId}) diff --git a/packages/server/graphql/queries/helpers/resolveSelectedTemplate.ts b/packages/server/graphql/queries/helpers/resolveSelectedTemplate.ts index 4f09128912e..5d556ffdf3b 100644 --- a/packages/server/graphql/queries/helpers/resolveSelectedTemplate.ts +++ b/packages/server/graphql/queries/helpers/resolveSelectedTemplate.ts @@ -1,6 +1,7 @@ import getRethink from '../../../database/rethinkDriver' import MeetingSettingsPoker from '../../../database/types/MeetingSettingsPoker' import MeetingSettingsRetrospective from '../../../database/types/MeetingSettingsRetrospective' +import getKysely from '../../../postgres/getKysely' import {GQLContext} from '../../graphql' const resolveSelectedTemplate = @@ -23,6 +24,12 @@ const resolveSelectedTemplate = .get(settingsId) .update({selectedTemplateId: fallbackTemplateId}) .run() + await getKysely() + .updateTable('MeetingSettings') + .set({selectedTemplateId: fallbackTemplateId}) + .where('id', '=', settingsId) + .execute() + return dataLoader.get('meetingTemplates').loadNonNull(fallbackTemplateId) } diff --git a/packages/server/postgres/migrations/1723061869934_MeetingSettings-phase1.ts b/packages/server/postgres/migrations/1723061869934_MeetingSettings-phase1.ts new file mode 100644 index 00000000000..d8f1741cd7d --- /dev/null +++ b/packages/server/postgres/migrations/1723061869934_MeetingSettings-phase1.ts @@ -0,0 +1,73 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'NewMeetingPhaseTypeEnum') THEN + CREATE TYPE "NewMeetingPhaseTypeEnum" AS ENUM ( + 'ESTIMATE', + 'SCOPE', + 'SUMMARY', + 'agendaitems', + 'checkin', + 'TEAM_HEALTH', + 'discuss', + 'firstcall', + 'group', + 'lastcall', + 'lobby', + 'reflect', + 'updates', + 'vote', + 'RESPONSES' + ); + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'MeetingTypeEnum') THEN + CREATE TYPE "MeetingTypeEnum" AS ENUM ( + 'action', + 'retrospective', + 'poker', + 'teamPrompt' + ); + END IF; + CREATE TABLE IF NOT EXISTS "MeetingSettings" ( + "id" VARCHAR(100) PRIMARY KEY, + "phaseTypes" "NewMeetingPhaseTypeEnum"[] NOT NULL, + "meetingType" "MeetingTypeEnum" NOT NULL, + "teamId" VARCHAR(100) NOT NULL, + "selectedTemplateId" VARCHAR(100), + "jiraSearchQueries" JSONB, + "maxVotesPerGroup" SMALLINT, + "totalVotes" SMALLINT, + "disableAnonymity" BOOLEAN, + "videoMeetingURL" VARCHAR(2056), + UNIQUE("teamId", "meetingType"), + CONSTRAINT "fk_teamId" + FOREIGN KEY("teamId") + REFERENCES "Team"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_selectedTemplateId" + FOREIGN KEY("selectedTemplateId") + REFERENCES "MeetingTemplate"("id") + ON DELETE SET NULL + ); + CREATE INDEX IF NOT EXISTS "idx_MeetingSettings_teamId" ON "MeetingSettings"("teamId"); + END $$; +`) + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE "MeetingSettings"; + DROP TYPE "NewMeetingPhaseTypeEnum"; + DROP TYPE "MeetingTypeEnum"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index 96a0c4e3917..15bd911c327 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -1,5 +1,6 @@ import type {JSONContent} from '@tiptap/core' import {NotNull, sql} from 'kysely' +import {NewMeetingPhaseTypeEnum} from '../graphql/public/resolverTypes' import getKysely from './getKysely' export const selectTimelineEvent = () => { @@ -166,3 +167,41 @@ export const selectTeamPromptResponses = () => ]) .$narrowType<{content: JSONContent}>() .select(({fn}) => [fn('to_json', ['reactjis']).as('reactjis')]) + +export type JiraSearchQuery = { + id: string + queryString: string + isJQL: boolean + projectKeyFilters?: string[] + lastUsedAt: Date +} + +export const selectMeetingSettings = () => + getKysely() + .selectFrom('MeetingSettings') + .select([ + 'id', + 'phaseTypes', + 'meetingType', + 'teamId', + 'selectedTemplateId', + 'jiraSearchQueries', + 'maxVotesPerGroup', + 'totalVotes', + 'disableAnonymity', + 'videoMeetingURL' + ]) + .$narrowType< + // NewMeeetingPhaseTypeEnum[] should be inferred from kysely-codegen, but it's not + | {meetingType: NotNull; phaseTypes: NewMeetingPhaseTypeEnum[]} + | { + meetingType: 'retrospective' + phaseTypes: NewMeetingPhaseTypeEnum[] + maxVotesPerGroup: NotNull + totalVotes: NotNull + disableAnonymity: NotNull + } + >() + .select(({fn}) => [ + fn('to_json', ['jiraSearchQueries']).as('jiraSearchQueries') + ]) diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index af90f4583a8..f5260cea94f 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -6,6 +6,7 @@ import { TeamMember as TeamMemberPG } from '../pg.d' import { + selectMeetingSettings, selectOrganizations, selectRetroReflections, selectSuggestedAction, @@ -39,3 +40,5 @@ export type TemplateScale = ExtractTypeFromQueryBuilderSelect + +export type MeetingSettings = ExtractTypeFromQueryBuilderSelect diff --git a/packages/server/utils/analytics/analytics.ts b/packages/server/utils/analytics/analytics.ts index 6da67df7976..9424b16a716 100644 --- a/packages/server/utils/analytics/analytics.ts +++ b/packages/server/utils/analytics/analytics.ts @@ -91,7 +91,7 @@ export type TaskEstimateProperties = { export type MeetingSettings = { hasIcebreaker?: boolean hasTeamHealth?: boolean - disableAnonymity?: boolean + disableAnonymity?: boolean | null videoMeetingURL?: string | null } From 602dbbbb6c5197e3086cc976fc442dcac7e283f5 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:40:44 -0700 Subject: [PATCH 402/529] chore(release): release v7.43.0 (#10100) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index f005d1c8ce6..aef4662aaf9 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.42.2" + ".": "7.43.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index c97007c47bc..12ec5079e21 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.43.0](https://github.com/ParabolInc/parabol/compare/v7.42.2...v7.43.0) (2024-08-12) + + +### Added + +* update meeting summary UI ([#10081](https://github.com/ParabolInc/parabol/issues/10081)) ([bf1851e](https://github.com/ParabolInc/parabol/commit/bf1851e9ebfe88aa470952b1daf053a83c04c865)) + + +### Changed + +* **rethinkdb:** Invoice: Remove ([#10086](https://github.com/ParabolInc/parabol/issues/10086)) ([10164a8](https://github.com/ParabolInc/parabol/commit/10164a8a43850c9ca80042a151b4da6020ad37d6)) +* **rethinkdb:** MeetingSettings: Phase 1 ([#10088](https://github.com/ParabolInc/parabol/issues/10088)) ([40d8c8c](https://github.com/ParabolInc/parabol/commit/40d8c8c15b9804a5ee2ef49649893cdc5dfdd665)) + ## [7.42.2](https://github.com/ParabolInc/parabol/compare/v7.42.1...v7.42.2) (2024-08-08) diff --git a/package.json b/package.json index af680d38988..af77b461503 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.42.2", + "version": "7.43.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 59e597f5a46..a7849afd4ad 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.42.2", + "version": "7.43.0", "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.42.2" + "parabol-server": "7.43.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 6fa20806ca7..2235e8e1440 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.42.2", + "version": "7.43.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 7331557996b..10e25af40f1 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.42.2", + "version": "7.43.0", "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 161a7a3a181..23cf3bcce89 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.42.2", + "version": "7.43.0", "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.42.2", - "parabol-server": "7.42.2", + "parabol-client": "7.43.0", + "parabol-server": "7.43.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 107f835a451..f032f8fd418 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.42.2", + "version": "7.43.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 4f5d153f0d2..1a7b9e32630 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.42.2", + "version": "7.43.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.42.2", + "parabol-client": "7.43.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From dfc6422532ea2f39b1773d66154d304527a07371 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 12 Aug 2024 16:10:38 -0700 Subject: [PATCH 403/529] chore(rethinkdb): MeetingSettings: Phase 2 (#10089) Signed-off-by: Matt Krick --- .../1723074637531_MeetingSettings-phase2.ts | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 packages/server/postgres/migrations/1723074637531_MeetingSettings-phase2.ts diff --git a/packages/server/postgres/migrations/1723074637531_MeetingSettings-phase2.ts b/packages/server/postgres/migrations/1723074637531_MeetingSettings-phase2.ts new file mode 100644 index 00000000000..1bb1e99f0a0 --- /dev/null +++ b/packages/server/postgres/migrations/1723074637531_MeetingSettings-phase2.ts @@ -0,0 +1,102 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + const MAX_PG_PARAMS = 65545 + + const PG_COLS = [ + 'id', + 'phaseTypes', + 'meetingType', + 'teamId', + 'selectedTemplateId', + 'jiraSearchQueries', + 'maxVotesPerGroup', + 'totalVotes', + 'disableAnonymity', + 'videoMeetingURL' + ] as const + type MeetingSettings = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + + const startAt = new Date() + let curId = r.minval + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, curId) + let rawRowsToInsert = (await r + .table('MeetingSettings') + .between(curId, r.maxval, { + index: 'id', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'id'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as MeetingSettings[] + if (rawRowsToInsert.length === 0) { + // since we don't have a createdAt, it's possible new rows were created while this was running. + // Grab those new teams & get their settings, too + const newTeams = await pg + .selectFrom('Team') + .select('id') + .where('createdAt', '>', startAt) + .execute() + const newTeamIds = newTeams.map((team) => team.id) + console.log('got new TeamIds!', newTeamIds) + if (newTeamIds.length === 0) break + rawRowsToInsert = (await r + .table('MeetingSettings') + .getAll(r.args(newTeamIds)) + .pluck(...PG_COLS) + .run()) as MeetingSettings[] + } + const rowsToInsert = rawRowsToInsert.map((row) => ({ + ...row + })) + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curId = lastRow.id + await Promise.all( + rowsToInsert.map(async (row) => { + try { + await pg + .insertInto('MeetingSettings') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_teamId') { + // console.log(`Skipping ${row.id} because it has no team`) + return + } + if (e.constraint === 'fk_selectedTemplateId') { + // console.log(`Skipping ${row.id} because it has no template`) + return + } + console.log(e, row) + } + }) + ) + } +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql`TRUNCATE TABLE "MeetingSettings" CASCADE`.execute(pg) +} From 889ac2f762b690d8dc320d792191eb921f108405 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Mon, 12 Aug 2024 16:17:44 -0700 Subject: [PATCH 404/529] chore(release): release v7.43.1 (#10102) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index aef4662aaf9..d37ddc75bda 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.43.0" + ".": "7.43.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 12ec5079e21..9d15ead0db3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.43.1](https://github.com/ParabolInc/parabol/compare/v7.43.0...v7.43.1) (2024-08-12) + + +### Changed + +* **rethinkdb:** MeetingSettings: Phase 2 ([#10089](https://github.com/ParabolInc/parabol/issues/10089)) ([dfc6422](https://github.com/ParabolInc/parabol/commit/dfc6422532ea2f39b1773d66154d304527a07371)) + ## [7.43.0](https://github.com/ParabolInc/parabol/compare/v7.42.2...v7.43.0) (2024-08-12) diff --git a/package.json b/package.json index af77b461503..f484d9110ae 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.43.0", + "version": "7.43.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index a7849afd4ad..79d23783bde 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.43.0", + "version": "7.43.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.43.0" + "parabol-server": "7.43.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index 2235e8e1440..d016310c2f0 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.43.0", + "version": "7.43.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 10e25af40f1..163202a1c5b 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.43.0", + "version": "7.43.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 23cf3bcce89..cb3dc4b284e 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.43.0", + "version": "7.43.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.43.0", - "parabol-server": "7.43.0", + "parabol-client": "7.43.1", + "parabol-server": "7.43.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index f032f8fd418..6af164f337a 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.43.0", + "version": "7.43.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 1a7b9e32630..61f443884ae 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.43.0", + "version": "7.43.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -123,7 +123,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.43.0", + "parabol-client": "7.43.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 5debeb959c7b6c9fbbf2f8a20a0377bb09efc3e0 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Wed, 14 Aug 2024 10:01:49 +0100 Subject: [PATCH 405/529] fix: email-meeting-summaries (#10104) --- .../WholeMeetingSummaryResult.tsx | 4 +- packages/client/package.json | 2 + packages/server/package.json | 2 + yarn.lock | 91 ++++++++++++++++++- 4 files changed, 93 insertions(+), 6 deletions(-) diff --git a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/WholeMeetingSummaryResult.tsx b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/WholeMeetingSummaryResult.tsx index c9e45cad738..1f1c899ef7b 100644 --- a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/WholeMeetingSummaryResult.tsx +++ b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/WholeMeetingSummaryResult.tsx @@ -1,8 +1,8 @@ import graphql from 'babel-plugin-relay/macro' -import DOMPurify from 'dompurify' import {marked} from 'marked' import React, {useEffect} from 'react' import {useFragment} from 'react-relay' +import sanitizeHtml from 'sanitize-html' import {WholeMeetingSummaryResult_meeting$key} from '../../../../../__generated__/WholeMeetingSummaryResult_meeting.graphql' import useAtmosphere from '../../../../../hooks/useAtmosphere' import {PALETTE} from '../../../../../styles/paletteV3' @@ -73,7 +73,7 @@ const WholeMeetingSummaryResult = ({meetingRef}: Props) => { gfm: true, breaks: true }) as string - const sanitizedSummary = DOMPurify.sanitize(renderedSummary) + const sanitizedSummary = sanitizeHtml(renderedSummary) const explainerText = team?.tier === 'starter' ? AIExplainer.STARTER : AIExplainer.PREMIUM_MEETING diff --git a/packages/client/package.json b/packages/client/package.json index d016310c2f0..89ef44501b5 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -47,6 +47,7 @@ "@types/react-textarea-autosize": "^8.0.0", "@types/react-virtualized": "^9.21.20", "@types/relay-runtime": "^14.1.9", + "@types/sanitize-html": "^2.11.0", "@types/stripe-v2": "^2.0.1", "babel-plugin-relay": "^12.0.0", "debug": "^4.1.1", @@ -139,6 +140,7 @@ "relay-runtime": "^14.1.0", "resize-observer-polyfill": "^1.5.0", "rrule": "^2.7.2", + "sanitize-html": "^2.13.0", "string-score": "^1.0.1", "swiper": "^8.3.1", "tailwind-merge": "^1.13.2", diff --git a/packages/server/package.json b/packages/server/package.json index 61f443884ae..d8222193349 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -43,6 +43,7 @@ "@types/node": "^20.11.17", "@types/nodemailer": "^6.4.14", "@types/relay-runtime": "^14.1.9", + "@types/sanitize-html": "^2.11.0", "@types/sharp": "^0.32.0", "@types/string-similarity": "^3.0.0", "@types/uuid": "^8.3.3", @@ -131,6 +132,7 @@ "rethinkdb-ts": "2.6.0", "rrule": "^2.7.2", "samlify": "^2.8.2", + "sanitize-html": "^2.13.0", "sharp": "^0.32.6", "string-similarity": "^3.0.0", "stripe": "^9.13.0", diff --git a/yarn.lock b/yarn.lock index 267fb2c403b..17fed322d80 100644 --- a/yarn.lock +++ b/yarn.lock @@ -9201,6 +9201,13 @@ resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== +"@types/sanitize-html@^2.11.0": + version "2.11.0" + resolved "https://registry.yarnpkg.com/@types/sanitize-html/-/sanitize-html-2.11.0.tgz#582d8c72215c0228e3af2be136e40e0b531addf2" + integrity sha512-7oxPGNQHXLHE48r/r/qjn7q0hlrs3kL7oZnGj0Wf/h9tj/6ibFyRkNbsDxaBBZ4XUZ0Dx5LGCyDJ04ytSofacQ== + dependencies: + htmlparser2 "^8.0.0" + "@types/semver@^7.5.0": version "7.5.6" resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339" @@ -10924,9 +10931,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001517, caniuse-lite@^1.0.30001580, caniuse-lite@^1.0.30001640, caniuse-lite@~1.0.0: - version "1.0.30001649" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001649.tgz#3ec700309ca0da2b0d3d5fb03c411b191761c992" - integrity sha512-fJegqZZ0ZX8HOWr6rcafGr72+xcgJKI9oWfDW5DrD7ExUtgZC7a7R7ZYmZqplh7XDocFdGeIFn7roAxhOeYrPQ== + version "1.0.30001651" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz#52de59529e8b02b1aedcaaf5c05d9e23c0c28138" + integrity sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg== capital-case@^1.0.4: version "1.0.4" @@ -12428,6 +12435,15 @@ dom-serializer@^1.0.1, dom-serializer@^1.3.2: domhandler "^4.2.0" entities "^2.0.0" +dom-serializer@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-2.0.0.tgz#e41b802e1eedf9f6cae183ce5e622d789d7d8e53" + integrity sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.2" + entities "^4.2.0" + dom7@^4.0.4: version "4.0.4" resolved "https://registry.yarnpkg.com/dom7/-/dom7-4.0.4.tgz#8b68c5d8e5e2ed0fddb1cb93e433bc9060c8f3fb" @@ -12440,6 +12456,11 @@ domelementtype@^2.0.1, domelementtype@^2.2.0: resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.2.0.tgz#9a0b6c2782ed6a1c7323d42267183df9bd8b1d57" integrity sha512-DtBMo82pv1dFtUmHyr48beiuq792Sxohr+8Hm9zoxklYPfa6n0Z3Byjj2IV7bmr2IyqClnqEQhfgHJJ5QF0R5A== +domelementtype@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/domelementtype/-/domelementtype-2.3.0.tgz#5c45e8e869952626331d7aab326d01daf65d589d" + integrity sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw== + domexception@^4.0.0: version "4.0.0" resolved "https://registry.npmmirror.com/domexception/-/domexception-4.0.0.tgz#4ad1be56ccadc86fc76d033353999a8037d03673" @@ -12454,6 +12475,13 @@ domhandler@^4.0.0, domhandler@^4.2.0, domhandler@^4.3.0: dependencies: domelementtype "^2.2.0" +domhandler@^5.0.2, domhandler@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/domhandler/-/domhandler-5.0.3.tgz#cc385f7f751f1d1fc650c21374804254538c7d31" + integrity sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w== + dependencies: + domelementtype "^2.3.0" + dompurify@^2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.1.tgz#f9cb1a275fde9af6f2d0a2644ef648dd6847b631" @@ -12468,6 +12496,15 @@ domutils@^2.5.2, domutils@^2.7.0, domutils@^2.8.0: domelementtype "^2.2.0" domhandler "^4.2.0" +domutils@^3.0.1: + version "3.1.0" + resolved "https://registry.yarnpkg.com/domutils/-/domutils-3.1.0.tgz#c47f551278d3dc4b0b1ab8cbb42d751a6f0d824e" + integrity sha512-H78uMmQtI2AhgDJjWeQmHwJJ2bLPD3GMmO7Zja/ZZh84wkm+4ut+IUnUdRa8uCGX88DiVx1j6FRe1XfxEgjEZA== + dependencies: + dom-serializer "^2.0.0" + domelementtype "^2.3.0" + domhandler "^5.0.3" + dot-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/dot-case/-/dot-case-3.0.4.tgz#9b2b670d00a431667a8a75ba29cd1b98809ce751" @@ -12705,6 +12742,11 @@ entities@^2.0.0: resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" integrity sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A== +entities@^4.2.0: + version "4.5.0" + resolved "https://registry.yarnpkg.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + entities@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/entities/-/entities-4.4.0.tgz#97bdaba170339446495e653cfd2db78962900174" @@ -14546,6 +14588,16 @@ htmlparser2@^6.1.0: domutils "^2.5.2" entities "^2.0.0" +htmlparser2@^8.0.0: + version "8.0.2" + resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-8.0.2.tgz#f002151705b383e62433b5cf466f5b716edaec21" + integrity sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA== + dependencies: + domelementtype "^2.3.0" + domhandler "^5.0.3" + domutils "^3.0.1" + entities "^4.4.0" + http-cache-semantics@^4.1.0: version "4.1.1" resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz#abe02fcb2985460bf0323be664436ec3476a6d5a" @@ -17386,7 +17438,7 @@ nan@^2.18.0: resolved "https://registry.yarnpkg.com/nan/-/nan-2.19.0.tgz#bb58122ad55a6c5bc973303908d5b16cfdd5a8c0" integrity sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw== -nanoid@^3.1.31, nanoid@^3.3.6: +nanoid@^3.1.31, nanoid@^3.3.6, nanoid@^3.3.7: version "3.3.7" resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== @@ -18477,6 +18529,11 @@ parse-path@^7.0.0: dependencies: protocols "^2.0.0" +parse-srcset@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/parse-srcset/-/parse-srcset-1.0.2.tgz#f2bd221f6cc970a938d88556abc589caaaa2bde1" + integrity sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q== + parse-url@^7.0.2, parse-url@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/parse-url/-/parse-url-8.1.0.tgz#972e0827ed4b57fc85f0ea6b0d839f0d8a57a57d" @@ -18975,6 +19032,15 @@ postcss@^8.0.9, postcss@^8.1.4, postcss@^8.4.21: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.3.11: + version "8.4.41" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.41.tgz#d6104d3ba272d882fe18fc07d15dc2da62fa2681" + integrity sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ== + dependencies: + nanoid "^3.3.7" + picocolors "^1.0.1" + source-map-js "^1.2.0" + postgres-array@~2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" @@ -20480,6 +20546,18 @@ samlify@^2.8.2: xml-crypto "^2.1.3" xpath "^0.0.32" +sanitize-html@^2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/sanitize-html/-/sanitize-html-2.13.0.tgz#71aedcdb777897985a4ea1877bf4f895a1170dae" + integrity sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA== + dependencies: + deepmerge "^4.2.2" + escape-string-regexp "^4.0.0" + htmlparser2 "^8.0.0" + is-plain-object "^5.0.0" + parse-srcset "^1.0.2" + postcss "^8.3.11" + sanitizer@^0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/sanitizer/-/sanitizer-0.1.3.tgz#d4f0af7475d9a7baf2a9e5a611718baa178a39e1" @@ -20992,6 +21070,11 @@ source-map-js@^1.0.2: resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== +source-map-js@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.2.0.tgz#16b809c162517b5b8c3e7dcd315a2a5c2612b2af" + integrity sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg== + source-map-support@0.5.13: version "0.5.13" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.13.tgz#31b24a9c2e73c2de85066c0feb7d44767ed52932" From d5ddf496f068948f35e27cac7620d30162b7eb44 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 14 Aug 2024 13:48:30 +0100 Subject: [PATCH 406/529] chore(release): release v7.43.2 (#10105) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d37ddc75bda..08a5315d448 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.43.1" + ".": "7.43.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9d15ead0db3..0fbc86e4ae4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.43.2](https://github.com/ParabolInc/parabol/compare/v7.43.1...v7.43.2) (2024-08-14) + + +### Fixed + +* email-meeting-summaries ([#10104](https://github.com/ParabolInc/parabol/issues/10104)) ([5debeb9](https://github.com/ParabolInc/parabol/commit/5debeb959c7b6c9fbbf2f8a20a0377bb09efc3e0)) + ## [7.43.1](https://github.com/ParabolInc/parabol/compare/v7.43.0...v7.43.1) (2024-08-12) diff --git a/package.json b/package.json index f484d9110ae..fed847a106d 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.43.1", + "version": "7.43.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 79d23783bde..58e54bc609b 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.43.1", + "version": "7.43.2", "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.43.1" + "parabol-server": "7.43.2" } } diff --git a/packages/client/package.json b/packages/client/package.json index 89ef44501b5..28df8dc95ca 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.43.1", + "version": "7.43.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 163202a1c5b..f850815daab 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.43.1", + "version": "7.43.2", "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 cb3dc4b284e..0eef5c28ee9 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.43.1", + "version": "7.43.2", "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.43.1", - "parabol-server": "7.43.1", + "parabol-client": "7.43.2", + "parabol-server": "7.43.2", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 6af164f337a..38db05eea03 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.43.1", + "version": "7.43.2", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index d8222193349..bb851a1492d 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.43.1", + "version": "7.43.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.43.1", + "parabol-client": "7.43.2", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 7aa172bed5df7bdfdf981a920c19d4bcce9710e2 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 14 Aug 2024 13:14:29 -0700 Subject: [PATCH 407/529] chore(rethinkdb): MeetingSettings: Phase 3 (#10090) Signed-off-by: Matt Krick --- codegen.json | 7 +- packages/client/modules/demo/initDB.ts | 3 +- packages/server/database/rethinkDriver.ts | 6 +- .../server/database/types/MeetingSettings.ts | 25 ------ .../database/types/MeetingSettingsAction.ts | 20 ----- .../database/types/MeetingSettingsPoker.ts | 23 ----- .../types/MeetingSettingsRetrospective.ts | 49 ----------- .../types/MeetingSettingsTeamPrompt.ts | 14 --- .../types/TeamPromptResponsesPhase.ts | 9 +- .../server/dataloader/customLoaderMakers.ts | 39 +++------ .../dataloader/primaryKeyLoaderMakers.ts | 2 +- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../helpers/createNewMeetingPhases.ts | 15 ---- .../mutations/helpers/createTeamAndLeader.ts | 43 +++++---- .../mutations/helpers/safeCreateTeamPrompt.ts | 31 ++++--- .../graphql/mutations/removePokerTemplate.ts | 87 ------------------- .../mutations/removeReflectTemplate.ts | 12 +-- .../graphql/mutations/selectTemplate.ts | 18 +--- .../graphql/mutations/startSprintPoker.ts | 6 +- .../graphql/mutations/updateRetroMaxVotes.ts | 9 -- .../private/mutations/processRecurrence.ts | 3 +- .../public/mutations/removePokerTemplate.ts | 64 ++++++++++++++ .../public/mutations/setMeetingSettings.ts | 53 ++--------- .../public/mutations/startRetrospective.ts | 18 +--- .../public/types/PokerMeetingSettings.ts | 2 +- .../types/RemovePokerTemplatePayload.ts | 25 ++++++ .../helpers/resolveSelectedTemplate.ts | 29 +++---- packages/server/graphql/rootMutation.ts | 2 - .../graphql/types/PokerMeetingSettings.ts | 8 -- .../types/RemovePokerTemplatePayload.ts | 30 ------- packages/server/postgres/select.ts | 8 +- packages/server/postgres/types/Meeting.d.ts | 10 --- packages/server/postgres/types/index.d.ts | 2 + 33 files changed, 197 insertions(+), 476 deletions(-) delete mode 100644 packages/server/database/types/MeetingSettings.ts delete mode 100644 packages/server/database/types/MeetingSettingsAction.ts delete mode 100644 packages/server/database/types/MeetingSettingsPoker.ts delete mode 100644 packages/server/database/types/MeetingSettingsRetrospective.ts delete mode 100644 packages/server/database/types/MeetingSettingsTeamPrompt.ts delete mode 100644 packages/server/graphql/mutations/removePokerTemplate.ts create mode 100644 packages/server/graphql/public/mutations/removePokerTemplate.ts create mode 100644 packages/server/graphql/public/types/RemovePokerTemplatePayload.ts delete mode 100644 packages/server/graphql/types/PokerMeetingSettings.ts delete mode 100644 packages/server/graphql/types/RemovePokerTemplatePayload.ts diff --git a/codegen.json b/codegen.json index c1edd8f890f..83eaf7bf530 100644 --- a/codegen.json +++ b/codegen.json @@ -46,6 +46,11 @@ "config": { "contextType": "../graphql#GQLContext", "mappers": { + "TeamMeetingSettings": "../../postgres/types/index#MeetingSettings as TeamMeetingSettingsDB", + "TeamPromptMeetingSettings": "../../postgres/types/index#MeetingSettings as TeamMeetingSettingsDB", + "PokerMeetingSettings": "../../postgres/types/index#PokerMeetingSettings as PokerMeetingSettingsDB", + "RetrospectiveMeetingSettings": "../../postgres/types/index#RetrospectiveMeetingSettings as RetrospectiveMeetingSettingsDB", + "RemovePokerTemplatePayload": "./types/RemovePokerTemplatePayload#RemovePokerTemplatePayloadSource", "JiraRemoteAvatarUrls": "./types/JiraRemoteAvatarUrls#JiraRemoteAvatarUrlsSource", "TemplateDimensionRef": "./types/TemplateDimensionRef#TemplateDimensionRefSource", "UpdateIntegrationProviderSuccess": "./types/UpdateIntegrationProviderSuccess#UpdateIntegrationProviderSuccessSource", @@ -139,7 +144,6 @@ "RetroReflectionGroup": "./types/RetroReflectionGroup#RetroReflectionGroupSource", "RetrospectiveMeeting": "../../database/types/MeetingRetrospective#default", "RetrospectiveMeetingMember": "../../database/types/RetroMeetingMember#default", - "RetrospectiveMeetingSettings": "../../database/types/MeetingSettingsRetrospective#default", "SAML": "./types/SAML#SAMLSource", "SetMeetingSettingsPayload": "../types/SetMeetingSettingsPayload#SetMeetingSettingsPayloadSource", "SetNotificationStatusPayload": "./types/SetNotificationStatusPayload#SetNotificationStatusPayloadSource", @@ -153,7 +157,6 @@ "StripeFailPaymentPayload": "./types/StripeFailPaymentPayload#StripeFailPaymentPayloadSource", "Task": "../../database/types/Task#default", "Team": "../../postgres/types/index#Team as TeamDB", - "TeamPromptMeetingSettings": "../../database/types/MeetingSettingsTeamPrompt#default as MeetingSettingsTeamPromptDB", "TeamHealthPhase": "./types/TeamHealthPhase#TeamHealthPhaseSource", "TeamHealthStage": "./types/TeamHealthStage#TeamHealthStageSource", "TeamInvitation": "../../database/types/TeamInvitation#default", diff --git a/packages/client/modules/demo/initDB.ts b/packages/client/modules/demo/initDB.ts index d452d9ee3af..f719a3f9270 100644 --- a/packages/client/modules/demo/initDB.ts +++ b/packages/client/modules/demo/initDB.ts @@ -1,7 +1,6 @@ import {SlackNotificationEventEnum} from '~/__generated__/SlackNotificationList_viewer.graphql' import {PALETTE} from '~/styles/paletteV3' import RetrospectiveMeeting from '../../../server/database/types/MeetingRetrospective' -import RetrospectiveMeetingSettings from '../../../server/database/types/MeetingSettingsRetrospective' import ITask from '../../../server/database/types/Task' import JiraProjectId from '../../shared/gqlIds/JiraProjectId' import demoUserAvatar from '../../styles/theme/images/avatar-user.svg' @@ -35,7 +34,7 @@ type IRetrospectiveMeeting = Omit< votesRemaining: number } -type IRetrospectiveMeetingSettings = RetrospectiveMeetingSettings & { +type IRetrospectiveMeetingSettings = { team: any } diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index 03dc30c72c3..f36a3a73eef 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -2,7 +2,7 @@ import {MasterPool, r} from 'rethinkdb-ts' import SlackAuth from '../database/types/SlackAuth' import SlackNotification from '../database/types/SlackNotification' import TeamInvitation from '../database/types/TeamInvitation' -import {AnyMeeting, AnyMeetingSettings, AnyMeetingTeamMember} from '../postgres/types/Meeting' +import {AnyMeeting, AnyMeetingTeamMember} from '../postgres/types/Meeting' import getRethinkConfig from './getRethinkConfig' import {R} from './stricterR' import AgendaItem from './types/AgendaItem' @@ -40,10 +40,6 @@ export type RethinkSchema = { type: MassInvitation index: 'teamMemberId' } - MeetingSettings: { - type: AnyMeetingSettings - index: 'teamId' - } MeetingMember: { type: AnyMeetingTeamMember index: 'meetingId' | 'teamId' | 'userId' diff --git a/packages/server/database/types/MeetingSettings.ts b/packages/server/database/types/MeetingSettings.ts deleted file mode 100644 index b66143e0e25..00000000000 --- a/packages/server/database/types/MeetingSettings.ts +++ /dev/null @@ -1,25 +0,0 @@ -import generateUID from '../../generateUID' -import {MeetingTypeEnum} from '../../graphql/public/resolverTypes' -import {NewMeetingPhaseTypeEnum} from './GenericMeetingPhase' - -interface Input { - id?: string - phaseTypes: NewMeetingPhaseTypeEnum[] - meetingType: MeetingTypeEnum - teamId: string -} - -export default abstract class MeetingSettings { - id: string - phaseTypes: NewMeetingPhaseTypeEnum[] - meetingType: MeetingTypeEnum - teamId: string - - constructor(input: Input) { - const {id, meetingType, phaseTypes, teamId} = input - this.id = id ?? generateUID() - this.meetingType = meetingType - this.teamId = teamId - this.phaseTypes = phaseTypes - } -} diff --git a/packages/server/database/types/MeetingSettingsAction.ts b/packages/server/database/types/MeetingSettingsAction.ts deleted file mode 100644 index 381b519c44d..00000000000 --- a/packages/server/database/types/MeetingSettingsAction.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {NewMeetingPhaseTypeEnum} from './GenericMeetingPhase' -import MeetingSettings from './MeetingSettings' -interface Input { - teamId: string - id?: string -} - -const phaseTypes = [ - 'checkin', - 'updates', - 'firstcall', - 'agendaitems', - 'lastcall' -] as NewMeetingPhaseTypeEnum[] -export default class MeetingSettingsAction extends MeetingSettings { - constructor(input: Input) { - const {teamId, id} = input - super({teamId, id, meetingType: 'action', phaseTypes}) - } -} diff --git a/packages/server/database/types/MeetingSettingsPoker.ts b/packages/server/database/types/MeetingSettingsPoker.ts deleted file mode 100644 index 5f379770230..00000000000 --- a/packages/server/database/types/MeetingSettingsPoker.ts +++ /dev/null @@ -1,23 +0,0 @@ -import {NewMeetingPhaseTypeEnum} from './GenericMeetingPhase' -import JiraSearchQuery from './JiraSearchQuery' -import MeetingSettings from './MeetingSettings' - -interface Input { - teamId: string - id?: string - maxVotesPerGroup?: number - totalVotes?: number - selectedTemplateId?: string -} - -const phaseTypes = ['checkin', 'SCOPE', 'ESTIMATE'] as NewMeetingPhaseTypeEnum[] - -export default class MeetingSettingsPoker extends MeetingSettings { - selectedTemplateId: string - jiraSearchQueries?: JiraSearchQuery[] - constructor(input: Input) { - const {teamId, id, selectedTemplateId} = input - super({teamId, id, meetingType: 'poker', phaseTypes}) - this.selectedTemplateId = selectedTemplateId || 'estimatedEffortTemplate' - } -} diff --git a/packages/server/database/types/MeetingSettingsRetrospective.ts b/packages/server/database/types/MeetingSettingsRetrospective.ts deleted file mode 100644 index a91f3c9315f..00000000000 --- a/packages/server/database/types/MeetingSettingsRetrospective.ts +++ /dev/null @@ -1,49 +0,0 @@ -import {MeetingSettingsThreshold} from '../../../client/types/constEnums' -import {NewMeetingPhaseTypeEnum} from './GenericMeetingPhase' -import MeetingSettings from './MeetingSettings' - -interface Input { - teamId: string - id?: string - maxVotesPerGroup?: number - totalVotes?: number - selectedTemplateId?: string - disableAnonymity?: boolean - videoMeetingURL?: string -} - -const phaseTypes = [ - 'checkin', - 'TEAM_HEALTH', - 'reflect', - 'group', - 'vote', - 'discuss' -] as NewMeetingPhaseTypeEnum[] - -export default class MeetingSettingsRetrospective extends MeetingSettings { - maxVotesPerGroup: number - totalVotes: number - selectedTemplateId: string - disableAnonymity: boolean - videoMeetingURL?: string | null - - constructor(input: Input) { - const { - teamId, - id, - maxVotesPerGroup, - selectedTemplateId, - totalVotes, - disableAnonymity, - videoMeetingURL - } = input - super({teamId, id, meetingType: 'retrospective', phaseTypes}) - this.maxVotesPerGroup = - maxVotesPerGroup ?? MeetingSettingsThreshold.RETROSPECTIVE_MAX_VOTES_PER_GROUP_DEFAULT - this.totalVotes = totalVotes ?? MeetingSettingsThreshold.RETROSPECTIVE_TOTAL_VOTES_DEFAULT - this.selectedTemplateId = selectedTemplateId || 'workingStuckTemplate' - this.disableAnonymity = disableAnonymity ?? false - this.videoMeetingURL = videoMeetingURL - } -} diff --git a/packages/server/database/types/MeetingSettingsTeamPrompt.ts b/packages/server/database/types/MeetingSettingsTeamPrompt.ts deleted file mode 100644 index 1caed27eecd..00000000000 --- a/packages/server/database/types/MeetingSettingsTeamPrompt.ts +++ /dev/null @@ -1,14 +0,0 @@ -import {NewMeetingPhaseTypeEnum} from './GenericMeetingPhase' -import MeetingSettings from './MeetingSettings' -interface Input { - teamId: string - id?: string -} - -const phaseTypes = ['RESPONSES'] as NewMeetingPhaseTypeEnum[] -export default class MeetingSettingsTeamPrompt extends MeetingSettings { - constructor(input: Input) { - const {teamId, id} = input - super({teamId, id, meetingType: 'teamPrompt', phaseTypes}) - } -} diff --git a/packages/server/database/types/TeamPromptResponsesPhase.ts b/packages/server/database/types/TeamPromptResponsesPhase.ts index a49fd29bdf8..c5d1e00ef0b 100644 --- a/packages/server/database/types/TeamPromptResponsesPhase.ts +++ b/packages/server/database/types/TeamPromptResponsesPhase.ts @@ -2,11 +2,16 @@ import GenericMeetingPhase from './GenericMeetingPhase' import TeamPromptResponseStage from './TeamPromptResponseStage' export default class TeamPromptResponsesPhase extends GenericMeetingPhase { - stages: TeamPromptResponseStage[] + stages: [TeamPromptResponseStage, ...TeamPromptResponseStage[]] phaseType!: 'RESPONSES' constructor(teamMemberIds: string[]) { super('RESPONSES') - this.stages = teamMemberIds.map((teamMemberId) => new TeamPromptResponseStage({teamMemberId})) + if (teamMemberIds.length < 1) { + throw new Error('TeamPromptResponsesPhase must have at least one team member') + } + this.stages = teamMemberIds.map( + (teamMemberId) => new TeamPromptResponseStage({teamMemberId}) + ) as [TeamPromptResponseStage, ...TeamPromptResponseStage[]] } } diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 6dd43e5141e..1a837fcd689 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -4,7 +4,6 @@ import {Selectable, SqlBool, sql} from 'kysely' import {PARABOL_AI_USER_ID} from '../../client/utils/constants' import getRethink from '../database/rethinkDriver' import {RDatum} from '../database/stricterR' -import MeetingSettingsTeamPrompt from '../database/types/MeetingSettingsTeamPrompt' import MeetingTemplate from '../database/types/MeetingTemplate' import Task, {TaskStatusEnum} from '../database/types/Task' import getFileStoreManager from '../fileStorage/getFileStoreManager' @@ -290,33 +289,19 @@ export const meetingSettingsByType = (parent: RootDataLoader, dependsOn: Registe dependsOn('meetingSettings') return new DataLoader( async (keys) => { - const r = await getRethink() - const types = {} as Record - keys.forEach((key) => { - const {meetingType} = key - types[meetingType] = types[meetingType] || [] - types[meetingType]!.push(key.teamId) - }) - const entries = Object.entries(types) as [MeetingTypeEnum, string[]][] - const resultsByType = await Promise.all( - entries.map((entry) => { - const [meetingType, teamIds] = entry - return r - .table('MeetingSettings') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter({meetingType: meetingType}) - .run() - }) + const res = await selectMeetingSettings() + .where(({eb, refTuple, tuple}) => + eb( + refTuple('teamId', 'meetingType'), + 'in', + keys.map((key) => tuple(key.teamId, key.meetingType)) + ) + ) + .execute() + return keys.map( + (key) => + res.find((doc) => doc.teamId === key.teamId && doc.meetingType === key.meetingType)! ) - const docs = resultsByType.flat() - return keys.map((key) => { - const {teamId, meetingType} = key - // until we decide the final shape of the team prompt settings, let's return a temporary hardcoded value - if (meetingType === 'teamPrompt') { - return new MeetingSettingsTeamPrompt({teamId}) as any - } - return docs.find((doc) => doc.teamId === teamId && doc.meetingType === meetingType)! - }) }, { ...parent.dataLoaderOptions, diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index a5e46c9e45b..0aa989dd98a 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -87,6 +87,6 @@ export const suggestedActions = primaryKeyLoaderMaker((ids: readonly string[]) = return selectSuggestedAction().where('id', 'in', ids).execute() }) -export const _PGmeetingSettings = primaryKeyLoaderMaker((ids: readonly string[]) => { +export const meetingSettings = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectMeetingSettings().where('id', 'in', ids).execute() }) diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index a0fa13be86a..b16d76e3adf 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -7,7 +7,6 @@ export const agendaItems = new RethinkPrimaryKeyLoaderMaker('AgendaItem') export const comments = new RethinkPrimaryKeyLoaderMaker('Comment') export const reflectPrompts = new RethinkPrimaryKeyLoaderMaker('ReflectPrompt') export const massInvitations = new RethinkPrimaryKeyLoaderMaker('MassInvitation') -export const meetingSettings = new RethinkPrimaryKeyLoaderMaker('MeetingSettings') export const meetingMembers = new RethinkPrimaryKeyLoaderMaker('MeetingMember') export const newMeetings = new RethinkPrimaryKeyLoaderMaker('NewMeeting') export const newFeatures = new RethinkPrimaryKeyLoaderMaker('NewFeature') diff --git a/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts b/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts index 5ac8998e8a3..56d4115ef63 100644 --- a/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts +++ b/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts @@ -21,7 +21,6 @@ import GenericMeetingPhase from '../../../database/types/GenericMeetingPhase' import ReflectPhase from '../../../database/types/ReflectPhase' import TeamHealthPhase from '../../../database/types/TeamHealthPhase' import TeamHealthStage from '../../../database/types/TeamHealthStage' -import TeamPromptResponsesPhase from '../../../database/types/TeamPromptResponsesPhase' import UpdatesPhase from '../../../database/types/UpdatesPhase' import UpdatesStage from '../../../database/types/UpdatesStage' import insertDiscussions from '../../../postgres/queries/insertDiscussions' @@ -145,20 +144,6 @@ const createNewMeetingPhases = async ( case LAST_CALL: case 'SCOPE': return new GenericMeetingPhase(phaseType, durations) - case 'RESPONSES': - const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) - const teamMemberIds = teamMembers.map(({id}) => id) - const teamPromptResponsesPhase = new TeamPromptResponsesPhase(teamMemberIds) - const {stages: teamPromptStages} = teamPromptResponsesPhase - const teamMemberResponseDiscussion = teamPromptStages.map((stage) => ({ - id: stage.discussionId, - teamId, - meetingId, - discussionTopicId: stage.teamMemberId, - discussionTopicType: 'teamPromptResponse' as const - })) - asyncSideEffects.push(insertDiscussions(teamMemberResponseDiscussion)) - return teamPromptResponsesPhase default: throw new Error(`Unhandled phaseType: ${phaseType}`) } diff --git a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts index c7e672d1c69..e0cfd12c4fc 100644 --- a/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts +++ b/packages/server/graphql/mutations/helpers/createTeamAndLeader.ts @@ -1,9 +1,6 @@ import {sql} from 'kysely' import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' -import getRethink from '../../../database/rethinkDriver' -import MeetingSettingsAction from '../../../database/types/MeetingSettingsAction' -import MeetingSettingsPoker from '../../../database/types/MeetingSettingsPoker' -import MeetingSettingsRetrospective from '../../../database/types/MeetingSettingsRetrospective' +import {MeetingSettingsThreshold} from '../../../../client/types/constEnums' import Team from '../../../database/types/Team' import TimelineEventCreatedTeam from '../../../database/types/TimelineEventCreatedTeam' import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' @@ -24,18 +21,12 @@ export default async function createTeamAndLeader( newTeam: ValidNewTeam, dataLoader: DataLoaderInstance ) { - const r = await getRethink() const {id: userId, picture, preferredName, email} = user const {id: teamId, orgId} = newTeam const organization = await dataLoader.get('organizations').loadNonNull(orgId) const {tier, trialStartDate} = organization const verifiedTeam = new Team({...newTeam, createdBy: userId, tier, trialStartDate}) - const meetingSettings = [ - new MeetingSettingsRetrospective({teamId}), - new MeetingSettingsAction({teamId}), - new MeetingSettingsPoker({teamId}) - ] const timelineEvent = new TimelineEventCreatedTeam({ createdAt: new Date(Date.now() + 5), userId, @@ -79,15 +70,35 @@ export default async function createTeamAndLeader( .onConflict((oc) => oc.columns(['userId', 'type']).doNothing()) ) .with('MeetingSettingsInsert', (qc) => - qc - .insertInto('MeetingSettings') - .values(meetingSettings.map((s) => ({...s, jiraSearchQueries: null}))) + qc.insertInto('MeetingSettings').values([ + { + id: generateUID(), + teamId, + meetingType: 'retrospective', + phaseTypes: ['checkin', 'TEAM_HEALTH', 'reflect', 'group', 'vote', 'discuss'], + disableAnonymity: false, + maxVotesPerGroup: MeetingSettingsThreshold.RETROSPECTIVE_MAX_VOTES_PER_GROUP_DEFAULT, + totalVotes: MeetingSettingsThreshold.RETROSPECTIVE_TOTAL_VOTES_DEFAULT, + selectedTemplateId: 'workingStuckTemplate' + }, + { + id: generateUID(), + teamId, + meetingType: 'action', + phaseTypes: ['checkin', 'updates', 'firstcall', 'agendaitems', 'lastcall'] + }, + { + id: generateUID(), + teamId, + meetingType: 'poker', + phaseTypes: ['checkin', 'SCOPE', 'ESTIMATE'], + selectedTemplateId: 'estimatedEffortTemplate' + } + ]) ) .insertInto('TimelineEvent') .values(timelineEvent) - .execute(), - // add meeting settings - r.table('MeetingSettings').insert(meetingSettings).run() + .execute() ]) dataLoader.clearAll(['teams', 'users', 'teamMembers', 'timelineEvents', 'meetingSettings']) } diff --git a/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts b/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts index 47430122668..7416d9d78dc 100644 --- a/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts +++ b/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts @@ -1,9 +1,11 @@ import {ParabolR} from '../../../database/rethinkDriver' import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' +import TeamPromptResponsesPhase from '../../../database/types/TeamPromptResponsesPhase' import generateUID from '../../../generateUID' +import getKysely from '../../../postgres/getKysely' import {MeetingTypeEnum} from '../../../postgres/types/Meeting' import {DataLoaderWorker} from '../../graphql' -import createNewMeetingPhases from './createNewMeetingPhases' +import {primePhases} from './createNewMeetingPhases' export const DEFAULT_PROMPT = 'What are you working on today? Stuck on anything?' @@ -24,20 +26,29 @@ const safeCreateTeamPrompt = async ( .default(0) .run() const meetingId = generateUID() - const phases = await createNewMeetingPhases( - facilitatorId, - teamId, - meetingId, - meetingCount, - meetingType, - dataLoader - ) + const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) + const teamMemberIds = teamMembers.map(({id}) => id) + const teamPromptResponsesPhase = new TeamPromptResponsesPhase(teamMemberIds) + const {stages: teamPromptStages} = teamPromptResponsesPhase + await getKysely() + .insertInto('Discussion') + .values( + teamPromptStages.map((stage) => ({ + id: stage.discussionId, + teamId, + meetingId, + discussionTopicId: stage.teamMemberId, + discussionTopicType: 'teamPromptResponse' + })) + ) + .execute() + primePhases([teamPromptResponsesPhase]) return new MeetingTeamPrompt({ id: meetingId, name, teamId, meetingCount, - phases, + phases: [teamPromptResponsesPhase], facilitatorUserId: facilitatorId, meetingPrompt: DEFAULT_PROMPT, // :TODO: (jmtaber129): Get this from meeting settings. ...meetingOverrideProps diff --git a/packages/server/graphql/mutations/removePokerTemplate.ts b/packages/server/graphql/mutations/removePokerTemplate.ts deleted file mode 100644 index 7d1b9f399a8..00000000000 --- a/packages/server/graphql/mutations/removePokerTemplate.ts +++ /dev/null @@ -1,87 +0,0 @@ -import {GraphQLID, GraphQLNonNull} from 'graphql' -import {SprintPokerDefaults, SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import MeetingSettingsPoker from '../../database/types/MeetingSettingsPoker' -import getKysely from '../../postgres/getKysely' -import {getUserId, isTeamMember} from '../../utils/authorization' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import RemovePokerTemplatePayload from '../types/RemovePokerTemplatePayload' - -const removePokerTemplate = { - description: 'Remove a poker meeting template', - type: new GraphQLNonNull(RemovePokerTemplatePayload), - args: { - templateId: { - type: new GraphQLNonNull(GraphQLID) - } - }, - async resolve( - _source: unknown, - {templateId}: {templateId: string}, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) { - const r = await getRethink() - const now = new Date() - const pg = getKysely() - const operationId = dataLoader.share() - const subOptions = {operationId, mutatorId} - const template = await dataLoader.get('meetingTemplates').load(templateId) - const viewerId = getUserId(authToken) - - // AUTH - if (!template || !template.isActive) { - return standardError(new Error('Template not found'), {userId: viewerId}) - } - if (!isTeamMember(authToken, template.teamId)) { - return standardError(new Error('Team not found'), {userId: viewerId}) - } - - // VALIDATION - const {teamId} = template - const [templates, settings] = await Promise.all([ - dataLoader.get('meetingTemplatesByType').load({meetingType: 'poker', teamId}), - dataLoader - .get('meetingSettingsByType') - .load({meetingType: 'poker', teamId}) as any as MeetingSettingsPoker - ]) - - // RESOLUTION - const {id: settingsId} = settings - template.isActive = false - await pg - .with('MeetingTemplateDelete', (qc) => - qc.updateTable('MeetingTemplate').set({isActive: false}).where('id', '=', templateId) - ) - .updateTable('TemplateDimension') - .set({removedAt: now}) - .where('templateId', '=', templateId) - .execute() - dataLoader.clearAll(['meetingTemplates', 'templateDimensions']) - - if (settings.selectedTemplateId === templateId) { - const nextTemplate = templates.find((template) => template.id !== templateId) - const nextTemplateId = nextTemplate?.id ?? SprintPokerDefaults.DEFAULT_TEMPLATE_ID - await getKysely() - .updateTable('MeetingSettings') - .set({selectedTemplateId: nextTemplateId}) - .where('id', '=', settingsId) - .execute() - await r - .table('MeetingSettings') - .get(settingsId) - .update({ - selectedTemplateId: nextTemplateId - }) - .run() - dataLoader.clearAll('meetingSettings') - } - - const data = {templateId, settingsId} - publish(SubscriptionChannel.TEAM, teamId, 'RemovePokerTemplatePayload', data, subOptions) - return data - } -} - -export default removePokerTemplate diff --git a/packages/server/graphql/mutations/removeReflectTemplate.ts b/packages/server/graphql/mutations/removeReflectTemplate.ts index 251f0f631a2..3357551a51a 100644 --- a/packages/server/graphql/mutations/removeReflectTemplate.ts +++ b/packages/server/graphql/mutations/removeReflectTemplate.ts @@ -1,7 +1,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' -import MeetingSettingsRetrospective from '../../database/types/MeetingSettingsRetrospective' import getKysely from '../../postgres/getKysely' import removeMeetingTemplate from '../../postgres/queries/removeMeetingTemplate' import {getUserId, isTeamMember} from '../../utils/authorization' @@ -42,9 +41,7 @@ const removeReflectTemplate = { const {teamId} = template const [templates, settings] = await Promise.all([ dataLoader.get('meetingTemplatesByType').load({meetingType: 'retrospective', teamId}), - dataLoader - .get('meetingSettingsByType') - .load({meetingType: 'retrospective', teamId}) as any as MeetingSettingsRetrospective + dataLoader.get('meetingSettingsByType').load({meetingType: 'retrospective', teamId}) ]) // RESOLUTION @@ -72,13 +69,6 @@ const removeReflectTemplate = { .set({selectedTemplateId: nextTemplateId}) .where('id', '=', settingsId) .execute() - await r - .table('MeetingSettings') - .get(settingsId) - .update({ - selectedTemplateId: nextTemplateId - }) - .run() dataLoader.clearAll('meetingSettings') } diff --git a/packages/server/graphql/mutations/selectTemplate.ts b/packages/server/graphql/mutations/selectTemplate.ts index 861f9a37545..6434070c22a 100644 --- a/packages/server/graphql/mutations/selectTemplate.ts +++ b/packages/server/graphql/mutations/selectTemplate.ts @@ -1,6 +1,5 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' import MeetingTemplate from '../../database/types/MeetingTemplate' import getKysely from '../../postgres/getKysely' import {Logger} from '../../utils/Logger' @@ -26,7 +25,6 @@ const selectTemplate = { {selectedTemplateId, teamId}: {selectedTemplateId: string; teamId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const viewerId = getUserId(authToken) @@ -54,21 +52,7 @@ const selectTemplate = { } // RESOLUTION - const meetingSettingsId = await r - .table('MeetingSettings') - .getAll(teamId, {index: 'teamId'}) - .filter({ - meetingType: template.type - }) - .update( - { - selectedTemplateId - }, - {returnChanges: true} - )('changes')(0)('old_val')('id') - .default(null) - .run() - await getKysely() + const meetingSettingsId = await getKysely() .updateTable('MeetingSettings') .set({selectedTemplateId}) .where('teamId', '=', teamId) diff --git a/packages/server/graphql/mutations/startSprintPoker.ts b/packages/server/graphql/mutations/startSprintPoker.ts index 3b7b7766c32..0fd4ecd04f9 100644 --- a/packages/server/graphql/mutations/startSprintPoker.ts +++ b/packages/server/graphql/mutations/startSprintPoker.ts @@ -3,7 +3,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import toTeamMemberId from '../../../client/utils/relay/toTeamMemberId' import getRethink from '../../database/rethinkDriver' import MeetingPoker from '../../database/types/MeetingPoker' -import MeetingSettingsPoker from '../../database/types/MeetingSettingsPoker' import PokerMeetingMember from '../../database/types/PokerMeetingMember' import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' @@ -135,7 +134,10 @@ export default { const meetingSettings = await dataLoader .get('meetingSettingsByType') .load({teamId, meetingType: 'poker'}) - const {selectedTemplateId} = meetingSettings as MeetingSettingsPoker + const {selectedTemplateId} = meetingSettings + if (!selectedTemplateId) { + throw new Error('selectedTemplateId is required') + } const templateRefId = await freezeTemplateAsRef(selectedTemplateId, dataLoader) const meeting = new MeetingPoker({ diff --git a/packages/server/graphql/mutations/updateRetroMaxVotes.ts b/packages/server/graphql/mutations/updateRetroMaxVotes.ts index e687a9b9bb8..db5c0beca24 100644 --- a/packages/server/graphql/mutations/updateRetroMaxVotes.ts +++ b/packages/server/graphql/mutations/updateRetroMaxVotes.ts @@ -146,15 +146,6 @@ const updateRetroMaxVotes = { .where('teamId', '=', teamId) .where('meetingType', '=', 'retrospective') .execute(), - r - .table('MeetingSettings') - .getAll(teamId, {index: 'teamId'}) - .filter({meetingType: 'retrospective'}) - .update({ - totalVotes, - maxVotesPerGroup - }) - .run(), r .table('NewMeeting') .get(meetingId) diff --git a/packages/server/graphql/private/mutations/processRecurrence.ts b/packages/server/graphql/private/mutations/processRecurrence.ts index 22bdd3b5815..8b1828eadd8 100644 --- a/packages/server/graphql/private/mutations/processRecurrence.ts +++ b/packages/server/graphql/private/mutations/processRecurrence.ts @@ -7,7 +7,6 @@ import getRethink from '../../../database/rethinkDriver' import MeetingRetrospective, { isMeetingRetrospective } from '../../../database/types/MeetingRetrospective' -import MeetingSettingsRetrospective from '../../../database/types/MeetingSettingsRetrospective' import MeetingTeamPrompt, {isMeetingTeamPrompt} from '../../../database/types/MeetingTeamPrompt' import {getActiveMeetingSeries} from '../../../postgres/queries/getActiveMeetingSeries' import {MeetingSeries} from '../../../postgres/types/MeetingSeries' @@ -79,7 +78,7 @@ const startRecurringMeeting = async ( } else if (meetingSeries.meetingType === 'retrospective') { const {totalVotes, maxVotesPerGroup, disableAnonymity, templateId} = (lastMeeting as MeetingRetrospective) ?? { - templateId: (meetingSettings as MeetingSettingsRetrospective).selectedTemplateId, + templateId: meetingSettings.selectedTemplateId, ...meetingSettings } const meeting = await safeCreateRetrospective( diff --git a/packages/server/graphql/public/mutations/removePokerTemplate.ts b/packages/server/graphql/public/mutations/removePokerTemplate.ts new file mode 100644 index 00000000000..2916695a05c --- /dev/null +++ b/packages/server/graphql/public/mutations/removePokerTemplate.ts @@ -0,0 +1,64 @@ +import {SprintPokerDefaults, SubscriptionChannel} from 'parabol-client/types/constEnums' +import getKysely from '../../../postgres/getKysely' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const removePokerTemplate: MutationResolvers['removePokerTemplate'] = async ( + _source, + {templateId}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const now = new Date() + const pg = getKysely() + const operationId = dataLoader.share() + const subOptions = {operationId, mutatorId} + const template = await dataLoader.get('meetingTemplates').load(templateId) + const viewerId = getUserId(authToken) + + // AUTH + if (!template || !template.isActive) { + return standardError(new Error('Template not found'), {userId: viewerId}) + } + if (!isTeamMember(authToken, template.teamId)) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + + // VALIDATION + const {teamId} = template + const [templates, settings] = await Promise.all([ + dataLoader.get('meetingTemplatesByType').load({meetingType: 'poker', teamId}), + dataLoader.get('meetingSettingsByType').load({meetingType: 'poker', teamId}) + ]) + + // RESOLUTION + const {id: settingsId} = settings + template.isActive = false + await pg + .with('MeetingTemplateDelete', (qc) => + qc.updateTable('MeetingTemplate').set({isActive: false}).where('id', '=', templateId) + ) + .updateTable('TemplateDimension') + .set({removedAt: now}) + .where('templateId', '=', templateId) + .execute() + dataLoader.clearAll(['meetingTemplates', 'templateDimensions']) + + if (settings.selectedTemplateId === templateId) { + const nextTemplate = templates.find((template) => template.id !== templateId) + const nextTemplateId = nextTemplate?.id ?? SprintPokerDefaults.DEFAULT_TEMPLATE_ID + await getKysely() + .updateTable('MeetingSettings') + .set({selectedTemplateId: nextTemplateId}) + .where('id', '=', settingsId) + .execute() + dataLoader.clearAll('meetingSettings') + } + + const data = {templateId, settingsId} + publish(SubscriptionChannel.TEAM, teamId, 'RemovePokerTemplatePayload', data, subOptions) + return data +} + +export default removePokerTemplate diff --git a/packages/server/graphql/public/mutations/setMeetingSettings.ts b/packages/server/graphql/public/mutations/setMeetingSettings.ts index 92c339d1b41..f9363bee4b6 100644 --- a/packages/server/graphql/public/mutations/setMeetingSettings.ts +++ b/packages/server/graphql/public/mutations/setMeetingSettings.ts @@ -1,13 +1,8 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {isNotNull} from 'parabol-client/utils/predicates' -import getRethink from '../../../database/rethinkDriver' -import {RValue} from '../../../database/stricterR' import getKysely from '../../../postgres/getKysely' import {MeetingSettings} from '../../../postgres/types' -import { - analytics, - MeetingSettings as MeetingSettingsAnalytics -} from '../../../utils/analytics/analytics' +import {analytics} from '../../../utils/analytics/analytics' import {getUserId} from '../../../utils/authorization' import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' @@ -18,7 +13,6 @@ const setMeetingSettings: MutationResolvers['setMeetingSettings'] = async ( {settingsId, checkinEnabled, teamHealthEnabled, disableAnonymity, videoMeetingURL}, {authToken, dataLoader, socketId: mutatorId} ) => { - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -39,7 +33,6 @@ const setMeetingSettings: MutationResolvers['setMeetingSettings'] = async ( const {featureFlags} = organization const hasTranscriptFlag = featureFlags?.includes('zoomTranscription') - const meetingSettings = {} as MeetingSettingsAnalytics const firstPhases: NewMeetingPhaseTypeEnum[] = [] if (checkinEnabled || (checkinEnabled !== false && phaseTypes.includes('checkin'))) { firstPhases.push('checkin') @@ -60,44 +53,12 @@ const setMeetingSettings: MutationResolvers['setMeetingSettings'] = async ( : null } - await r - .table('MeetingSettings') - .get(settingsId) - .update((row: RValue) => { - const updatedSettings: {[key: string]: any} = {} - if (isNotNull(checkinEnabled)) { - updatedSettings.phaseTypes = r.branch( - row('phaseTypes').contains('checkin'), - checkinEnabled ? row('phaseTypes') : row('phaseTypes').difference(['checkin']), - checkinEnabled ? row('phaseTypes').prepend('checkin') : row('phaseTypes') - ) - meetingSettings.hasIcebreaker = checkinEnabled - } - - if (isNotNull(teamHealthEnabled)) { - updatedSettings.phaseTypes = r.branch( - row('phaseTypes').contains('TEAM_HEALTH'), - teamHealthEnabled ? row('phaseTypes') : row('phaseTypes').difference(['TEAM_HEALTH']), - row('phaseTypes').contains('checkin'), - teamHealthEnabled ? row('phaseTypes').insertAt(1, 'TEAM_HEALTH') : row('phaseTypes'), - teamHealthEnabled ? row('phaseTypes').prepend('TEAM_HEALTH') : row('phaseTypes') - ) - meetingSettings.hasTeamHealth = teamHealthEnabled - } - - if (isNotNull(disableAnonymity)) { - updatedSettings.disableAnonymity = disableAnonymity - meetingSettings.disableAnonymity = disableAnonymity - } - - if (hasTranscriptFlag) { - updatedSettings.videoMeetingURL = videoMeetingURL - meetingSettings.videoMeetingURL = videoMeetingURL - } - - return updatedSettings - }) - .run() + await getKysely() + .updateTable('MeetingSettings') + .set(nextSettings) + .where('id', '=', settings.id) + .execute() + dataLoader.clearAll('meetingSettings') await getKysely() .updateTable('MeetingSettings') diff --git a/packages/server/graphql/public/mutations/startRetrospective.ts b/packages/server/graphql/public/mutations/startRetrospective.ts index 81b5a500f0d..541e983f79b 100644 --- a/packages/server/graphql/public/mutations/startRetrospective.ts +++ b/packages/server/graphql/public/mutations/startRetrospective.ts @@ -1,6 +1,5 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../../database/rethinkDriver' -import MeetingSettingsRetrospective from '../../../database/types/MeetingSettingsRetrospective' import RetroMeetingMember from '../../../database/types/RetroMeetingMember' import getKysely from '../../../postgres/getKysely' import updateMeetingTemplateLastUsedAt from '../../../postgres/queries/updateMeetingTemplateLastUsedAt' @@ -40,9 +39,7 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( const meetingType: MeetingTypeEnum = 'retrospective' const [viewer, meetingSettings, meetingCount] = await Promise.all([ dataLoader.get('users').loadNonNull(viewerId), - dataLoader - .get('meetingSettingsByType') - .load({teamId, meetingType}) as Promise, + dataLoader.get('meetingSettingsByType').load({teamId, meetingType}), dataLoader.get('meetingCount').load({teamId, meetingType}) ]) @@ -50,11 +47,10 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( id: meetingSettingsId, totalVotes, maxVotesPerGroup, - selectedTemplateId, disableAnonymity, videoMeetingURL - } = meetingSettings - + } = meetingSettings as typeof meetingSettings & {meetingType: 'retrospective'} + const selectedTemplateId = meetingSettings.selectedTemplateId || 'workingStuckTemplate' const meetingName = !name ? `Retro #${meetingCount + 1}` : rrule @@ -107,14 +103,6 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( ) .run(), updateTeamByTeamId(updates, teamId), - videoMeetingURL && - r - .table('MeetingSettings') - .get(meetingSettingsId) - .update({ - videoMeetingURL: null - }) - .run(), videoMeetingURL && pg .updateTable('MeetingSettings') diff --git a/packages/server/graphql/public/types/PokerMeetingSettings.ts b/packages/server/graphql/public/types/PokerMeetingSettings.ts index 98eef614ec9..40df579d5a2 100644 --- a/packages/server/graphql/public/types/PokerMeetingSettings.ts +++ b/packages/server/graphql/public/types/PokerMeetingSettings.ts @@ -8,7 +8,7 @@ import {PokerMeetingSettingsResolvers} from '../resolverTypes' const PokerMeetingSettings: PokerMeetingSettingsResolvers = { __isTypeOf: ({meetingType}) => meetingType === 'poker', - selectedTemplate: resolveSelectedTemplate('estimatedEffortTemplate'), + selectedTemplate: resolveSelectedTemplate<'poker'>('estimatedEffortTemplate'), teamTemplates: async ({teamId}, _args, {dataLoader}) => { const templates = await dataLoader diff --git a/packages/server/graphql/public/types/RemovePokerTemplatePayload.ts b/packages/server/graphql/public/types/RemovePokerTemplatePayload.ts new file mode 100644 index 00000000000..3afa1ee5bda --- /dev/null +++ b/packages/server/graphql/public/types/RemovePokerTemplatePayload.ts @@ -0,0 +1,25 @@ +import {RemovePokerTemplatePayloadResolvers} from '../resolverTypes' + +export type RemovePokerTemplatePayloadSource = + | { + templateId: string + settingsId: string + } + | {error: {message: string}} + +const RemovePokerTemplatePayload: RemovePokerTemplatePayloadResolvers = { + pokerTemplate: (source, _args, {dataLoader}) => { + if ('error' in source) return null + const {templateId} = source + return dataLoader.get('meetingTemplates').loadNonNull(templateId) + }, + + pokerMeetingSettings: async (source, _args, {dataLoader}) => { + if ('error' in source) return null + const {settingsId} = source + const settings = await dataLoader.get('meetingSettings').loadNonNull(settingsId) + return settings as typeof settings & {meetingType: 'poker'} + } +} + +export default RemovePokerTemplatePayload diff --git a/packages/server/graphql/queries/helpers/resolveSelectedTemplate.ts b/packages/server/graphql/queries/helpers/resolveSelectedTemplate.ts index 5d556ffdf3b..4a8209d5629 100644 --- a/packages/server/graphql/queries/helpers/resolveSelectedTemplate.ts +++ b/packages/server/graphql/queries/helpers/resolveSelectedTemplate.ts @@ -1,36 +1,27 @@ -import getRethink from '../../../database/rethinkDriver' -import MeetingSettingsPoker from '../../../database/types/MeetingSettingsPoker' -import MeetingSettingsRetrospective from '../../../database/types/MeetingSettingsRetrospective' import getKysely from '../../../postgres/getKysely' +import {MeetingSettings} from '../../../postgres/types' import {GQLContext} from '../../graphql' const resolveSelectedTemplate = - (fallbackTemplateId: string) => - async ( - source: MeetingSettingsPoker | MeetingSettingsRetrospective, - _args: unknown, - {dataLoader}: GQLContext - ) => { + (fallbackTemplateId: string) => + async (source: MeetingSettings, _args: unknown, {dataLoader}: GQLContext) => { const {id: settingsId, selectedTemplateId} = source - const template = await dataLoader.get('meetingTemplates').load(selectedTemplateId) - if (template) { - return template + if (selectedTemplateId) { + const template = await dataLoader.get('meetingTemplates').load(selectedTemplateId) + if (template) { + return template + } } // there may be holes in our template deletion or reselection logic, so doing this to be safe source.selectedTemplateId = fallbackTemplateId - const r = await getRethink() - await r - .table('MeetingSettings') - .get(settingsId) - .update({selectedTemplateId: fallbackTemplateId}) - .run() await getKysely() .updateTable('MeetingSettings') .set({selectedTemplateId: fallbackTemplateId}) .where('id', '=', settingsId) .execute() - return dataLoader.get('meetingTemplates').loadNonNull(fallbackTemplateId) + const res = await dataLoader.get('meetingTemplates').loadNonNull(fallbackTemplateId) + return res as typeof res & {type: TMeetingTypeEnum} } export default resolveSelectedTemplate diff --git a/packages/server/graphql/rootMutation.ts b/packages/server/graphql/rootMutation.ts index 74dcd4d81de..91654c128b6 100644 --- a/packages/server/graphql/rootMutation.ts +++ b/packages/server/graphql/rootMutation.ts @@ -68,7 +68,6 @@ import removeAtlassianAuth from './mutations/removeAtlassianAuth' import removeGitHubAuth from './mutations/removeGitHubAuth' import removeIntegrationProvider from './mutations/removeIntegrationProvider' import removeOrgUser from './mutations/removeOrgUser' -import removePokerTemplate from './mutations/removePokerTemplate' import removePokerTemplateDimension from './mutations/removePokerTemplateDimension' import removePokerTemplateScale from './mutations/removePokerTemplateScale' import removePokerTemplateScaleValue from './mutations/removePokerTemplateScaleValue' @@ -178,7 +177,6 @@ export default new GraphQLObjectType({ removeAtlassianAuth, removeGitHubAuth, removeOrgUser, - removePokerTemplate, removeReflectTemplate, removeReflectTemplatePrompt, removePokerTemplateDimension, diff --git a/packages/server/graphql/types/PokerMeetingSettings.ts b/packages/server/graphql/types/PokerMeetingSettings.ts deleted file mode 100644 index a63971cd4df..00000000000 --- a/packages/server/graphql/types/PokerMeetingSettings.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {GraphQLObjectType} from 'graphql' - -const PokerMeetingSettings = new GraphQLObjectType({ - name: 'PokerMeetingSettings', - fields: {} -}) - -export default PokerMeetingSettings diff --git a/packages/server/graphql/types/RemovePokerTemplatePayload.ts b/packages/server/graphql/types/RemovePokerTemplatePayload.ts deleted file mode 100644 index fadc2591d22..00000000000 --- a/packages/server/graphql/types/RemovePokerTemplatePayload.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import PokerMeetingSettings from './PokerMeetingSettings' -import PokerTemplate from './PokerTemplate' -import StandardMutationError from './StandardMutationError' - -const RemovePokerTemplatePayload = new GraphQLObjectType({ - name: 'RemovePokerTemplatePayload', - fields: () => ({ - error: { - type: StandardMutationError - }, - pokerTemplate: { - type: PokerTemplate, - resolve: ({templateId}, _args: unknown, {dataLoader}) => { - if (!templateId) return null - return dataLoader.get('meetingTemplates').load(templateId) - } - }, - pokerMeetingSettings: { - type: PokerMeetingSettings, - resolve: ({settingsId}, _args: unknown, {dataLoader}) => { - if (!settingsId) return null - return dataLoader.get('meetingSettings').load(settingsId) - } - } - }) -}) - -export default RemovePokerTemplatePayload diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index 15bd911c327..77559cab3ce 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -181,11 +181,9 @@ export const selectMeetingSettings = () => .selectFrom('MeetingSettings') .select([ 'id', - 'phaseTypes', 'meetingType', 'teamId', 'selectedTemplateId', - 'jiraSearchQueries', 'maxVotesPerGroup', 'totalVotes', 'disableAnonymity', @@ -193,15 +191,15 @@ export const selectMeetingSettings = () => ]) .$narrowType< // NewMeeetingPhaseTypeEnum[] should be inferred from kysely-codegen, but it's not - | {meetingType: NotNull; phaseTypes: NewMeetingPhaseTypeEnum[]} + | {meetingType: 'action' | 'poker' | 'teamPrompt'} | { meetingType: 'retrospective' - phaseTypes: NewMeetingPhaseTypeEnum[] maxVotesPerGroup: NotNull totalVotes: NotNull disableAnonymity: NotNull } >() .select(({fn}) => [ - fn('to_json', ['jiraSearchQueries']).as('jiraSearchQueries') + fn('to_json', ['jiraSearchQueries']).as('jiraSearchQueries'), + fn('to_json', ['phaseTypes']).as('phaseTypes') ]) diff --git a/packages/server/postgres/types/Meeting.d.ts b/packages/server/postgres/types/Meeting.d.ts index 5feb066120d..65d7995ce18 100644 --- a/packages/server/postgres/types/Meeting.d.ts +++ b/packages/server/postgres/types/Meeting.d.ts @@ -2,10 +2,6 @@ import ActionMeetingMember from '../../database/types/ActionMeetingMember' import MeetingAction from '../../database/types/MeetingAction' import MeetingPoker from '../../database/types/MeetingPoker' import MeetingRetrospective from '../../database/types/MeetingRetrospective' -import MeetingSettingsAction from '../../database/types/MeetingSettingsAction' -import MeetingSettingsPoker from '../../database/types/MeetingSettingsPoker' -import MeetingSettingsRetrospective from '../../database/types/MeetingSettingsRetrospective' -import MeetingSettingsTeamPrompt from '../../database/types/MeetingSettingsTeamPrompt' import MeetingTeamPrompt from '../../database/types/MeetingTeamPrompt' import PokerMeetingMember from '../../database/types/PokerMeetingMember' import RetroMeetingMember from '../../database/types/RetroMeetingMember' @@ -21,9 +17,3 @@ export type AnyMeetingTeamMember = | RetroMeetingMember | ActionMeetingMember | TeamPromptMeetingMember - -export type AnyMeetingSettings = - | MeetingSettingsRetrospective - | MeetingSettingsAction - | MeetingSettingsPoker - | MeetingSettingsTeamPrompt diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index f5260cea94f..f6706233236 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -42,3 +42,5 @@ export type TemplateScale = ExtractTypeFromQueryBuilderSelect export type MeetingSettings = ExtractTypeFromQueryBuilderSelect +export type PokerMeetingSettings = MeetingSettings & {meetingType: 'poker'} +export type RetrospectiveMeetingSettings = MeetingSettings & {meetingType: 'retrospective'} From 27e68c3b44f28944fd12bd10b66b2ca0f4dcf330 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 15 Aug 2024 13:34:59 -0700 Subject: [PATCH 408/529] chore(rethinkdb): AgendaItem: Phase 1 (#10108) Signed-off-by: Matt Krick --- .../AgendaListAndInput/AgendaListAndInput.tsx | 1 - .../server/dataloader/customLoaderMakers.ts | 25 ---------- .../dataloader/foreignKeyLoaderMakers.ts | 21 ++++++++ .../dataloader/primaryKeyLoaderMakers.ts | 5 ++ .../server/graphql/mutations/addAgendaItem.ts | 21 +++++++- .../server/graphql/mutations/endCheckIn.ts | 44 ++++++++++++----- .../addAgendaItemToActiveActionMeeting.ts | 2 + .../mutations/helpers/removeTeamMember.ts | 12 ++--- .../graphql/mutations/removeAgendaItem.ts | 7 +++ .../graphql/mutations/updateAgendaItem.ts | 24 +++++++++ .../public/mutations/setMeetingSettings.ts | 7 --- .../graphql/public/mutations/startCheckIn.ts | 8 ++- .../1723491538114_AgendaItem-phase1.ts | 49 +++++++++++++++++++ packages/server/postgres/select.ts | 2 + 14 files changed, 172 insertions(+), 56 deletions(-) create mode 100644 packages/server/postgres/migrations/1723491538114_AgendaItem-phase1.ts diff --git a/packages/client/modules/teamDashboard/components/AgendaListAndInput/AgendaListAndInput.tsx b/packages/client/modules/teamDashboard/components/AgendaListAndInput/AgendaListAndInput.tsx index f338fe6a7a0..5733d314b27 100644 --- a/packages/client/modules/teamDashboard/components/AgendaListAndInput/AgendaListAndInput.tsx +++ b/packages/client/modules/teamDashboard/components/AgendaListAndInput/AgendaListAndInput.tsx @@ -55,7 +55,6 @@ const AgendaListAndInput = (props: Props) => { agendaItems { id content - sortOrder ...AgendaList_agendaItems } } diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 1a837fcd689..8a5b883dce3 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -310,31 +310,6 @@ export const meetingSettingsByType = (parent: RootDataLoader, dependsOn: Registe ) } -export const _PGmeetingSettingsByType = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { - dependsOn('meetingSettings') - return new DataLoader( - async (keys) => { - const res = await selectMeetingSettings() - .where(({eb, refTuple, tuple}) => - eb( - refTuple('teamId', 'meetingType'), - 'in', - keys.map((key) => tuple(key.teamId, key.meetingType)) - ) - ) - .execute() - return keys.map( - (key) => - res.find((doc) => doc.teamId === key.teamId && doc.meetingType === key.meetingType)! - ) - }, - { - ...parent.dataLoaderOptions, - cacheKeyFn: (key) => `${key.teamId}:${key.meetingType}` - } - ) -} - export const organizationApprovedDomainsByOrgId = (parent: RootDataLoader) => { return new DataLoader( async (orgIds) => { diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 98146663a5d..51e21b4ca0b 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -1,6 +1,7 @@ import getKysely from '../postgres/getKysely' import {getTeamPromptResponsesByMeetingIds} from '../postgres/queries/getTeamPromptResponsesByMeetingIds' import { + selectAgendaItems, selectOrganizations, selectRetroReflections, selectSuggestedAction, @@ -170,3 +171,23 @@ export const teamPromptResponsesByMeetingId = foreignKeyLoaderMaker( 'meetingId', getTeamPromptResponsesByMeetingIds ) + +export const _pgagendaItemsByTeamId = foreignKeyLoaderMaker( + '_pgagendaItems', + 'teamId', + async (teamIds) => { + return selectAgendaItems() + .where('teamId', 'in', teamIds) + .where('isActive', '=', true) + .orderBy('sortOrder') + .execute() + } +) + +export const _pgagendaItemsByMeetingId = foreignKeyLoaderMaker( + '_pgagendaItems', + 'meetingId', + async (meetingIds) => { + return selectAgendaItems().where('meetingId', 'in', meetingIds).orderBy('sortOrder').execute() + } +) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 0aa989dd98a..0849187482d 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -6,6 +6,7 @@ import getMeetingTemplatesByIds from '../postgres/queries/getMeetingTemplatesByI import getTemplateRefsByIds from '../postgres/queries/getTemplateRefsByIds' import {getUsersByIds} from '../postgres/queries/getUsersByIds' import { + selectAgendaItems, selectMeetingSettings, selectOrganizations, selectRetroReflections, @@ -90,3 +91,7 @@ export const suggestedActions = primaryKeyLoaderMaker((ids: readonly string[]) = export const meetingSettings = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectMeetingSettings().where('id', 'in', ids).execute() }) + +export const _pgagendaItems = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectAgendaItems().where('id', 'in', ids).execute() +}) diff --git a/packages/server/graphql/mutations/addAgendaItem.ts b/packages/server/graphql/mutations/addAgendaItem.ts index c9200d89215..c6fc4425fc0 100644 --- a/packages/server/graphql/mutations/addAgendaItem.ts +++ b/packages/server/graphql/mutations/addAgendaItem.ts @@ -1,9 +1,11 @@ import {GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import makeAgendaItemSchema from 'parabol-client/validation/makeAgendaItemSchema' +import {positionAfter} from '../../../client/shared/sortOrder' import getRethink from '../../database/rethinkDriver' import AgendaItem, {AgendaItemInput} from '../../database/types/AgendaItem' import generateUID from '../../generateUID' +import getKysely from '../../postgres/getKysely' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -45,18 +47,33 @@ export default { } // RESOLUTION + const teamAgendaItems = await dataLoader.get('agendaItemsByTeamId').load(teamId) + const lastAgendaItem = teamAgendaItems.at(-1) + const lastSortOrder = lastAgendaItem?.sortOrder ? String(lastAgendaItem.sortOrder) : '' + // this is just during the migration of AgendaItem table + const sortOrder = positionAfter(lastSortOrder) const agendaItemId = `${teamId}::${generateUID()}` await r .table('AgendaItem') .insert( new AgendaItem({ ...validNewAgendaItem, - id: agendaItemId, teamId } as AgendaItemInput) ) .run() - + await getKysely() + .insertInto('AgendaItem') + .values({ + id: agendaItemId, + content: newAgendaItem.content, + meetingId: newAgendaItem.meetingId, + pinned: newAgendaItem.pinned, + sortOrder, + teamId, + teamMemberId: newAgendaItem.teamMemberId + }) + .execute() const meetingId = await addAgendaItemToActiveActionMeeting(agendaItemId, teamId, dataLoader) analytics.addedAgendaItem(viewer, teamId, meetingId) const data = {agendaItemId, meetingId} diff --git a/packages/server/graphql/mutations/endCheckIn.ts b/packages/server/graphql/mutations/endCheckIn.ts index f009f8347d3..c5bfbca7a28 100644 --- a/packages/server/graphql/mutations/endCheckIn.ts +++ b/packages/server/graphql/mutations/endCheckIn.ts @@ -3,6 +3,7 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {AGENDA_ITEMS, DONE, LAST_CALL} from 'parabol-client/utils/constants' import getMeetingPhase from 'parabol-client/utils/getMeetingPhase' import findStageById from 'parabol-client/utils/meetings/findStageById' +import {positionAfter} from '../../../client/shared/sortOrder' import {checkTeamsLimit} from '../../billing/helpers/teamLimitsCheck' import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' @@ -10,6 +11,7 @@ import AgendaItem from '../../database/types/AgendaItem' import MeetingAction from '../../database/types/MeetingAction' import Task from '../../database/types/Task' import TimelineEventCheckinComplete from '../../database/types/TimelineEventCheckinComplete' +import {DataLoaderInstance} from '../../dataloader/RootDataLoader' import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' import archiveTasksForDB from '../../safeMutations/archiveTasksForDB' @@ -61,8 +63,14 @@ const updateTaskSortOrders = async (userIds: string[], tasks: SortOrderTask[]) = return tasks } -const clearAgendaItems = async (teamId: string) => { +const clearAgendaItems = async (teamId: string, dataLoader: DataLoaderInstance) => { + await getKysely() + .updateTable('AgendaItem') + .set({isActive: false}) + .where('teamId', '=', teamId) + .execute() const r = await getRethink() + dataLoader.clearAll('agendaItems') return r .table('AgendaItem') .getAll(teamId, {index: 'teamId'}) @@ -72,16 +80,15 @@ const clearAgendaItems = async (teamId: string) => { .run() } -const getPinnedAgendaItems = async (teamId: string) => { - const r = await getRethink() - return r - .table('AgendaItem') - .getAll(teamId, {index: 'teamId'}) - .filter({isActive: true, pinned: true}) - .run() +const getPinnedAgendaItems = async (teamId: string, dataLoader: DataLoaderInstance) => { + const agendaItems = await dataLoader.get('agendaItemsByTeamId').load(teamId) + return agendaItems.filter((agendaItem) => agendaItem.pinned) } -const clonePinnedAgendaItems = async (pinnedAgendaItems: AgendaItem[]) => { +const clonePinnedAgendaItems = async ( + pinnedAgendaItems: AgendaItem[], + dataLoader: DataLoaderInstance +) => { const r = await getRethink() const clonedPins = pinnedAgendaItems.map((agendaItem) => { const agendaItemId = `${agendaItem.teamId}::${generateUID()}` @@ -96,6 +103,17 @@ const clonePinnedAgendaItems = async (pinnedAgendaItems: AgendaItem[]) => { }) }) await r.table('AgendaItem').insert(clonedPins).run() + let curSortOrder = '' + const pgClonedPins = clonedPins.map((agendaItems) => { + const sortOrder = positionAfter(curSortOrder) + curSortOrder = sortOrder + return { + ...agendaItems, + sortOrder + } + }) + await getKysely().insertInto('AgendaItem').values(pgClonedPins).execute() + dataLoader.clearAll('agendaItems') } const summarizeCheckInMeeting = async (meeting: MeetingAction, dataLoader: DataLoaderWorker) => { @@ -120,7 +138,7 @@ const summarizeCheckInMeeting = async (meeting: MeetingAction, dataLoader: DataL .filter({status: DONE}) .filter((task: RDatum) => task('tags').contains('archived').not()) .run(), - r.table('AgendaItem').getAll(teamId, {index: 'teamId'}).filter({isActive: true}).run() + dataLoader.get('agendaItemsByTeamId').load(teamId) ]) const agendaItemPhase = getPhase(phases, 'agendaitems') @@ -128,12 +146,12 @@ const summarizeCheckInMeeting = async (meeting: MeetingAction, dataLoader: DataL const discussionIds = stages.map((stage) => stage.discussionId) const userIds = meetingMembers.map(({userId}) => userId) const meetingPhase = getMeetingPhase(phases) - const pinnedAgendaItems = await getPinnedAgendaItems(teamId) + const pinnedAgendaItems = await getPinnedAgendaItems(teamId, dataLoader) const isKill = !!(meetingPhase && ![AGENDA_ITEMS, LAST_CALL].includes(meetingPhase.phaseType)) - if (!isKill) await clearAgendaItems(teamId) + if (!isKill) await clearAgendaItems(teamId, dataLoader) await Promise.all([ isKill ? undefined : archiveTasksForDB(doneTasks, meetingId), - isKill ? undefined : clonePinnedAgendaItems(pinnedAgendaItems), + isKill ? undefined : clonePinnedAgendaItems(pinnedAgendaItems, dataLoader), updateTaskSortOrders(userIds, tasks), r .table('NewMeeting') diff --git a/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts b/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts index dbd1859dd78..db01b387b0d 100644 --- a/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts +++ b/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts @@ -1,6 +1,7 @@ import getRethink from '../../../database/rethinkDriver' import AgendaItemsStage from '../../../database/types/AgendaItemsStage' import MeetingAction from '../../../database/types/MeetingAction' +import getKysely from '../../../postgres/getKysely' import insertDiscussions from '../../../postgres/queries/insertDiscussions' import getPhase from '../../../utils/getPhase' import {DataLoaderWorker} from '../../graphql' @@ -47,6 +48,7 @@ const addAgendaItemToActiveActionMeeting = async ( }) .run(), r.table('AgendaItem').get(agendaItemId).update({meetingId: meetingId}).run(), + getKysely().updateTable('AgendaItem').set({meetingId}).where('id', '=', agendaItemId).execute(), insertDiscussions([ { id: discussionId, diff --git a/packages/server/graphql/mutations/helpers/removeTeamMember.ts b/packages/server/graphql/mutations/helpers/removeTeamMember.ts index d9b92314e63..e1e7c1d4fee 100644 --- a/packages/server/graphql/mutations/helpers/removeTeamMember.ts +++ b/packages/server/graphql/mutations/helpers/removeTeamMember.ts @@ -105,13 +105,11 @@ const removeTeamMember = async ( const archivedTasks = await archiveTasksForDB(integratedTasksToArchive) const archivedTaskIds = archivedTasks.map(({id}) => id) - const agendaItemIds = await r - .table('AgendaItem') - .getAll(teamId, {index: 'teamId'}) - .filter((row: RDatum) => row('teamMemberId').eq(teamMemberId)) - .getField('id') - .run() - + const teamAgendaItems = await dataLoader.get('agendaItemsByTeamId').load(teamId) + const agendaItemIds = teamAgendaItems + .filter((agendaItem) => agendaItem.teamMemberId === teamMemberId) + .map(({id}) => id) + dataLoader.clearAll('agendaItems') // if a new meeting was currently running, remove them from it const filterFn = (stage: CheckInStage | UpdatesStage | EstimateStage | AgendaItemsStage) => (stage as CheckInStage | UpdatesStage).teamMemberId === teamMemberId || diff --git a/packages/server/graphql/mutations/removeAgendaItem.ts b/packages/server/graphql/mutations/removeAgendaItem.ts index 4cf87cb57bd..941c0f9dc30 100644 --- a/packages/server/graphql/mutations/removeAgendaItem.ts +++ b/packages/server/graphql/mutations/removeAgendaItem.ts @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import AgendaItemsStage from '../../database/types/AgendaItemsStage' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -42,6 +43,12 @@ export default { .update({isActive: false}, {returnChanges: true})('changes')(0)('old_val') .default(null) .run() + await getKysely() + .updateTable('AgendaItem') + .set({isActive: false}) + .where('id', '=', agendaItemId) + .returning('id') + .execute() if (!agendaItem) { return standardError(new Error('Agenda item not found'), {userId: viewerId}) } diff --git a/packages/server/graphql/mutations/updateAgendaItem.ts b/packages/server/graphql/mutations/updateAgendaItem.ts index 6a9c688ce5d..46d08998dbc 100644 --- a/packages/server/graphql/mutations/updateAgendaItem.ts +++ b/packages/server/graphql/mutations/updateAgendaItem.ts @@ -1,8 +1,10 @@ import {GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import makeUpdateAgendaItemSchema from 'parabol-client/validation/makeUpdateAgendaItemSchema' +import {getSortOrder} from '../../../client/shared/sortOrder' import getRethink from '../../database/rethinkDriver' import AgendaItemsStage from '../../database/types/AgendaItemsStage' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' import publish from '../../utils/publish' @@ -27,6 +29,7 @@ export default { ) { const now = new Date() const r = await getRethink() + const pg = getKysely() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const viewerId = getUserId(authToken) @@ -49,6 +52,8 @@ export default { } // RESOLUTION + const oldAgendaItems = await dataLoader.get('agendaItemsByTeamId').load(teamId) + const fromIdx = oldAgendaItems.findIndex((agendaItem) => agendaItem.id === id) await r .table('AgendaItem') .get(id) @@ -57,6 +62,25 @@ export default { updatedAt: now }) .run() + dataLoader.clearAll('agendaItems') + if (doc.sortOrder !== null && doc.sortOrder !== undefined) { + const nextAgendaItems = await dataLoader.get('agendaItemsByTeamId').load(teamId) + const pgagendaItems = await dataLoader.get('_pgagendaItemsByTeamId').load(teamId) + const toIdx = nextAgendaItems.findIndex((agendaItem) => agendaItem.id === id) + const pgSortOrder = getSortOrder(pgagendaItems, fromIdx, toIdx) + await pg + .updateTable('AgendaItem') + .set({sortOrder: pgSortOrder}) + .where('id', '=', id) + .execute() + } else { + await pg + .updateTable('AgendaItem') + .set({pinned: doc.pinned, content: doc.content}) + .where('id', '=', id) + .execute() + } + const activeMeetings = await dataLoader.get('activeMeetingsByTeamId').load(teamId) const actionMeeting = activeMeetings.find( (activeMeeting) => activeMeeting.meetingType === 'action' diff --git a/packages/server/graphql/public/mutations/setMeetingSettings.ts b/packages/server/graphql/public/mutations/setMeetingSettings.ts index f9363bee4b6..7dda9b23d33 100644 --- a/packages/server/graphql/public/mutations/setMeetingSettings.ts +++ b/packages/server/graphql/public/mutations/setMeetingSettings.ts @@ -60,13 +60,6 @@ const setMeetingSettings: MutationResolvers['setMeetingSettings'] = async ( .execute() dataLoader.clearAll('meetingSettings') - await getKysely() - .updateTable('MeetingSettings') - .set(nextSettings) - .where('id', '=', settings.id) - .execute() - dataLoader.clearAll('meetingSettings') - const data = {settingsId} analytics.meetingSettingsChanged(viewer, teamId, meetingType, { disableAnonymity: nextSettings.disableAnonymity, diff --git a/packages/server/graphql/public/mutations/startCheckIn.ts b/packages/server/graphql/public/mutations/startCheckIn.ts index c411c3fcd9d..a832930b505 100644 --- a/packages/server/graphql/public/mutations/startCheckIn.ts +++ b/packages/server/graphql/public/mutations/startCheckIn.ts @@ -3,6 +3,7 @@ import getRethink from '../../../database/rethinkDriver' import ActionMeetingMember from '../../../database/types/ActionMeetingMember' import MeetingAction from '../../../database/types/MeetingAction' import generateUID from '../../../generateUID' +import getKysely from '../../../postgres/getKysely' import updateTeamByTeamId from '../../../postgres/queries/updateTeamByTeamId' import {MeetingTypeEnum} from '../../../postgres/types/Meeting' import {analytics} from '../../../utils/analytics/analytics' @@ -89,7 +90,12 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( .insert(new ActionMeetingMember({meetingId, userId: viewerId, teamId})) .run(), updateTeamByTeamId(updates, teamId), - r.table('AgendaItem').getAll(r.args(agendaItemIds)).update({meetingId}).run() + r.table('AgendaItem').getAll(r.args(agendaItemIds)).update({meetingId}).run(), + getKysely() + .updateTable('AgendaItem') + .set({meetingId}) + .where('id', 'in', agendaItemIds) + .execute() ]) IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting) diff --git a/packages/server/postgres/migrations/1723491538114_AgendaItem-phase1.ts b/packages/server/postgres/migrations/1723491538114_AgendaItem-phase1.ts new file mode 100644 index 00000000000..cfa935f2a7c --- /dev/null +++ b/packages/server/postgres/migrations/1723491538114_AgendaItem-phase1.ts @@ -0,0 +1,49 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS "AgendaItem" ( + "id" VARCHAR(100) PRIMARY KEY, + "content" VARCHAR(64) NOT NULL, + "createdAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + "isActive" BOOLEAN NOT NULL DEFAULT TRUE, + "isComplete" BOOLEAN NOT NULL DEFAULT FALSE, + "sortOrder" VARCHAR(64) NOT NULL COLLATE "C", + "teamId" VARCHAR(100) NOT NULL, + "teamMemberId" VARCHAR(100) NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE DEFAULT CURRENT_TIMESTAMP NOT NULL, + "meetingId" VARCHAR(100), + "pinned" BOOLEAN NOT NULL DEFAULT FALSE, + "pinnedParentId" VARCHAR(100), + CONSTRAINT "fk_teamId" + FOREIGN KEY("teamId") + REFERENCES "Team"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_teamMemberId" + FOREIGN KEY("teamMemberId") + REFERENCES "TeamMember"("id") + ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS "idx_AgendaItem_teamId" ON "AgendaItem"("teamId"); + CREATE INDEX IF NOT EXISTS "idx_AgendaItem_meetingId" ON "AgendaItem"("meetingId"); + CREATE INDEX IF NOT EXISTS "idx_AgendaItem_teamMemberId" ON "AgendaItem"("teamMemberId"); + DROP TRIGGER IF EXISTS "update_AgendaItem_updatedAt" ON "AgendaItem"; + CREATE TRIGGER "update_AgendaItem_updatedAt" BEFORE UPDATE ON "AgendaItem" 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 "AgendaItem"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index 77559cab3ce..9a6df8e0d30 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -203,3 +203,5 @@ export const selectMeetingSettings = () => fn('to_json', ['jiraSearchQueries']).as('jiraSearchQueries'), fn('to_json', ['phaseTypes']).as('phaseTypes') ]) + +export const selectAgendaItems = () => getKysely().selectFrom('AgendaItem').selectAll() From 9efa909d1ef3aaf2a89bb5313159dfe08f7e91f4 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 13:39:17 -0700 Subject: [PATCH 409/529] chore(release): release v7.43.3 (#10107) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 08a5315d448..062bc529820 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.43.2" + ".": "7.43.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fbc86e4ae4..ec0f5764277 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.43.3](https://github.com/ParabolInc/parabol/compare/v7.43.2...v7.43.3) (2024-08-15) + + +### Changed + +* **rethinkdb:** AgendaItem: Phase 1 ([#10108](https://github.com/ParabolInc/parabol/issues/10108)) ([27e68c3](https://github.com/ParabolInc/parabol/commit/27e68c3b44f28944fd12bd10b66b2ca0f4dcf330)) +* **rethinkdb:** MeetingSettings: Phase 3 ([#10090](https://github.com/ParabolInc/parabol/issues/10090)) ([7aa172b](https://github.com/ParabolInc/parabol/commit/7aa172bed5df7bdfdf981a920c19d4bcce9710e2)) + ## [7.43.2](https://github.com/ParabolInc/parabol/compare/v7.43.1...v7.43.2) (2024-08-14) diff --git a/package.json b/package.json index fed847a106d..5c394901b69 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.43.2", + "version": "7.43.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 58e54bc609b..c5cd1b0e5bc 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.43.2", + "version": "7.43.3", "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.43.2" + "parabol-server": "7.43.3" } } diff --git a/packages/client/package.json b/packages/client/package.json index 28df8dc95ca..137ee26de66 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.43.2", + "version": "7.43.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index f850815daab..92bb1030837 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.43.2", + "version": "7.43.3", "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 0eef5c28ee9..8770383395d 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.43.2", + "version": "7.43.3", "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.43.2", - "parabol-server": "7.43.2", + "parabol-client": "7.43.3", + "parabol-server": "7.43.3", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 38db05eea03..342ac8c3982 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.43.2", + "version": "7.43.3", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index bb851a1492d..2cf7855ff91 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.43.2", + "version": "7.43.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.43.2", + "parabol-client": "7.43.3", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 7c5e5327d5acce8a02661a7d1c5db2bfa1857992 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 15 Aug 2024 15:39:02 -0700 Subject: [PATCH 410/529] fix: action meetings that had templates. wtf (#10130) Signed-off-by: Matt Krick --- ...723492538114_MeetingSettings-phase2redo.ts | 99 +++++++++++++++++++ 1 file changed, 99 insertions(+) create mode 100644 packages/server/postgres/migrations/1723492538114_MeetingSettings-phase2redo.ts diff --git a/packages/server/postgres/migrations/1723492538114_MeetingSettings-phase2redo.ts b/packages/server/postgres/migrations/1723492538114_MeetingSettings-phase2redo.ts new file mode 100644 index 00000000000..07da2947dfa --- /dev/null +++ b/packages/server/postgres/migrations/1723492538114_MeetingSettings-phase2redo.ts @@ -0,0 +1,99 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + const MAX_PG_PARAMS = 65545 + + const PG_COLS = [ + 'id', + 'phaseTypes', + 'meetingType', + 'teamId', + 'selectedTemplateId', + 'jiraSearchQueries', + 'maxVotesPerGroup', + 'totalVotes', + 'disableAnonymity', + 'videoMeetingURL' + ] as const + type MeetingSettings = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + const handleInsert = async (row: MeetingSettings) => { + try { + await pg + .insertInto('MeetingSettings') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_teamId') { + // console.log(`Skipping ${row.id} because it has no team`) + return + } + if (e.constraint === 'fk_selectedTemplateId') { + return handleInsert({...row, selectedTemplateId: null}) + } + console.log(e, row) + } + } + const startAt = new Date() + let curId = r.minval + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, curId) + let rawRowsToInsert = (await r + .table('MeetingSettings') + .between(curId, r.maxval, { + index: 'id', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'id'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as MeetingSettings[] + if (rawRowsToInsert.length === 0) { + // since we don't have a createdAt, it's possible new rows were created while this was running. + // Grab those new teams & get their settings, too + const newTeams = await pg + .selectFrom('Team') + .select('id') + .where('createdAt', '>', startAt) + .execute() + const newTeamIds = newTeams.map((team) => team.id) + console.log('got new TeamIds!', newTeamIds) + if (newTeamIds.length === 0) break + rawRowsToInsert = (await r + .table('MeetingSettings') + .getAll(r.args(newTeamIds)) + .pluck(...PG_COLS) + .run()) as MeetingSettings[] + } + const rowsToInsert = rawRowsToInsert.map((row) => ({ + ...row + })) + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curId = lastRow.id + await Promise.all(rowsToInsert.map(handleInsert)) + } +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql`TRUNCATE TABLE "MeetingSettings" CASCADE`.execute(pg) +} From 0bc5d765c8afd3f082c6e7fb286fb596b5a3f8d5 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 15:45:30 -0700 Subject: [PATCH 411/529] chore(release): release v7.43.4 (#10131) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 062bc529820..15ec41171d5 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.43.3" + ".": "7.43.4" } diff --git a/CHANGELOG.md b/CHANGELOG.md index ec0f5764277..ee8267db690 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.43.4](https://github.com/ParabolInc/parabol/compare/v7.43.3...v7.43.4) (2024-08-15) + + +### Fixed + +* action meetings that had templates. wtf ([#10130](https://github.com/ParabolInc/parabol/issues/10130)) ([7c5e532](https://github.com/ParabolInc/parabol/commit/7c5e5327d5acce8a02661a7d1c5db2bfa1857992)) + ## [7.43.3](https://github.com/ParabolInc/parabol/compare/v7.43.2...v7.43.3) (2024-08-15) diff --git a/package.json b/package.json index 5c394901b69..b45e4cef62b 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.43.3", + "version": "7.43.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index c5cd1b0e5bc..4bd43f3ffda 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.43.3", + "version": "7.43.4", "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.43.3" + "parabol-server": "7.43.4" } } diff --git a/packages/client/package.json b/packages/client/package.json index 137ee26de66..51990383964 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.43.3", + "version": "7.43.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 92bb1030837..68bcd165703 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.43.3", + "version": "7.43.4", "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 8770383395d..23fe2b0252d 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.43.3", + "version": "7.43.4", "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.43.3", - "parabol-server": "7.43.3", + "parabol-client": "7.43.4", + "parabol-server": "7.43.4", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 342ac8c3982..cde2780e029 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.43.3", + "version": "7.43.4", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 2cf7855ff91..2519d8b6307 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.43.3", + "version": "7.43.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.43.3", + "parabol-client": "7.43.4", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From ae28cdef4e02b91a1ac4ce67d26cfed2f8a4beda Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 15 Aug 2024 17:09:44 -0700 Subject: [PATCH 412/529] fix: handle empty array to PG. fixup error handling (#10133) Signed-off-by: Matt Krick --- packages/server/graphql/executeGraphQL.ts | 6 +++--- .../server/graphql/handleGraphQLTrebuchetRequest.ts | 6 ------ .../server/graphql/public/mutations/startCheckIn.ts | 11 ++++++----- packages/server/utils/sendToSentry.ts | 7 +------ 4 files changed, 10 insertions(+), 20 deletions(-) diff --git a/packages/server/graphql/executeGraphQL.ts b/packages/server/graphql/executeGraphQL.ts index 8fea40bc2a5..b8685ec669d 100644 --- a/packages/server/graphql/executeGraphQL.ts +++ b/packages/server/graphql/executeGraphQL.ts @@ -8,6 +8,7 @@ import tracer from 'dd-trace' import {graphql} from 'graphql' import {FormattedExecutionResult} from 'graphql/execution/execute' import type {GQLRequest} from '../types/custom' +import sendToSentry from '../utils/sendToSentry' import CompiledQueryCache from './CompiledQueryCache' import getDataLoader from './getDataLoader' import getRateLimiter from './getRateLimiter' @@ -59,10 +60,9 @@ const executeGraphQL = async (req: GQLRequest) => { response = {errors: [new Error(message)] as any} } } - if (!__PRODUCTION__ && response.errors) { + if (response.errors) { const [firstError] = response.errors - console.log((firstError as Error).stack) - console.trace({error: JSON.stringify(response)}) + sendToSentry(firstError as Error) } dataLoader.dispose() return response diff --git a/packages/server/graphql/handleGraphQLTrebuchetRequest.ts b/packages/server/graphql/handleGraphQLTrebuchetRequest.ts index 51fdc7b9bd1..3721f51805d 100644 --- a/packages/server/graphql/handleGraphQLTrebuchetRequest.ts +++ b/packages/server/graphql/handleGraphQLTrebuchetRequest.ts @@ -49,12 +49,6 @@ const handleGraphQLTrebuchetRequest = async ( ip, carrier }) - if (result.errors?.[0]) { - const [firstError] = result.errors - const safeError = new Error(firstError.message) - safeError.stack = firstError.stack - sendToSentry(safeError) - } const safeResult = sanitizeGraphQLErrors(result) // TODO if multiple results, send GQL_DATA for all but the last const messageType = result.data ? 'complete' : 'error' diff --git a/packages/server/graphql/public/mutations/startCheckIn.ts b/packages/server/graphql/public/mutations/startCheckIn.ts index a832930b505..6fe86b06f0a 100644 --- a/packages/server/graphql/public/mutations/startCheckIn.ts +++ b/packages/server/graphql/public/mutations/startCheckIn.ts @@ -91,11 +91,12 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( .run(), updateTeamByTeamId(updates, teamId), r.table('AgendaItem').getAll(r.args(agendaItemIds)).update({meetingId}).run(), - getKysely() - .updateTable('AgendaItem') - .set({meetingId}) - .where('id', 'in', agendaItemIds) - .execute() + agendaItemIds.length && + getKysely() + .updateTable('AgendaItem') + .set({meetingId}) + .where('id', 'in', agendaItemIds) + .execute() ]) IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting) diff --git a/packages/server/utils/sendToSentry.ts b/packages/server/utils/sendToSentry.ts index 252a6e11719..1403cee2c96 100644 --- a/packages/server/utils/sendToSentry.ts +++ b/packages/server/utils/sendToSentry.ts @@ -13,12 +13,7 @@ export interface SentryOptions { // Even though this is a promise we'll never need to await it, so we'll never need to worry about catching an error const sendToSentry = async (error: Error, options: SentryOptions = {}) => { - console.trace( - 'SEND TO SENTRY', - error.message || error?.toString() || JSON.stringify(error), - JSON.stringify(options.tags), - JSON.stringify(options.extras) - ) + console.log('SEND TO SENTRY', error || JSON.stringify(error)) const {sampleRate, tags, extras, userId, ip} = options if (sampleRate && Math.random() > sampleRate) return const fullUser = userId ? await getUserById(userId) : null From 93f6692e4d03fe39f4d5dbbd4c880e1aa582f54c Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 17:14:12 -0700 Subject: [PATCH 413/529] chore(release): release v7.43.5 (#10134) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 15ec41171d5..da52ed3dd60 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.43.4" + ".": "7.43.5" } diff --git a/CHANGELOG.md b/CHANGELOG.md index ee8267db690..6cf52ebd64e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.43.5](https://github.com/ParabolInc/parabol/compare/v7.43.4...v7.43.5) (2024-08-16) + + +### Fixed + +* handle empty array to PG. fixup error handling ([#10133](https://github.com/ParabolInc/parabol/issues/10133)) ([ae28cde](https://github.com/ParabolInc/parabol/commit/ae28cdef4e02b91a1ac4ce67d26cfed2f8a4beda)) + ## [7.43.4](https://github.com/ParabolInc/parabol/compare/v7.43.3...v7.43.4) (2024-08-15) diff --git a/package.json b/package.json index b45e4cef62b..bb084a1d29c 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.43.4", + "version": "7.43.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 4bd43f3ffda..26e055a7233 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.43.4", + "version": "7.43.5", "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.43.4" + "parabol-server": "7.43.5" } } diff --git a/packages/client/package.json b/packages/client/package.json index 51990383964..5b2f2836233 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.43.4", + "version": "7.43.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 68bcd165703..0fde9d92d74 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.43.4", + "version": "7.43.5", "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 23fe2b0252d..244c9959397 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.43.4", + "version": "7.43.5", "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.43.4", - "parabol-server": "7.43.4", + "parabol-client": "7.43.5", + "parabol-server": "7.43.5", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index cde2780e029..767d5270d0c 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.43.4", + "version": "7.43.5", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 2519d8b6307..57f81dbc6c6 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.43.4", + "version": "7.43.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.43.4", + "parabol-client": "7.43.5", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 62abfa0c047383a2922957f859dab484abafa2b4 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 15 Aug 2024 17:31:51 -0700 Subject: [PATCH 414/529] fix: add missing ID (#10136) Signed-off-by: Matt Krick --- packages/server/graphql/mutations/addAgendaItem.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/graphql/mutations/addAgendaItem.ts b/packages/server/graphql/mutations/addAgendaItem.ts index c6fc4425fc0..c51a15c2f59 100644 --- a/packages/server/graphql/mutations/addAgendaItem.ts +++ b/packages/server/graphql/mutations/addAgendaItem.ts @@ -58,6 +58,7 @@ export default { .insert( new AgendaItem({ ...validNewAgendaItem, + id: agendaItemId, teamId } as AgendaItemInput) ) From 60f50bd286816ad1f6d5ca50e36369e56907a031 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 17:36:04 -0700 Subject: [PATCH 415/529] chore(release): release v7.43.6 (#10137) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index da52ed3dd60..e03de66a184 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.43.5" + ".": "7.43.6" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 6cf52ebd64e..b5954516426 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.43.6](https://github.com/ParabolInc/parabol/compare/v7.43.5...v7.43.6) (2024-08-16) + + +### Fixed + +* add missing ID ([#10136](https://github.com/ParabolInc/parabol/issues/10136)) ([62abfa0](https://github.com/ParabolInc/parabol/commit/62abfa0c047383a2922957f859dab484abafa2b4)) + ## [7.43.5](https://github.com/ParabolInc/parabol/compare/v7.43.4...v7.43.5) (2024-08-16) diff --git a/package.json b/package.json index bb084a1d29c..1369526ce17 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.43.5", + "version": "7.43.6", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 26e055a7233..c07bdc66033 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.43.5", + "version": "7.43.6", "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.43.5" + "parabol-server": "7.43.6" } } diff --git a/packages/client/package.json b/packages/client/package.json index 5b2f2836233..5bdc1b07386 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.43.5", + "version": "7.43.6", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 0fde9d92d74..715399f2166 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.43.5", + "version": "7.43.6", "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 244c9959397..3b69f5cc112 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.43.5", + "version": "7.43.6", "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.43.5", - "parabol-server": "7.43.5", + "parabol-client": "7.43.6", + "parabol-server": "7.43.6", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 767d5270d0c..00f5ddf0c1b 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.43.5", + "version": "7.43.6", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 57f81dbc6c6..5ebe6a0d03a 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.43.5", + "version": "7.43.6", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.43.5", + "parabol-client": "7.43.6", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From bd802d52866498310fe45cc951d059b2f30b9da6 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 15 Aug 2024 17:55:34 -0700 Subject: [PATCH 416/529] chore(rethinkdb): AgendaItem: Phase 3 (#10109) Signed-off-by: Matt Krick --- codegen.json | 5 +- .../components/AgendaInput/AgendaInput.tsx | 4 +- .../components/AgendaList/AgendaList.tsx | 16 +- .../client/validation/makeAgendaItemSchema.ts | 14 -- .../validation/makeUpdateAgendaItemSchema.ts | 15 -- packages/server/database/rethinkDriver.ts | 5 - packages/server/database/types/AgendaItem.ts | 61 ------ .../dataloader/foreignKeyLoaderMakers.ts | 8 +- .../dataloader/primaryKeyLoaderMakers.ts | 2 +- .../rethinkForeignKeyLoaderMakers.ts | 27 --- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../server/graphql/mutations/addAgendaItem.ts | 84 -------- .../server/graphql/mutations/endCheckIn.ts | 30 +-- .../addAgendaItemToActiveActionMeeting.ts | 1 - .../graphql/mutations/removeAgendaItem.ts | 61 ------ .../graphql/mutations/updateAgendaItem.ts | 111 ----------- .../private/mutations/hardDeleteUser.ts | 6 - .../graphql/public/mutations/addAgendaItem.ts | 54 +++++ .../public/mutations/removeAgendaItem.ts | 43 ++++ .../graphql/public/mutations/startCheckIn.ts | 1 - .../public/mutations/updateAgendaItem.ts | 73 +++++++ .../public/typeDefs/AgendaItem.graphql | 2 +- .../typeDefs/CreateAgendaItemInput.graphql | 2 +- .../typeDefs/UpdateAgendaItemInput.graphql | 2 +- .../graphql/public/types/ActionMeeting.ts | 2 +- .../public/types/AddAgendaItemPayload.ts | 24 +++ .../graphql/public/types/AgendaItemsStage.ts | 2 +- .../public/types/RemoveAgendaItemPayload.ts | 24 +++ .../public/types/UpdateAgendaItemPayload.ts | 24 +++ packages/server/graphql/rootMutation.ts | 6 - .../graphql/types/AddAgendaItemPayload.ts | 32 --- .../graphql/types/CreateAgendaItemInput.ts | 48 ----- .../graphql/types/RemoveAgendaItemPayload.ts | 29 --- .../graphql/types/UpdateAgendaItemInput.ts | 44 ----- .../graphql/types/UpdateAgendaItemPayload.ts | 32 --- .../1723672980596_AgendaItem-phase2.ts | 184 ++++++++++++++++++ packages/server/postgres/types/index.d.ts | 3 + 37 files changed, 455 insertions(+), 627 deletions(-) delete mode 100644 packages/client/validation/makeAgendaItemSchema.ts delete mode 100644 packages/client/validation/makeUpdateAgendaItemSchema.ts delete mode 100644 packages/server/database/types/AgendaItem.ts delete mode 100644 packages/server/graphql/mutations/addAgendaItem.ts delete mode 100644 packages/server/graphql/mutations/removeAgendaItem.ts delete mode 100644 packages/server/graphql/mutations/updateAgendaItem.ts create mode 100644 packages/server/graphql/public/mutations/addAgendaItem.ts create mode 100644 packages/server/graphql/public/mutations/removeAgendaItem.ts create mode 100644 packages/server/graphql/public/mutations/updateAgendaItem.ts create mode 100644 packages/server/graphql/public/types/AddAgendaItemPayload.ts create mode 100644 packages/server/graphql/public/types/RemoveAgendaItemPayload.ts create mode 100644 packages/server/graphql/public/types/UpdateAgendaItemPayload.ts delete mode 100644 packages/server/graphql/types/AddAgendaItemPayload.ts delete mode 100644 packages/server/graphql/types/CreateAgendaItemInput.ts delete mode 100644 packages/server/graphql/types/RemoveAgendaItemPayload.ts delete mode 100644 packages/server/graphql/types/UpdateAgendaItemInput.ts delete mode 100644 packages/server/graphql/types/UpdateAgendaItemPayload.ts create mode 100644 packages/server/postgres/migrations/1723672980596_AgendaItem-phase2.ts diff --git a/codegen.json b/codegen.json index 83eaf7bf530..b584d84cc68 100644 --- a/codegen.json +++ b/codegen.json @@ -46,6 +46,9 @@ "config": { "contextType": "../graphql#GQLContext", "mappers": { + "RemoveAgendaItemPayload": "./types/RemoveAgendaItemPayload#RemoveAgendaItemPayloadSource", + "AddAgendaItemPayload": "./types/AddAgendaItemPayload#AddAgendaItemPayloadSource", + "UpdateAgendaItemPayload": "./types/UpdateAgendaItemPayload#UpdateAgendaItemPayloadSource", "TeamMeetingSettings": "../../postgres/types/index#MeetingSettings as TeamMeetingSettingsDB", "TeamPromptMeetingSettings": "../../postgres/types/index#MeetingSettings as TeamMeetingSettingsDB", "PokerMeetingSettings": "../../postgres/types/index#PokerMeetingSettings as PokerMeetingSettingsDB", @@ -66,7 +69,7 @@ "AddTeamMemberIntegrationAuthSuccess": "./types/AddTeamMemberIntegrationAuthPayload#AddTeamMemberIntegrationAuthSuccessSource", "AddTranscriptionBotSuccess": "./types/AddTranscriptionBotSuccess#AddTranscriptionBotSuccessSource", "AddedNotification": "./types/AddedNotification#AddedNotificationSource", - "AgendaItem": "../../database/types/AgendaItem#default as AgendaItemDB", + "AgendaItem": "../../postgres/types/index#AgendaItem as AgendaItemDB", "ArchiveTeamPayload": "./types/ArchiveTeamPayload#ArchiveTeamPayloadSource", "AtlassianIntegration": "../../postgres/queries/getAtlassianAuthByUserIdTeamId#AtlassianAuth as AtlassianAuthDB", "AuthTokenPayload": "./types/AuthTokenPayload#AuthTokenPayloadSource", diff --git a/packages/client/modules/teamDashboard/components/AgendaInput/AgendaInput.tsx b/packages/client/modules/teamDashboard/components/AgendaInput/AgendaInput.tsx index 774fe017c27..25f37cfee19 100644 --- a/packages/client/modules/teamDashboard/components/AgendaInput/AgendaInput.tsx +++ b/packages/client/modules/teamDashboard/components/AgendaInput/AgendaInput.tsx @@ -12,11 +12,11 @@ import useHotkey from '../../../../hooks/useHotkey' import useMutationProps from '../../../../hooks/useMutationProps' import useTooltip from '../../../../hooks/useTooltip' import AddAgendaItemMutation from '../../../../mutations/AddAgendaItemMutation' +import {positionAfter} from '../../../../shared/sortOrder' import makeFieldColorPalette from '../../../../styles/helpers/makeFieldColorPalette' import makePlaceholderStyles from '../../../../styles/helpers/makePlaceholderStyles' import {PALETTE} from '../../../../styles/paletteV3' import ui from '../../../../styles/ui' -import getNextSortOrder from '../../../../utils/getNextSortOrder' import toTeamMemberId from '../../../../utils/relay/toTeamMemberId' const AgendaInputBlock = styled('div')({ @@ -123,7 +123,7 @@ const AgendaInput = (props: Props) => { const newAgendaItem = { content, pinned: false, - sortOrder: getNextSortOrder(agendaItems), + sortOrder: positionAfter(agendaItems.at(-1)?.sortOrder ?? ''), teamId, teamMemberId: toTeamMemberId(teamId, atmosphere.viewerId) } diff --git a/packages/client/modules/teamDashboard/components/AgendaList/AgendaList.tsx b/packages/client/modules/teamDashboard/components/AgendaList/AgendaList.tsx index 2ff4a1a27ea..b17b5291aea 100644 --- a/packages/client/modules/teamDashboard/components/AgendaList/AgendaList.tsx +++ b/packages/client/modules/teamDashboard/components/AgendaList/AgendaList.tsx @@ -9,9 +9,9 @@ import useAtmosphere from '../../../../hooks/useAtmosphere' import useEventCallback from '../../../../hooks/useEventCallback' import useGotoStageId from '../../../../hooks/useGotoStageId' import UpdateAgendaItemMutation from '../../../../mutations/UpdateAgendaItemMutation' +import {getSortOrder} from '../../../../shared/sortOrder' import {navItemRaised} from '../../../../styles/elevation' -import {AGENDA_ITEM, SORT_STEP} from '../../../../utils/constants' -import dndNoise from '../../../../utils/dndNoise' +import {AGENDA_ITEM} from '../../../../utils/constants' import AgendaItem from '../AgendaItem/AgendaItem' import AgendaListEmptyState from './AgendaListEmptyState' @@ -83,17 +83,7 @@ const AgendaList = (props: Props) => { return } - let sortOrder - if (destination.index === 0) { - sortOrder = destinationItem.sortOrder - SORT_STEP + dndNoise() - } else if (destination.index === agendaItems.length - 1) { - sortOrder = destinationItem.sortOrder + SORT_STEP + dndNoise() - } else { - const offset = source.index > destination.index ? -1 : 1 - sortOrder = - (agendaItems[destination.index + offset]!.sortOrder + destinationItem.sortOrder) / 2 + - dndNoise() - } + const sortOrder = getSortOrder(agendaItems, source.index, destination.index) UpdateAgendaItemMutation( atmosphere, {updatedAgendaItem: {id: sourceItem.id, sortOrder}}, diff --git a/packages/client/validation/makeAgendaItemSchema.ts b/packages/client/validation/makeAgendaItemSchema.ts deleted file mode 100644 index 3c3cc027008..00000000000 --- a/packages/client/validation/makeAgendaItemSchema.ts +++ /dev/null @@ -1,14 +0,0 @@ -import legitify from './legitify' -import Legitity from './Legitity' -import {compositeId, id} from './templates' - -export default function makeAgendaItemSchema() { - return legitify({ - content: (value: Legitity) => value.trim().max(63, 'Try something a little shorter'), - isActive: (value: Legitity) => value.boolean(), - pinned: (value: Legitity) => value.boolean(), - sortOrder: (value: Legitity) => value.float(), - teamId: id, - teamMemberId: compositeId - }) -} diff --git a/packages/client/validation/makeUpdateAgendaItemSchema.ts b/packages/client/validation/makeUpdateAgendaItemSchema.ts deleted file mode 100644 index 4967d9ed0bb..00000000000 --- a/packages/client/validation/makeUpdateAgendaItemSchema.ts +++ /dev/null @@ -1,15 +0,0 @@ -import legitify from './legitify' -import Legitity from './Legitity' -import {compositeId, id} from './templates' - -export default function makeUpdateAgendaItemSchema() { - return legitify({ - id: compositeId, - content: (value: Legitity) => value.trim().max(63, 'Try something a little shorter'), - isActive: (value: Legitity) => value.boolean(), - pinned: (value: Legitity) => value.boolean(), - sortOrder: (value: Legitity) => value.float(), - teamId: id, - teamMemberId: compositeId - }) -} diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index f36a3a73eef..18de76dcfcd 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -5,7 +5,6 @@ import TeamInvitation from '../database/types/TeamInvitation' import {AnyMeeting, AnyMeetingTeamMember} from '../postgres/types/Meeting' import getRethinkConfig from './getRethinkConfig' import {R} from './stricterR' -import AgendaItem from './types/AgendaItem' import Comment from './types/Comment' import MassInvitation from './types/MassInvitation' import NotificationKickedOut from './types/NotificationKickedOut' @@ -24,10 +23,6 @@ import RetrospectivePrompt from './types/RetrospectivePrompt' import Task from './types/Task' export type RethinkSchema = { - AgendaItem: { - type: AgendaItem - index: 'teamId' | 'meetingId' - } Comment: { type: Comment index: 'discussionId' diff --git a/packages/server/database/types/AgendaItem.ts b/packages/server/database/types/AgendaItem.ts deleted file mode 100644 index e88304fe36c..00000000000 --- a/packages/server/database/types/AgendaItem.ts +++ /dev/null @@ -1,61 +0,0 @@ -import generateUID from '../../generateUID' - -export interface AgendaItemInput { - id?: string - createdAt?: Date - isActive?: boolean - isComplete?: boolean - sortOrder?: number - teamId: string - teamMemberId: string - updatedAt?: Date - content: string - meetingId?: string - pinned?: boolean - pinnedParentId?: string -} - -export default class AgendaItem { - id: string - content: string - createdAt: Date - isActive: boolean - isComplete: boolean - sortOrder: number - teamId: string - teamMemberId: string - updatedAt: Date - meetingId?: string - pinned?: boolean - pinnedParentId?: string - - constructor(input: AgendaItemInput) { - const { - id, - createdAt, - isActive, - isComplete, - sortOrder, - teamId, - teamMemberId, - updatedAt, - content, - meetingId, - pinned, - pinnedParentId - } = input - const now = new Date() - this.id = id || generateUID() - this.createdAt = createdAt || now - this.isActive = isActive ?? true - this.isComplete = isComplete ?? false - this.sortOrder = sortOrder || 0 - this.teamId = teamId - this.teamMemberId = teamMemberId - this.updatedAt = updatedAt || now - this.content = content || '' - this.meetingId = meetingId - this.pinned = pinned - this.pinnedParentId = pinnedParentId - } -} diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 51e21b4ca0b..d5855cf4e3c 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -172,8 +172,8 @@ export const teamPromptResponsesByMeetingId = foreignKeyLoaderMaker( getTeamPromptResponsesByMeetingIds ) -export const _pgagendaItemsByTeamId = foreignKeyLoaderMaker( - '_pgagendaItems', +export const agendaItemsByTeamId = foreignKeyLoaderMaker( + 'agendaItems', 'teamId', async (teamIds) => { return selectAgendaItems() @@ -184,8 +184,8 @@ export const _pgagendaItemsByTeamId = foreignKeyLoaderMaker( } ) -export const _pgagendaItemsByMeetingId = foreignKeyLoaderMaker( - '_pgagendaItems', +export const agendaItemsByMeetingId = foreignKeyLoaderMaker( + 'agendaItems', 'meetingId', async (meetingIds) => { return selectAgendaItems().where('meetingId', 'in', meetingIds).orderBy('sortOrder').execute() diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 0849187482d..7ac2ed6389d 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -92,6 +92,6 @@ export const meetingSettings = primaryKeyLoaderMaker((ids: readonly string[]) => return selectMeetingSettings().where('id', 'in', ids).execute() }) -export const _pgagendaItems = primaryKeyLoaderMaker((ids: readonly string[]) => { +export const agendaItems = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectAgendaItems().where('id', 'in', ids).execute() }) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index 8cde8dc0bb6..24afe08e897 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -16,33 +16,6 @@ export const activeMeetingsByTeamId = new RethinkForeignKeyLoaderMaker( } ) -export const agendaItemsByTeamId = new RethinkForeignKeyLoaderMaker( - 'agendaItems', - 'teamId', - async (teamIds) => { - const r = await getRethink() - return r - .table('AgendaItem') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter({isActive: true}) - .orderBy('sortOrder') - .run() - } -) - -export const agendaItemsByMeetingId = new RethinkForeignKeyLoaderMaker( - 'agendaItems', - 'meetingId', - async (meetingIds) => { - const r = await getRethink() - return r - .table('AgendaItem') - .getAll(r.args(meetingIds), {index: 'meetingId'}) - .orderBy('sortOrder') - .run() - } -) - export const commentsByDiscussionId = new RethinkForeignKeyLoaderMaker( 'comments', 'discussionId', diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index b16d76e3adf..e2840c3ed0a 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -3,7 +3,6 @@ import RethinkPrimaryKeyLoaderMaker from './RethinkPrimaryKeyLoaderMaker' /** * all rethink dataloader types which also must exist in {@link rethinkDriver/RethinkSchema} */ -export const agendaItems = new RethinkPrimaryKeyLoaderMaker('AgendaItem') export const comments = new RethinkPrimaryKeyLoaderMaker('Comment') export const reflectPrompts = new RethinkPrimaryKeyLoaderMaker('ReflectPrompt') export const massInvitations = new RethinkPrimaryKeyLoaderMaker('MassInvitation') diff --git a/packages/server/graphql/mutations/addAgendaItem.ts b/packages/server/graphql/mutations/addAgendaItem.ts deleted file mode 100644 index c51a15c2f59..00000000000 --- a/packages/server/graphql/mutations/addAgendaItem.ts +++ /dev/null @@ -1,84 +0,0 @@ -import {GraphQLNonNull} from 'graphql' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import makeAgendaItemSchema from 'parabol-client/validation/makeAgendaItemSchema' -import {positionAfter} from '../../../client/shared/sortOrder' -import getRethink from '../../database/rethinkDriver' -import AgendaItem, {AgendaItemInput} from '../../database/types/AgendaItem' -import generateUID from '../../generateUID' -import getKysely from '../../postgres/getKysely' -import {analytics} from '../../utils/analytics/analytics' -import {getUserId, isTeamMember} from '../../utils/authorization' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import AddAgendaItemPayload from '../types/AddAgendaItemPayload' -import CreateAgendaItemInput, {CreateAgendaItemInputType} from '../types/CreateAgendaItemInput' -import {GQLContext} from './../graphql' -import addAgendaItemToActiveActionMeeting from './helpers/addAgendaItemToActiveActionMeeting' - -export default { - type: AddAgendaItemPayload, - description: 'Create a new agenda item', - args: { - newAgendaItem: { - type: new GraphQLNonNull(CreateAgendaItemInput), - description: 'The new task including an id, teamMemberId, and content' - } - }, - async resolve( - _source: unknown, - {newAgendaItem}: {newAgendaItem: CreateAgendaItemInputType}, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) { - const r = await getRethink() - const operationId = dataLoader.share() - const subOptions = {mutatorId, operationId} - const viewerId = getUserId(authToken) - // AUTH - const {teamId} = newAgendaItem - if (!isTeamMember(authToken, teamId)) { - return standardError(new Error('Team not found'), {userId: viewerId}) - } - const viewer = await dataLoader.get('users').loadNonNull(viewerId) - // VALIDATION - const schema = makeAgendaItemSchema() - const {errors, data: validNewAgendaItem} = schema(newAgendaItem) - if (Object.keys(errors).length) { - return standardError(new Error('Failed input validation'), {userId: viewerId}) - } - - // RESOLUTION - const teamAgendaItems = await dataLoader.get('agendaItemsByTeamId').load(teamId) - const lastAgendaItem = teamAgendaItems.at(-1) - const lastSortOrder = lastAgendaItem?.sortOrder ? String(lastAgendaItem.sortOrder) : '' - // this is just during the migration of AgendaItem table - const sortOrder = positionAfter(lastSortOrder) - const agendaItemId = `${teamId}::${generateUID()}` - await r - .table('AgendaItem') - .insert( - new AgendaItem({ - ...validNewAgendaItem, - id: agendaItemId, - teamId - } as AgendaItemInput) - ) - .run() - await getKysely() - .insertInto('AgendaItem') - .values({ - id: agendaItemId, - content: newAgendaItem.content, - meetingId: newAgendaItem.meetingId, - pinned: newAgendaItem.pinned, - sortOrder, - teamId, - teamMemberId: newAgendaItem.teamMemberId - }) - .execute() - const meetingId = await addAgendaItemToActiveActionMeeting(agendaItemId, teamId, dataLoader) - analytics.addedAgendaItem(viewer, teamId, meetingId) - const data = {agendaItemId, meetingId} - publish(SubscriptionChannel.TEAM, teamId, 'AddAgendaItemPayload', data, subOptions) - return data - } -} diff --git a/packages/server/graphql/mutations/endCheckIn.ts b/packages/server/graphql/mutations/endCheckIn.ts index c5bfbca7a28..a3e0c2573da 100644 --- a/packages/server/graphql/mutations/endCheckIn.ts +++ b/packages/server/graphql/mutations/endCheckIn.ts @@ -7,13 +7,13 @@ import {positionAfter} from '../../../client/shared/sortOrder' import {checkTeamsLimit} from '../../billing/helpers/teamLimitsCheck' import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' -import AgendaItem from '../../database/types/AgendaItem' import MeetingAction from '../../database/types/MeetingAction' import Task from '../../database/types/Task' import TimelineEventCheckinComplete from '../../database/types/TimelineEventCheckinComplete' import {DataLoaderInstance} from '../../dataloader/RootDataLoader' import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' +import {AgendaItem} from '../../postgres/types' import archiveTasksForDB from '../../safeMutations/archiveTasksForDB' import removeSuggestedAction from '../../safeMutations/removeSuggestedAction' import {Logger} from '../../utils/Logger' @@ -69,15 +69,7 @@ const clearAgendaItems = async (teamId: string, dataLoader: DataLoaderInstance) .set({isActive: false}) .where('teamId', '=', teamId) .execute() - const r = await getRethink() dataLoader.clearAll('agendaItems') - return r - .table('AgendaItem') - .getAll(teamId, {index: 'teamId'}) - .update({ - isActive: false - }) - .run() } const getPinnedAgendaItems = async (teamId: string, dataLoader: DataLoaderInstance) => { @@ -89,30 +81,22 @@ const clonePinnedAgendaItems = async ( pinnedAgendaItems: AgendaItem[], dataLoader: DataLoaderInstance ) => { - const r = await getRethink() + let curSortOrder = '' const clonedPins = pinnedAgendaItems.map((agendaItem) => { const agendaItemId = `${agendaItem.teamId}::${generateUID()}` - return new AgendaItem({ + const sortOrder = positionAfter(curSortOrder) + curSortOrder = sortOrder + return { id: agendaItemId, content: agendaItem.content, pinned: agendaItem.pinned, pinnedParentId: agendaItem.pinnedParentId ? agendaItem.pinnedParentId : agendaItemId, - sortOrder: agendaItem.sortOrder, + sortOrder, teamId: agendaItem.teamId, teamMemberId: agendaItem.teamMemberId - }) - }) - await r.table('AgendaItem').insert(clonedPins).run() - let curSortOrder = '' - const pgClonedPins = clonedPins.map((agendaItems) => { - const sortOrder = positionAfter(curSortOrder) - curSortOrder = sortOrder - return { - ...agendaItems, - sortOrder } }) - await getKysely().insertInto('AgendaItem').values(pgClonedPins).execute() + await getKysely().insertInto('AgendaItem').values(clonedPins).execute() dataLoader.clearAll('agendaItems') } diff --git a/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts b/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts index db01b387b0d..4ceb9ab23c9 100644 --- a/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts +++ b/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts @@ -47,7 +47,6 @@ const addAgendaItemToActiveActionMeeting = async ( updatedAt: now }) .run(), - r.table('AgendaItem').get(agendaItemId).update({meetingId: meetingId}).run(), getKysely().updateTable('AgendaItem').set({meetingId}).where('id', '=', agendaItemId).execute(), insertDiscussions([ { diff --git a/packages/server/graphql/mutations/removeAgendaItem.ts b/packages/server/graphql/mutations/removeAgendaItem.ts deleted file mode 100644 index 941c0f9dc30..00000000000 --- a/packages/server/graphql/mutations/removeAgendaItem.ts +++ /dev/null @@ -1,61 +0,0 @@ -import {GraphQLID, GraphQLNonNull} from 'graphql' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import AgendaItemsStage from '../../database/types/AgendaItemsStage' -import getKysely from '../../postgres/getKysely' -import {getUserId, isTeamMember} from '../../utils/authorization' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import RemoveAgendaItemPayload from '../types/RemoveAgendaItemPayload' -import removeStagesFromMeetings from './helpers/removeStagesFromMeetings' - -export default { - type: RemoveAgendaItemPayload, - description: 'Remove an agenda item', - args: { - agendaItemId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The agenda item unique id' - } - }, - async resolve( - _source: unknown, - {agendaItemId}: {agendaItemId: string}, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) { - const r = await getRethink() - const operationId = dataLoader.share() - const subOptions = {mutatorId, operationId} - const viewerId = getUserId(authToken) - - // AUTH - // id is of format 'teamId::randomId' - const [teamId] = agendaItemId.split('::') as [string] - if (!isTeamMember(authToken, teamId)) { - return standardError(new Error('Team not found'), {userId: viewerId}) - } - - // RESOLUTION - const agendaItem = await r - .table('AgendaItem') - .get(agendaItemId) - .update({isActive: false}, {returnChanges: true})('changes')(0)('old_val') - .default(null) - .run() - await getKysely() - .updateTable('AgendaItem') - .set({isActive: false}) - .where('id', '=', agendaItemId) - .returning('id') - .execute() - if (!agendaItem) { - return standardError(new Error('Agenda item not found'), {userId: viewerId}) - } - const filterFn = (stage: AgendaItemsStage) => stage.agendaItemId === agendaItemId - await removeStagesFromMeetings(filterFn, teamId, dataLoader) - const data = {agendaItem, meetingId: agendaItem.meetingId} - publish(SubscriptionChannel.TEAM, teamId, 'RemoveAgendaItemPayload', data, subOptions) - return data - } -} diff --git a/packages/server/graphql/mutations/updateAgendaItem.ts b/packages/server/graphql/mutations/updateAgendaItem.ts deleted file mode 100644 index 46d08998dbc..00000000000 --- a/packages/server/graphql/mutations/updateAgendaItem.ts +++ /dev/null @@ -1,111 +0,0 @@ -import {GraphQLNonNull} from 'graphql' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import makeUpdateAgendaItemSchema from 'parabol-client/validation/makeUpdateAgendaItemSchema' -import {getSortOrder} from '../../../client/shared/sortOrder' -import getRethink from '../../database/rethinkDriver' -import AgendaItemsStage from '../../database/types/AgendaItemsStage' -import getKysely from '../../postgres/getKysely' -import {getUserId, isTeamMember} from '../../utils/authorization' -import getPhase from '../../utils/getPhase' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import UpdateAgendaItemInput, {UpdateAgendaItemInputType} from '../types/UpdateAgendaItemInput' -import UpdateAgendaItemPayload from '../types/UpdateAgendaItemPayload' - -export default { - type: UpdateAgendaItemPayload, - description: 'Update an agenda item', - args: { - updatedAgendaItem: { - type: new GraphQLNonNull(UpdateAgendaItemInput), - description: 'The updated item including an id, content, status, sortOrder' - } - }, - async resolve( - _source: unknown, - {updatedAgendaItem}: {updatedAgendaItem: UpdateAgendaItemInputType}, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) { - const now = new Date() - const r = await getRethink() - const pg = getKysely() - const operationId = dataLoader.share() - const subOptions = {mutatorId, operationId} - const viewerId = getUserId(authToken) - - // AUTH - const {id: agendaItemId} = updatedAgendaItem - const [teamId] = agendaItemId.split('::') as [string] - if (!isTeamMember(authToken, teamId)) { - return standardError(new Error('Team not found'), {userId: viewerId}) - } - - // VALIDATION - const schema = makeUpdateAgendaItemSchema() - const { - errors, - data: {id, ...doc} - } = schema(updatedAgendaItem) as any - if (Object.keys(errors).length) { - return standardError(new Error('Failed input validation'), {userId: viewerId}) - } - - // RESOLUTION - const oldAgendaItems = await dataLoader.get('agendaItemsByTeamId').load(teamId) - const fromIdx = oldAgendaItems.findIndex((agendaItem) => agendaItem.id === id) - await r - .table('AgendaItem') - .get(id) - .update({ - ...doc, - updatedAt: now - }) - .run() - dataLoader.clearAll('agendaItems') - if (doc.sortOrder !== null && doc.sortOrder !== undefined) { - const nextAgendaItems = await dataLoader.get('agendaItemsByTeamId').load(teamId) - const pgagendaItems = await dataLoader.get('_pgagendaItemsByTeamId').load(teamId) - const toIdx = nextAgendaItems.findIndex((agendaItem) => agendaItem.id === id) - const pgSortOrder = getSortOrder(pgagendaItems, fromIdx, toIdx) - await pg - .updateTable('AgendaItem') - .set({sortOrder: pgSortOrder}) - .where('id', '=', id) - .execute() - } else { - await pg - .updateTable('AgendaItem') - .set({pinned: doc.pinned, content: doc.content}) - .where('id', '=', id) - .execute() - } - - const activeMeetings = await dataLoader.get('activeMeetingsByTeamId').load(teamId) - const actionMeeting = activeMeetings.find( - (activeMeeting) => activeMeeting.meetingType === 'action' - ) - const meetingId = actionMeeting?.id ?? null - if (actionMeeting) { - const {id: meetingId, phases} = actionMeeting - const agendaItemPhase = getPhase(phases, 'agendaitems') - const {stages} = agendaItemPhase - const agendaItems = await dataLoader.get('agendaItemsByTeamId').load(teamId) - const getSortOrder = (stage: AgendaItemsStage) => { - const agendaItem = agendaItems.find((item) => item.id === stage.agendaItemId) - return (agendaItem && agendaItem.sortOrder) || 0 - } - stages.sort((a, b) => (getSortOrder(a) > getSortOrder(b) ? 1 : -1)) - await r - .table('NewMeeting') - .get(meetingId) - .update({ - phases - }) - .run() - } - const data = {agendaItemId, meetingId} - publish(SubscriptionChannel.TEAM, teamId, 'UpdateAgendaItemPayload', data, subOptions) - return data - } -} diff --git a/packages/server/graphql/private/mutations/hardDeleteUser.ts b/packages/server/graphql/private/mutations/hardDeleteUser.ts index 7bf8932ca67..cb7a05dc94b 100644 --- a/packages/server/graphql/private/mutations/hardDeleteUser.ts +++ b/packages/server/graphql/private/mutations/hardDeleteUser.ts @@ -67,7 +67,6 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( dataLoader.get('meetingMembersByUserId').load(userIdToDelete) ]) const teamIds = teamMembers.map(({teamId}) => teamId) - const teamMemberIds = teamMembers.map(({id}) => id) const meetingIds = meetingMembers.map(({meetingId}) => meetingId) const discussions = await pg.query(`SELECT "id" FROM "Discussion" WHERE "teamId" = ANY ($1);`, [ @@ -94,11 +93,6 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( .getAll(r.args(teamIds), {index: 'teamId'}) .filter((row: RValue) => row('createdBy').eq(userIdToDelete)) .delete(), - agendaItem: r - .table('AgendaItem') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((row: RValue) => r(teamMemberIds).contains(row('teamMemberId'))) - .delete(), pushInvitation: r.table('PushInvitation').getAll(userIdToDelete, {index: 'userId'}).delete(), slackNotification: r .table('SlackNotification') diff --git a/packages/server/graphql/public/mutations/addAgendaItem.ts b/packages/server/graphql/public/mutations/addAgendaItem.ts new file mode 100644 index 00000000000..823be535cc0 --- /dev/null +++ b/packages/server/graphql/public/mutations/addAgendaItem.ts @@ -0,0 +1,54 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import {positionAfter} from '../../../../client/shared/sortOrder' +import generateUID from '../../../generateUID' +import getKysely from '../../../postgres/getKysely' +import {analytics} from '../../../utils/analytics/analytics' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import addAgendaItemToActiveActionMeeting from '../../mutations/helpers/addAgendaItemToActiveActionMeeting' +import {MutationResolvers} from '../resolverTypes' + +const addAgendaItem: MutationResolvers['addAgendaItem'] = async ( + _source, + {newAgendaItem}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const operationId = dataLoader.share() + const subOptions = {mutatorId, operationId} + const viewerId = getUserId(authToken) + // AUTH + const {teamId} = newAgendaItem + if (!isTeamMember(authToken, teamId)) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + const viewer = await dataLoader.get('users').loadNonNull(viewerId) + // VALIDATION + if (newAgendaItem.content.length > 64) { + return {error: {message: 'Agenda item must be shorter'}} + } + + // RESOLUTION + const teamAgendaItems = await dataLoader.get('agendaItemsByTeamId').load(teamId) + const lastAgendaItem = teamAgendaItems.at(-1) + const agendaItemId = `${teamId}::${generateUID()}` + await getKysely() + .insertInto('AgendaItem') + .values({ + id: agendaItemId, + content: newAgendaItem.content, + meetingId: newAgendaItem.meetingId, + pinned: newAgendaItem.pinned, + sortOrder: positionAfter(lastAgendaItem?.sortOrder ?? ''), + teamId, + teamMemberId: newAgendaItem.teamMemberId + }) + .execute() + const meetingId = await addAgendaItemToActiveActionMeeting(agendaItemId, teamId, dataLoader) + analytics.addedAgendaItem(viewer, teamId, meetingId) + const data = {agendaItemId, meetingId} + publish(SubscriptionChannel.TEAM, teamId, 'AddAgendaItemPayload', data, subOptions) + return data +} + +export default addAgendaItem diff --git a/packages/server/graphql/public/mutations/removeAgendaItem.ts b/packages/server/graphql/public/mutations/removeAgendaItem.ts new file mode 100644 index 00000000000..58b964ae9f8 --- /dev/null +++ b/packages/server/graphql/public/mutations/removeAgendaItem.ts @@ -0,0 +1,43 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import AgendaItemsStage from '../../../database/types/AgendaItemsStage' +import getKysely from '../../../postgres/getKysely' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import removeStagesFromMeetings from '../../mutations/helpers/removeStagesFromMeetings' +import {MutationResolvers} from '../resolverTypes' + +const removeAgendaItem: MutationResolvers['removeAgendaItem'] = async ( + _source, + {agendaItemId}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const operationId = dataLoader.share() + const subOptions = {mutatorId, operationId} + const viewerId = getUserId(authToken) + + // AUTH + // id is of format 'teamId::randomId' + const [teamId] = agendaItemId.split('::') as [string] + if (!isTeamMember(authToken, teamId)) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + + // RESOLUTION + const agendaItem = await getKysely() + .updateTable('AgendaItem') + .set({isActive: false}) + .where('id', '=', agendaItemId) + .returning(['id', 'meetingId']) + .executeTakeFirst() + if (!agendaItem) { + return standardError(new Error('Agenda item not found'), {userId: viewerId}) + } + const filterFn = (stage: AgendaItemsStage) => stage.agendaItemId === agendaItemId + await removeStagesFromMeetings(filterFn, teamId, dataLoader) + const data = {agendaItemId, meetingId: agendaItem.meetingId} + publish(SubscriptionChannel.TEAM, teamId, 'RemoveAgendaItemPayload', data, subOptions) + return data +} + +export default removeAgendaItem diff --git a/packages/server/graphql/public/mutations/startCheckIn.ts b/packages/server/graphql/public/mutations/startCheckIn.ts index 6fe86b06f0a..b90ffd146a7 100644 --- a/packages/server/graphql/public/mutations/startCheckIn.ts +++ b/packages/server/graphql/public/mutations/startCheckIn.ts @@ -90,7 +90,6 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( .insert(new ActionMeetingMember({meetingId, userId: viewerId, teamId})) .run(), updateTeamByTeamId(updates, teamId), - r.table('AgendaItem').getAll(r.args(agendaItemIds)).update({meetingId}).run(), agendaItemIds.length && getKysely() .updateTable('AgendaItem') diff --git a/packages/server/graphql/public/mutations/updateAgendaItem.ts b/packages/server/graphql/public/mutations/updateAgendaItem.ts new file mode 100644 index 00000000000..51c506e8fdb --- /dev/null +++ b/packages/server/graphql/public/mutations/updateAgendaItem.ts @@ -0,0 +1,73 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import getRethink from '../../../database/rethinkDriver' +import AgendaItemsStage from '../../../database/types/AgendaItemsStage' +import getKysely from '../../../postgres/getKysely' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import getPhase from '../../../utils/getPhase' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const updateAgendaItem: MutationResolvers['updateAgendaItem'] = async ( + _source, + {updatedAgendaItem}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const r = await getRethink() + const pg = getKysely() + const operationId = dataLoader.share() + const subOptions = {mutatorId, operationId} + const viewerId = getUserId(authToken) + + // AUTH + const {id: agendaItemId, content, pinned, sortOrder} = updatedAgendaItem + const [teamId] = agendaItemId.split('::') as [string] + if (!isTeamMember(authToken, teamId)) { + return standardError(new Error('Team not found'), {userId: viewerId}) + } + + // VALIDATION + if (content && content.length > 64) { + return {error: {message: 'Agenda item must be shorter'}} + } + + // RESOLUTION + await pg + .updateTable('AgendaItem') + .set({ + pinned: pinned ?? undefined, + content: content ?? undefined, + sortOrder: sortOrder ?? undefined + }) + .where('id', '=', agendaItemId) + .execute() + + const activeMeetings = await dataLoader.get('activeMeetingsByTeamId').load(teamId) + const actionMeeting = activeMeetings.find( + (activeMeeting) => activeMeeting.meetingType === 'action' + ) + const meetingId = actionMeeting?.id ?? null + if (actionMeeting) { + const {id: meetingId, phases} = actionMeeting + const agendaItemPhase = getPhase(phases, 'agendaitems') + const {stages} = agendaItemPhase + const agendaItems = await dataLoader.get('agendaItemsByTeamId').load(teamId) + const getSortOrder = (stage: AgendaItemsStage) => { + const agendaItem = agendaItems.find((item) => item.id === stage.agendaItemId) + return (agendaItem && agendaItem.sortOrder) || 0 + } + stages.sort((a, b) => (getSortOrder(a) > getSortOrder(b) ? 1 : -1)) + await r + .table('NewMeeting') + .get(meetingId) + .update({ + phases + }) + .run() + } + const data = {agendaItemId, meetingId} + publish(SubscriptionChannel.TEAM, teamId, 'UpdateAgendaItemPayload', data, subOptions) + return data +} + +export default updateAgendaItem diff --git a/packages/server/graphql/public/typeDefs/AgendaItem.graphql b/packages/server/graphql/public/typeDefs/AgendaItem.graphql index a2871a78b03..c985ed2bf38 100644 --- a/packages/server/graphql/public/typeDefs/AgendaItem.graphql +++ b/packages/server/graphql/public/typeDefs/AgendaItem.graphql @@ -40,7 +40,7 @@ type AgendaItem { """ The sort order of the agenda item in the list """ - sortOrder: Float! + sortOrder: String! """ *The team for this agenda item diff --git a/packages/server/graphql/public/typeDefs/CreateAgendaItemInput.graphql b/packages/server/graphql/public/typeDefs/CreateAgendaItemInput.graphql index d04e556d7de..17981db5e0a 100644 --- a/packages/server/graphql/public/typeDefs/CreateAgendaItemInput.graphql +++ b/packages/server/graphql/public/typeDefs/CreateAgendaItemInput.graphql @@ -18,7 +18,7 @@ input CreateAgendaItemInput { """ The sort order of the agenda item in the list """ - sortOrder: Float + sortOrder: String """ The meeting ID of the agenda item diff --git a/packages/server/graphql/public/typeDefs/UpdateAgendaItemInput.graphql b/packages/server/graphql/public/typeDefs/UpdateAgendaItemInput.graphql index d0110ae03ca..ce4bea7817b 100644 --- a/packages/server/graphql/public/typeDefs/UpdateAgendaItemInput.graphql +++ b/packages/server/graphql/public/typeDefs/UpdateAgendaItemInput.graphql @@ -22,5 +22,5 @@ input UpdateAgendaItemInput { """ The sort order of the agenda item in the list """ - sortOrder: Float + sortOrder: String } diff --git a/packages/server/graphql/public/types/ActionMeeting.ts b/packages/server/graphql/public/types/ActionMeeting.ts index daed42c627f..f6a7e04939f 100644 --- a/packages/server/graphql/public/types/ActionMeeting.ts +++ b/packages/server/graphql/public/types/ActionMeeting.ts @@ -5,7 +5,7 @@ import {ActionMeetingResolvers} from '../resolverTypes' const ActionMeeting: ActionMeetingResolvers = { agendaItem: async ({id: meetingId}, {agendaItemId}, {dataLoader}) => { - const agendaItem = await dataLoader.get('agendaItems').load(agendaItemId) + const agendaItem = await dataLoader.get('agendaItems').loadNonNull(agendaItemId) if (agendaItem.meetingId !== meetingId) return null return agendaItem }, diff --git a/packages/server/graphql/public/types/AddAgendaItemPayload.ts b/packages/server/graphql/public/types/AddAgendaItemPayload.ts new file mode 100644 index 00000000000..f82f7966890 --- /dev/null +++ b/packages/server/graphql/public/types/AddAgendaItemPayload.ts @@ -0,0 +1,24 @@ +import {AddAgendaItemPayloadResolvers} from '../resolverTypes' + +export type AddAgendaItemPayloadSource = + | { + agendaItemId: string + meetingId?: string + } + | {error: {message: string}} + +const AddAgendaItemPayload: AddAgendaItemPayloadResolvers = { + agendaItem: (source, _args, {dataLoader}) => { + return 'agendaItemId' in source + ? dataLoader.get('agendaItems').loadNonNull(source.agendaItemId) + : null + }, + + meeting: (source, _args, {dataLoader}) => { + return 'meetingId' in source && source.meetingId + ? dataLoader.get('newMeetings').load(source.meetingId) + : null + } +} + +export default AddAgendaItemPayload diff --git a/packages/server/graphql/public/types/AgendaItemsStage.ts b/packages/server/graphql/public/types/AgendaItemsStage.ts index d260932a88f..1dcfed67720 100644 --- a/packages/server/graphql/public/types/AgendaItemsStage.ts +++ b/packages/server/graphql/public/types/AgendaItemsStage.ts @@ -3,7 +3,7 @@ import {AgendaItemsStageResolvers} from '../resolverTypes' const AgendaItemsStage: AgendaItemsStageResolvers = { __isTypeOf: ({phaseType}) => phaseType === 'agendaitems', agendaItem: ({agendaItemId}, _args, {dataLoader}) => { - return dataLoader.get('agendaItems').load(agendaItemId) + return dataLoader.get('agendaItems').loadNonNull(agendaItemId) } } diff --git a/packages/server/graphql/public/types/RemoveAgendaItemPayload.ts b/packages/server/graphql/public/types/RemoveAgendaItemPayload.ts new file mode 100644 index 00000000000..3527f938e25 --- /dev/null +++ b/packages/server/graphql/public/types/RemoveAgendaItemPayload.ts @@ -0,0 +1,24 @@ +import {RemoveAgendaItemPayloadResolvers} from '../resolverTypes' + +export type RemoveAgendaItemPayloadSource = + | { + agendaItemId: string + meetingId?: string | null + } + | {error: {message: string}} + +const RemoveAgendaItemPayload: RemoveAgendaItemPayloadResolvers = { + agendaItem: (source, _args, {dataLoader}) => { + return 'agendaItemId' in source + ? dataLoader.get('agendaItems').loadNonNull(source.agendaItemId) + : null + }, + + meeting: (source, _args, {dataLoader}) => { + return 'meetingId' in source && source.meetingId + ? dataLoader.get('newMeetings').load(source.meetingId) + : null + } +} + +export default RemoveAgendaItemPayload diff --git a/packages/server/graphql/public/types/UpdateAgendaItemPayload.ts b/packages/server/graphql/public/types/UpdateAgendaItemPayload.ts new file mode 100644 index 00000000000..f267c8be980 --- /dev/null +++ b/packages/server/graphql/public/types/UpdateAgendaItemPayload.ts @@ -0,0 +1,24 @@ +import {UpdateAgendaItemPayloadResolvers} from '../resolverTypes' + +export type UpdateAgendaItemPayloadSource = + | { + agendaItemId: string + meetingId?: string | null + } + | {error: {message: string}} + +const UpdateAgendaItemPayload: UpdateAgendaItemPayloadResolvers = { + agendaItem: (source, _args, {dataLoader}) => { + return 'agendaItemId' in source + ? dataLoader.get('agendaItems').loadNonNull(source.agendaItemId) + : null + }, + + meeting: (source, _args, {dataLoader}) => { + return 'meetingId' in source && source.meetingId + ? dataLoader.get('newMeetings').load(source.meetingId) + : null + } +} + +export default UpdateAgendaItemPayload diff --git a/packages/server/graphql/rootMutation.ts b/packages/server/graphql/rootMutation.ts index 91654c128b6..9493f6e092c 100644 --- a/packages/server/graphql/rootMutation.ts +++ b/packages/server/graphql/rootMutation.ts @@ -1,6 +1,5 @@ import {GraphQLObjectType} from 'graphql' import {GQLContext} from './graphql' -import addAgendaItem from './mutations/addAgendaItem' import addAtlassianAuth from './mutations/addAtlassianAuth' import addComment from './mutations/addComment' import addGitHubAuth from './mutations/addGitHubAuth' @@ -63,7 +62,6 @@ import promoteToTeamLead from './mutations/promoteToTeamLead' import pushInvitation from './mutations/pushInvitation' import reflectTemplatePromptUpdateDescription from './mutations/reflectTemplatePromptUpdateDescription' import reflectTemplatePromptUpdateGroupColor from './mutations/reflectTemplatePromptUpdateGroupColor' -import removeAgendaItem from './mutations/removeAgendaItem' import removeAtlassianAuth from './mutations/removeAtlassianAuth' import removeGitHubAuth from './mutations/removeGitHubAuth' import removeIntegrationProvider from './mutations/removeIntegrationProvider' @@ -96,7 +94,6 @@ import setTaskHighlight from './mutations/setTaskHighlight' import startDraggingReflection from './mutations/startDraggingReflection' import startSprintPoker from './mutations/startSprintPoker' import toggleTeamDrawer from './mutations/toggleTeamDrawer' -import updateAgendaItem from './mutations/updateAgendaItem' import updateAzureDevOpsDimensionField from './mutations/updateAzureDevOpsDimensionField' import updateCommentContent from './mutations/updateCommentContent' import updateDragLocation from './mutations/updateDragLocation' @@ -119,7 +116,6 @@ export default new GraphQLObjectType({ name: 'Mutation', fields: () => ({ - addAgendaItem, addAtlassianAuth, addComment, addPokerTemplateDimension, @@ -173,7 +169,6 @@ export default new GraphQLObjectType({ reflectTemplatePromptUpdateDescription, pokerTemplateDimensionUpdateDescription, reflectTemplatePromptUpdateGroupColor, - removeAgendaItem, removeAtlassianAuth, removeGitHubAuth, removeOrgUser, @@ -201,7 +196,6 @@ export default new GraphQLObjectType({ startDraggingReflection, startSprintPoker, setTaskHighlight, - updateAgendaItem, updateCommentContent, oldUpdateCreditCard, updatePokerTemplateDimensionScale, diff --git a/packages/server/graphql/types/AddAgendaItemPayload.ts b/packages/server/graphql/types/AddAgendaItemPayload.ts deleted file mode 100644 index b2960e97088..00000000000 --- a/packages/server/graphql/types/AddAgendaItemPayload.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {GraphQLID, GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import AgendaItem from './AgendaItem' -import NewMeeting from './NewMeeting' -import StandardMutationError from './StandardMutationError' - -const AddAgendaItemPayload = new GraphQLObjectType({ - name: 'AddAgendaItemPayload', - fields: () => ({ - error: { - type: StandardMutationError - }, - agendaItem: { - type: AgendaItem, - resolve: ({agendaItemId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('agendaItems').load(agendaItemId) - } - }, - meetingId: { - type: GraphQLID - }, - meeting: { - type: NewMeeting, - description: 'The meeting with the updated agenda item, if any', - resolve: ({meetingId}, _args: unknown, {dataLoader}) => { - return meetingId ? dataLoader.get('newMeetings').load(meetingId) : null - } - } - }) -}) - -export default AddAgendaItemPayload diff --git a/packages/server/graphql/types/CreateAgendaItemInput.ts b/packages/server/graphql/types/CreateAgendaItemInput.ts deleted file mode 100644 index 35a9e121f2a..00000000000 --- a/packages/server/graphql/types/CreateAgendaItemInput.ts +++ /dev/null @@ -1,48 +0,0 @@ -import { - GraphQLBoolean, - GraphQLFloat, - GraphQLID, - GraphQLInputObjectType, - GraphQLNonNull, - GraphQLString -} from 'graphql' - -export type CreateAgendaItemInputType = { - content: string - pinned: boolean - teamId: string - teamMemberId: string - sortOrder?: number - meetingId?: string -} - -const CreateAgendaItemInput = new GraphQLInputObjectType({ - name: 'CreateAgendaItemInput', - fields: () => ({ - content: { - type: new GraphQLNonNull(GraphQLString), - description: 'The content of the agenda item' - }, - pinned: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'True if the agenda item has been pinned' - }, - teamId: { - type: new GraphQLNonNull(GraphQLID) - }, - teamMemberId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The team member ID of the person creating the agenda item' - }, - sortOrder: { - type: GraphQLFloat, - description: 'The sort order of the agenda item in the list' - }, - meetingId: { - type: GraphQLString, - description: 'The meeting ID of the agenda item' - } - }) -}) - -export default CreateAgendaItemInput diff --git a/packages/server/graphql/types/RemoveAgendaItemPayload.ts b/packages/server/graphql/types/RemoveAgendaItemPayload.ts deleted file mode 100644 index 3e41c11a090..00000000000 --- a/packages/server/graphql/types/RemoveAgendaItemPayload.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {GraphQLID, GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import AgendaItem from './AgendaItem' -import NewMeeting from './NewMeeting' -import StandardMutationError from './StandardMutationError' - -const RemoveAgendaItemPayload = new GraphQLObjectType({ - name: 'RemoveAgendaItemPayload', - fields: () => ({ - error: { - type: StandardMutationError - }, - agendaItem: { - type: AgendaItem - }, - meetingId: { - type: GraphQLID - }, - meeting: { - type: NewMeeting, - description: 'The meeting with the updated agenda item, if any', - resolve: ({meetingId}, _args: unknown, {dataLoader}) => { - return meetingId ? dataLoader.get('newMeetings').load(meetingId) : null - } - } - }) -}) - -export default RemoveAgendaItemPayload diff --git a/packages/server/graphql/types/UpdateAgendaItemInput.ts b/packages/server/graphql/types/UpdateAgendaItemInput.ts deleted file mode 100644 index 06f581c1407..00000000000 --- a/packages/server/graphql/types/UpdateAgendaItemInput.ts +++ /dev/null @@ -1,44 +0,0 @@ -import { - GraphQLBoolean, - GraphQLFloat, - GraphQLID, - GraphQLInputObjectType, - GraphQLNonNull, - GraphQLString -} from 'graphql' - -const UpdateAgendaItemInput = new GraphQLInputObjectType({ - name: 'UpdateAgendaItemInput', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'The unique agenda item ID, composed of a teamId::shortid' - }, - content: { - type: GraphQLString, - description: 'The content of the agenda item' - }, - pinned: { - type: GraphQLBoolean, - description: 'True if agenda item has been pinned' - }, - isActive: { - type: GraphQLBoolean, - description: 'True if not processed or deleted' - }, - sortOrder: { - type: GraphQLFloat, - description: 'The sort order of the agenda item in the list' - } - }) -}) - -export type UpdateAgendaItemInputType = { - id: string - content?: string | null - pinned?: boolean - isActive?: boolean | null - sortOrder?: number | null -} - -export default UpdateAgendaItemInput diff --git a/packages/server/graphql/types/UpdateAgendaItemPayload.ts b/packages/server/graphql/types/UpdateAgendaItemPayload.ts deleted file mode 100644 index d10a6e6155c..00000000000 --- a/packages/server/graphql/types/UpdateAgendaItemPayload.ts +++ /dev/null @@ -1,32 +0,0 @@ -import {GraphQLID, GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import AgendaItem from './AgendaItem' -import NewMeeting from './NewMeeting' -import StandardMutationError from './StandardMutationError' - -const UpdateAgendaItemPayload = new GraphQLObjectType({ - name: 'UpdateAgendaItemPayload', - fields: () => ({ - agendaItem: { - type: AgendaItem, - resolve: ({agendaItemId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('agendaItems').load(agendaItemId) - } - }, - meetingId: { - type: GraphQLID - }, - meeting: { - type: NewMeeting, - description: 'The meeting with the updated agenda item, if any', - resolve: ({meetingId}, _args: unknown, {dataLoader}) => { - return meetingId ? dataLoader.get('newMeetings').load(meetingId) : null - } - }, - error: { - type: StandardMutationError - } - }) -}) - -export default UpdateAgendaItemPayload diff --git a/packages/server/postgres/migrations/1723672980596_AgendaItem-phase2.ts b/packages/server/postgres/migrations/1723672980596_AgendaItem-phase2.ts new file mode 100644 index 00000000000..42a199bba9f --- /dev/null +++ b/packages/server/postgres/migrations/1723672980596_AgendaItem-phase2.ts @@ -0,0 +1,184 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +const START_CHAR_CODE = 32 +const END_CHAR_CODE = 126 + +export function positionAfter(pos: string) { + for (let i = pos.length - 1; i >= 0; i--) { + const curCharCode = pos.charCodeAt(i) + if (curCharCode < END_CHAR_CODE) { + return pos.substr(0, i) + String.fromCharCode(curCharCode + 1) + } + } + return pos + String.fromCharCode(START_CHAR_CODE + 1) +} + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + // add a dummy date for nulls + const parabolEpoch = new Date('2016-06-01') + await r + .table('AgendaItem') + .update((row) => ({ + updatedAt: row('updatedAt').default(parabolEpoch), + createdAt: row('createdAt').default(parabolEpoch) + })) + .run() + const strDates = await r + .table('AgendaItem') + .filter((row) => row('updatedAt').typeOf().eq('STRING')) + .pluck('updatedAt', 'id', 'createdAt') + .run() + const dateDates = strDates.map((d) => ({ + id: d.id, + updatedAt: new Date(d.updatedAt), + createdAt: new Date(d.createdAt) + })) + // some dates are + await r(dateDates) + .forEach((row: any) => { + return r + .table('AgendaItem') + .get(row('id')) + .update({updatedAt: row('updatedAt')}) + }) + .run() + + try { + console.log('Adding index') + await r + .table('AgendaItem') + .indexCreate('updatedAtId', (row: any) => [row('updatedAt'), row('id')]) + .run() + await r.table('AgendaItem').indexWait().run() + } catch { + // index already exists + } + + console.log('Adding index complete') + const MAX_PG_PARAMS = 65545 + const PG_COLS = [ + 'id', + 'content', + 'createdAt', + 'isActive', + 'isComplete', + 'sortOrder', + 'teamId', + 'teamMemberId', + 'updatedAt', + 'meetingId', + 'pinned', + 'pinnedParentId' + ] as const + type AgendaItem = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + + let curUpdatedAt = r.minval + let curId = r.minval + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, String(curUpdatedAt), String(curId)) + const rawRowsToInsert = (await r + .table('AgendaItem') + .between([curUpdatedAt, curId], [r.maxval, r.maxval], { + index: 'updatedAtId', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'updatedAtId'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as AgendaItem[] + + const rowsToInsert = rawRowsToInsert.map((row) => { + const {sortOrder, ...rest} = row as any + return { + ...rest, + sortOrder: String(sortOrder) + } + }) + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curUpdatedAt = lastRow.updatedAt + curId = lastRow.id + try { + await pg + .insertInto('AgendaItem') + .values(rowsToInsert) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + await Promise.all( + rowsToInsert.map(async (row) => { + try { + await pg + .insertInto('AgendaItem') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_teamMemberId' || e.constraint === 'fk_teamId') { + console.log(`Skipping ${row.id} because it has no user/team`) + return + } + console.log(e, row) + } + }) + ) + } + } + + // remap the sortOrder in PG because rethinkdb is too slow to group + const pgRows = await sql<{items: {sortOrder: string; id: string}[]}>` + select jsonb_agg(jsonb_build_object('sortOrder', "sortOrder", 'id', "id", 'meetingId', "meetingId", 'teamId', "teamId") ORDER BY "sortOrder") items from "AgendaItem" +group by "teamId", "meetingId";`.execute(pg) + + const groups = pgRows.rows.map((row) => { + const {items} = row + let curSortOrder = '' + for (let i = 0; i < items.length; i++) { + const item = items[i] + curSortOrder = positionAfter(curSortOrder) + item.sortOrder = curSortOrder + } + return row + }) + for (let i = 0; i < groups.length; i++) { + const group = groups[i] + await Promise.all( + group.items.map((item) => { + return pg + .updateTable('AgendaItem') + .set({sortOrder: item.sortOrder}) + .where('id', '=', item.id) + .execute() + }) + ) + } +} + +export async function down() { + // await connectRethinkDB() + // try { + // await r.table('AgendaItem').indexDrop('updatedAtId').run() + // } catch { + // // index already dropped + // } + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql`TRUNCATE TABLE "AgendaItem" CASCADE`.execute(pg) +} diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index f6706233236..ca262a72b5e 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -6,6 +6,7 @@ import { TeamMember as TeamMemberPG } from '../pg.d' import { + selectAgendaItems, selectMeetingSettings, selectOrganizations, selectRetroReflections, @@ -44,3 +45,5 @@ export type TemplateScaleRef = ExtractTypeFromQueryBuilderSelect export type PokerMeetingSettings = MeetingSettings & {meetingType: 'poker'} export type RetrospectiveMeetingSettings = MeetingSettings & {meetingType: 'retrospective'} + +export type AgendaItem = ExtractTypeFromQueryBuilderSelect From 4a7df485ae184028bcdc401bf879d6229568c723 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 15 Aug 2024 18:08:52 -0700 Subject: [PATCH 417/529] chore(release): release v7.43.7 (#10139) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e03de66a184..b20356a9f69 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.43.6" + ".": "7.43.7" } diff --git a/CHANGELOG.md b/CHANGELOG.md index b5954516426..4eeec985a29 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.43.7](https://github.com/ParabolInc/parabol/compare/v7.43.6...v7.43.7) (2024-08-16) + + +### Changed + +* **rethinkdb:** AgendaItem: Phase 3 ([#10109](https://github.com/ParabolInc/parabol/issues/10109)) ([bd802d5](https://github.com/ParabolInc/parabol/commit/bd802d52866498310fe45cc951d059b2f30b9da6)) + ## [7.43.6](https://github.com/ParabolInc/parabol/compare/v7.43.5...v7.43.6) (2024-08-16) diff --git a/package.json b/package.json index 1369526ce17..b620e741068 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.43.6", + "version": "7.43.7", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index c07bdc66033..63e7be21f18 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.43.6", + "version": "7.43.7", "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.43.6" + "parabol-server": "7.43.7" } } diff --git a/packages/client/package.json b/packages/client/package.json index 5bdc1b07386..a27347000bf 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.43.6", + "version": "7.43.7", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 715399f2166..6bb6a0550ea 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.43.6", + "version": "7.43.7", "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 3b69f5cc112..e52148528e3 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.43.6", + "version": "7.43.7", "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.43.6", - "parabol-server": "7.43.6", + "parabol-client": "7.43.7", + "parabol-server": "7.43.7", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 00f5ddf0c1b..fca6f06cefe 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.43.6", + "version": "7.43.7", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 5ebe6a0d03a..3abe45348f6 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.43.6", + "version": "7.43.7", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.43.6", + "parabol-client": "7.43.7", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From a1599e90e90df1e4e32963fe304a0f85a0af020d Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 21 Aug 2024 10:46:55 -0700 Subject: [PATCH 418/529] fix: no team leads (#10145) Signed-off-by: Matt Krick --- .../client/hooks/useMeetingMemberAvatars.ts | 2 +- .../mutations/helpers/removeTeamMember.ts | 29 ++++++++---- .../graphql/mutations/promoteToTeamLead.ts | 5 +- .../mutations/acceptRequestToJoinDomain.ts | 2 +- .../migrations/1724174924811_oneTeamLead.ts | 47 +++++++++++++++++++ .../safeMutations/acceptTeamInvitation.ts | 2 +- 6 files changed, 75 insertions(+), 12 deletions(-) create mode 100644 packages/server/postgres/migrations/1724174924811_oneTeamLead.ts diff --git a/packages/client/hooks/useMeetingMemberAvatars.ts b/packages/client/hooks/useMeetingMemberAvatars.ts index 57be6fe00a2..1a4d81136fa 100644 --- a/packages/client/hooks/useMeetingMemberAvatars.ts +++ b/packages/client/hooks/useMeetingMemberAvatars.ts @@ -30,7 +30,7 @@ const useMeetingMemberAvatars = (meetingRef: useMeetingMemberAvatars_meeting$key return meetingMembers .map(({user}) => user) .filter((user) => { - return user.lastSeenAtURLs?.includes(`/meet/${meetingId}`) && user.isConnected + return user?.lastSeenAtURLs?.includes(`/meet/${meetingId}`) && user?.isConnected }) .sort((a, b) => (a.id === viewerId ? -1 : a.lastSeenAt < b.lastSeenAt ? -1 : 1)) }, [meetingMembers]) diff --git a/packages/server/graphql/mutations/helpers/removeTeamMember.ts b/packages/server/graphql/mutations/helpers/removeTeamMember.ts index e1e7c1d4fee..8088ee60491 100644 --- a/packages/server/graphql/mutations/helpers/removeTeamMember.ts +++ b/packages/server/graphql/mutations/helpers/removeTeamMember.ts @@ -32,14 +32,27 @@ const removeTeamMember = async ( // see if they were a leader, make a new guy leader so later we can reassign tasks const activeTeamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) const teamMember = activeTeamMembers.find((t) => t.id === teamMemberId) - const {isLead, isNotRemoved} = teamMember ?? {} - // if the guy being removed is the leader & not the last, pick a new one. else, use him - const teamLeader = activeTeamMembers.find((t) => t.isLead === !isLead) || teamMember - if (!isNotRemoved || !teamMember || !teamLeader) { - throw new Error('Team member already removed') + if (!teamMember) { + return { + user: undefined, + notificationId: undefined, + archivedTaskIds: [] as string[], + reassignedTaskIds: [] as string[] + } } + const currentTeamLeader = activeTeamMembers.find((t) => t.isLead)! + if (!currentTeamLeader) { + throw new Error('Team lead does not exist') + } + + const {isLead} = teamMember + const willArchive = activeTeamMembers.length === 1 + const nextTeamLead = + isLead && !willArchive + ? activeTeamMembers.find((teamMember) => teamMember.id !== teamMemberId)! + : currentTeamLeader - if (activeTeamMembers.length === 1) { + if (willArchive) { await Promise.all([ // archive single-person teams pg.updateTable('Team').set({isArchived: true}).where('id', '=', teamId).execute(), @@ -51,7 +64,7 @@ const removeTeamMember = async ( await pg .updateTable('TeamMember') .set(({not}) => ({isLead: not('isLead')})) - .where('id', 'in', [teamMemberId, teamLeader.id]) + .where('id', 'in', [teamMemberId, nextTeamLead.id]) .execute() } @@ -82,7 +95,7 @@ const removeTeamMember = async ( ) .update( { - userId: teamLeader.userId + userId: nextTeamLead.userId }, {returnChanges: true} )('changes')('new_val') diff --git a/packages/server/graphql/mutations/promoteToTeamLead.ts b/packages/server/graphql/mutations/promoteToTeamLead.ts index 83cd3f9b4ca..2da1f08fddc 100644 --- a/packages/server/graphql/mutations/promoteToTeamLead.ts +++ b/packages/server/graphql/mutations/promoteToTeamLead.ts @@ -36,7 +36,10 @@ export default { dataLoader.get('teamMembersByTeamId').load(teamId), dataLoader.get('teams').loadNonNull(teamId) ]) - const oldLead = teamMembers.find(({isLead}) => isLead)! + const oldLead = teamMembers.find(({isLead}) => isLead) + if (!oldLead) { + return standardError(new Error('Team has no team lead'), {userId: viewerId}) + } const {id: oldLeadTeamMemberId} = oldLead if (!isSuperUser(authToken)) { const isOrgAdmin = await isUserOrgAdmin(viewerId, team.orgId, dataLoader) diff --git a/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts b/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts index e67a1728c9f..f1d20f32d18 100644 --- a/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts +++ b/packages/server/graphql/public/mutations/acceptRequestToJoinDomain.ts @@ -106,7 +106,7 @@ const acceptRequestToJoinDomain: MutationResolvers['acceptRequestToJoinDomain'] email, openDrawer: 'manageTeam' }) - .onConflict((oc) => oc.column('id').doUpdateSet({isNotRemoved: true})) + .onConflict((oc) => oc.column('id').doUpdateSet({isNotRemoved: true, isLead: false})) .execute() ]) diff --git a/packages/server/postgres/migrations/1724174924811_oneTeamLead.ts b/packages/server/postgres/migrations/1724174924811_oneTeamLead.ts new file mode 100644 index 00000000000..c0d0da60ebf --- /dev/null +++ b/packages/server/postgres/migrations/1724174924811_oneTeamLead.ts @@ -0,0 +1,47 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + const teamsWithout1Leader = await pg + .selectFrom('TeamMember') + .select('teamId') + .select(({fn}) => fn.count('id').as('count')) + .where('isNotRemoved', '=', true) + .groupBy('teamId') + .having(sql`SUM(CASE WHEN "isLead" = true THEN 1 ELSE 0 END) != 1`) + .execute() + + await Promise.all( + teamsWithout1Leader.map(async (row) => { + const {teamId} = row + // remove all leads for the cases where we had more than 1 + await pg.updateTable('TeamMember').set({isLead: false}).where('teamId', '=', teamId).execute() + await pg + .with('NextLead', (qb) => + qb + .selectFrom('TeamMember') + .select('id') + .where('teamId', '=', teamId) + .where('isNotRemoved', '=', true) + .orderBy('createdAt', 'asc') + .limit(1) + ) + .updateTable('TeamMember') + .set({isLead: true}) + .where(({eb, selectFrom}) => eb('id', '=', selectFrom('NextLead').select('id'))) + .returning('id') + .execute() + }) + ) +} + +export async function down() { + // noop +} diff --git a/packages/server/safeMutations/acceptTeamInvitation.ts b/packages/server/safeMutations/acceptTeamInvitation.ts index c512b6bef49..ec91003e8c2 100644 --- a/packages/server/safeMutations/acceptTeamInvitation.ts +++ b/packages/server/safeMutations/acceptTeamInvitation.ts @@ -103,7 +103,7 @@ const acceptTeamInvitation = async (team: Team, userId: string, dataLoader: Data email, openDrawer: 'manageTeam' }) - .onConflict((oc) => oc.column('id').doUpdateSet({isNotRemoved: true})) + .onConflict((oc) => oc.column('id').doUpdateSet({isNotRemoved: true, isLead: false})) .execute(), r .table('TeamInvitation') From 6492679641bede9265b7df57295ad79caf3eadc9 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 21 Aug 2024 11:48:23 -0700 Subject: [PATCH 419/529] fix: use period end instead of due at (#10151) Signed-off-by: Matt Krick --- .../userDashboard/components/InvoiceRow/InvoiceRow.tsx | 8 ++++---- packages/server/graphql/public/fields/invoices.ts | 8 ++++---- packages/server/graphql/public/typeDefs/Invoice.graphql | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/packages/client/modules/userDashboard/components/InvoiceRow/InvoiceRow.tsx b/packages/client/modules/userDashboard/components/InvoiceRow/InvoiceRow.tsx index 292094e8b97..f9a28f7b28e 100644 --- a/packages/client/modules/userDashboard/components/InvoiceRow/InvoiceRow.tsx +++ b/packages/client/modules/userDashboard/components/InvoiceRow/InvoiceRow.tsx @@ -64,7 +64,7 @@ const InvoiceRow = (props: Props) => { graphql` fragment InvoiceRow_invoice on Invoice { id - dueAt + periodEndAt total payUrl status @@ -72,7 +72,7 @@ const InvoiceRow = (props: Props) => { `, invoiceRef ) - const {dueAt, total, payUrl, status} = invoice + const {periodEndAt, total, payUrl, status} = invoice const isEstimate = status === 'UPCOMING' return ( @@ -88,8 +88,8 @@ const InvoiceRow = (props: Props) => { {status === 'UPCOMING' - ? `Due on ${makeDateString(dueAt)}` - : `${makeDateString(dueAt)}`} + ? `Due on ${makeDateString(periodEndAt)}` + : `${makeDateString(periodEndAt)}`} diff --git a/packages/server/graphql/public/fields/invoices.ts b/packages/server/graphql/public/fields/invoices.ts index 9bb4050f4f3..990d9355245 100644 --- a/packages/server/graphql/public/fields/invoices.ts +++ b/packages/server/graphql/public/fields/invoices.ts @@ -40,26 +40,26 @@ export const invoices: NonNullable = async ( ]) const parabolUpcomingInvoice: Invoice = { id: `upcoming_${orgId}`, - dueAt: fromEpochSeconds(upcomingInvoice.due_date!), + periodEndAt: fromEpochSeconds(upcomingInvoice.period_end!), total: upcomingInvoice.total, payUrl: session.url, status: 'UPCOMING' } const parabolPastInvoices: Invoice[] = invoices.data.map((stripeInvoice) => { - const {id, due_date, total, status: stripeStatus} = stripeInvoice + const {id, period_end, total, status: stripeStatus} = stripeInvoice const status: InvoiceStatusEnum = stripeStatus === 'uncollectible' ? 'FAILED' : stripeStatus === 'paid' ? 'PAID' : 'PENDING' return { id, - dueAt: fromEpochSeconds(due_date!), + periodEndAt: fromEpochSeconds(period_end!), total, payUrl: session.url, status } }) const edges = [parabolUpcomingInvoice, ...parabolPastInvoices].map((node) => ({ - cursor: node.dueAt, + cursor: node.periodEndAt, node })) const firstEdge = edges[0] diff --git a/packages/server/graphql/public/typeDefs/Invoice.graphql b/packages/server/graphql/public/typeDefs/Invoice.graphql index 65b47d98b34..cf8663494f5 100644 --- a/packages/server/graphql/public/typeDefs/Invoice.graphql +++ b/packages/server/graphql/public/typeDefs/Invoice.graphql @@ -8,9 +8,9 @@ type Invoice { id: ID! """ - The datetime the invoice is due + The datetime the invoice period has ended """ - dueAt: DateTime! + periodEndAt: DateTime! """ The total amount for the invoice (in USD) From 5a95f622f91ee6b992f37d5feb5bda850f82cf0b Mon Sep 17 00:00:00 2001 From: Terry Acker Date: Wed, 21 Aug 2024 14:44:50 -0500 Subject: [PATCH 420/529] chore: update contributing doc (#10148) --- CONTRIBUTING.md | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1fc5d0e8c94..e6d7fec42a6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -111,17 +111,6 @@ to say, 200 points, 300 points, and so on. We currently will reevaluate this conversion rate once per calendar quarter at our board meeting. -# Community - -Want to collaborate? Have questions about our stack? -Join us on our Slack community! - -[![Slack Status](http://slackin.parabol.co/badge.svg)](http://slackin.parabol.co/) - -Sign up at http://slackin.parabol.co/. - ## Questions -If you’ve got questions you'd like to inquire about privately, please don’t -hesitate to reach out. You can reach us at -[love@parabol.co](mailto:love@parabol.co). +Want to collaborate? Have questions about our stack? Please don’t hesitate to reach out. You can reach us at [love@parabol.co](mailto:love@parabol.co). From c70b87a4561b1742ae726dd08f47811b3f1bd4ff Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Thu, 22 Aug 2024 07:54:33 -0700 Subject: [PATCH 421/529] fix(orgAdmins): archived teams should be removed from the OrgTeams view (#10142) --- packages/client/mutations/ArchiveTeamMutation.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/mutations/ArchiveTeamMutation.ts b/packages/client/mutations/ArchiveTeamMutation.ts index c06eae11d37..f30b867733f 100644 --- a/packages/client/mutations/ArchiveTeamMutation.ts +++ b/packages/client/mutations/ArchiveTeamMutation.ts @@ -106,6 +106,7 @@ export const archiveTeamTeamUpdater: SharedUpdater { safeRemoveNodeFromArray(teamId, org, 'teams') + safeRemoveNodeFromArray(teamId, org, 'allTeams') }) const notification = payload.getLinkedRecord('notification') From cd570acd3a9aebb9b09e6139263a5ef85d406dc0 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Thu, 22 Aug 2024 07:54:52 -0700 Subject: [PATCH 422/529] fix(orgAdmins): Billing leaders should not see all teams in the org (#10141) --- .../components/OrgTeamMembers/OrgTeamMembers.tsx | 8 ++------ .../userDashboard/components/OrgTeams/OrgTeams.tsx | 5 ++--- packages/server/graphql/public/types/Organization.ts | 7 +++---- 3 files changed, 7 insertions(+), 13 deletions(-) diff --git a/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembers.tsx b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembers.tsx index 77b85049f3b..395c7833334 100644 --- a/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembers.tsx +++ b/packages/client/modules/userDashboard/components/OrgTeamMembers/OrgTeamMembers.tsx @@ -27,9 +27,6 @@ const query = graphql` isViewerLead name orgId - organization { - isBillingLeader - } teamMembers(sortBy: "preferredName") { id isNotRemoved @@ -56,9 +53,8 @@ export const OrgTeamMembers = (props: Props) => { } = useDialogState() if (!team) return null - const {isViewerLead, isOrgAdmin: isViewerOrgAdmin, teamMembers, organization} = team - const {isBillingLeader} = organization - const showMenuButton = isViewerLead || isBillingLeader || isViewerOrgAdmin + const {isViewerLead, isOrgAdmin: isViewerOrgAdmin, teamMembers} = team + const showMenuButton = isViewerLead || isViewerOrgAdmin return (
diff --git a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx index 8284f82906c..1afe0340e6c 100644 --- a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx +++ b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx @@ -19,7 +19,6 @@ const OrgTeams = (props: Props) => { fragment OrgTeams_organization on Organization { id isOrgAdmin - isBillingLeader featureFlags { publicTeams } @@ -38,9 +37,9 @@ const OrgTeams = (props: Props) => { isOpen: isAddTeamDialogOpened } = useDialogState() - const {allTeams, isBillingLeader, isOrgAdmin, featureFlags} = organization + const {allTeams, isOrgAdmin, featureFlags} = organization const hasPublicTeamsFlag = featureFlags.publicTeams - const showAllTeams = isBillingLeader || isOrgAdmin || hasPublicTeamsFlag + const showAllTeams = isOrgAdmin || hasPublicTeamsFlag return (
diff --git a/packages/server/graphql/public/types/Organization.ts b/packages/server/graphql/public/types/Organization.ts index 1991fa4790b..216634df8b8 100644 --- a/packages/server/graphql/public/types/Organization.ts +++ b/packages/server/graphql/public/types/Organization.ts @@ -56,15 +56,14 @@ const Organization: OrganizationResolvers = { allTeams: async ({id: orgId}, _args, {dataLoader, authToken}) => { const viewerId = getUserId(authToken) - const [allTeamsOnOrg, organization, isOrgAdmin, isBillingLeader] = await Promise.all([ + const [allTeamsOnOrg, organization, isOrgAdmin] = await Promise.all([ dataLoader.get('teamsByOrgIds').load(orgId), dataLoader.get('organizations').loadNonNull(orgId), - isUserOrgAdmin(viewerId, orgId, dataLoader), - isUserBillingLeader(viewerId, orgId, dataLoader) + isUserOrgAdmin(viewerId, orgId, dataLoader) ]) const sortedTeamsOnOrg = allTeamsOnOrg.sort((a, b) => a.name.localeCompare(b.name)) const hasPublicTeamsFlag = !!organization.featureFlags?.includes('publicTeams') - if (isBillingLeader || isOrgAdmin || isSuperUser(authToken) || hasPublicTeamsFlag) { + if (isOrgAdmin || isSuperUser(authToken) || hasPublicTeamsFlag) { const viewerTeams = sortedTeamsOnOrg.filter((team) => authToken.tms.includes(team.id)) const otherTeams = sortedTeamsOnOrg.filter((team) => !authToken.tms.includes(team.id)) return [...viewerTeams, ...otherTeams] From 4f294c8dc4550b315e151dde2a145ea065bb10f4 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 22 Aug 2024 11:36:57 -0700 Subject: [PATCH 423/529] chore(release): release v7.43.8 (#10149) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 15 +++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 27 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index b20356a9f69..46e2b5b3b15 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.43.7" + ".": "7.43.8" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4eeec985a29..585427aa969 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.43.8](https://github.com/ParabolInc/parabol/compare/v7.43.7...v7.43.8) (2024-08-22) + + +### Fixed + +* no team leads ([#10145](https://github.com/ParabolInc/parabol/issues/10145)) ([a1599e9](https://github.com/ParabolInc/parabol/commit/a1599e90e90df1e4e32963fe304a0f85a0af020d)) +* **orgAdmins:** archived teams should be removed from the OrgTeams view ([#10142](https://github.com/ParabolInc/parabol/issues/10142)) ([c70b87a](https://github.com/ParabolInc/parabol/commit/c70b87a4561b1742ae726dd08f47811b3f1bd4ff)) +* **orgAdmins:** Billing leaders should not see all teams in the org ([#10141](https://github.com/ParabolInc/parabol/issues/10141)) ([cd570ac](https://github.com/ParabolInc/parabol/commit/cd570acd3a9aebb9b09e6139263a5ef85d406dc0)) +* use period end instead of due at ([#10151](https://github.com/ParabolInc/parabol/issues/10151)) ([6492679](https://github.com/ParabolInc/parabol/commit/6492679641bede9265b7df57295ad79caf3eadc9)) + + +### Changed + +* update contributing doc ([#10148](https://github.com/ParabolInc/parabol/issues/10148)) ([5a95f62](https://github.com/ParabolInc/parabol/commit/5a95f622f91ee6b992f37d5feb5bda850f82cf0b)) + ## [7.43.7](https://github.com/ParabolInc/parabol/compare/v7.43.6...v7.43.7) (2024-08-16) diff --git a/package.json b/package.json index b620e741068..679b0e156af 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.43.7", + "version": "7.43.8", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 63e7be21f18..f7c4f8d6f5e 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.43.7", + "version": "7.43.8", "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.43.7" + "parabol-server": "7.43.8" } } diff --git a/packages/client/package.json b/packages/client/package.json index a27347000bf..8f35e3d3d8e 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.43.7", + "version": "7.43.8", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 6bb6a0550ea..fe567f609fe 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.43.7", + "version": "7.43.8", "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 e52148528e3..9105c88ffc0 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.43.7", + "version": "7.43.8", "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.43.7", - "parabol-server": "7.43.7", + "parabol-client": "7.43.8", + "parabol-server": "7.43.8", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index fca6f06cefe..2ae859d6bf8 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.43.7", + "version": "7.43.8", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 3abe45348f6..cbfdbfecba7 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.43.7", + "version": "7.43.8", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.43.7", + "parabol-client": "7.43.8", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 34d519467dfcfdfbc5c13781ca257917faf9eb1a Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Thu, 22 Aug 2024 21:05:50 +0100 Subject: [PATCH 424/529] feat: upgrade suggest groups openai models (#10153) --- packages/server/utils/OpenAIServerManager.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/server/utils/OpenAIServerManager.ts b/packages/server/utils/OpenAIServerManager.ts index 47a0b416fbc..32728edaaa0 100644 --- a/packages/server/utils/OpenAIServerManager.ts +++ b/packages/server/utils/OpenAIServerManager.ts @@ -179,7 +179,7 @@ class OpenAIServerManager { try { const response = await this.openAIApi.chat.completions.create({ - model: 'gpt-4', + model: 'gpt-4o', messages: [ { role: 'user', @@ -251,7 +251,7 @@ class OpenAIServerManager { )}, and the following reflection: "${reflection}", classify the reflection into the theme it fits in best. The reflection can only be added to one theme. Do not edit the reflection text. Your output should just be the theme name, and must be one of the themes I've provided.` const response = await this.openAIApi!.chat.completions.create({ - model: 'gpt-4', + model: 'gpt-4o', messages: [ { role: 'user', From 420f07270177e36a837f138eb203f2b6b56fd730 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 26 Aug 2024 14:32:45 -0700 Subject: [PATCH 425/529] chore(rethinkdb): SlackAuth (#10154) Signed-off-by: Matt Krick --- codegen.json | 3 + .../dataloader/foreignKeyLoaderMakers.ts | 5 + .../dataloader/primaryKeyLoaderMakers.ts | 5 + .../rethinkForeignKeyLoaderMakers.ts | 9 -- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../server/graphql/mutations/addSlackAuth.ts | 135 ------------------ .../helpers/activatePrevSlackAuth.ts | 34 ++--- .../helpers/notifications/SlackNotifier.ts | 13 +- .../mutations/helpers/removeSlackAuths.ts | 31 ++-- .../graphql/mutations/removeSlackAuth.ts | 4 +- .../mutations/setDefaultSlackChannel.ts | 74 ---------- .../graphql/mutations/setSlackNotification.ts | 83 ----------- .../private/mutations/messageAllSlackUsers.ts | 8 +- .../private/mutations/removeAllSlackAuths.ts | 14 +- .../graphql/private/queries/dailyPulse.ts | 12 +- .../public/mutations/acceptTeamInvitation.ts | 2 +- .../graphql/public/mutations/addSlackAuth.ts | 131 +++++++++++++++++ .../mutations/setDefaultSlackChannel.ts | 61 ++++++++ .../public/mutations/setSlackNotification.ts | 60 ++++++++ .../public/types/AddSlackAuthPayload.ts | 22 +++ .../types/SetDefaultSlackChannelSuccess.ts | 17 +++ .../types/SetSlackNotificationPayload.ts | 25 ++++ .../graphql/public/types/SlackNotification.ts | 10 ++ packages/server/graphql/rootMutation.ts | 6 - .../graphql/types/AddSlackAuthPayload.ts | 30 ---- .../types/SetDefaultSlackChannelPayload.ts | 30 ---- .../types/SetSlackNotificationPayload.ts | 29 ---- .../server/graphql/types/SlackIntegration.ts | 8 -- .../server/graphql/types/SlackNotification.ts | 39 ----- .../types/SlackNotificationEventEnum.ts | 16 --- .../types/SlackNotificationEventTypeEnum.ts | 18 --- .../migrations/1724261917988_SlackAuth.ts | 108 ++++++++++++++ packages/server/postgres/select.ts | 2 + 33 files changed, 494 insertions(+), 551 deletions(-) delete mode 100644 packages/server/graphql/mutations/addSlackAuth.ts delete mode 100644 packages/server/graphql/mutations/setDefaultSlackChannel.ts delete mode 100644 packages/server/graphql/mutations/setSlackNotification.ts create mode 100644 packages/server/graphql/public/mutations/addSlackAuth.ts create mode 100644 packages/server/graphql/public/mutations/setDefaultSlackChannel.ts create mode 100644 packages/server/graphql/public/mutations/setSlackNotification.ts create mode 100644 packages/server/graphql/public/types/AddSlackAuthPayload.ts create mode 100644 packages/server/graphql/public/types/SetDefaultSlackChannelSuccess.ts create mode 100644 packages/server/graphql/public/types/SetSlackNotificationPayload.ts create mode 100644 packages/server/graphql/public/types/SlackNotification.ts delete mode 100644 packages/server/graphql/types/AddSlackAuthPayload.ts delete mode 100644 packages/server/graphql/types/SetDefaultSlackChannelPayload.ts delete mode 100644 packages/server/graphql/types/SetSlackNotificationPayload.ts delete mode 100644 packages/server/graphql/types/SlackIntegration.ts delete mode 100644 packages/server/graphql/types/SlackNotification.ts delete mode 100644 packages/server/graphql/types/SlackNotificationEventEnum.ts delete mode 100644 packages/server/graphql/types/SlackNotificationEventTypeEnum.ts create mode 100644 packages/server/postgres/migrations/1724261917988_SlackAuth.ts diff --git a/codegen.json b/codegen.json index b584d84cc68..8bbcb4dfe13 100644 --- a/codegen.json +++ b/codegen.json @@ -46,6 +46,9 @@ "config": { "contextType": "../graphql#GQLContext", "mappers": { + "SetSlackNotificationPayload": "./types/SetSlackNotificationPayload#SetSlackNotificationPayloadSource", + "SetDefaultSlackChannelSuccess": "./types/SetDefaultSlackChannelSuccess#SetDefaultSlackChannelSuccessSource", + "AddSlackAuthPayload": "./types/AddSlackAuthPayload#AddSlackAuthPayloadSource", "RemoveAgendaItemPayload": "./types/RemoveAgendaItemPayload#RemoveAgendaItemPayloadSource", "AddAgendaItemPayload": "./types/AddAgendaItemPayload#AddAgendaItemPayloadSource", "UpdateAgendaItemPayload": "./types/UpdateAgendaItemPayload#UpdateAgendaItemPayloadSource", diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index d5855cf4e3c..50f21bd06d4 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -4,6 +4,7 @@ import { selectAgendaItems, selectOrganizations, selectRetroReflections, + selectSlackAuths, selectSuggestedAction, selectTeams, selectTemplateDimension, @@ -191,3 +192,7 @@ export const agendaItemsByMeetingId = foreignKeyLoaderMaker( return selectAgendaItems().where('meetingId', 'in', meetingIds).orderBy('sortOrder').execute() } ) + +export const slackAuthByUserId = foreignKeyLoaderMaker('slackAuths', 'userId', async (userIds) => { + return selectSlackAuths().where('userId', 'in', userIds).execute() +}) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 7ac2ed6389d..3c4d1696d7e 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -10,6 +10,7 @@ import { selectMeetingSettings, selectOrganizations, selectRetroReflections, + selectSlackAuths, selectSuggestedAction, selectTeamPromptResponses, selectTeams, @@ -95,3 +96,7 @@ export const meetingSettings = primaryKeyLoaderMaker((ids: readonly string[]) => export const agendaItems = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectAgendaItems().where('id', 'in', ids).execute() }) + +export const slackAuths = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectSlackAuths().where('id', 'in', ids).execute() +}) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index 24afe08e897..4a4e1d3a8f1 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -89,15 +89,6 @@ export const meetingMembersByUserId = new RethinkForeignKeyLoaderMaker( } ) -export const slackAuthByUserId = new RethinkForeignKeyLoaderMaker( - 'slackAuths', - 'userId', - async (userIds) => { - const r = await getRethink() - return r.table('SlackAuth').getAll(r.args(userIds), {index: 'userId'}).run() - } -) - export const slackNotificationsByTeamId = new RethinkForeignKeyLoaderMaker( 'slackNotifications', 'teamId', diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index e2840c3ed0a..fbb1e5c1efe 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -10,7 +10,6 @@ export const meetingMembers = new RethinkPrimaryKeyLoaderMaker('MeetingMember') export const newMeetings = new RethinkPrimaryKeyLoaderMaker('NewMeeting') export const newFeatures = new RethinkPrimaryKeyLoaderMaker('NewFeature') export const notifications = new RethinkPrimaryKeyLoaderMaker('Notification') -export const slackAuths = new RethinkPrimaryKeyLoaderMaker('SlackAuth') export const slackNotifications = new RethinkPrimaryKeyLoaderMaker('SlackNotification') export const tasks = new RethinkPrimaryKeyLoaderMaker('Task') export const teamInvitations = new RethinkPrimaryKeyLoaderMaker('TeamInvitation') diff --git a/packages/server/graphql/mutations/addSlackAuth.ts b/packages/server/graphql/mutations/addSlackAuth.ts deleted file mode 100644 index 5c6110e0840..00000000000 --- a/packages/server/graphql/mutations/addSlackAuth.ts +++ /dev/null @@ -1,135 +0,0 @@ -import {GraphQLID, GraphQLNonNull} from 'graphql' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import SlackAuth from '../../database/types/SlackAuth' -import SlackNotification, {SlackNotificationEvent} from '../../database/types/SlackNotification' -import SlackServerManager from '../../utils/SlackServerManager' -import {analytics} from '../../utils/analytics/analytics' -import {getUserId, isTeamMember} from '../../utils/authorization' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import AddSlackAuthPayload from '../types/AddSlackAuthPayload' - -export const upsertNotifications = async ( - viewerId: string, - teamId: string, - teamChannelId: string, - channelId: string -) => { - const r = await getRethink() - const existingNotifications = await r - .table('SlackNotification') - .getAll(viewerId, {index: 'userId'}) - .filter({teamId}) - .run() - const teamEvents = [ - 'meetingStart', - 'meetingEnd', - 'MEETING_STAGE_TIME_LIMIT_START', - 'STANDUP_RESPONSE_SUBMITTED' - ] as SlackNotificationEvent[] - const userEvents = ['MEETING_STAGE_TIME_LIMIT_END'] as SlackNotificationEvent[] - const events = [...teamEvents, ...userEvents] - const upsertableNotifications = events.map((event) => { - const existingNotification = existingNotifications.find( - (notification) => notification.event === event - ) - return new SlackNotification({ - event, - // the existing notification channel could be a bad one (legacy reasons, bad means not public or not @Parabol) - channelId: teamEvents.includes(event) ? teamChannelId : channelId, - teamId, - userId: viewerId, - id: (existingNotification && existingNotification.id) || undefined - }) - }) - await r.table('SlackNotification').insert(upsertableNotifications, {conflict: 'replace'}).run() -} - -const upsertAuth = async ( - viewerId: string, - teamId: string, - teamChannelId: string, - slackUserName: string, - slackRes: NonNullable -) => { - const r = await getRethink() - const existingAuth = (await r - .table('SlackAuth') - .getAll(viewerId, {index: 'userId'}) - .filter({teamId}) - .nth(0) - .default(null) - .run()) as SlackAuth | null - - const slackAuth = new SlackAuth({ - id: (existingAuth && existingAuth.id) || undefined, - createdAt: (existingAuth && existingAuth.createdAt) || undefined, - defaultTeamChannelId: teamChannelId, - teamId, - userId: viewerId, - slackTeamId: slackRes.team.id, - slackTeamName: slackRes.team.name, - slackUserId: slackRes.authed_user.id, - slackUserName, - botUserId: slackRes.bot_user_id, - botAccessToken: slackRes.access_token - }) - await r.table('SlackAuth').insert(slackAuth, {conflict: 'replace'}).run() - return slackAuth.id -} - -export default { - name: 'AddSlackAuth', - type: new GraphQLNonNull(AddSlackAuthPayload), - args: { - code: { - type: new GraphQLNonNull(GraphQLID) - }, - teamId: { - type: new GraphQLNonNull(GraphQLID) - } - }, - resolve: async ( - _source: unknown, - {code, teamId}: {code: string; teamId: string}, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) => { - const viewerId = getUserId(authToken) - const operationId = dataLoader.share() - const subOptions = {mutatorId, operationId} - - // AUTH - if (!isTeamMember(authToken, teamId)) { - return standardError(new Error('Attempted teamId spoof'), {userId: viewerId}) - } - - // RESOLUTION - const manager = await SlackServerManager.init(code) - const {response} = manager - const slackUserId = response.authed_user.id - const defaultChannelId = response.incoming_webhook.channel_id - const [joinConvoRes, userInfoRes, viewer] = await Promise.all([ - manager.joinConversation(defaultChannelId), - manager.getUserInfo(slackUserId), - dataLoader.get('users').loadNonNull(viewerId) - ]) - if (!userInfoRes.ok) { - return standardError(new Error(userInfoRes.error), {userId: viewerId}) - } - - // The default channel could be anything: public, private, im, mpim. Only allow public channels or the @Parabol channel - // Using the slackUserId sends a DM to the user from @Parabol - const teamChannelId = joinConvoRes.ok ? joinConvoRes.channel.id : slackUserId - - const [, slackAuthId] = await Promise.all([ - upsertNotifications(viewerId, teamId, teamChannelId, defaultChannelId), - upsertAuth(viewerId, teamId, teamChannelId, userInfoRes.user.profile.display_name, response) - ]) - analytics.integrationAdded(viewer, teamId, 'slack') - const data = {slackAuthId, userId: viewerId} - publish(SubscriptionChannel.TEAM, teamId, 'AddSlackAuthPayload', data, subOptions) - return data - } -} diff --git a/packages/server/graphql/mutations/helpers/activatePrevSlackAuth.ts b/packages/server/graphql/mutations/helpers/activatePrevSlackAuth.ts index dc2a59eaea4..1a41522487c 100644 --- a/packages/server/graphql/mutations/helpers/activatePrevSlackAuth.ts +++ b/packages/server/graphql/mutations/helpers/activatePrevSlackAuth.ts @@ -1,20 +1,17 @@ import ms from 'ms' -import getRethink from '../../../database/rethinkDriver' +import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' +import getKysely from '../../../postgres/getKysely' import {Logger} from '../../../utils/Logger' import SlackServerManager from '../../../utils/SlackServerManager' -import {upsertNotifications} from '../addSlackAuth' - -const activatePrevSlackAuth = async (userId: string, teamId: string) => { - const r = await getRethink() - const now = new Date() - const previousAuth = await r - .table('SlackAuth') - .getAll(userId, {index: 'userId'}) - .filter({teamId}) - .nth(0) - .default(null) - .run() +import {upsertNotifications} from '../../public/mutations/addSlackAuth' +const activatePrevSlackAuth = async ( + userId: string, + teamId: string, + dataLoader: DataLoaderInstance +) => { + const previousAuths = await dataLoader.get('slackAuthByUserId').load(userId) + const previousAuth = previousAuths.find((auth) => auth.teamId === teamId) if (!previousAuth) return const LAST_YEAR = new Date(Date.now() - ms('1y')) const { @@ -34,10 +31,13 @@ const activatePrevSlackAuth = async (userId: string, teamId: string) => { return } - await r({ - auth: r.table('SlackAuth').get(authId).update({isActive: true, updatedAt: now}), - notifications: upsertNotifications(userId, teamId, defaultTeamChannelId, slackUserId) - }).run() + await getKysely() + .updateTable('SlackAuth') + .set({isActive: true}) + .where('id', '=', authId) + .execute() + dataLoader.clearAll('slackAuths') + await upsertNotifications(userId, teamId, defaultTeamChannelId, slackUserId) } } diff --git a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts index e2e37ad240c..ca2524fb233 100644 --- a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts @@ -592,22 +592,15 @@ export const SlackNotifier = { stageIndex: number, channelId: string ) { - const r = await getRethink() - const [team, user, meeting, reflectionGroup, reflections, slackAuth] = await Promise.all([ + const [team, user, meeting, reflectionGroup, reflections, userSlackAuths] = await Promise.all([ dataLoader.get('teams').loadNonNull(teamId), dataLoader.get('users').loadNonNull(userId), dataLoader.get('newMeetings').load(meetingId), dataLoader.get('retroReflectionGroups').loadNonNull(reflectionGroupId), dataLoader.get('retroReflectionsByGroupId').load(reflectionGroupId), - r - .table('SlackAuth') - .getAll(userId, {index: 'userId'}) - .filter({teamId}) - .nth(0) - .default(null) - .run() + dataLoader.get('slackAuthByUserId').load(userId) ]) - + const slackAuth = userSlackAuths.find((auth) => auth.teamId === teamId) if (!slackAuth) { throw new Error('Slack auth not found') } diff --git a/packages/server/graphql/mutations/helpers/removeSlackAuths.ts b/packages/server/graphql/mutations/helpers/removeSlackAuths.ts index 07db0d45fc1..32481e4f741 100644 --- a/packages/server/graphql/mutations/helpers/removeSlackAuths.ts +++ b/packages/server/graphql/mutations/helpers/removeSlackAuths.ts @@ -1,4 +1,5 @@ import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' const removeSlackAuths = async ( userId: string, @@ -6,36 +7,26 @@ const removeSlackAuths = async ( removeToken?: boolean ) => { const r = await getRethink() - const now = new Date() const teamIdsArr = Array.isArray(teamIds) ? teamIds : [teamIds] - const existingAuths = await r - .table('SlackAuth') - .getAll(r.args(teamIdsArr), {index: 'teamId'}) - .filter({userId}) - .run() - - if (!existingAuths.length) { - const error = new Error('Auth not found') - return {authIds: null, error} + if (teamIdsArr.length === 0) { + return {authIds: null, error: 'No teams provided'} } + const inactiveAuths = await getKysely() + .updateTable('SlackAuth') + .set({botAccessToken: removeToken ? null : undefined, isActive: false}) + .where('teamId', 'in', teamIdsArr) + .where('userId', '=', userId) + .returning('id') + .execute() - const authIds = existingAuths.map(({id}) => id) await r({ - auth: r - .table('SlackAuth') - .getAll(r.args(authIds)) - .update({ - botAccessToken: removeToken ? null : undefined, - isActive: false, - updatedAt: now - }), notifications: r .table('SlackNotification') .getAll(r.args(teamIdsArr), {index: 'teamId'}) .filter({userId}) .delete() }).run() - + const authIds = inactiveAuths.map(({id}) => id) return {authIds, error: null} } diff --git a/packages/server/graphql/mutations/removeSlackAuth.ts b/packages/server/graphql/mutations/removeSlackAuth.ts index 3e11d1d45a6..3a77b17a141 100644 --- a/packages/server/graphql/mutations/removeSlackAuth.ts +++ b/packages/server/graphql/mutations/removeSlackAuth.ts @@ -37,8 +37,8 @@ export default { removeSlackAuths(viewerId, teamId, true), dataLoader.get('users').loadNonNull(viewerId) ]) - if (res.error) { - return standardError(res.error, {userId: viewerId}) + if (!res.authIds) { + return {error: {message: res.error}} } const authId = res.authIds[0] diff --git a/packages/server/graphql/mutations/setDefaultSlackChannel.ts b/packages/server/graphql/mutations/setDefaultSlackChannel.ts deleted file mode 100644 index d33709c3bdc..00000000000 --- a/packages/server/graphql/mutations/setDefaultSlackChannel.ts +++ /dev/null @@ -1,74 +0,0 @@ -import {GraphQLID, GraphQLNonNull} from 'graphql' -import getRethink from '../../database/rethinkDriver' -import SlackServerManager from '../../utils/SlackServerManager' -import {getUserId, isTeamMember} from '../../utils/authorization' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import SetDefaultSlackChannelPayload from '../types/SetDefaultSlackChannelPayload' - -const setDefaultSlackChannel = { - type: new GraphQLNonNull(SetDefaultSlackChannelPayload), - description: 'Update the default Slack channel where notifications are sent', - args: { - slackChannelId: { - type: new GraphQLNonNull(GraphQLID) - }, - teamId: { - type: new GraphQLNonNull(GraphQLID) - } - }, - resolve: async ( - _source: unknown, - {slackChannelId, teamId}: {slackChannelId: string; teamId: string}, - {authToken, dataLoader}: GQLContext - ) => { - const r = await getRethink() - const viewerId = getUserId(authToken) - - // AUTH - if (!isTeamMember(authToken, teamId)) { - return standardError(new Error('Attempted teamId spoof'), {userId: viewerId}) - } - - // VALIDATION - const slackAuths = await dataLoader.get('slackAuthByUserId').load(viewerId) - const slackAuth = slackAuths.find((auth) => auth.teamId === teamId) - if (!slackAuth) { - return standardError(new Error('Slack authentication not found'), {userId: viewerId}) - } - const {id: slackAuthId, botAccessToken, defaultTeamChannelId, slackUserId} = slackAuth - const manager = new SlackServerManager(botAccessToken!) - const channelInfo = await manager.getConversationInfo(slackChannelId) - - // should either be a public / private channel or the slackUserId if messaging from @Parabol - if (slackChannelId !== slackUserId) { - if (!channelInfo.ok) { - return standardError(new Error(channelInfo.error), {userId: viewerId}) - } - const {channel} = channelInfo - const {id: channelId, is_member: isMember, is_archived: isArchived} = channel - if (isArchived) { - return standardError(new Error('Slack channel archived'), {userId: viewerId}) - } - if (!isMember) { - const joinConvoRes = await manager.joinConversation(channelId) - if (!joinConvoRes.ok) { - return standardError(new Error('Unable to join slack channel'), {userId: viewerId}) - } - } - } - - // RESOLUTION - if (slackChannelId !== defaultTeamChannelId) { - await r - .table('SlackAuth') - .get(slackAuthId) - .update({defaultTeamChannelId: slackChannelId}) - .run() - } - const data = {slackChannelId, teamId, userId: viewerId} - return data - } -} - -export default setDefaultSlackChannel diff --git a/packages/server/graphql/mutations/setSlackNotification.ts b/packages/server/graphql/mutations/setSlackNotification.ts deleted file mode 100644 index 34557980b88..00000000000 --- a/packages/server/graphql/mutations/setSlackNotification.ts +++ /dev/null @@ -1,83 +0,0 @@ -import {GraphQLID, GraphQLList, GraphQLNonNull} from 'graphql' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import {RDatum} from '../../database/stricterR' -import SlackNotification, { - SlackNotificationEventEnum as TSlackNotificationEventEnum -} from '../../database/types/SlackNotification' -import {getUserId, isTeamMember} from '../../utils/authorization' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import SetSlackNotificationPayload from '../types/SetSlackNotificationPayload' -import SlackNotificationEventEnum from '../types/SlackNotificationEventEnum' - -type SetSlackNotificationMutationVariables = { - slackNotificationEvents: Array - slackChannelId?: string | null - teamId: string -} -export default { - name: 'SetSlackNotification', - type: new GraphQLNonNull(SetSlackNotificationPayload), - args: { - slackChannelId: { - type: GraphQLID - }, - slackNotificationEvents: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(SlackNotificationEventEnum))) - }, - teamId: { - type: new GraphQLNonNull(GraphQLID) - } - }, - resolve: async ( - _source: unknown, - {slackChannelId, slackNotificationEvents, teamId}: SetSlackNotificationMutationVariables, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) => { - const viewerId = getUserId(authToken) - const operationId = dataLoader.share() - const subOptions = {mutatorId, operationId} - const r = await getRethink() - - // AUTH - if (!isTeamMember(authToken, teamId)) { - return standardError(new Error('Attempted teamId spoof'), {userId: viewerId}) - } - - // VALIDATION - const slackAuths = await dataLoader.get('slackAuthByUserId').load(viewerId) - const slackAuth = slackAuths.find((auth) => auth.teamId === teamId) - - if (!slackAuth) { - return standardError(new Error('Slack authentication not found'), {userId: viewerId}) - } - - // RESOLUTION - const existingNotifications = await r - .table('SlackNotification') - .getAll(viewerId, {index: 'userId'}) - .filter({teamId}) - .filter((row: RDatum) => r(slackNotificationEvents).contains(row('event'))) - .run() - - const notifications = slackNotificationEvents.map((event) => { - const existingNotification = existingNotifications.find( - (notification) => notification.event === event - ) - return new SlackNotification({ - event, - channelId: slackChannelId, - teamId, - userId: viewerId, - id: (existingNotification && existingNotification.id) || undefined - }) - }) - await r.table('SlackNotification').insert(notifications, {conflict: 'replace'}).run() - const slackNotificationIds = notifications.map(({id}) => id) - const data = {userId: viewerId, slackNotificationIds} - publish(SubscriptionChannel.TEAM, teamId, 'SetSlackNotificationPayload', data, subOptions) - return data - } -} diff --git a/packages/server/graphql/private/mutations/messageAllSlackUsers.ts b/packages/server/graphql/private/mutations/messageAllSlackUsers.ts index 4066a661188..802353a1c69 100644 --- a/packages/server/graphql/private/mutations/messageAllSlackUsers.ts +++ b/packages/server/graphql/private/mutations/messageAllSlackUsers.ts @@ -1,4 +1,4 @@ -import getRethink from '../../../database/rethinkDriver' +import {selectSlackAuths} from '../../../postgres/select' import SlackServerManager from '../../../utils/SlackServerManager' import standardError from '../../../utils/standardError' import {MutationResolvers} from '../resolverTypes' @@ -12,11 +12,9 @@ const messageAllSlackUsers: MutationResolvers['messageAllSlackUsers'] = async ( _source, {message} ) => { - const r = await getRethink() - // RESOLUTION - const allSlackAuths = await r.table('SlackAuth').filter({isActive: true}).run() - if (!allSlackAuths || !allSlackAuths.length) { + const allSlackAuths = await selectSlackAuths().where('isActive', '=', true).execute() + if (!allSlackAuths.length) { return standardError(new Error('No authorised Slack users')) } diff --git a/packages/server/graphql/private/mutations/removeAllSlackAuths.ts b/packages/server/graphql/private/mutations/removeAllSlackAuths.ts index c63dd3b9bf8..6350b5a903a 100644 --- a/packages/server/graphql/private/mutations/removeAllSlackAuths.ts +++ b/packages/server/graphql/private/mutations/removeAllSlackAuths.ts @@ -1,19 +1,17 @@ import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {MutationResolvers} from '../resolverTypes' const removeAllSlackAuths: MutationResolvers['removeAllSlackAuths'] = async () => { const r = await getRethink() - const now = new Date() // RESOLUTION - const allSlackAuths = await r.table('SlackAuth').filter({isActive: true}).run() - const allSlackAuthIds = allSlackAuths.map(({id}) => id) const [slackAuthRes, slackNotificationRes] = await Promise.all([ - r - .table('SlackAuth') - .getAll(r.args(allSlackAuthIds)) - .update({botAccessToken: null, isActive: false, updatedAt: now}) - .run(), + getKysely() + .updateTable('SlackAuth') + .set({botAccessToken: null, isActive: false}) + .returning('id') + .execute(), r.table('SlackNotification').delete().run() ]) const data = { diff --git a/packages/server/graphql/private/queries/dailyPulse.ts b/packages/server/graphql/private/queries/dailyPulse.ts index 6cb63ed20a5..3a6546acaf1 100644 --- a/packages/server/graphql/private/queries/dailyPulse.ts +++ b/packages/server/graphql/private/queries/dailyPulse.ts @@ -1,5 +1,3 @@ -import getRethink from '../../../database/rethinkDriver' -import {RValue} from '../../../database/stricterR' import getPg from '../../../postgres/getPg' import {getUserByEmail} from '../../../postgres/queries/getUsersByEmails' import SlackServerManager from '../../../utils/SlackServerManager' @@ -88,17 +86,11 @@ const dailyPulse: QueryResolvers['dailyPulse'] = async ( {after, email, channelId}, {dataLoader} ) => { - const r = await getRethink() const user = await getUserByEmail(email) if (!user) throw new Error('Bad user') const {id: userId} = user - const slackAuth = await r - .table('SlackAuth') - .getAll(userId, {index: 'userId'}) - .filter((row: RValue) => row('botAccessToken').default(null).ne(null)) - .nth(0) - .default(null) - .run() + const slackAuths = await dataLoader.get('slackAuthByUserId').load(userId) + const slackAuth = slackAuths.find((auth) => auth.isActive && auth.botAccessToken) if (!slackAuth) throw new Error('No Slack Auth Found!') const {botAccessToken} = slackAuth const [rawSignups, rawLogins] = await Promise.all([ diff --git a/packages/server/graphql/public/mutations/acceptTeamInvitation.ts b/packages/server/graphql/public/mutations/acceptTeamInvitation.ts index 34f06d3943d..54a83956347 100644 --- a/packages/server/graphql/public/mutations/acceptTeamInvitation.ts +++ b/packages/server/graphql/public/mutations/acceptTeamInvitation.ts @@ -94,7 +94,7 @@ const acceptTeamInvitation: MutationResolvers['acceptTeamInvitation'] = async ( viewerId, dataLoader ) - activatePrevSlackAuth(viewerId, teamId) + activatePrevSlackAuth(viewerId, teamId, dataLoader) await redisLock.unlock() const tms = authToken.tms ? authToken.tms.concat(teamId) : [teamId] // IMPORTANT! mutate the current authToken so any queries or subscriptions can get the latest diff --git a/packages/server/graphql/public/mutations/addSlackAuth.ts b/packages/server/graphql/public/mutations/addSlackAuth.ts new file mode 100644 index 00000000000..63a650989de --- /dev/null +++ b/packages/server/graphql/public/mutations/addSlackAuth.ts @@ -0,0 +1,131 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import getRethink from '../../../database/rethinkDriver' +import SlackNotification, {SlackNotificationEvent} from '../../../database/types/SlackNotification' +import generateUID from '../../../generateUID' +import getKysely from '../../../postgres/getKysely' +import SlackServerManager from '../../../utils/SlackServerManager' +import {analytics} from '../../../utils/analytics/analytics' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +export const upsertNotifications = async ( + viewerId: string, + teamId: string, + teamChannelId: string, + channelId: string +) => { + const r = await getRethink() + const existingNotifications = await r + .table('SlackNotification') + .getAll(viewerId, {index: 'userId'}) + .filter({teamId}) + .run() + const teamEvents = [ + 'meetingStart', + 'meetingEnd', + 'MEETING_STAGE_TIME_LIMIT_START', + 'STANDUP_RESPONSE_SUBMITTED' + ] as SlackNotificationEvent[] + const userEvents = ['MEETING_STAGE_TIME_LIMIT_END'] as SlackNotificationEvent[] + const events = [...teamEvents, ...userEvents] + const upsertableNotifications = events.map((event) => { + const existingNotification = existingNotifications.find( + (notification) => notification.event === event + ) + return new SlackNotification({ + event, + // the existing notification channel could be a bad one (legacy reasons, bad means not public or not @Parabol) + channelId: teamEvents.includes(event) ? teamChannelId : channelId, + teamId, + userId: viewerId, + id: (existingNotification && existingNotification.id) || undefined + }) + }) + await r.table('SlackNotification').insert(upsertableNotifications, {conflict: 'replace'}).run() +} + +const upsertAuth = async ( + viewerId: string, + teamId: string, + teamChannelId: string, + slackUserName: string, + slackRes: NonNullable +) => { + const pg = getKysely() + const res = await pg + .insertInto('SlackAuth') + .values({ + id: generateUID(), + defaultTeamChannelId: teamChannelId, + teamId, + userId: viewerId, + slackTeamId: slackRes.team.id, + slackTeamName: slackRes.team.name, + slackUserId: slackRes.authed_user.id, + slackUserName, + botUserId: slackRes.bot_user_id, + botAccessToken: slackRes.access_token + }) + .onConflict((oc) => + oc.columns(['teamId', 'userId']).doUpdateSet((eb) => ({ + isActive: true, + defaultTeamChannelId: eb.ref('excluded.defaultTeamChannelId'), + slackTeamId: eb.ref('excluded.slackTeamId'), + slackTeamName: eb.ref('excluded.slackTeamName'), + slackUserId: eb.ref('excluded.slackUserId'), + slackUserName: eb.ref('excluded.slackUserName'), + botUserId: eb.ref('excluded.botUserId'), + botAccessToken: eb.ref('excluded.botAccessToken') + })) + ) + .returning('id') + .executeTakeFirstOrThrow() + + return res.id +} + +const addSlackAuth: MutationResolvers['addSlackAuth'] = async ( + _source, + {code, teamId}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const viewerId = getUserId(authToken) + const operationId = dataLoader.share() + const subOptions = {mutatorId, operationId} + + // AUTH + if (!isTeamMember(authToken, teamId)) { + return standardError(new Error('Attempted teamId spoof'), {userId: viewerId}) + } + + // RESOLUTION + const manager = await SlackServerManager.init(code) + const {response} = manager + const slackUserId = response.authed_user.id + const defaultChannelId = response.incoming_webhook.channel_id + const [joinConvoRes, userInfoRes, viewer] = await Promise.all([ + manager.joinConversation(defaultChannelId), + manager.getUserInfo(slackUserId), + dataLoader.get('users').loadNonNull(viewerId) + ]) + if (!userInfoRes.ok) { + return standardError(new Error(userInfoRes.error), {userId: viewerId}) + } + + // The default channel could be anything: public, private, im, mpim. Only allow public channels or the @Parabol channel + // Using the slackUserId sends a DM to the user from @Parabol + const teamChannelId = joinConvoRes.ok ? joinConvoRes.channel.id : slackUserId + + const [, slackAuthId] = await Promise.all([ + upsertNotifications(viewerId, teamId, teamChannelId, defaultChannelId), + upsertAuth(viewerId, teamId, teamChannelId, userInfoRes.user.profile.display_name, response) + ]) + analytics.integrationAdded(viewer, teamId, 'slack') + const data = {slackAuthId, userId: viewerId} + publish(SubscriptionChannel.TEAM, teamId, 'AddSlackAuthPayload', data, subOptions) + return data +} + +export default addSlackAuth diff --git a/packages/server/graphql/public/mutations/setDefaultSlackChannel.ts b/packages/server/graphql/public/mutations/setDefaultSlackChannel.ts new file mode 100644 index 00000000000..b33559b9fd5 --- /dev/null +++ b/packages/server/graphql/public/mutations/setDefaultSlackChannel.ts @@ -0,0 +1,61 @@ +import getKysely from '../../../postgres/getKysely' +import SlackServerManager from '../../../utils/SlackServerManager' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const setDefaultSlackChannel: MutationResolvers['setDefaultSlackChannel'] = async ( + _source, + {slackChannelId, teamId}, + {authToken, dataLoader} +) => { + const viewerId = getUserId(authToken) + + // AUTH + if (!isTeamMember(authToken, teamId)) { + return standardError(new Error('Attempted teamId spoof'), {userId: viewerId}) + } + + // VALIDATION + const slackAuths = await dataLoader.get('slackAuthByUserId').load(viewerId) + const slackAuth = slackAuths.find((auth) => auth.teamId === teamId) + if (!slackAuth) { + return standardError(new Error('Slack authentication not found'), {userId: viewerId}) + } + const {id: slackAuthId, botAccessToken, defaultTeamChannelId, slackUserId} = slackAuth + const manager = new SlackServerManager(botAccessToken!) + const channelInfo = await manager.getConversationInfo(slackChannelId) + + // should either be a public / private channel or the slackUserId if messaging from @Parabol + if (slackChannelId !== slackUserId) { + if (!channelInfo.ok) { + return standardError(new Error(channelInfo.error), {userId: viewerId}) + } + const {channel} = channelInfo + const {id: channelId, is_member: isMember, is_archived: isArchived} = channel + if (isArchived) { + return standardError(new Error('Slack channel archived'), {userId: viewerId}) + } + if (!isMember) { + const joinConvoRes = await manager.joinConversation(channelId) + if (!joinConvoRes.ok) { + return standardError(new Error('Unable to join slack channel'), {userId: viewerId}) + } + } + } + + // RESOLUTION + if (slackChannelId !== defaultTeamChannelId) { + await getKysely() + .updateTable('SlackAuth') + .set({ + defaultTeamChannelId: slackChannelId + }) + .where('id', '=', slackAuthId) + .execute() + } + const data = {slackChannelId, teamId, userId: viewerId} + return data +} + +export default setDefaultSlackChannel diff --git a/packages/server/graphql/public/mutations/setSlackNotification.ts b/packages/server/graphql/public/mutations/setSlackNotification.ts new file mode 100644 index 00000000000..6cddacc36b1 --- /dev/null +++ b/packages/server/graphql/public/mutations/setSlackNotification.ts @@ -0,0 +1,60 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import getRethink from '../../../database/rethinkDriver' +import {RDatum} from '../../../database/stricterR' +import SlackNotification from '../../../database/types/SlackNotification' +import {getUserId, isTeamMember} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const setSlackNotification: MutationResolvers['setSlackNotification'] = async ( + _source, + {slackChannelId, slackNotificationEvents, teamId}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const viewerId = getUserId(authToken) + const operationId = dataLoader.share() + const subOptions = {mutatorId, operationId} + const r = await getRethink() + + // AUTH + if (!isTeamMember(authToken, teamId)) { + return standardError(new Error('Attempted teamId spoof'), {userId: viewerId}) + } + + // VALIDATION + const slackAuths = await dataLoader.get('slackAuthByUserId').load(viewerId) + const slackAuth = slackAuths.find((auth) => auth.teamId === teamId) + + if (!slackAuth) { + return standardError(new Error('Slack authentication not found'), {userId: viewerId}) + } + + // RESOLUTION + const existingNotifications = await r + .table('SlackNotification') + .getAll(viewerId, {index: 'userId'}) + .filter({teamId}) + .filter((row: RDatum) => r(slackNotificationEvents).contains(row('event'))) + .run() + + const notifications = slackNotificationEvents.map((event) => { + const existingNotification = existingNotifications.find( + (notification) => notification.event === event + ) + return new SlackNotification({ + event, + channelId: slackChannelId, + teamId, + userId: viewerId, + id: (existingNotification && existingNotification.id) || undefined + }) + }) + await r.table('SlackNotification').insert(notifications, {conflict: 'replace'}).run() + const slackNotificationIds = notifications.map(({id}) => id) + const data = {userId: viewerId, slackNotificationIds} + publish(SubscriptionChannel.TEAM, teamId, 'SetSlackNotificationPayload', data, subOptions) + return data +} + +export default setSlackNotification diff --git a/packages/server/graphql/public/types/AddSlackAuthPayload.ts b/packages/server/graphql/public/types/AddSlackAuthPayload.ts new file mode 100644 index 00000000000..25b2b042a01 --- /dev/null +++ b/packages/server/graphql/public/types/AddSlackAuthPayload.ts @@ -0,0 +1,22 @@ +import {AddSlackAuthPayloadResolvers} from '../resolverTypes' + +export type AddSlackAuthPayloadSource = + | { + slackAuthId: string + userId: string + } + | {error: {message: string}} + +const AddSlackAuthPayload: AddSlackAuthPayloadResolvers = { + slackIntegration: async (source, _args, {dataLoader}) => { + return 'slackAuthId' in source + ? dataLoader.get('slackAuths').loadNonNull(source.slackAuthId) + : null + }, + + user: (source, _args, {dataLoader}) => { + return 'userId' in source ? dataLoader.get('users').loadNonNull(source.userId) : null + } +} + +export default AddSlackAuthPayload diff --git a/packages/server/graphql/public/types/SetDefaultSlackChannelSuccess.ts b/packages/server/graphql/public/types/SetDefaultSlackChannelSuccess.ts new file mode 100644 index 00000000000..a3a889b1f49 --- /dev/null +++ b/packages/server/graphql/public/types/SetDefaultSlackChannelSuccess.ts @@ -0,0 +1,17 @@ +import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' +import {SetDefaultSlackChannelSuccessResolvers} from '../resolverTypes' + +export type SetDefaultSlackChannelSuccessSource = { + slackChannelId: string + userId: string + teamId: string +} + +const SetDefaultSlackChannelSuccess: SetDefaultSlackChannelSuccessResolvers = { + teamMember: ({userId, teamId}, _args, {dataLoader}) => { + const teamMemberId = toTeamMemberId(teamId, userId) + return dataLoader.get('teamMembers').loadNonNull(teamMemberId) + } +} + +export default SetDefaultSlackChannelSuccess diff --git a/packages/server/graphql/public/types/SetSlackNotificationPayload.ts b/packages/server/graphql/public/types/SetSlackNotificationPayload.ts new file mode 100644 index 00000000000..74b9517f484 --- /dev/null +++ b/packages/server/graphql/public/types/SetSlackNotificationPayload.ts @@ -0,0 +1,25 @@ +import isValid from '../../isValid' +import {SetSlackNotificationPayloadResolvers} from '../resolverTypes' + +export type SetSlackNotificationPayloadSource = + | { + slackNotificationIds: string[] + userId: string + } + | {error: {message: string}} + +const SetSlackNotificationPayload: SetSlackNotificationPayloadResolvers = { + slackNotifications: async (source, _args, {dataLoader}) => { + return 'slackNotificationIds' in source + ? (await dataLoader.get('slackNotifications').loadMany(source.slackNotificationIds)).filter( + isValid + ) + : null + }, + + user: (source, _args, {dataLoader}) => { + return 'userId' in source ? dataLoader.get('users').loadNonNull(source.userId) : null + } +} + +export default SetSlackNotificationPayload diff --git a/packages/server/graphql/public/types/SlackNotification.ts b/packages/server/graphql/public/types/SlackNotification.ts new file mode 100644 index 00000000000..38a92e72dac --- /dev/null +++ b/packages/server/graphql/public/types/SlackNotification.ts @@ -0,0 +1,10 @@ +import {slackNotificationEventTypeLookup} from '../../../database/types/SlackNotification' +import {SlackNotificationResolvers} from '../resolverTypes' + +const SlackNotification: SlackNotificationResolvers = { + eventType: ({event}) => { + return slackNotificationEventTypeLookup[event] + } +} + +export default SlackNotification diff --git a/packages/server/graphql/rootMutation.ts b/packages/server/graphql/rootMutation.ts index 9493f6e092c..71fde7a3e06 100644 --- a/packages/server/graphql/rootMutation.ts +++ b/packages/server/graphql/rootMutation.ts @@ -9,7 +9,6 @@ import addPokerTemplateDimension from './mutations/addPokerTemplateDimension' import addPokerTemplateScale from './mutations/addPokerTemplateScale' import addPokerTemplateScaleValue from './mutations/addPokerTemplateScaleValue' import addReflectTemplatePrompt from './mutations/addReflectTemplatePrompt' -import addSlackAuth from './mutations/addSlackAuth' import addTeam from './mutations/addTeam' import archiveOrganization from './mutations/archiveOrganization' import archiveTeam from './mutations/archiveTeam' @@ -83,11 +82,9 @@ import resetPassword from './mutations/resetPassword' import resetRetroMeetingToGroupStage from './mutations/resetRetroMeetingToGroupStage' import selectTemplate from './mutations/selectTemplate' import setAppLocation from './mutations/setAppLocation' -import setDefaultSlackChannel from './mutations/setDefaultSlackChannel' import setNotificationStatus from './mutations/setNotificationStatus' import setPhaseFocus from './mutations/setPhaseFocus' import setPokerSpectate from './mutations/setPokerSpectate' -import setSlackNotification from './mutations/setSlackNotification' import setStageTimer from './mutations/setStageTimer' import setTaskEstimate from './mutations/setTaskEstimate' import setTaskHighlight from './mutations/setTaskHighlight' @@ -122,7 +119,6 @@ export default new GraphQLObjectType({ addPokerTemplateScale, addPokerTemplateScaleValue, addReflectTemplatePrompt, - addSlackAuth, addGitHubAuth, addOrg, addTeam, @@ -189,10 +185,8 @@ export default new GraphQLObjectType({ resetRetroMeetingToGroupStage, selectTemplate, setAppLocation, - setDefaultSlackChannel, setPhaseFocus, setStageTimer, - setSlackNotification, startDraggingReflection, startSprintPoker, setTaskHighlight, diff --git a/packages/server/graphql/types/AddSlackAuthPayload.ts b/packages/server/graphql/types/AddSlackAuthPayload.ts deleted file mode 100644 index 530cdef88b3..00000000000 --- a/packages/server/graphql/types/AddSlackAuthPayload.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import SlackIntegration from './SlackIntegration' -import StandardMutationError from './StandardMutationError' -import User from './User' - -const AddSlackAuthPayload = new GraphQLObjectType({ - name: 'AddSlackAuthPayload', - fields: () => ({ - error: { - type: StandardMutationError - }, - slackIntegration: { - type: SlackIntegration, - description: 'The newly created auth', - resolve: async ({slackAuthId}, _args: unknown, {dataLoader}: GQLContext) => { - return dataLoader.get('slackAuths').load(slackAuthId) - } - }, - user: { - type: User, - description: 'The user with updated slackAuth', - resolve: ({userId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('users').load(userId) - } - } - }) -}) - -export default AddSlackAuthPayload diff --git a/packages/server/graphql/types/SetDefaultSlackChannelPayload.ts b/packages/server/graphql/types/SetDefaultSlackChannelPayload.ts deleted file mode 100644 index 81381afe1bf..00000000000 --- a/packages/server/graphql/types/SetDefaultSlackChannelPayload.ts +++ /dev/null @@ -1,30 +0,0 @@ -import {GraphQLID, GraphQLNonNull, GraphQLObjectType} from 'graphql' -import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' -import {GQLContext} from '../graphql' -import TeamMember from './TeamMember' -import makeMutationPayload from './makeMutationPayload' - -export const SetDefaultSlackChannelSuccess = new GraphQLObjectType({ - name: 'SetDefaultSlackChannelSuccess', - fields: () => ({ - slackChannelId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The id of the slack channel that is now the default slack channel' - }, - teamMember: { - type: new GraphQLNonNull(TeamMember), - description: 'The team member with the updated slack channel', - resolve: ({teamId, userId}, _args: unknown, {dataLoader}) => { - const teamMemberId = toTeamMemberId(teamId, userId) - return dataLoader.get('teamMembers').load(teamMemberId) - } - } - }) -}) - -const SetDefaultSlackChannelPayload = makeMutationPayload( - 'SetDefaultSlackChannelPayload', - SetDefaultSlackChannelSuccess -) - -export default SetDefaultSlackChannelPayload diff --git a/packages/server/graphql/types/SetSlackNotificationPayload.ts b/packages/server/graphql/types/SetSlackNotificationPayload.ts deleted file mode 100644 index b9725031f75..00000000000 --- a/packages/server/graphql/types/SetSlackNotificationPayload.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {GraphQLList, GraphQLNonNull, GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import SlackNotification from './SlackNotification' -import StandardMutationError from './StandardMutationError' -import User from './User' - -const SetSlackNotificationPayload = new GraphQLObjectType({ - name: 'SetSlackNotificationPayload', - fields: () => ({ - error: { - type: StandardMutationError - }, - slackNotifications: { - type: new GraphQLList(new GraphQLNonNull(SlackNotification)), - resolve: async ({slackNotificationIds}, _args: unknown, {dataLoader}) => { - return dataLoader.get('slackNotifications').loadMany(slackNotificationIds) - } - }, - user: { - type: User, - description: 'The user with updated slack notifications', - resolve: ({userId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('users').load(userId) - } - } - }) -}) - -export default SetSlackNotificationPayload diff --git a/packages/server/graphql/types/SlackIntegration.ts b/packages/server/graphql/types/SlackIntegration.ts deleted file mode 100644 index ae9c958569d..00000000000 --- a/packages/server/graphql/types/SlackIntegration.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {GraphQLObjectType} from 'graphql' - -const SlackIntegration = new GraphQLObjectType({ - name: 'SlackIntegration', - fields: {} -}) - -export default SlackIntegration diff --git a/packages/server/graphql/types/SlackNotification.ts b/packages/server/graphql/types/SlackNotification.ts deleted file mode 100644 index 000d8bdfcce..00000000000 --- a/packages/server/graphql/types/SlackNotification.ts +++ /dev/null @@ -1,39 +0,0 @@ -import {GraphQLID, GraphQLNonNull, GraphQLObjectType} from 'graphql' -import { - SlackNotificationEvent, - slackNotificationEventTypeLookup -} from '../../database/types/SlackNotification' -import {GQLContext} from '../graphql' -import SlackNotificationEventEnum from './SlackNotificationEventEnum' -import SlackNotificationEventTypeEnum from './SlackNotificationEventTypeEnum' - -const SlackNotification = new GraphQLObjectType({ - name: 'SlackNotification', - description: 'an event trigger and slack channel to receive it', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID) - }, - event: { - type: new GraphQLNonNull(SlackNotificationEventEnum) - }, - eventType: { - type: new GraphQLNonNull(SlackNotificationEventTypeEnum), - resolve: ({event}: {event: SlackNotificationEvent}) => { - return slackNotificationEventTypeLookup[event] - } - }, - channelId: { - type: GraphQLID, - description: 'null if no notification is to be sent' - }, - teamId: { - type: new GraphQLNonNull(GraphQLID) - }, - userId: { - type: new GraphQLNonNull(GraphQLID) - } - }) -}) - -export default SlackNotification diff --git a/packages/server/graphql/types/SlackNotificationEventEnum.ts b/packages/server/graphql/types/SlackNotificationEventEnum.ts deleted file mode 100644 index 9100c62384b..00000000000 --- a/packages/server/graphql/types/SlackNotificationEventEnum.ts +++ /dev/null @@ -1,16 +0,0 @@ -import {GraphQLEnumType} from 'graphql' - -const SlackNotificationEventEnum = new GraphQLEnumType({ - name: 'SlackNotificationEventEnum', - description: 'The event that triggers a slack notification', - values: { - meetingStart: {}, - meetingEnd: {}, - MEETING_STAGE_TIME_LIMIT_END: {}, // user event - MEETING_STAGE_TIME_LIMIT_START: {}, - TOPIC_SHARED: {}, - STANDUP_RESPONSE_SUBMITTED: {} - } -}) - -export default SlackNotificationEventEnum diff --git a/packages/server/graphql/types/SlackNotificationEventTypeEnum.ts b/packages/server/graphql/types/SlackNotificationEventTypeEnum.ts deleted file mode 100644 index f84499cffe8..00000000000 --- a/packages/server/graphql/types/SlackNotificationEventTypeEnum.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {GraphQLEnumType} from 'graphql' - -const SlackNotificationEventTypeEnum = new GraphQLEnumType({ - name: 'SlackNotificationEventTypeEnum', - description: 'The type of event for a slack notification', - values: { - team: { - value: 'team', - description: 'notification that concerns the whole team' - }, - member: { - value: 'member', - description: 'notification that concerns a single member on the team' - } - } -}) - -export default SlackNotificationEventTypeEnum diff --git a/packages/server/postgres/migrations/1724261917988_SlackAuth.ts b/packages/server/postgres/migrations/1724261917988_SlackAuth.ts new file mode 100644 index 00000000000..0a4549fe0e2 --- /dev/null +++ b/packages/server/postgres/migrations/1724261917988_SlackAuth.ts @@ -0,0 +1,108 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {Client} from 'pg' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' +import getPgConfig from '../getPgConfig' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + await sql` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS "SlackAuth" ( + "isActive" BOOLEAN NOT NULL DEFAULT TRUE, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "id" VARCHAR(100) PRIMARY KEY, + "botUserId" VARCHAR(100) NOT NULL, + "botAccessToken" VARCHAR(100), + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "defaultTeamChannelId" VARCHAR(100) NOT NULL, + "teamId" VARCHAR(100) NOT NULL, + "userId" VARCHAR(100) NOT NULL, + "slackTeamId" VARCHAR(100) NOT NULL, + "slackTeamName" VARCHAR(100) NOT NULL, + "slackUserId" VARCHAR(100) NOT NULL, + "slackUserName" VARCHAR(100) NOT NULL, + CONSTRAINT "fk_teamId" + FOREIGN KEY("teamId") + REFERENCES "Team"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_userId" + FOREIGN KEY("userId") + REFERENCES "User"("id") + ON DELETE CASCADE, + UNIQUE("teamId", "userId") + ); + CREATE INDEX IF NOT EXISTS "idx_SlackAuth_teamId" ON "SlackAuth"("teamId"); + CREATE INDEX IF NOT EXISTS "idx_SlackAuth_userId" ON "SlackAuth"("userId"); + DROP TRIGGER IF EXISTS "update_SlackAuth_updatedAt" ON "SlackAuth"; + CREATE TRIGGER "update_SlackAuth_updatedAt" BEFORE UPDATE ON "SlackAuth" FOR EACH ROW EXECUTE PROCEDURE "set_updatedAt"(); + END $$; +`.execute(pg) + + const rAuths = await r.table('SlackAuth').coerceTo('array').run() + + await Promise.all( + rAuths.map(async (auth) => { + const { + isActive, + updatedAt, + id, + botUserId, + botAccessToken, + createdAt, + defaultTeamChannelId, + teamId, + userId, + slackTeamId, + slackTeamName, + slackUserId, + slackUserName + } = auth + if (!botUserId || !botAccessToken) return + try { + return await pg + .insertInto('SlackAuth') + .values({ + isActive, + updatedAt, + id, + botUserId, + botAccessToken, + createdAt, + defaultTeamChannelId, + teamId, + userId, + slackTeamId, + slackTeamName, + slackUserId, + slackUserName + }) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_teamId' || e.constraint === 'fk_userId') { + console.log(`Skipping ${auth.id} because it has no user/team`) + return + } + console.log(e, auth) + } + }) + ) +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE IF EXISTS "SlackAuth"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index 9a6df8e0d30..308709c8454 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -205,3 +205,5 @@ export const selectMeetingSettings = () => ]) export const selectAgendaItems = () => getKysely().selectFrom('AgendaItem').selectAll() + +export const selectSlackAuths = () => getKysely().selectFrom('SlackAuth').selectAll() From 1043859fe2b09dde80786271897f49276cd5aba7 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 27 Aug 2024 10:33:44 -0700 Subject: [PATCH 426/529] chore(rethinkdb): SlackNotification (#10163) Signed-off-by: Matt Krick --- .github/workflows/build.yml | 1 + .github/workflows/release-to-prod.yml | 1 + .github/workflows/release-to-staging.yml | 1 + .github/workflows/style-check.yml | 1 + .github/workflows/test.yml | 1 + codegen.json | 4 +- .../20190612194707-scheduledJobs.js | 29 +++---- packages/server/database/rethinkDriver.ts | 10 --- .../database/types/SlackNotification.ts | 44 ----------- .../dataloader/foreignKeyLoaderMakers.ts | 9 +++ .../dataloader/integrationAuthLoaders.ts | 66 +++++++--------- .../dataloader/primaryKeyLoaderMakers.ts | 5 ++ .../rethinkForeignKeyLoaderMakers.ts | 9 --- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../helpers/notifications/MSTeamsNotifier.ts | 5 +- .../notifications/MattermostNotifier.ts | 5 +- .../helpers/notifications/Notifier.ts | 4 +- .../helpers/notifications/SlackNotifier.ts | 31 ++++---- .../mutations/helpers/removeSlackAuths.ts | 16 ++-- .../private/mutations/hardDeleteUser.ts | 4 - .../private/mutations/removeAllSlackAuths.ts | 9 +-- .../graphql/public/mutations/addSlackAuth.ts | 46 +++++------ .../public/mutations/setSlackNotification.ts | 44 +++++------ .../graphql/public/types/SlackNotification.ts | 10 ++- .../1724358892236_SlackNotification.ts | 79 +++++++++++++++++++ packages/server/postgres/select.ts | 3 + packages/server/postgres/types/index.d.ts | 6 ++ packages/server/utils/analytics/analytics.ts | 9 +-- 28 files changed, 227 insertions(+), 226 deletions(-) delete mode 100644 packages/server/database/types/SlackNotification.ts create mode 100644 packages/server/postgres/migrations/1724358892236_SlackNotification.ts diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 6c8d455a6f1..d6842fb2dc4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -17,6 +17,7 @@ env: jobs: build: runs-on: ubuntu-8cores + timeout-minutes: 30 permissions: contents: "read" id-token: "write" diff --git a/.github/workflows/release-to-prod.yml b/.github/workflows/release-to-prod.yml index bed9fb2b96b..39c6964b5cc 100644 --- a/.github/workflows/release-to-prod.yml +++ b/.github/workflows/release-to-prod.yml @@ -8,6 +8,7 @@ jobs: release: if: ${{ github.event.pull_request.merged == true }} runs-on: ubuntu-latest + timeout-minutes: 30 permissions: contents: "read" id-token: "write" diff --git a/.github/workflows/release-to-staging.yml b/.github/workflows/release-to-staging.yml index e1ed2df1f79..c36292228bf 100644 --- a/.github/workflows/release-to-staging.yml +++ b/.github/workflows/release-to-staging.yml @@ -9,6 +9,7 @@ jobs: release: if: ${{ github.event.pull_request.merged == true && startsWith(github.head_ref, 'release-please--') }} runs-on: ubuntu-latest + timeout-minutes: 30 permissions: contents: "write" id-token: "write" diff --git a/.github/workflows/style-check.yml b/.github/workflows/style-check.yml index c32ada2e9a6..793a2ab8a85 100644 --- a/.github/workflows/style-check.yml +++ b/.github/workflows/style-check.yml @@ -10,6 +10,7 @@ concurrency: jobs: style-check: runs-on: ubuntu-latest + timeout-minutes: 30 permissions: contents: "read" id-token: "write" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b294e8933c6..895c724e44c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,6 +12,7 @@ env: jobs: test: runs-on: ubuntu-8cores + timeout-minutes: 30 permissions: contents: "read" id-token: "write" diff --git a/codegen.json b/codegen.json index 8bbcb4dfe13..f8e66b6e79c 100644 --- a/codegen.json +++ b/codegen.json @@ -155,8 +155,8 @@ "SetNotificationStatusPayload": "./types/SetNotificationStatusPayload#SetNotificationStatusPayloadSource", "SetOrgUserRoleSuccess": "./types/SetOrgUserRoleSuccess#SetOrgUserRoleSuccessSource", "ShareTopicSuccess": "./types/ShareTopicSuccess#ShareTopicSuccessSource", - "SlackIntegration": "../../database/types/SlackAuth#default as SlackAuthDB", - "SlackNotification": "../../database/types/SlackNotification#default as SlackNotificationDB", + "SlackIntegration": "../../postgres/types/index#SlackAuth as SlackAuthDB", + "SlackNotification": "../../postgres/types/index#SlackNotification as SlackNotificationDB", "StartCheckInSuccess": "./types/StartCheckInSuccess#StartCheckInSuccessSource", "StartRetrospectiveSuccess": "./types/StartRetrospectiveSuccess#StartRetrospectiveSuccessSource", "StartTeamPromptSuccess": "./types/StartTeamPromptSuccess#StartTeamPromptSuccessSource", diff --git a/packages/server/database/migrations/20190612194707-scheduledJobs.js b/packages/server/database/migrations/20190612194707-scheduledJobs.js index d9faa1590ef..d01757fd367 100644 --- a/packages/server/database/migrations/20190612194707-scheduledJobs.js +++ b/packages/server/database/migrations/20190612194707-scheduledJobs.js @@ -1,4 +1,4 @@ -import SlackNotification from '../types/SlackNotification' +import generateUID from '../../generateUID' exports.up = async (r) => { try { @@ -7,23 +7,14 @@ exports.up = async (r) => { console.log(e) } try { - await r - .table('ScheduledJob') - .indexCreate('type') - .run() - await r - .table('ScheduledJob') - .indexCreate('runAt') - .run() + await r.table('ScheduledJob').indexCreate('type').run() + await r.table('ScheduledJob').indexCreate('runAt').run() } catch (e) { console.log(e) } try { - const slackUsers = await r - .table('SlackAuth') - .filter({isActive: true}) - .run() + const slackUsers = await r.table('SlackAuth').filter({isActive: true}).run() const slackNotifications = await r .table('SlackNotification') .pluck('userId', 'teamId', 'channelId') @@ -31,12 +22,13 @@ exports.up = async (r) => { .run() const stageCompleteNotifications = slackUsers.map((slackUser) => { const {userId, teamId} = slackUser - return new SlackNotification({ + return { userId, teamId, channelId: null, - event: 'MEETING_STAGE_TIME_LIMIT_END' - }) + event: 'MEETING_STAGE_TIME_LIMIT_END', + id: generateUID() + } }) const stageReadyNotifications = slackUsers.map((slackUser) => { const {userId, teamId} = slackUser @@ -68,10 +60,7 @@ exports.up = async (r) => { .filter({event: 'meetingStageTimeLimit'}) .update({event: 'MEETING_STAGE_TIME_LIMIT_END'}) .run() - await r - .table('SlackNotification') - .insert(records) - .run() + await r.table('SlackNotification').insert(records).run() await r(authUpdates) .forEach((auth) => { return r diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index 18de76dcfcd..b45f322d350 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -1,6 +1,4 @@ import {MasterPool, r} from 'rethinkdb-ts' -import SlackAuth from '../database/types/SlackAuth' -import SlackNotification from '../database/types/SlackNotification' import TeamInvitation from '../database/types/TeamInvitation' import {AnyMeeting, AnyMeetingTeamMember} from '../postgres/types/Meeting' import getRethinkConfig from './getRethinkConfig' @@ -74,14 +72,6 @@ export type RethinkSchema = { type: PushInvitation index: 'userId' } - SlackAuth: { - type: SlackAuth - index: 'teamId' | 'userId' - } - SlackNotification: { - type: SlackNotification - index: 'teamId' | 'userId' - } Task: { type: Task index: diff --git a/packages/server/database/types/SlackNotification.ts b/packages/server/database/types/SlackNotification.ts deleted file mode 100644 index f70a28c1508..00000000000 --- a/packages/server/database/types/SlackNotification.ts +++ /dev/null @@ -1,44 +0,0 @@ -import generateUID from '../../generateUID' - -export const slackNotificationEventTypeLookup = { - meetingStart: 'team', - meetingEnd: 'team', - MEETING_STAGE_TIME_LIMIT_END: 'member', - MEETING_STAGE_TIME_LIMIT_START: 'team', - TOPIC_SHARED: 'member', - STANDUP_RESPONSE_SUBMITTED: 'team' -} as const - -export type SlackNotificationEvent = keyof typeof slackNotificationEventTypeLookup -export type SlackNotificationEventEnum = - | 'MEETING_STAGE_TIME_LIMIT_END' - | 'MEETING_STAGE_TIME_LIMIT_START' - | 'meetingEnd' - | 'meetingStart' - | 'TOPIC_SHARED' - | 'STANDUP_RESPONSE_SUBMITTED' - -interface Input { - event: SlackNotificationEvent - channelId: string | null | undefined - teamId: string - userId: string - id?: string -} - -export default class SlackNotification { - id: string - event: SlackNotificationEvent - channelId: string | null - teamId: string - userId: string - - constructor(input: Input) { - const {event, channelId, teamId, userId, id} = input - this.id = id || generateUID() - this.event = event - this.channelId = channelId || null - this.teamId = teamId - this.userId = userId - } -} diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 50f21bd06d4..2ff2918b551 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -5,6 +5,7 @@ import { selectOrganizations, selectRetroReflections, selectSlackAuths, + selectSlackNotifications, selectSuggestedAction, selectTeams, selectTemplateDimension, @@ -196,3 +197,11 @@ export const agendaItemsByMeetingId = foreignKeyLoaderMaker( export const slackAuthByUserId = foreignKeyLoaderMaker('slackAuths', 'userId', async (userIds) => { return selectSlackAuths().where('userId', 'in', userIds).execute() }) + +export const slackNotificationsByTeamId = foreignKeyLoaderMaker( + 'slackNotifications', + 'teamId', + async (teamIds) => { + return selectSlackNotifications().where('teamId', 'in', teamIds).execute() + } +) diff --git a/packages/server/dataloader/integrationAuthLoaders.ts b/packages/server/dataloader/integrationAuthLoaders.ts index 582a66a7aab..6fda9b74f56 100644 --- a/packages/server/dataloader/integrationAuthLoaders.ts +++ b/packages/server/dataloader/integrationAuthLoaders.ts @@ -1,8 +1,5 @@ import DataLoader from 'dataloader' import TeamMemberIntegrationAuthId from '../../client/shared/gqlIds/TeamMemberIntegrationAuthId' -import getRethink from '../database/rethinkDriver' -import SlackAuth from '../database/types/SlackAuth' -import SlackNotification, {SlackNotificationEvent} from '../database/types/SlackNotification' import errorFilter from '../graphql/errorFilter' import isValid from '../graphql/isValid' import getKysely from '../postgres/getKysely' @@ -14,6 +11,8 @@ import getIntegrationProvidersByIds, { TIntegrationProvider } from '../postgres/queries/getIntegrationProvidersByIds' import getTeamMemberIntegrationAuth from '../postgres/queries/getTeamMemberIntegrationAuth' +import {selectSlackNotifications} from '../postgres/select' +import {SlackAuth, SlackNotification} from '../postgres/types' import NullableDataLoader from './NullableDataLoader' import RootDataLoader from './RootDataLoader' @@ -166,52 +165,41 @@ export const bestTeamIntegrationAuths = (parent: RootDataLoader) => { interface TeamNotificationEvent { teamId: string - event: SlackNotificationEvent + event: SlackNotification['event'] } export type SlackNotificationAuth = SlackNotification & {auth: SlackAuth} export const slackNotificationsByTeamIdAndEvent = (parent: RootDataLoader) => { return new DataLoader(async (keys) => { - const r = await getRethink() - const notifications = await r - .expr(keys) - .concatMap((key) => - r - .table('SlackNotification') - .getAll(key('teamId'), {index: 'teamId'}) - .filter({event: key('event')}) + const res = await selectSlackNotifications() + .where(({eb, refTuple, tuple}) => + eb( + refTuple('teamId', 'event'), + 'in', + keys.map((key) => tuple(key.teamId, key.event)) + ) ) - .coerceTo('array') - .run() - - const distinctChannelNotifications = keys.map((key) => { - const usedChannelIds = new Set() - return notifications.filter((notification) => { - const {teamId, event, channelId} = notification - if (teamId !== key.teamId || event !== key.event) return false - if (!channelId || usedChannelIds.has(channelId)) return false - usedChannelIds.add(channelId) - return true - }) - }) - const notificationUserIds = distinctChannelNotifications.flatMap((not) => - not.map(({userId}) => userId) - ) - const userSlackAuths = (await parent.get('slackAuthByUserId').loadMany(notificationUserIds)) + .execute() + const userIds = [...new Set(res.map(({userId}) => userId))] + const userSlackAuths = (await parent.get('slackAuthByUserId').loadMany(userIds)) .filter(errorFilter) .flat() - return distinctChannelNotifications.map( - (notifications) => - notifications - .map((notification) => ({ + return keys.map((key) => { + return res + .filter((doc) => doc.teamId === key.teamId && doc.event === key.event) + .map((notification) => { + const auth = userSlackAuths.find( + (auth) => auth.userId === notification.userId && auth.teamId === notification.teamId + ) + if (!auth) return null + return { ...notification, - auth: userSlackAuths.find( - ({userId, teamId}) => userId === notification.userId && teamId === notification.teamId - ) - })) - .filter(({auth}) => !!auth) as SlackNotificationAuth[] - ) + auth + } + }) + .filter(isValid) + }) }) } diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 3c4d1696d7e..d514172dde0 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -11,6 +11,7 @@ import { selectOrganizations, selectRetroReflections, selectSlackAuths, + selectSlackNotifications, selectSuggestedAction, selectTeamPromptResponses, selectTeams, @@ -100,3 +101,7 @@ export const agendaItems = primaryKeyLoaderMaker((ids: readonly string[]) => { export const slackAuths = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectSlackAuths().where('id', 'in', ids).execute() }) + +export const slackNotifications = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectSlackNotifications().where('id', 'in', ids).execute() +}) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index 4a4e1d3a8f1..3f9392197a6 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -89,15 +89,6 @@ export const meetingMembersByUserId = new RethinkForeignKeyLoaderMaker( } ) -export const slackNotificationsByTeamId = new RethinkForeignKeyLoaderMaker( - 'slackNotifications', - 'teamId', - async (teamIds) => { - const r = await getRethink() - return r.table('SlackNotification').getAll(r.args(teamIds), {index: 'teamId'}).run() - } -) - export const tasksByDiscussionId = new RethinkForeignKeyLoaderMaker( 'tasks', 'discussionId', diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index fbb1e5c1efe..48019c4fcd3 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -10,6 +10,5 @@ export const meetingMembers = new RethinkPrimaryKeyLoaderMaker('MeetingMember') export const newMeetings = new RethinkPrimaryKeyLoaderMaker('NewMeeting') export const newFeatures = new RethinkPrimaryKeyLoaderMaker('NewFeature') export const notifications = new RethinkPrimaryKeyLoaderMaker('Notification') -export const slackNotifications = new RethinkPrimaryKeyLoaderMaker('SlackNotification') export const tasks = new RethinkPrimaryKeyLoaderMaker('Task') export const teamInvitations = new RethinkPrimaryKeyLoaderMaker('TeamInvitation') diff --git a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts index 4e0701bfc45..b96aec374fc 100644 --- a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts @@ -4,9 +4,8 @@ import findStageById from 'parabol-client/utils/meetings/findStageById' import {phaseLabelLookup} from 'parabol-client/utils/meetings/lookups' import appOrigin from '../../../../appOrigin' import Meeting from '../../../../database/types/Meeting' -import {SlackNotificationEventEnum as EventEnum} from '../../../../database/types/SlackNotification' import {IntegrationProviderMSTeams as IIntegrationProviderMSTeams} from '../../../../postgres/queries/getIntegrationProvidersByIds' -import {Team} from '../../../../postgres/types' +import {SlackNotification, Team} from '../../../../postgres/types' import IUser from '../../../../postgres/types/IUser' import {MeetingTypeEnum} from '../../../../postgres/types/Meeting' import MSTeamsServerManager from '../../../../utils/MSTeamsServerManager' @@ -18,7 +17,7 @@ import {createNotifier} from './Notifier' import getSummaryText from './getSummaryText' const notifyMSTeams = async ( - event: EventEnum, + event: SlackNotification['event'], webhookUrl: string, user: IUser, teamId: string, diff --git a/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts index 61688c3160a..62eb866722c 100644 --- a/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts @@ -5,9 +5,8 @@ import findStageById from 'parabol-client/utils/meetings/findStageById' import {phaseLabelLookup} from 'parabol-client/utils/meetings/lookups' import appOrigin from '../../../../appOrigin' import Meeting from '../../../../database/types/Meeting' -import {SlackNotificationEventEnum as EventEnum} from '../../../../database/types/SlackNotification' import {IntegrationProviderMattermost as IIntegrationProviderMattermost} from '../../../../postgres/queries/getIntegrationProvidersByIds' -import {Team} from '../../../../postgres/types' +import {SlackNotification, Team} from '../../../../postgres/types' import IUser from '../../../../postgres/types/IUser' import {MeetingTypeEnum} from '../../../../postgres/types/Meeting' import MattermostServerManager from '../../../../utils/MattermostServerManager' @@ -28,7 +27,7 @@ import { type IntegrationProviderMattermost = IIntegrationProviderMattermost & {teamId: string} const notifyMattermost = async ( - event: EventEnum, + event: SlackNotification['event'], webhookUrl: string, user: IUser, teamId: string, diff --git a/packages/server/graphql/mutations/helpers/notifications/Notifier.ts b/packages/server/graphql/mutations/helpers/notifications/Notifier.ts index 7010db338c6..6ba69226bed 100644 --- a/packages/server/graphql/mutations/helpers/notifications/Notifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/Notifier.ts @@ -1,5 +1,5 @@ -import {SlackNotificationEvent} from '../../../../database/types/SlackNotification' import {getTeamPromptResponsesByMeetingId} from '../../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {SlackNotification} from '../../../../postgres/types' import {DataLoaderWorker} from '../../../graphql' import {NotificationIntegration, NotifyResponse} from './NotificationIntegrationHelper' @@ -41,7 +41,7 @@ export type NotificationIntegrationLoader = ( dataLoader: DataLoaderWorker, teamId: string, userId: string, - event: SlackNotificationEvent + event: SlackNotification['event'] ) => Promise async function loadMeetingTeam(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { diff --git a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts index ca2524fb233..0e73db46043 100644 --- a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts @@ -9,10 +9,10 @@ import appOrigin from '../../../../appOrigin' import getRethink, {RethinkSchema} from '../../../../database/rethinkDriver' import Meeting from '../../../../database/types/Meeting' import SlackAuth from '../../../../database/types/SlackAuth' -import {SlackNotificationEvent} from '../../../../database/types/SlackNotification' import {SlackNotificationAuth} from '../../../../dataloader/integrationAuthLoaders' +import getKysely from '../../../../postgres/getKysely' import {getTeamPromptResponsesByMeetingId} from '../../../../postgres/queries/getTeamPromptResponsesByMeetingIds' -import {Team, TeamPromptResponse} from '../../../../postgres/types' +import {SlackNotification, Team, TeamPromptResponse} from '../../../../postgres/types' import User from '../../../../postgres/types/IUser' import {AnyMeeting, MeetingTypeEnum} from '../../../../postgres/types/Meeting' import SlackServerManager from '../../../../utils/SlackServerManager' @@ -26,7 +26,7 @@ import {createNotifier} from './Notifier' import getSummaryText from './getSummaryText' import {makeButtons, makeHeader, makeSection, makeSections} from './makeSlackBlocks' -type SlackNotification = { +type SlackNotificationMessage = { title: string blocks: string | Array<{type: string}> } @@ -45,15 +45,12 @@ const handleError = async ( if ('error' in res) { const {error} = res if (error === 'channel_not_found') { - const r = await getRethink() - await r - .table('SlackNotification') - .getAll(teamId, {index: 'teamId'}) - .filter({channelId}) - .update({ - channelId: null - }) - .run() + await getKysely() + .updateTable('SlackNotification') + .set({channelId: null}) + .where('teamId', '=', teamId) + .where('channelId', '=', channelId) + .execute() return { error: new Error('channel_not_found') } @@ -77,7 +74,7 @@ const handleError = async ( const notifySlack = async ( notificationChannel: NotificationChannel, - event: SlackNotificationEvent, + event: SlackNotification['event'], teamId: string, user: User, slackMessage: string | Array<{type: string}>, @@ -145,7 +142,7 @@ const makeTeamPromptStartMeetingNotification = ( team: Team, meeting: Meeting, meetingUrl: string -): SlackNotification => { +): SlackNotificationMessage => { const title = `*${meeting.name}* is open :speech_balloon: ` const blocks = [ makeSection(title), @@ -160,7 +157,7 @@ const makeGenericStartMeetingNotification = ( team: Team, meeting: Meeting, meetingUrl: string -): SlackNotification => { +): SlackNotificationMessage => { const title = 'Meeting started :wave: ' const blocks = [ makeSection(title), @@ -173,7 +170,7 @@ const makeGenericStartMeetingNotification = ( const makeStartMeetingNotificationLookup: Record< MeetingTypeEnum, - (team: Team, meeting: Meeting, meetingUrl: string) => SlackNotification + (team: Team, meeting: Meeting, meetingUrl: string) => SlackNotificationMessage > = { teamPrompt: makeTeamPromptStartMeetingNotification, action: makeGenericStartMeetingNotification, @@ -539,7 +536,7 @@ async function getSlack( dataLoader: DataLoaderWorker, teamId: string, _userId: string, - event: SlackNotificationEvent + event: SlackNotification['event'] ) { const notifications = await dataLoader .get('slackNotificationsByTeamIdAndEvent') diff --git a/packages/server/graphql/mutations/helpers/removeSlackAuths.ts b/packages/server/graphql/mutations/helpers/removeSlackAuths.ts index 32481e4f741..72ea5a51857 100644 --- a/packages/server/graphql/mutations/helpers/removeSlackAuths.ts +++ b/packages/server/graphql/mutations/helpers/removeSlackAuths.ts @@ -1,4 +1,3 @@ -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' const removeSlackAuths = async ( @@ -6,26 +5,23 @@ const removeSlackAuths = async ( teamIds: string | string[], removeToken?: boolean ) => { - const r = await getRethink() const teamIdsArr = Array.isArray(teamIds) ? teamIds : [teamIds] if (teamIdsArr.length === 0) { return {authIds: null, error: 'No teams provided'} } const inactiveAuths = await getKysely() + .with('RemoveNotifications', (qb) => + qb + .deleteFrom('SlackNotification') + .where('userId', '=', userId) + .where('teamId', 'in', teamIdsArr) + ) .updateTable('SlackAuth') .set({botAccessToken: removeToken ? null : undefined, isActive: false}) .where('teamId', 'in', teamIdsArr) .where('userId', '=', userId) .returning('id') .execute() - - await r({ - notifications: r - .table('SlackNotification') - .getAll(r.args(teamIdsArr), {index: 'teamId'}) - .filter({userId}) - .delete() - }).run() const authIds = inactiveAuths.map(({id}) => id) return {authIds, error: null} } diff --git a/packages/server/graphql/private/mutations/hardDeleteUser.ts b/packages/server/graphql/private/mutations/hardDeleteUser.ts index cb7a05dc94b..0771f18b95c 100644 --- a/packages/server/graphql/private/mutations/hardDeleteUser.ts +++ b/packages/server/graphql/private/mutations/hardDeleteUser.ts @@ -94,10 +94,6 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( .filter((row: RValue) => row('createdBy').eq(userIdToDelete)) .delete(), pushInvitation: r.table('PushInvitation').getAll(userIdToDelete, {index: 'userId'}).delete(), - slackNotification: r - .table('SlackNotification') - .getAll(userIdToDelete, {index: 'userId'}) - .delete(), invitedByTeamInvitation: r .table('TeamInvitation') .getAll(r.args(teamIds), {index: 'teamId'}) diff --git a/packages/server/graphql/private/mutations/removeAllSlackAuths.ts b/packages/server/graphql/private/mutations/removeAllSlackAuths.ts index 6350b5a903a..117397c36a1 100644 --- a/packages/server/graphql/private/mutations/removeAllSlackAuths.ts +++ b/packages/server/graphql/private/mutations/removeAllSlackAuths.ts @@ -1,18 +1,17 @@ -import getRethink from '../../../database/rethinkDriver' +import {sql} from 'kysely' import getKysely from '../../../postgres/getKysely' import {MutationResolvers} from '../resolverTypes' const removeAllSlackAuths: MutationResolvers['removeAllSlackAuths'] = async () => { - const r = await getRethink() - + const pg = getKysely() // RESOLUTION const [slackAuthRes, slackNotificationRes] = await Promise.all([ - getKysely() + pg .updateTable('SlackAuth') .set({botAccessToken: null, isActive: false}) .returning('id') .execute(), - r.table('SlackNotification').delete().run() + sql`TRUNCATE TABLE "SlackNotification"`.execute(pg) ]) const data = { slackAuthRes: JSON.stringify(slackAuthRes), diff --git a/packages/server/graphql/public/mutations/addSlackAuth.ts b/packages/server/graphql/public/mutations/addSlackAuth.ts index 63a650989de..26bb7346478 100644 --- a/packages/server/graphql/public/mutations/addSlackAuth.ts +++ b/packages/server/graphql/public/mutations/addSlackAuth.ts @@ -1,6 +1,4 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' -import SlackNotification, {SlackNotificationEvent} from '../../../database/types/SlackNotification' import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' import SlackServerManager from '../../../utils/SlackServerManager' @@ -9,6 +7,7 @@ import {getUserId, isTeamMember} from '../../../utils/authorization' import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' import {MutationResolvers} from '../resolverTypes' +import {slackNotificationEventTypeLookup} from '../types/SlackNotification' export const upsertNotifications = async ( viewerId: string, @@ -16,34 +15,27 @@ export const upsertNotifications = async ( teamChannelId: string, channelId: string ) => { - const r = await getRethink() - const existingNotifications = await r - .table('SlackNotification') - .getAll(viewerId, {index: 'userId'}) - .filter({teamId}) - .run() - const teamEvents = [ - 'meetingStart', - 'meetingEnd', - 'MEETING_STAGE_TIME_LIMIT_START', - 'STANDUP_RESPONSE_SUBMITTED' - ] as SlackNotificationEvent[] - const userEvents = ['MEETING_STAGE_TIME_LIMIT_END'] as SlackNotificationEvent[] - const events = [...teamEvents, ...userEvents] - const upsertableNotifications = events.map((event) => { - const existingNotification = existingNotifications.find( - (notification) => notification.event === event - ) - return new SlackNotification({ - event, - // the existing notification channel could be a bad one (legacy reasons, bad means not public or not @Parabol) - channelId: teamEvents.includes(event) ? teamChannelId : channelId, + const pg = getKysely() + const upsertableNotifications = Object.keys(slackNotificationEventTypeLookup).map((event) => { + const type = + slackNotificationEventTypeLookup[event as keyof typeof slackNotificationEventTypeLookup] + return { + id: generateUID(), + event: event as keyof typeof slackNotificationEventTypeLookup, teamId, userId: viewerId, - id: (existingNotification && existingNotification.id) || undefined - }) + channelId: type === 'team' ? teamChannelId : channelId + } }) - await r.table('SlackNotification').insert(upsertableNotifications, {conflict: 'replace'}).run() + await pg + .insertInto('SlackNotification') + .values(upsertableNotifications) + .onConflict((oc) => + oc.columns(['teamId', 'userId', 'event']).doUpdateSet((eb) => ({ + channelId: eb.ref('excluded.channelId') + })) + ) + .execute() } const upsertAuth = async ( diff --git a/packages/server/graphql/public/mutations/setSlackNotification.ts b/packages/server/graphql/public/mutations/setSlackNotification.ts index 6cddacc36b1..6c37505d992 100644 --- a/packages/server/graphql/public/mutations/setSlackNotification.ts +++ b/packages/server/graphql/public/mutations/setSlackNotification.ts @@ -1,7 +1,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' -import {RDatum} from '../../../database/stricterR' -import SlackNotification from '../../../database/types/SlackNotification' +import generateUID from '../../../generateUID' +import getKysely from '../../../postgres/getKysely' import {getUserId, isTeamMember} from '../../../utils/authorization' import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' @@ -15,7 +14,7 @@ const setSlackNotification: MutationResolvers['setSlackNotification'] = async ( const viewerId = getUserId(authToken) const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} - const r = await getRethink() + const pg = getKysely() // AUTH if (!isTeamMember(authToken, teamId)) { @@ -31,27 +30,24 @@ const setSlackNotification: MutationResolvers['setSlackNotification'] = async ( } // RESOLUTION - const existingNotifications = await r - .table('SlackNotification') - .getAll(viewerId, {index: 'userId'}) - .filter({teamId}) - .filter((row: RDatum) => r(slackNotificationEvents).contains(row('event'))) - .run() - - const notifications = slackNotificationEvents.map((event) => { - const existingNotification = existingNotifications.find( - (notification) => notification.event === event + const notifications = slackNotificationEvents.map((event) => ({ + event, + channelId: slackChannelId, + teamId, + userId: viewerId, + id: generateUID() + })) + const results = await pg + .insertInto('SlackNotification') + .values(notifications) + .onConflict((oc) => + oc.columns(['teamId', 'userId', 'event']).doUpdateSet((eb) => ({ + channelId: eb.ref('excluded.channelId') + })) ) - return new SlackNotification({ - event, - channelId: slackChannelId, - teamId, - userId: viewerId, - id: (existingNotification && existingNotification.id) || undefined - }) - }) - await r.table('SlackNotification').insert(notifications, {conflict: 'replace'}).run() - const slackNotificationIds = notifications.map(({id}) => id) + .returning('id') + .execute() + const slackNotificationIds = results.map(({id}) => id) const data = {userId: viewerId, slackNotificationIds} publish(SubscriptionChannel.TEAM, teamId, 'SetSlackNotificationPayload', data, subOptions) return data diff --git a/packages/server/graphql/public/types/SlackNotification.ts b/packages/server/graphql/public/types/SlackNotification.ts index 38a92e72dac..c44b45faa84 100644 --- a/packages/server/graphql/public/types/SlackNotification.ts +++ b/packages/server/graphql/public/types/SlackNotification.ts @@ -1,6 +1,14 @@ -import {slackNotificationEventTypeLookup} from '../../../database/types/SlackNotification' import {SlackNotificationResolvers} from '../resolverTypes' +export const slackNotificationEventTypeLookup = { + meetingStart: 'team', + meetingEnd: 'team', + MEETING_STAGE_TIME_LIMIT_END: 'member', + MEETING_STAGE_TIME_LIMIT_START: 'team', + TOPIC_SHARED: 'member', + STANDUP_RESPONSE_SUBMITTED: 'team' +} as const + const SlackNotification: SlackNotificationResolvers = { eventType: ({event}) => { return slackNotificationEventTypeLookup[event] diff --git a/packages/server/postgres/migrations/1724358892236_SlackNotification.ts b/packages/server/postgres/migrations/1724358892236_SlackNotification.ts new file mode 100644 index 00000000000..f45b4be1c93 --- /dev/null +++ b/packages/server/postgres/migrations/1724358892236_SlackNotification.ts @@ -0,0 +1,79 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {Client} from 'pg' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' +import getPgConfig from '../getPgConfig' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql` + DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'SlackNotificationEventEnum') THEN + CREATE TYPE "SlackNotificationEventEnum" AS ENUM ( + 'meetingStart', + 'meetingEnd', + 'MEETING_STAGE_TIME_LIMIT_END', + 'MEETING_STAGE_TIME_LIMIT_START', + 'TOPIC_SHARED', + 'STANDUP_RESPONSE_SUBMITTED' + ); + END IF; + CREATE TABLE IF NOT EXISTS "SlackNotification" ( + "id" VARCHAR(100) PRIMARY KEY, + "event" "SlackNotificationEventEnum" NOT NULL, + "channelId" VARCHAR(100), + "teamId" VARCHAR(100) NOT NULL, + "userId" VARCHAR(100) NOT NULL, + CONSTRAINT "fk_teamId" + FOREIGN KEY("teamId") + REFERENCES "Team"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_userId" + FOREIGN KEY("userId") + REFERENCES "User"("id") + ON DELETE CASCADE, + UNIQUE("teamId", "userId", "event") + ); + CREATE INDEX IF NOT EXISTS "idx_SlackNotification_teamId" ON "SlackNotification" ("teamId"); + CREATE INDEX IF NOT EXISTS "idx_SlackNotification_userId" ON "SlackNotification" ("userId"); + END $$; +`.execute(pg) + + const rNotifications = await r.table('SlackNotification').coerceTo('array').run() + + await Promise.all( + rNotifications.map(async (notification) => { + const {channelName, ...pgVal} = notification + try { + return await pg + .insertInto('SlackNotification') + .values(pgVal) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_teamId' || e.constraint === 'fk_userId') { + console.log(`Skipping ${notification.id} because it has no user/team`) + return + } + console.log(e, notification) + } + }) + ) +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TYPE "SlackNotificationEventEnum" CASCADE; + DROP TABLE IF EXISTS "SlackNotification"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index 308709c8454..481164d8916 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -207,3 +207,6 @@ export const selectMeetingSettings = () => export const selectAgendaItems = () => getKysely().selectFrom('AgendaItem').selectAll() export const selectSlackAuths = () => getKysely().selectFrom('SlackAuth').selectAll() + +export const selectSlackNotifications = () => + getKysely().selectFrom('SlackNotification').selectAll() diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index ca262a72b5e..a3abdf24161 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -10,6 +10,8 @@ import { selectMeetingSettings, selectOrganizations, selectRetroReflections, + selectSlackAuths, + selectSlackNotifications, selectSuggestedAction, selectTeamPromptResponses, selectTeams, @@ -47,3 +49,7 @@ export type PokerMeetingSettings = MeetingSettings & {meetingType: 'poker'} export type RetrospectiveMeetingSettings = MeetingSettings & {meetingType: 'retrospective'} export type AgendaItem = ExtractTypeFromQueryBuilderSelect + +export type SlackAuth = ExtractTypeFromQueryBuilderSelect + +export type SlackNotification = ExtractTypeFromQueryBuilderSelect diff --git a/packages/server/utils/analytics/analytics.ts b/packages/server/utils/analytics/analytics.ts index 9424b16a716..0476981a555 100644 --- a/packages/server/utils/analytics/analytics.ts +++ b/packages/server/utils/analytics/analytics.ts @@ -7,12 +7,11 @@ import Meeting from '../../database/types/Meeting' import MeetingMember from '../../database/types/MeetingMember' import MeetingRetrospective from '../../database/types/MeetingRetrospective' import MeetingTemplate from '../../database/types/MeetingTemplate' -import {SlackNotificationEventEnum} from '../../database/types/SlackNotification' import {TaskServiceEnum} from '../../database/types/Task' import {DataLoaderWorker} from '../../graphql/graphql' import {ModifyType, ReactableEnum} from '../../graphql/public/resolverTypes' import {IntegrationProviderServiceEnumType} from '../../graphql/types/IntegrationProviderServiceEnum' -import {TeamPromptResponse, TemplateScale} from '../../postgres/types' +import {SlackNotification, TeamPromptResponse, TemplateScale} from '../../postgres/types' import {MeetingTypeEnum} from '../../postgres/types/Meeting' import {MeetingSeries} from '../../postgres/types/MeetingSeries' import {AmplitudeAnalytics} from './amplitude/AmplitudeAnalytics' @@ -636,7 +635,7 @@ class Analytics { mattermostNotificationSent = ( user: AnalyticsUser, teamId: string, - notificationEvent: SlackNotificationEventEnum + notificationEvent: SlackNotification['event'] ) => { this.track(user, 'Mattermost notification sent', {teamId, notificationEvent}) } @@ -648,7 +647,7 @@ class Analytics { teamsNotificationSent = ( user: AnalyticsUser, teamId: string, - notificationEvent: SlackNotificationEventEnum + notificationEvent: SlackNotification['event'] ) => { this.track(user, 'MSTeams notification sent', {teamId, notificationEvent}) } @@ -668,7 +667,7 @@ class Analytics { slackNotificationSent = ( user: AnalyticsUser, teamId: string, - notificationEvent: SlackNotificationEventEnum, + notificationEvent: SlackNotification['event'], reflectionGroupId?: string ) => { this.track(user, 'Slack notification sent', {teamId, notificationEvent, reflectionGroupId}) From a1817be6c58a32625c83d21f792ec5c1feaf837e Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 27 Aug 2024 11:48:07 -0700 Subject: [PATCH 427/529] chore(release): release v7.44.0 (#10156) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 46e2b5b3b15..97af9affd7b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.43.8" + ".": "7.44.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 585427aa969..ec7d9e5d9f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.44.0](https://github.com/ParabolInc/parabol/compare/v7.43.8...v7.44.0) (2024-08-27) + + +### Added + +* upgrade suggest groups openai models ([#10153](https://github.com/ParabolInc/parabol/issues/10153)) ([34d5194](https://github.com/ParabolInc/parabol/commit/34d519467dfcfdfbc5c13781ca257917faf9eb1a)) + + +### Changed + +* **rethinkdb:** SlackAuth ([#10154](https://github.com/ParabolInc/parabol/issues/10154)) ([420f072](https://github.com/ParabolInc/parabol/commit/420f07270177e36a837f138eb203f2b6b56fd730)) +* **rethinkdb:** SlackNotification ([#10163](https://github.com/ParabolInc/parabol/issues/10163)) ([1043859](https://github.com/ParabolInc/parabol/commit/1043859fe2b09dde80786271897f49276cd5aba7)) + ## [7.43.8](https://github.com/ParabolInc/parabol/compare/v7.43.7...v7.43.8) (2024-08-22) diff --git a/package.json b/package.json index 679b0e156af..5a029ec6f4b 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.43.8", + "version": "7.44.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index f7c4f8d6f5e..c8d856a4e45 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.43.8", + "version": "7.44.0", "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.43.8" + "parabol-server": "7.44.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 8f35e3d3d8e..88ad238bffe 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.43.8", + "version": "7.44.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index fe567f609fe..c8a223ce119 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.43.8", + "version": "7.44.0", "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 9105c88ffc0..44c4933d7b0 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.43.8", + "version": "7.44.0", "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.43.8", - "parabol-server": "7.43.8", + "parabol-client": "7.44.0", + "parabol-server": "7.44.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 2ae859d6bf8..b86d686091c 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.43.8", + "version": "7.44.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index cbfdbfecba7..d01fc48448d 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.43.8", + "version": "7.44.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.43.8", + "parabol-client": "7.44.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 089a537899ac2ca9e71906b570146a1564233b1c Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Wed, 28 Aug 2024 09:50:21 +0100 Subject: [PATCH 428/529] feat: add ai summary to demo (#10160) --- packages/client/modules/demo/initDB.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/modules/demo/initDB.ts b/packages/client/modules/demo/initDB.ts index f719a3f9270..aa9dfc85899 100644 --- a/packages/client/modules/demo/initDB.ts +++ b/packages/client/modules/demo/initDB.ts @@ -527,6 +527,7 @@ const initNewMeeting = ( votesRemaining: teamMembers.length * 5, phases: initPhases() as any[], summarySentAt: null, + summary: `The team are feeling the strain of too many meetings and over-packed sprints, which is stifling creativity, especially for the interns and junior staff. Clarifying processes, reducing unproductive group chats, and giving everyone more space to share ideas should help.`, totalVotes: MeetingSettingsThreshold.RETROSPECTIVE_TOTAL_VOTES_DEFAULT, maxVotesPerGroup: MeetingSettingsThreshold.RETROSPECTIVE_MAX_VOTES_PER_GROUP_DEFAULT, teamId: demoTeamId From 9c9da95a47c263611b83299699598a49c9525e9a Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 14:40:03 +0100 Subject: [PATCH 429/529] chore(release): release v7.45.0 (#10167) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 97af9affd7b..86cb919ad4c 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.44.0" + ".": "7.45.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index ec7d9e5d9f1..ccb8674fcd8 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.45.0](https://github.com/ParabolInc/parabol/compare/v7.44.0...v7.45.0) (2024-08-28) + + +### Added + +* add ai summary to demo ([#10160](https://github.com/ParabolInc/parabol/issues/10160)) ([089a537](https://github.com/ParabolInc/parabol/commit/089a537899ac2ca9e71906b570146a1564233b1c)) + ## [7.44.0](https://github.com/ParabolInc/parabol/compare/v7.43.8...v7.44.0) (2024-08-27) diff --git a/package.json b/package.json index 5a029ec6f4b..8bca55ddd77 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.44.0", + "version": "7.45.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index c8d856a4e45..cdf0380c986 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.44.0", + "version": "7.45.0", "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.44.0" + "parabol-server": "7.45.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 88ad238bffe..1c7f7f1b5e7 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.44.0", + "version": "7.45.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index c8a223ce119..91a4d002ed0 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.44.0", + "version": "7.45.0", "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 44c4933d7b0..8b792c0a608 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.44.0", + "version": "7.45.0", "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.44.0", - "parabol-server": "7.44.0", + "parabol-client": "7.45.0", + "parabol-server": "7.45.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index b86d686091c..6150fa84faf 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.44.0", + "version": "7.45.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index d01fc48448d..8758bfbeaa5 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.44.0", + "version": "7.45.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.44.0", + "parabol-client": "7.45.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From b172a2fa4e29245a916fe4046b29366b0f0b2b0e Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 28 Aug 2024 14:04:09 -0700 Subject: [PATCH 430/529] fix: consolidate org user menus (#10162) Signed-off-by: Matt Krick --- .../components/BillingLeaderActionMenu.tsx | 90 ------------ .../client/components/BillingLeaderMenu.tsx | 40 ------ packages/client/components/FlatButton.tsx | 1 + .../client/components/OrgAdminActionMenu.tsx | 92 +++++++++---- .../components/OrgBilling/BillingLeader.tsx | 101 +++----------- .../components/OrgMembers/OrgMembers.tsx | 11 -- .../components/OrgUserRow/OrgMemberRow.tsx | 128 ++---------------- packages/client/ui/Button/Button.tsx | 7 +- .../server/graphql/public/fields/invoices.ts | 18 ++- 9 files changed, 125 insertions(+), 363 deletions(-) delete mode 100644 packages/client/components/BillingLeaderActionMenu.tsx delete mode 100644 packages/client/components/BillingLeaderMenu.tsx diff --git a/packages/client/components/BillingLeaderActionMenu.tsx b/packages/client/components/BillingLeaderActionMenu.tsx deleted file mode 100644 index 79811629b26..00000000000 --- a/packages/client/components/BillingLeaderActionMenu.tsx +++ /dev/null @@ -1,90 +0,0 @@ -import graphql from 'babel-plugin-relay/macro' -import React from 'react' -import {useFragment} from 'react-relay' -import useAtmosphere from '~/hooks/useAtmosphere' -import {BillingLeaderActionMenu_organization$key} from '../__generated__/BillingLeaderActionMenu_organization.graphql' -import {BillingLeaderActionMenu_organizationUser$key} from '../__generated__/BillingLeaderActionMenu_organizationUser.graphql' -import {MenuProps} from '../hooks/useMenu' -import SetOrgUserRoleMutation from '../mutations/SetOrgUserRoleMutation' -import withMutationProps, {WithMutationProps} from '../utils/relay/withMutationProps' -import Menu from './Menu' -import MenuItem from './MenuItem' - -interface Props extends WithMutationProps { - menuProps: MenuProps - isViewerLastBillingLeader: boolean - organizationUser: BillingLeaderActionMenu_organizationUser$key - organization: BillingLeaderActionMenu_organization$key - toggleLeave: () => void - toggleRemove: () => void -} - -const BillingLeaderActionMenu = (props: Props) => { - const { - menuProps, - isViewerLastBillingLeader, - organizationUser: organizationUserRef, - submitting, - submitMutation, - onError, - onCompleted, - organization: organizationRef, - toggleLeave, - toggleRemove - } = props - const organization = useFragment( - graphql` - fragment BillingLeaderActionMenu_organization on Organization { - id - } - `, - organizationRef - ) - const organizationUser = useFragment( - graphql` - fragment BillingLeaderActionMenu_organizationUser on OrganizationUser { - role - user { - id - } - } - `, - organizationUserRef - ) - const atmosphere = useAtmosphere() - const {id: orgId} = organization - const {viewerId} = atmosphere - const {role, user} = organizationUser - const isBillingLeader = role === 'BILLING_LEADER' - const {id: userId} = user - - const setRole = - (role: 'BILLING_LEADER' | null = null) => - () => { - if (submitting) return - submitMutation() - const variables = {orgId, userId, role} - SetOrgUserRoleMutation(atmosphere, variables, {onError, onCompleted}) - } - - return ( - <> - - {isBillingLeader && !isViewerLastBillingLeader && ( - - )} - {!isBillingLeader && ( - - )} - {viewerId === userId && !isViewerLastBillingLeader && ( - - )} - {viewerId !== userId && ( - - )} - - - ) -} - -export default withMutationProps(BillingLeaderActionMenu) diff --git a/packages/client/components/BillingLeaderMenu.tsx b/packages/client/components/BillingLeaderMenu.tsx deleted file mode 100644 index 3082a87b4d1..00000000000 --- a/packages/client/components/BillingLeaderMenu.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import React from 'react' -import useAtmosphere from '~/hooks/useAtmosphere' -import {MenuProps} from '../hooks/useMenu' -import useMutationProps from '../hooks/useMutationProps' -import SetOrgUserRoleMutation from '../mutations/SetOrgUserRoleMutation' -import Menu from './Menu' -import MenuItem from './MenuItem' - -type Props = { - menuProps: MenuProps - userId: string - toggleLeave: () => void - toggleRemove: () => void - orgId: string -} - -const BillingLeaderMenu = (props: Props) => { - const {menuProps, toggleRemove, userId, toggleLeave, orgId} = props - const atmosphere = useAtmosphere() - const {onError, onCompleted, submitting, submitMutation} = useMutationProps() - const {viewerId} = atmosphere - const isViewer = viewerId === userId - - const removeBillingLeader = () => { - if (submitting) return - submitMutation() - const variables = {orgId, userId, role: null} - SetOrgUserRoleMutation(atmosphere, variables, {onError, onCompleted}) - } - - return ( - - - {isViewer && } - {!isViewer && } - - ) -} - -export default BillingLeaderMenu diff --git a/packages/client/components/FlatButton.tsx b/packages/client/components/FlatButton.tsx index 9af7443b777..aec45c11895 100644 --- a/packages/client/components/FlatButton.tsx +++ b/packages/client/components/FlatButton.tsx @@ -1,3 +1,4 @@ +// DEPRECATED use packages/client/ui/Button/Button.tsx with variant='flat' import styled from '@emotion/styled' import React, {ReactNode} from 'react' import {PALETTE} from '../styles/paletteV3' diff --git a/packages/client/components/OrgAdminActionMenu.tsx b/packages/client/components/OrgAdminActionMenu.tsx index adb94de0977..37119d67ba5 100644 --- a/packages/client/components/OrgAdminActionMenu.tsx +++ b/packages/client/components/OrgAdminActionMenu.tsx @@ -1,28 +1,27 @@ +import {MoreVert} from '@mui/icons-material' +import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import graphql from 'babel-plugin-relay/macro' import React from 'react' import {useFragment} from 'react-relay' import useAtmosphere from '~/hooks/useAtmosphere' import {OrgAdminActionMenu_organization$key} from '../__generated__/OrgAdminActionMenu_organization.graphql' import {OrgAdminActionMenu_organizationUser$key} from '../__generated__/OrgAdminActionMenu_organizationUser.graphql' -import {MenuProps} from '../hooks/useMenu' import useMutationProps from '../hooks/useMutationProps' import SetOrgUserRoleMutation from '../mutations/SetOrgUserRoleMutation' -import Menu from './Menu' -import MenuItem from './MenuItem' +import {Button} from '../ui/Button/Button' +import {Menu} from '../ui/Menu/Menu' +import {MenuContent} from '../ui/Menu/MenuContent' +import {MenuItem} from '../ui/Menu/MenuItem' interface Props { - menuProps: MenuProps - isViewerLastOrgAdmin: boolean organizationUser: OrgAdminActionMenu_organizationUser$key organization: OrgAdminActionMenu_organization$key toggleLeave: () => void toggleRemove: () => void } -const OrgAdminActionMenu = (props: Props) => { +export const OrgAdminActionMenu = (props: Props) => { const { - menuProps, - isViewerLastOrgAdmin, organizationUser: organizationUserRef, organization: organizationRef, toggleLeave, @@ -32,6 +31,12 @@ const OrgAdminActionMenu = (props: Props) => { graphql` fragment OrgAdminActionMenu_organization on Organization { id + isBillingLeader + isOrgAdmin + billingLeaders { + id + role + } } `, organizationRef @@ -49,10 +54,21 @@ const OrgAdminActionMenu = (props: Props) => { ) const atmosphere = useAtmosphere() const {onError, onCompleted, submitting, submitMutation} = useMutationProps() - const {id: orgId} = organization + const { + id: orgId, + isBillingLeader: isViewerBillingLeaderPlus, + isOrgAdmin: isViewerOrgAdmin, + billingLeaders + } = organization const {viewerId} = atmosphere const {role, user} = organizationUser const {id: userId} = user + const orgAdminCount = billingLeaders.filter( + (billingLeader) => billingLeader.role === 'ORG_ADMIN' + ).length + const canEdit = isViewerOrgAdmin || (isViewerBillingLeaderPlus && role !== 'ORG_ADMIN') + const isViewerLastOrgAdmin = isViewerOrgAdmin && orgAdminCount === 1 + const isViewerLastRole = isViewerBillingLeaderPlus && billingLeaders.length === 1 const setRole = (role: 'ORG_ADMIN' | 'BILLING_LEADER' | null = null) => @@ -66,27 +82,53 @@ const OrgAdminActionMenu = (props: Props) => { const isOrgAdmin = role === 'ORG_ADMIN' const isBillingLeader = role === 'BILLING_LEADER' const isSelf = viewerId === userId - const canRemoveSelf = isSelf && !isViewerLastOrgAdmin const roleName = role === 'ORG_ADMIN' ? 'Org Admin' : 'Billing Leader' - + const canRemoveRole = + role && + ((isViewerOrgAdmin && (!isSelf || !isViewerLastOrgAdmin)) || + (isViewerBillingLeaderPlus && isBillingLeader && (!isSelf || !isViewerLastRole))) return ( - <> - - {!isOrgAdmin && } - {!isOrgAdmin && !isBillingLeader && ( - + + + + ) + } + > + + {isViewerOrgAdmin && !isOrgAdmin && ( + {'Promote to Org Admin'} + )} + {isViewerBillingLeaderPlus && !isOrgAdmin && !isBillingLeader && ( + {'Promote to Billing Leader'} )} - {isOrgAdmin && !isSelf && ( - + {isViewerOrgAdmin && isOrgAdmin && (!isSelf || !isViewerLastOrgAdmin) && ( + {'Change to Billing Leader'} )} - {((role && !isSelf) || canRemoveSelf) && ( - + {canRemoveRole && {`Remove ${roleName} role`}} + {isSelf && + ((isOrgAdmin && !isViewerLastOrgAdmin) || + (isBillingLeader && !isViewerLastRole) || + !isViewerBillingLeaderPlus) && ( + {'Leave Organization'} + )} + {!isSelf && (isViewerOrgAdmin || (isViewerBillingLeaderPlus && !isOrgAdmin)) && ( + {'Remove from Organization'} )} - {canRemoveSelf && } - {!isSelf && } - {isSelf && !canRemoveSelf && } - - + {isSelf && + ((isOrgAdmin && isViewerLastOrgAdmin) || (isBillingLeader && isViewerLastRole)) && ( + + {'Contact support@parabol.co to be removed'} + + )} + + ) } diff --git a/packages/client/modules/userDashboard/components/OrgBilling/BillingLeader.tsx b/packages/client/modules/userDashboard/components/OrgBilling/BillingLeader.tsx index 49f7753dde2..40d40e83b76 100644 --- a/packages/client/modules/userDashboard/components/OrgBilling/BillingLeader.tsx +++ b/packages/client/modules/userDashboard/components/OrgBilling/BillingLeader.tsx @@ -5,19 +5,13 @@ import {useFragment} from 'react-relay' import {BillingLeader_orgUser$key} from '../../../../__generated__/BillingLeader_orgUser.graphql' import {BillingLeader_organization$key} from '../../../../__generated__/BillingLeader_organization.graphql' import Avatar from '../../../../components/Avatar/Avatar' -import BillingLeaderMenu from '../../../../components/BillingLeaderMenu' -import FlatButton from '../../../../components/FlatButton' -import IconLabel from '../../../../components/IconLabel' import Row from '../../../../components/Row/Row' import RowActions from '../../../../components/Row/RowActions' import RowInfo from '../../../../components/Row/RowInfo' import RowInfoHeader from '../../../../components/Row/RowInfoHeader' import RowInfoHeading from '../../../../components/Row/RowInfoHeading' import BaseTag from '../../../../components/Tag/BaseTag' -import {MenuPosition} from '../../../../hooks/useCoords' -import useMenu from '../../../../hooks/useMenu' import useModal from '../../../../hooks/useModal' -import useTooltip from '../../../../hooks/useTooltip' import lazyPreload from '../../../../utils/lazyPreload' import LeaveOrgModal from '../LeaveOrgModal/LeaveOrgModal' import RemoveFromOrgModal from '../RemoveFromOrgModal/RemoveFromOrgModal' @@ -35,21 +29,9 @@ const ActionsBlock = styled('div')({ justifyContent: 'flex-end' }) -const MenuToggleBlock = styled('div')({ - width: 32 -}) - -const StyledButton = styled(FlatButton)({ - paddingLeft: 0, - paddingRight: 0, - width: '100%' -}) - -const BillingLeaderActionMenu = lazyPreload( +const OrgAdminActionMenu = lazyPreload( () => - import( - /* webpackChunkName: 'BillingLeaderActionMenu' */ '../../../../components/BillingLeaderActionMenu' - ) + import(/* webpackChunkName: 'OrgAdminActionMenu' */ '../../../../components/OrgAdminActionMenu') ) type Props = { @@ -60,11 +42,11 @@ type Props = { } const BillingLeader = (props: Props) => { - const {billingLeaderRef, isFirstRow, billingLeaderCount, organizationRef} = props - const {togglePortal, originRef, menuPortal, menuProps} = useMenu(MenuPosition.UPPER_RIGHT) + const {billingLeaderRef, isFirstRow, organizationRef} = props const billingLeader = useFragment( graphql` fragment BillingLeader_orgUser on OrganizationUser { + ...OrgAdminActionMenu_organizationUser role user { id @@ -78,37 +60,24 @@ const BillingLeader = (props: Props) => { const organization = useFragment( graphql` fragment BillingLeader_organization on Organization { + ...OrgAdminActionMenu_organization id - isViewerBillingLeader: isBillingLeader + isOrgAdmin + isBillingLeader } `, organizationRef ) - const {id: orgId, isViewerBillingLeader} = organization const { - tooltipPortal, - openTooltip, - closeTooltip, - originRef: tooltipRef - } = useTooltip(MenuPosition.LOWER_CENTER) + id: orgId, + isOrgAdmin: isViewerOrgAdmin, + isBillingLeader: isViewerBillingLeader + } = organization const {togglePortal: toggleLeave, modalPortal: leaveModal} = useModal() const {togglePortal: toggleRemove, modalPortal: removeModal} = useModal() - const {user: billingLeaderUser} = billingLeader + const {user: billingLeaderUser, role} = billingLeader const {id: userId, preferredName, picture} = billingLeaderUser - const isViewerLastBillingLeader = isViewerBillingLeader && billingLeaderCount === 1 - const canViewMenu = !isViewerLastBillingLeader && billingLeader.role !== 'ORG_ADMIN' - - const handleClick = () => { - togglePortal() - closeTooltip() - } - - const handleMouseOver = () => { - if (!canViewMenu) { - openTooltip() - } - } - + const canEdit = isViewerOrgAdmin || (isViewerBillingLeader && role === 'BILLING_LEADER') return ( @@ -122,42 +91,14 @@ const BillingLeader = (props: Props) => { )} - - {isViewerBillingLeader && ( - - - - - {tooltipPortal( - isViewerLastBillingLeader ? ( -
- {'You need to promote another Billing Leader'} -
- {'before you can remove this role.'} -
- ) : ( -
Contact support (love@parabol.co) to remove the Org Admin role
- ) - )} -
- )} - {menuPortal( - - )} -
+ {canEdit && ( + + )}
{leaveModal()} diff --git a/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx b/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx index e56be6deb45..e9534528b3b 100644 --- a/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx +++ b/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx @@ -70,15 +70,6 @@ const OrgMembers = (props: Props) => { const {organization} = viewer if (!organization) return null const {organizationUsers, name: orgName, isBillingLeader} = organization - const billingLeaderCount = organizationUsers.edges.reduce( - (count, {node}) => - ['BILLING_LEADER', 'ORG_ADMIN'].includes(node.role ?? '') ? count + 1 : count, - 0 - ) - const orgAdminCount = organizationUsers.edges.reduce( - (count, {node}) => (['ORG_ADMIN'].includes(node.role ?? '') ? count + 1 : count), - 0 - ) const exportToCSV = async () => { const rows = organizationUsers.edges.map((orgUser, idx) => { @@ -120,8 +111,6 @@ const OrgMembers = (props: Props) => { return ( diff --git a/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx b/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx index d6b0862a091..3ee6efc2135 100644 --- a/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx +++ b/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx @@ -1,8 +1,7 @@ import styled from '@emotion/styled' import graphql from 'babel-plugin-relay/macro' -import React, {forwardRef, Ref} from 'react' +import React from 'react' import {useFragment} from 'react-relay' -import useAtmosphere from '~/hooks/useAtmosphere' import { OrgMemberRow_organization$data, OrgMemberRow_organization$key @@ -12,8 +11,6 @@ import { OrgMemberRow_organizationUser$key } from '../../../../__generated__/OrgMemberRow_organizationUser.graphql' import Avatar from '../../../../components/Avatar/Avatar' -import FlatButton, {FlatButtonProps} from '../../../../components/FlatButton' -import IconLabel from '../../../../components/IconLabel' import Row from '../../../../components/Row/Row' import RowActions from '../../../../components/Row/RowActions' import RowInfo from '../../../../components/Row/RowInfo' @@ -23,13 +20,10 @@ import RowInfoLink from '../../../../components/Row/RowInfoLink' import BaseTag from '../../../../components/Tag/BaseTag' import InactiveTag from '../../../../components/Tag/InactiveTag' import RoleTag from '../../../../components/Tag/RoleTag' -import {MenuPosition} from '../../../../hooks/useCoords' -import useMenu from '../../../../hooks/useMenu' import useModal from '../../../../hooks/useModal' import defaultUserAvatar from '../../../../styles/theme/images/avatar-user.svg' import {Breakpoint} from '../../../../types/constEnums' import lazyPreload from '../../../../utils/lazyPreload' -import withMutationProps, {WithMutationProps} from '../../../../utils/relay/withMutationProps' const AvatarBlock = styled('div')({ display: 'none', @@ -56,45 +50,15 @@ const ActionsBlock = styled('div')({ justifyContent: 'flex-end' }) -const MenuToggleBlock = styled('div')({ - marginLeft: 8, - width: '2rem' -}) - -interface Props extends WithMutationProps { - billingLeaderCount: number - orgAdminCount: number +interface Props { organizationUser: OrgMemberRow_organizationUser$key organization: OrgMemberRow_organization$key } -const StyledButton = styled(FlatButton)({ - paddingLeft: 0, - paddingRight: 0, - width: '100%' -}) - -const StyledFlatButton = styled(FlatButton)({ - paddingLeft: 16, - paddingRight: 16 -}) - -const MenuButton = forwardRef((props: FlatButtonProps, ref: Ref) => ( - - - -)) - const LeaveOrgModal = lazyPreload( () => import(/* webpackChunkName: 'LeaveOrgModal' */ '../LeaveOrgModal/LeaveOrgModal') ) -const BillingLeaderActionMenu = lazyPreload( - () => - import( - /* webpackChunkName: 'BillingLeaderActionMenu' */ '../../../../components/BillingLeaderActionMenu' - ) -) const OrgAdminActionMenu = lazyPreload( () => import(/* webpackChunkName: 'OrgAdminActionMenu' */ '../../../../components/OrgAdminActionMenu') @@ -148,70 +112,31 @@ const UserInfo: React.FC = ({ ) interface UserActionsProps { - isViewerOrgAdmin: boolean - isViewerBillingLeader: boolean - isViewerLastOrgAdmin: boolean - isViewerLastBillingLeader: boolean organization: OrgMemberRow_organization$data organizationUser: OrgMemberRow_organizationUser$data preferredName: string - viewerId: string } const UserActions: React.FC = ({ - isViewerOrgAdmin, - isViewerBillingLeader, - isViewerLastOrgAdmin, - isViewerLastBillingLeader, organizationUser, organization, - preferredName, - viewerId + preferredName }) => { - const {orgId} = organization + const {id: orgId} = organization const { - user: {userId} + user: {id: userId} } = organizationUser - const {togglePortal, originRef, menuPortal, menuProps} = useMenu(MenuPosition.UPPER_RIGHT) const {togglePortal: toggleLeave, modalPortal: leaveModal} = useModal() const {togglePortal: toggleRemove, modalPortal: removeModal} = useModal() - const actionMenuProps = { - menuProps, - originRef, - togglePortal, - toggleLeave, - toggleRemove, - isViewerLastOrgAdmin, - isViewerLastBillingLeader, - organization, - organizationUser - } - - const showLeaveButton = !isViewerOrgAdmin && !isViewerBillingLeader && viewerId === userId - return ( - {showLeaveButton && ( - - Leave Organization - - )} - {(isViewerOrgAdmin || (isViewerBillingLeader && !isViewerLastBillingLeader)) && ( - - - - )} - {isViewerOrgAdmin && menuPortal()} - {!isViewerOrgAdmin && - isViewerBillingLeader && - menuPortal()} + {leaveModal()} {removeModal( @@ -222,21 +147,12 @@ const UserActions: React.FC = ({ } const OrgMemberRow = (props: Props) => { - const atmosphere = useAtmosphere() - const { - billingLeaderCount, - orgAdminCount, - organizationUser: organizationUserRef, - organization: organizationRef - } = props + const {organizationUser: organizationUserRef, organization: organizationRef} = props const organization = useFragment( graphql` fragment OrgMemberRow_organization on Organization { - isViewerBillingLeader: isBillingLeader - isViewerOrgAdmin: isOrgAdmin - orgId: id - ...BillingLeaderActionMenu_organization + id ...OrgAdminActionMenu_organization } `, @@ -247,35 +163,26 @@ const OrgMemberRow = (props: Props) => { graphql` fragment OrgMemberRow_organizationUser on OrganizationUser { user { - userId: id + id email inactive picture preferredName } role - ...BillingLeaderActionMenu_organizationUser ...OrgAdminActionMenu_organizationUser } `, organizationUserRef ) - const {isViewerBillingLeader, isViewerOrgAdmin} = organization - const { user: {email, inactive, picture, preferredName}, role } = organizationUser - const {viewerId} = atmosphere - const isBillingLeader = role === 'BILLING_LEADER' const isOrgAdmin = role === 'ORG_ADMIN' - const isViewerLastBillingLeader = - isViewerBillingLeader && isBillingLeader && billingLeaderCount === 1 - const isViewerLastOrgAdmin = isViewerOrgAdmin && isOrgAdmin && orgAdminCount === 1 - return ( @@ -287,17 +194,12 @@ const OrgMemberRow = (props: Props) => { inactive={inactive} /> ) } -export default withMutationProps(OrgMemberRow) +export default OrgMemberRow diff --git a/packages/client/ui/Button/Button.tsx b/packages/client/ui/Button/Button.tsx index a0e3bc1e373..c93a4a4b353 100644 --- a/packages/client/ui/Button/Button.tsx +++ b/packages/client/ui/Button/Button.tsx @@ -3,12 +3,12 @@ import clsx from 'clsx' import React from 'react' import {twMerge} from 'tailwind-merge' -type Variant = 'primary' | 'secondary' | 'destructive' | 'ghost' | 'link' | 'outline' +type Variant = 'primary' | 'secondary' | 'destructive' | 'ghost' | 'link' | 'outline' | 'flat' type Size = 'sm' | 'md' | 'lg' | 'default' type Shape = 'pill' | 'circle' | 'default' const BASE_STYLES = - 'cursor-pointer inline-flex items-center justify-center whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50' + 'cursor-pointer inline-flex items-center justify-center whitespace-nowrap transition-colors focus-visible:outline-none focus-visible:ring-1 disabled:pointer-events-none disabled:opacity-50' // TODO: make sure the styles match the designs const VARIANT_STYLES: Record = { @@ -18,7 +18,8 @@ const VARIANT_STYLES: Record = { 'text-slate-900 border border-slate-400 hover:bg-slate-200 px-2.5 py-1 bg-transparent font-semibold', secondary: 'bg-sky-500 text-white hover:bg-sky-500/80 font-semibold', ghost: 'hover:opacity-80 bg-transparent font-semibold', - link: 'text-primary underline-offset-4 hover:underline' + link: 'text-primary underline-offset-4 hover:underline', + flat: 'rounded-full bg-transparent outline-none shadow-none hover:bg-slate-200 focus:bg-slate-200 active:bg-slate-200 focus-visible:ring-0' } const SIZE_STYLES: Record = { diff --git a/packages/server/graphql/public/fields/invoices.ts b/packages/server/graphql/public/fields/invoices.ts index 990d9355245..c1eee29a95e 100644 --- a/packages/server/graphql/public/fields/invoices.ts +++ b/packages/server/graphql/public/fields/invoices.ts @@ -30,7 +30,7 @@ export const invoices: NonNullable = async ( return {edges: [], pageInfo: {hasNextPage: false, hasPreviousPage: false}} const manager = getStripeManager() - const [session, upcomingInvoice, invoices] = await Promise.all([ + const [sessionRes, upcomingInvoiceRes, invoicesRes] = await Promise.allSettled([ manager.stripe.billingPortal.sessions.create({ customer: stripeId, return_url: makeAppURL(appOrigin, `me/organizations/${orgId}/billing`) @@ -38,6 +38,22 @@ export const invoices: NonNullable = async ( manager.retrieveUpcomingInvoice(stripeId), manager.listInvoices(stripeId) ]) + if ( + sessionRes.status === 'rejected' || + upcomingInvoiceRes.status === 'rejected' || + invoicesRes.status === 'rejected' + ) { + return { + edges: [], + pageInfo: { + hasNextPage: false, + hasPreviousPage: false + } + } + } + const session = sessionRes.value + const upcomingInvoice = upcomingInvoiceRes.value + const invoices = invoicesRes.value const parabolUpcomingInvoice: Invoice = { id: `upcoming_${orgId}`, periodEndAt: fromEpochSeconds(upcomingInvoice.period_end!), From 002aac201159e99d45367f44634e065566f1553f Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 28 Aug 2024 14:37:41 -0700 Subject: [PATCH 431/529] chore: bump eslint (#10170) Signed-off-by: Matt Krick --- .eslintrc.js | 30 -- eslint.config.mjs | 61 +++ package.json | 10 +- packages/chronos/.eslintrc.js | 11 - packages/chronos/package.json | 8 +- packages/client/.eslintrc.js | 27 -- .../client/{.eslintignore => .prettierignore} | 0 .../ActivityDetails/ActivityDetails.tsx | 2 +- packages/client/components/AnalyticsPage.tsx | 1 - .../client/components/Dashboard/DashModal.tsx | 21 +- .../EditorSuggestions/EditorSuggestions.tsx | 1 - packages/client/components/MeetingCard.tsx | 3 - packages/client/components/Menu.tsx | 12 +- .../client/components/RetroDiscussPhase.tsx | 3 - packages/client/components/SAMLRedirect.tsx | 4 +- .../components/SelectMeetingDropdownItem.tsx | 3 - .../components/SwipeableDashSidebar.tsx | 19 +- .../WorkDrawer/GCalIntegrationResults.tsx | 2 +- .../client/components/ViewerNotOnTeam.tsx | 31 +- packages/client/eslint.config.mjs | 38 ++ packages/client/hooks/useEditorState.ts | 4 +- packages/client/hooks/useForm.tsx | 4 +- packages/client/hooks/useInitialSafeRoute.ts | 125 +++--- packages/client/hooks/useMenu.ts | 2 +- packages/client/hooks/useTransition.ts | 6 +- packages/client/lint-staged.config.js | 7 +- .../modules/demo/ClientGraphQLServer.ts | 4 +- .../components/SummaryEmail/ExportToCSV.tsx | 6 +- .../MeetingSummaryEmail/RetroTopic.tsx | 2 +- .../spinner/components/Spinner/Spinner.tsx | 2 - .../summary/components/NewMeetingSummary.tsx | 2 +- .../team/components/NewTeamOrgPicker.tsx | 17 +- packages/client/package.json | 8 +- packages/client/tsconfig.eslint.json | 5 - packages/client/utils/makeCalendarInvites.ts | 2 - packages/client/utils/requestIdleCallback.ts | 1 - .../client/utils/smartGroup/getGroupMatrix.ts | 2 - .../utils/smartGroup/groupReflections.ts | 2 +- packages/client/validation/regex.ts | 1 - packages/embedder/.eslintrc.js | 11 - .../ai_models/helpers/fetchWithRetry.ts | 2 +- packages/embedder/lint-staged.config.js | 7 +- packages/embedder/package.json | 4 +- packages/gql-executor/.eslintrc.js | 11 - packages/integration-tests/.eslintrc.js | 15 - .../{.eslintignore => .prettierignore} | 0 packages/server/.eslintrc.js | 9 - .../server/{.eslintignore => .prettierignore} | 1 - packages/server/eslint.config.mjs | 36 ++ packages/server/graphql/composeResolvers.ts | 1 + .../mutations/helpers/importTasksForPoker.ts | 1 - .../private/mutations/processRecurrence.ts | 6 +- .../public/mutations/requestToJoinDomain.ts | 2 +- .../public/mutations/toggleSummaryEmail.ts | 2 +- .../graphql/public/types/ShareTopicSuccess.ts | 2 +- .../graphql/public/types/TeamPromptMeeting.ts | 2 +- .../public/types/TeamPromptResponseStage.ts | 2 +- .../server/graphql/types/GraphQLURLType.ts | 1 - packages/server/graphql/types/Team.ts | 2 +- packages/server/graphql/uWSAsyncHandler.ts | 2 +- packages/server/lint-staged.config.js | 7 +- packages/server/package.json | 8 +- packages/server/safetyPatchRes.ts | 1 + packages/server/tsconfig.eslint.json | 5 - packages/server/types/webpackEnv.ts | 1 - .../server/utils/AtlassianServerManager.ts | 2 +- packages/server/utils/atlassian/jiraIssues.ts | 2 +- packages/server/utils/publishInternalGQL.ts | 2 +- packages/server/utils/publishWebhookGQL.ts | 2 +- packages/server/utils/samlXMLValidator.ts | 1 + yarn.lock | 420 +++++++++--------- 71 files changed, 512 insertions(+), 547 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 eslint.config.mjs delete mode 100644 packages/chronos/.eslintrc.js delete mode 100644 packages/client/.eslintrc.js rename packages/client/{.eslintignore => .prettierignore} (100%) create mode 100644 packages/client/eslint.config.mjs delete mode 100644 packages/client/tsconfig.eslint.json delete mode 100644 packages/embedder/.eslintrc.js delete mode 100644 packages/gql-executor/.eslintrc.js delete mode 100644 packages/integration-tests/.eslintrc.js rename packages/integration-tests/{.eslintignore => .prettierignore} (100%) delete mode 100644 packages/server/.eslintrc.js rename packages/server/{.eslintignore => .prettierignore} (93%) create mode 100644 packages/server/eslint.config.mjs delete mode 100644 packages/server/tsconfig.eslint.json diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 1962c0f1b60..00000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,30 +0,0 @@ -module.exports = { - // prettier comes last to turn off all the eslint rules that conflict with it - extends: ['plugin:@typescript-eslint/recommended', 'prettier'], - plugins: ['@typescript-eslint'], - parser: '@typescript-eslint/parser', - parserOptions: { - project: './tsconfig.base.json', - ecmaVersion: 2020, - sourceType: 'module' - }, - rules: { - eqeqeq: 'error', - '@typescript-eslint/ban-ts-comment': 'off', - '@typescript-eslint/camelcase': 'off', - '@typescript-eslint/no-empty-interface': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/no-object-literal-type-assertion': 'off', - '@typescript-eslint/no-use-before-define': 'off', - '@typescript-eslint/explicit-member-accessibility': 'off', - '@typescript-eslint/interface-name-prefix': 'off', - '@typescript-eslint/no-duplicate-enum-values': 'off', - '@typescript-eslint/no-unused-vars': 'off', - '@typescript-eslint/no-var-requires': 'off', - '@typescript-eslint/no-parameter-properties': 'off', - '@typescript-eslint/no-namespace': 'off', - '@typescript-eslint/explicit-module-boundary-types': 'off' - } -} diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 00000000000..6d24810d12e --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,61 @@ +import eslint from '@eslint/js' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + eslint.configs.recommended, + ...tseslint.configs.recommendedTypeChecked, + { + ignores: ['**/*.js', '**/*.mjs'] + }, + { + languageOptions: { + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module', + projectService: true, + tsconfigRootDir: import.meta.dirname + } + }, + rules: { + eqeqeq: 'error', + 'no-extra-boolean-cast': 'off', + 'no-case-declarations': 'off', + 'no-useless-escape': 'off', + 'no-constant-binary-expression': 'off', + '@typescript-eslint/ban-ts-comment': 'off', + '@typescript-eslint/camelcase': 'off', + '@typescript-eslint/no-empty-interface': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/no-object-literal-type-assertion': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/explicit-member-accessibility': 'off', + '@typescript-eslint/interface-name-prefix': 'off', + '@typescript-eslint/no-duplicate-enum-values': 'off', + '@typescript-eslint/no-unused-vars': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-parameter-properties': 'off', + '@typescript-eslint/no-namespace': 'off', + '@typescript-eslint/explicit-module-boundary-types': 'off', + '@typescript-eslint/no-unnecessary-type-assertion': 'off', + '@typescript-eslint/no-unused-expressions': 'off', // relay fragments + '@typescript-eslint/no-unsafe-return': 'off', + '@typescript-eslint/no-unsafe-assignment': 'off', + '@typescript-eslint/no-unsafe-member-access': 'off', + '@typescript-eslint/no-unsafe-call': 'off', + '@typescript-eslint/prefer-promise-reject-errors': 'off', + '@typescript-eslint/no-floating-promises': 'off', + '@typescript-eslint/no-unsafe-argument': 'off', + '@typescript-eslint/no-unsafe-enum-comparison': 'off', + '@typescript-eslint/require-await': 'off', + '@typescript-eslint/no-redundant-type-constituents': 'off', + '@typescript-eslint/no-misused-promises': 'off', + '@typescript-eslint/no-empty-object-type': 'off', + '@typescript-eslint/no-require-imports': 'off', + '@typescript-eslint/restrict-template-expressions': 'off', + '@typescript-eslint/restrict-plus-operands': 'off', + '@typescript-eslint/unbound-method': 'off' + } + } +) diff --git a/package.json b/package.json index 8bca55ddd77..cdd0f526bd9 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,7 @@ "@babel/core": "^7.20.12", "@babel/preset-env": "^7.25.0", "@datadog/datadog-ci": "^2.33.0", + "@eslint/compat": "^1.1.1", "@graphql-codegen/add": "^5.0.0", "@graphql-codegen/cli": "^5.0.0", "@graphql-codegen/typescript": "^4.0.1", @@ -92,13 +93,13 @@ "@types/dotenv": "^6.1.1", "@types/jscodeshift": "^0.11.3", "@types/lodash.toarray": "^4.4.7", - "@typescript-eslint/eslint-plugin": "^7.4.0", - "@typescript-eslint/parser": "^7.4.0", + "@typescript-eslint/eslint-plugin": "^8.3.0", + "@typescript-eslint/parser": "^8.3.0", "autoprefixer": "^10.4.13", "babel-loader": "^9.1.2", "concurrently": "^8.0.1", "copy-webpack-plugin": "^11.0.0", - "eslint": "^8.57.0", + "eslint": "^9.9.1", "eslint-config-prettier": "^9.1.0", "graphql": "16.9.0", "html-webpack-plugin": "^5.5.0", @@ -115,7 +116,7 @@ "postcss": "^8.4.21", "postcss-loader": "^7.0.2", "prettier": "^3.2.5", - "prettier-plugin-organize-imports": "^3.2.4", + "prettier-plugin-organize-imports": "^4.0.0", "prettier-plugin-tailwindcss": "^0.5.13", "raw-loader": "^4.0.2", "relay-compiler": "^14.1.0", @@ -125,6 +126,7 @@ "terser-webpack-plugin": "^5.3.9", "ts-loader": "9.2.6", "typescript": "^5.3.3", + "typescript-eslint": "^8.3.0", "vscode-apollo-relay": "^1.5.0", "webpack": "^5.89.0", "webpack-cli": "4.9.1", diff --git a/packages/chronos/.eslintrc.js b/packages/chronos/.eslintrc.js deleted file mode 100644 index a6a5d110f1e..00000000000 --- a/packages/chronos/.eslintrc.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - extends: [ - '../../.eslintrc.js' - ], - parserOptions: { - project: './tsconfig.json', - ecmaVersion: 2020, - sourceType: 'module' - }, - "ignorePatterns": ["**/lib", "*.js"] -} diff --git a/packages/chronos/package.json b/packages/chronos/package.json index cdf0380c986..2abd538d32a 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -10,10 +10,10 @@ "url": "git+https://github.com/ParabolInc/parabol.git" }, "scripts": { - "lint": "eslint --fix . --ext .ts,.tsx", - "lint:check": "eslint . --ext .ts,.tsx", - "prettier": "prettier --config ../../.prettierrc --ignore-path ./.eslintignore --write \"**/*.{ts,tsx,graphql}\"", - "prettier:check": "prettier --config ../../.prettierrc --ignore-path ./.eslintignore --check \"**/*.{ts,tsx,graphql}\"", + "lint": "yarn lint:check --fix .", + "lint:check": "node --max_old_space_size=8192 ../../node_modules/.bin/eslint .", + "prettier": "prettier --config ../../.prettierrc --ignore-path ./.prettierignore --write \"**/*.{ts,tsx,graphql}\"", + "prettier:check": "prettier --config ../../.prettierrc --ignore-path ./.prettierignore --check \"**/*.{ts,tsx,graphql}\"", "typecheck": "yarn tsc --noEmit -p tsconfig.json" }, "bugs": { diff --git a/packages/client/.eslintrc.js b/packages/client/.eslintrc.js deleted file mode 100644 index 801c2c8b662..00000000000 --- a/packages/client/.eslintrc.js +++ /dev/null @@ -1,27 +0,0 @@ -module.exports = { - extends: ['plugin:react/recommended', '../../.eslintrc.js'], - plugins: ['@typescript-eslint', 'emotion', 'react', 'react-hooks'], - parserOptions: { - project: 'tsconfig.eslint.json', - ecmaVersion: 2020, - sourceType: 'module' - }, - rules: { - 'emotion/jsx-import': 'error', - 'emotion/no-vanilla': 'error', - 'emotion/import-from-emotion': 'error', - 'emotion/styled-import': 'error', - 'react/no-find-dom-node': 'off', - 'react/no-unescaped-entities': 'off', - 'react/display-name': 'off', - 'react/prop-types': 'off', - // "react-hooks/exhaustive-deps": "error", // Checks effect dependencies, buggy so don't use with --fix - 'react-hooks/rules-of-hooks': 'warn', // Checks rules of Hooks - 'space-before-function-paren': 'off' - }, - settings: { - react: { - version: 'detect' - } - } -} diff --git a/packages/client/.eslintignore b/packages/client/.prettierignore similarity index 100% rename from packages/client/.eslintignore rename to packages/client/.prettierignore diff --git a/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx b/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx index 3f5eeede290..1434085894e 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx @@ -70,7 +70,7 @@ const ActivityDetails = (props: Props) => { if (!activity) { return } - // eslint-disable react-hooks/rules-of-hooks -- return above violates these rules, but is just a safeguard and not normal usage + /* eslint-disable react-hooks/rules-of-hooks */ useEffect(() => { SendClientSideEvent(atmosphere, 'Viewed Template', { meetingType: activity.type, diff --git a/packages/client/components/AnalyticsPage.tsx b/packages/client/components/AnalyticsPage.tsx index 9a6478fed22..bf18f09dd94 100644 --- a/packages/client/components/AnalyticsPage.tsx +++ b/packages/client/components/AnalyticsPage.tsx @@ -133,7 +133,6 @@ const AnalyticsPage = () => { configGA() }, [ReactGA.isInitialized, atmosphere.viewerId]) - /* eslint-disable */ const {href, pathname} = location useEffect(() => { diff --git a/packages/client/components/Dashboard/DashModal.tsx b/packages/client/components/Dashboard/DashModal.tsx index 68d1c64c1ce..0355f87ac64 100644 --- a/packages/client/components/Dashboard/DashModal.tsx +++ b/packages/client/components/Dashboard/DashModal.tsx @@ -1,3 +1,4 @@ +import {keyframes} from '@emotion/core' import styled from '@emotion/styled' import React, {ReactNode} from 'react' import {DECELERATE} from '../../styles/animation' @@ -5,16 +6,16 @@ import {modalShadow} from '../../styles/elevation' import {PALETTE} from '../../styles/paletteV3' import {Radius, ZIndex} from '../../types/constEnums' -const animateIn = { - '0%': { - opacity: '0', - transform: 'translate3d(0, -50px, 0)' - }, - '100%': { - opacity: '1', - transform: 'translate3d(0, 0, 0)' +export const animateIn = keyframes` + 0% { + opacity: 0; + transform: translate3d(0, -50px, 0); } -} + 100% { + opacity: 1; + transform: translate3d(0, 0, 0); + } +` const Backdrop = styled('div')({ alignItems: 'center', @@ -34,7 +35,7 @@ const Backdrop = styled('div')({ const Modal = styled('div')({ animationIterationCount: 1, - animation: `${animateIn} 200ms ${DECELERATE}`, + animation: `${animateIn.toString()} 200ms ${DECELERATE}`, background: '#FFFFFF', borderRadius: Radius.DIALOG, boxShadow: modalShadow, diff --git a/packages/client/components/EditorSuggestions/EditorSuggestions.tsx b/packages/client/components/EditorSuggestions/EditorSuggestions.tsx index 2a8a9edcb6d..dc519af891d 100644 --- a/packages/client/components/EditorSuggestions/EditorSuggestions.tsx +++ b/packages/client/components/EditorSuggestions/EditorSuggestions.tsx @@ -44,7 +44,6 @@ const EditorSuggestions = (props: Props) => { {suggestions.map((suggestion, idx) => { return ( - // eslint-disable-next-line
{ if (!team) { // 95% sure there's a bug in relay causing this const errObj = {id: meetingId} as any - if (meeting.hasOwnProperty('team')) { - errObj.team = team - } Sentry.captureException(new Error(`Missing Team on Meeting ${JSON.stringify(errObj)}`)) return null } diff --git a/packages/client/components/Menu.tsx b/packages/client/components/Menu.tsx index eafd1aa7feb..d2512bacf7b 100644 --- a/packages/client/components/Menu.tsx +++ b/packages/client/components/Menu.tsx @@ -66,15 +66,9 @@ const Menu = forwardRef((props: Props, ref: any) => { handleKeyDown })) - useEffect( - () => { - if (!keepParentFocus) menuRef.current && menuRef.current.focus() - }, - resetActiveOnChanges || - [ - /* eslint-disable-line react-hooks/exhaustive-deps*/ - ] - ) + useEffect(() => { + if (!keepParentFocus) menuRef.current && menuRef.current.focus() + }, resetActiveOnChanges || []) const handleMouseDown = useCallback( (e: React.MouseEvent) => { diff --git a/packages/client/components/RetroDiscussPhase.tsx b/packages/client/components/RetroDiscussPhase.tsx index 36cb0a6cd65..a6e90191f69 100644 --- a/packages/client/components/RetroDiscussPhase.tsx +++ b/packages/client/components/RetroDiscussPhase.tsx @@ -203,9 +203,6 @@ const RetroDiscussPhase = (props: Props) => { // this shouldn't ever happen, yet // https://sentry.io/organizations/parabol/issues/1322927523/?environment=client&project=107196&query=is%3Aunresolved const errObj = {id: reflectionGroup.id} as any - if (reflectionGroup.hasOwnProperty('reflections')) { - errObj.reflections = reflections - } Sentry.captureException(new Error(`NO REFLECTIONS ${JSON.stringify(errObj)}`)) } return ( diff --git a/packages/client/components/SAMLRedirect.tsx b/packages/client/components/SAMLRedirect.tsx index a2b52068d4a..a096351559e 100644 --- a/packages/client/components/SAMLRedirect.tsx +++ b/packages/client/components/SAMLRedirect.tsx @@ -21,7 +21,9 @@ const SAMLRedirect = () => { // cross-domain attempts to access opener.location.origin will throw // this makes sure that Parabol opened the popup isSameOriginPopup = !!window.opener.location.origin - } catch {} + } catch { + /* noop */ + } } if (isSameOriginPopup) { // SP-initiated diff --git a/packages/client/components/SelectMeetingDropdownItem.tsx b/packages/client/components/SelectMeetingDropdownItem.tsx index 8d5c9f340d5..f7432b55799 100644 --- a/packages/client/components/SelectMeetingDropdownItem.tsx +++ b/packages/client/components/SelectMeetingDropdownItem.tsx @@ -44,9 +44,6 @@ const SelectMeetingDropdownItem = (props: Props) => { if (!team) { // 95% sure there's a bug in relay causing this const errObj = {id: meetingId} as any - if (meeting.hasOwnProperty('team')) { - errObj.team = team - } Sentry.captureException(new Error(`Missing Team on Meeting ${JSON.stringify(errObj)}`)) return null } diff --git a/packages/client/components/SwipeableDashSidebar.tsx b/packages/client/components/SwipeableDashSidebar.tsx index f59b6294c86..ef6dc960759 100644 --- a/packages/client/components/SwipeableDashSidebar.tsx +++ b/packages/client/components/SwipeableDashSidebar.tsx @@ -120,18 +120,13 @@ const SwipeableDashSidebar = (props: Props) => { const SIDEBAR_WIDTH: number = sidebarWidth || NavSidebar.WIDTH const HYSTERESIS_THRESH = HYSTERESIS * SIDEBAR_WIDTH - useEffect( - () => { - openPortal() - return () => { - window.clearTimeout(swipe.peekTimeout) - hideSidebar() - } - }, - [ - /* eslint-disable-line react-hooks/exhaustive-deps*/ - ] - ) + useEffect(() => { + openPortal() + return () => { + window.clearTimeout(swipe.peekTimeout) + hideSidebar() + } + }, []) const hideSidebar = useCallback(() => { setX(0) diff --git a/packages/client/components/TeamPrompt/WorkDrawer/GCalIntegrationResults.tsx b/packages/client/components/TeamPrompt/WorkDrawer/GCalIntegrationResults.tsx index 3d125709b65..5cde5356f6b 100644 --- a/packages/client/components/TeamPrompt/WorkDrawer/GCalIntegrationResults.tsx +++ b/packages/client/components/TeamPrompt/WorkDrawer/GCalIntegrationResults.tsx @@ -38,7 +38,7 @@ const GCalIntegrationResults = (props: Props) => { const gcal = query.viewer.teamMember?.integrations.gcal - const gcalResults = gcal?.events ? [...gcal?.events] : null + const gcalResults = gcal?.events ? [...gcal!.events] : null if (order === 'DESC') { gcalResults?.reverse() } diff --git a/packages/client/components/ViewerNotOnTeam.tsx b/packages/client/components/ViewerNotOnTeam.tsx index cddcd3e536a..50fdefeed89 100644 --- a/packages/client/components/ViewerNotOnTeam.tsx +++ b/packages/client/components/ViewerNotOnTeam.tsx @@ -46,24 +46,19 @@ const ViewerNotOnTeam = (props: Props) => { const {history} = useRouter() const {onError, onCompleted} = useMutationProps() useDocumentTitle(`Invitation Required`, 'Invitation Required') - useEffect( - () => { - if (teamInvitation) { - // if an invitation already exists, accept it - AcceptTeamInvitationMutation( - atmosphere, - {invitationToken: teamInvitation.token}, - {history, meetingId} - ) - return - } else if (teamId) - PushInvitationMutation(atmosphere, {meetingId, teamId}, {onError, onCompleted}) - return undefined - }, - [ - /* eslint-disable-line react-hooks/exhaustive-deps*/ - ] - ) + useEffect(() => { + if (teamInvitation) { + // if an invitation already exists, accept it + AcceptTeamInvitationMutation( + atmosphere, + {invitationToken: teamInvitation.token}, + {history, meetingId} + ) + return + } else if (teamId) + PushInvitationMutation(atmosphere, {meetingId, teamId}, {onError, onCompleted}) + return undefined + }, []) if (teamInvitation) return null return ( diff --git a/packages/client/eslint.config.mjs b/packages/client/eslint.config.mjs new file mode 100644 index 00000000000..5d109f2ae8f --- /dev/null +++ b/packages/client/eslint.config.mjs @@ -0,0 +1,38 @@ +import {fixupPluginRules} from '@eslint/compat' +import typescriptEslint from '@typescript-eslint/eslint-plugin' +import emotion from 'eslint-plugin-emotion' +import react from 'eslint-plugin-react' +import reactHooks from 'eslint-plugin-react-hooks' +import base from '../../eslint.config.mjs' + +export default [ + ...base, + { + ignores: ['__generated__/*', '**/*/serviceWorker/*', '**/*.js', '**/*.mjs'] + }, + { + plugins: { + '@typescript-eslint': typescriptEslint, + emotion, + react, + 'react-hooks': fixupPluginRules(reactHooks) + }, + settings: { + react: { + version: 'detect' + } + }, + rules: { + 'emotion/jsx-import': 'error', + 'emotion/no-vanilla': 'error', + 'emotion/import-from-emotion': 'error', + 'emotion/styled-import': 'error', + 'react/no-find-dom-node': 'off', + 'react/no-unescaped-entities': 'off', + 'react/display-name': 'off', + 'react/prop-types': 'off', + 'react-hooks/rules-of-hooks': 'warn', + 'space-before-function-paren': 'off' + } + } +] diff --git a/packages/client/hooks/useEditorState.ts b/packages/client/hooks/useEditorState.ts index 832ca956828..f11441b9658 100644 --- a/packages/client/hooks/useEditorState.ts +++ b/packages/client/hooks/useEditorState.ts @@ -41,7 +41,9 @@ const useEditorState = (content?: string | null | undefined) => { editorStateKey, timeSinceLastRender: diff } - Sentry.captureException(new Error(`useEditorState fired in last ${minTime}ms. ${error}`)) + Sentry.captureException( + new Error(`useEditorState fired in last ${minTime}ms. ${JSON.stringify(error)}`) + ) isErrorSentToSentryRef.current = true } return diff --git a/packages/client/hooks/useForm.tsx b/packages/client/hooks/useForm.tsx index 6fcdccea612..f356bc0257f 100644 --- a/packages/client/hooks/useForm.tsx +++ b/packages/client/hooks/useForm.tsx @@ -94,9 +94,7 @@ const useForm = (fieldInputDict: T, deps: any[] = []) } return obj }, {} as FieldState), - [ - /* eslint-disable-line react-hooks/exhaustive-deps */ - ] + [] ) ) diff --git a/packages/client/hooks/useInitialSafeRoute.ts b/packages/client/hooks/useInitialSafeRoute.ts index 84d3e7fb75b..454b48c2352 100644 --- a/packages/client/hooks/useInitialSafeRoute.ts +++ b/packages/client/hooks/useInitialSafeRoute.ts @@ -51,76 +51,71 @@ const useInitialSafeRoute = ( meetingRef ) - useEffect( - () => { - const meetingPath = getMeetingPathParams() - const {phaseSlug, stageIdxSlug} = meetingPath - if (!meeting) { - // We should not reach this as we should filter out inaccessible meetings in MeetingSelector - setSafeRoute(false) - return - } - const {facilitatorStageId, facilitatorUserId, localStage, id: meetingId, phases} = meeting - const {viewerId} = atmosphere + useEffect(() => { + const meetingPath = getMeetingPathParams() + const {phaseSlug, stageIdxSlug} = meetingPath + if (!meeting) { + // We should not reach this as we should filter out inaccessible meetings in MeetingSelector + setSafeRoute(false) + return + } + const {facilitatorStageId, facilitatorUserId, localStage, id: meetingId, phases} = meeting + const {viewerId} = atmosphere - // I’m headed to the lobby but the meeting is already going, send me there - if (localStage && !phaseSlug) { - const {id: localStageId} = localStage - const nextUrl = fromStageIdToUrl(localStageId, meeting) - history.replace(nextUrl) - updateLocalStage(atmosphere, meeting, facilitatorStageId) - setSafeRoute(false) - return - } + // I’m headed to the lobby but the meeting is already going, send me there + if (localStage && !phaseSlug) { + const {id: localStageId} = localStage + const nextUrl = fromStageIdToUrl(localStageId, meeting) + history.replace(nextUrl) + updateLocalStage(atmosphere, meeting, facilitatorStageId) + setSafeRoute(false) + return + } - const localPhaseType = findKeyByValue(phaseTypeToSlug, phaseSlug as NewMeetingPhaseTypeEnum) - const stageIdx = stageIdxSlug ? Number(stageIdxSlug) - 1 : 0 - const phase = phases.find((curPhase) => curPhase.phaseType === localPhaseType) + const localPhaseType = findKeyByValue(phaseTypeToSlug, phaseSlug as NewMeetingPhaseTypeEnum) + const stageIdx = stageIdxSlug ? Number(stageIdxSlug) - 1 : 0 + const phase = phases.find((curPhase) => curPhase.phaseType === localPhaseType) - // typo in url, send to the facilitator - if (!phase) { - const nextUrl = fromStageIdToUrl(facilitatorStageId, meeting) - history.replace(nextUrl) - updateLocalStage(atmosphere, meeting, facilitatorStageId) - setSafeRoute(false) - return - } + // typo in url, send to the facilitator + if (!phase) { + const nextUrl = fromStageIdToUrl(facilitatorStageId, meeting) + history.replace(nextUrl) + updateLocalStage(atmosphere, meeting, facilitatorStageId) + setSafeRoute(false) + return + } - const stage = phase.stages[stageIdx] - const stageId = stage?.id - const isViewerFacilitator = viewerId === facilitatorUserId - const itemStage = stageId && findStageById(phases, stageId) - if (!itemStage) { - // useful for e.g. /discuss/2, especially on the demo - const nextUrl = - meetingId === RetroDemo.MEETING_ID - ? '/retrospective-demo/reflect' - : fromStageIdToUrl(facilitatorStageId, meeting) - updateLocalStage(atmosphere, meeting, facilitatorStageId) - history.replace(nextUrl) - setSafeRoute(false) - return - } - // const {stage} = itemStage - const {isNavigable, isNavigableByFacilitator} = stage - const canNavigate = isViewerFacilitator ? isNavigableByFacilitator : isNavigable - if (!canNavigate) { - // too early to visit meeting or typo, go to facilitator - const nextUrl = fromStageIdToUrl(facilitatorStageId, meeting) - history.replace(nextUrl) - updateLocalStage(atmosphere, meeting, facilitatorStageId) - setSafeRoute(false) - return - } + const stage = phase.stages[stageIdx] + const stageId = stage?.id + const isViewerFacilitator = viewerId === facilitatorUserId + const itemStage = stageId && findStageById(phases, stageId) + if (!itemStage) { + // useful for e.g. /discuss/2, especially on the demo + const nextUrl = + meetingId === RetroDemo.MEETING_ID + ? '/retrospective-demo/reflect' + : fromStageIdToUrl(facilitatorStageId, meeting) + updateLocalStage(atmosphere, meeting, facilitatorStageId) + history.replace(nextUrl) + setSafeRoute(false) + return + } + // const {stage} = itemStage + const {isNavigable, isNavigableByFacilitator} = stage + const canNavigate = isViewerFacilitator ? isNavigableByFacilitator : isNavigable + if (!canNavigate) { + // too early to visit meeting or typo, go to facilitator + const nextUrl = fromStageIdToUrl(facilitatorStageId, meeting) + history.replace(nextUrl) + updateLocalStage(atmosphere, meeting, facilitatorStageId) + setSafeRoute(false) + return + } - // legit URL! - updateLocalStage(atmosphere, meeting, stage.id) - setSafeRoute(true) - }, - [ - /* eslint-disable-line react-hooks/exhaustive-deps */ - ] - ) + // legit URL! + updateLocalStage(atmosphere, meeting, stage.id) + setSafeRoute(true) + }, []) } export default useInitialSafeRoute diff --git a/packages/client/hooks/useMenu.ts b/packages/client/hooks/useMenu.ts index b85c69694dd..7e9dd71e6b7 100644 --- a/packages/client/hooks/useMenu.ts +++ b/packages/client/hooks/useMenu.ts @@ -51,7 +51,7 @@ const useMenu = ( if (options.loadingWidth) return options.loadingWidth const bbox = getBBox(originRef.current) return Math.max(40, bbox ? bbox.width : 40) - }, [originRef.current /* eslint-disable-line react-hooks/exhaustive-deps */]) + }, [originRef.current]) const {loadingDelay, loadingDelayRef} = useLoadingDelay() const menuPortal = useMenuPortal( portal, diff --git a/packages/client/hooks/useTransition.ts b/packages/client/hooks/useTransition.ts index 1c7451186ea..c68e536f519 100644 --- a/packages/client/hooks/useTransition.ts +++ b/packages/client/hooks/useTransition.ts @@ -115,11 +115,7 @@ const useTransition = (children: T[]) => { previousTransitionChildrenRef.current = currentTChildren } return previousTransitionChildrenRef.current - }, [ - beginTransition, - children, - previousTransitionChildrenRef.current /* eslint-disable-line react-hooks/exhaustive-deps */ - ]) + }, [beginTransition, children, previousTransitionChildrenRef.current]) } export default useTransition diff --git a/packages/client/lint-staged.config.js b/packages/client/lint-staged.config.js index e308623dbac..b3321deffb0 100644 --- a/packages/client/lint-staged.config.js +++ b/packages/client/lint-staged.config.js @@ -1,5 +1,8 @@ module.exports = { - '*.{ts,tsx}': ['eslint --fix', 'prettier --config ../../.prettierrc --ignore-path ./.eslintignore --write'], - '*.graphql': ['prettier --config ../../.prettierrc --ignore-path ./.eslintignore --write'], + '*.{ts,tsx}': [ + 'eslint --fix', + 'prettier --config ../../.prettierrc --ignore-path ./.prettierignore --write' + ], + '*.graphql': ['prettier --config ../../.prettierrc --ignore-path ./.prettierignore --write'], '**/*.{ts,tsx}': () => 'tsc --noEmit -p tsconfig.json' } diff --git a/packages/client/modules/demo/ClientGraphQLServer.ts b/packages/client/modules/demo/ClientGraphQLServer.ts index eba6dfad064..e7ea44c8170 100644 --- a/packages/client/modules/demo/ClientGraphQLServer.ts +++ b/packages/client/modules/demo/ClientGraphQLServer.ts @@ -1085,7 +1085,7 @@ class ClientGraphQLServer extends (EventEmitter as GQLDemoEmitter) { retroReflectionGroup: reflectionGroup as any, updatedAt: now }) - reflectionGroup.reflections!.push(reflection as any) + reflectionGroup.reflections.push(reflection as any) reflectionGroup.reflections.sort((a, b) => (a.sortOrder < b.sortOrder ? 1 : -1)) oldReflections.splice( oldReflections.findIndex((reflection) => reflection === reflection), @@ -1233,7 +1233,7 @@ class ClientGraphQLServer extends (EventEmitter as GQLDemoEmitter) { reflectionGroup.voteCount = voterIds.length reflectionGroup.viewerVoteCount = voterIds.filter((id) => id === demoViewerId).length const voteCount = this.db.reflectionGroups.reduce( - (sum, group) => sum + group.voterIds!.length, + (sum, group) => sum + group.voterIds.length, 0 ) diff --git a/packages/client/modules/email/components/SummaryEmail/ExportToCSV.tsx b/packages/client/modules/email/components/SummaryEmail/ExportToCSV.tsx index 23858a3fffb..d68afb2e6e8 100644 --- a/packages/client/modules/email/components/SummaryEmail/ExportToCSV.tsx +++ b/packages/client/modules/email/components/SummaryEmail/ExportToCSV.tsx @@ -182,8 +182,8 @@ const ExportToCSV = (props: Props) => { const handlePokerMeeting = (meeting: Meeting) => { const rows = [] as CSVPokerRow[] const {phases} = meeting - const estimatePhase = phases!.find((phase) => phase.phaseType === 'ESTIMATE')! - const stages = estimatePhase.stages! + const estimatePhase = phases.find((phase) => phase.phaseType === 'ESTIMATE')! + const stages = estimatePhase.stages stages.forEach((stage) => { if (stage.__typename !== 'EstimateStage') return const {finalScore, dimensionRef, task, scores} = stage @@ -274,7 +274,7 @@ const ExportToCSV = (props: Props) => { const handleActionMeeting = (newMeeting: Meeting) => { const {phases} = newMeeting - const agendaItemPhase = phases!.find((phase) => phase.phaseType === 'agendaitems')! + const agendaItemPhase = phases.find((phase) => phase.phaseType === 'agendaitems')! const {stages} = agendaItemPhase const rows = [] as CSVActionRow[] stages.forEach((stage) => { diff --git a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx index 4e836b0cf75..7e2da89d3b4 100644 --- a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx +++ b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx @@ -120,7 +120,7 @@ const RetroTopic = (props: Props) => { const {reflectionGroup, discussion, id: stageId} = stage const {commentCount, discussionSummary} = discussion - const {reflections, title, voteCount} = reflectionGroup! + const {reflections, title, voteCount} = reflectionGroup const imageSource = isEmail ? 'static' : 'local' const icon = imageSource === 'local' ? 'thumb_up_18.svg' : 'thumb_up_18@3x.png' const src = `${ExternalLinks.EMAIL_CDN}${icon}` diff --git a/packages/client/modules/spinner/components/Spinner/Spinner.tsx b/packages/client/modules/spinner/components/Spinner/Spinner.tsx index fc21ec3fa45..2972c37b60e 100644 --- a/packages/client/modules/spinner/components/Spinner/Spinner.tsx +++ b/packages/client/modules/spinner/components/Spinner/Spinner.tsx @@ -8,7 +8,6 @@ import React from 'react' // 3. DELAY for staggered leaf animation // 4. TIMING for staggered leaf animation -/* eslint-disable max-len */ const PATHS = [ 'M770.249 249.923C754.375 348.834 719.936 443.85 668.748 529.961C653.962 555.594 638.208 580.027 621.753 603.191C778.06 568.879 887.271 490.842 887.271 399.989C887.271 342.112 842.879 289.409 770.249 249.79', 'M443.633 659.936C414.194 659.936 385.122 658.534 356.852 655.864C464.728 774.087 586.923 829.627 665.56 784.2C715.626 755.262 738.99 690.476 737.021 607.8C643.421 643.517 543.909 661.207 443.733 659.936', @@ -17,7 +16,6 @@ const PATHS = [ 'M443.634 140.041C473.106 140.041 502.145 141.443 530.416 144.114C422.673 25.9238 300.478 -29.6163 221.841 15.7771C171.774 44.7153 148.41 109.501 150.379 192.177C243.98 156.46 343.492 138.771 443.667 140.041', 'M668.721 270.01C683.44 295.51 696.758 321.378 708.774 347.312C757.104 194.71 744.087 61.2001 665.45 15.8067C615.384 -13.1649 547.427 -1.0489 476.867 42.1416C554.632 105.316 619.73 182.646 668.721 270.043' ] -/* eslint-enable max-len */ const DURATION = '3s' const DELAY = 150 const TIMING = 100 / PATHS.length diff --git a/packages/client/modules/summary/components/NewMeetingSummary.tsx b/packages/client/modules/summary/components/NewMeetingSummary.tsx index 318ebcd355a..3f4a443772e 100644 --- a/packages/client/modules/summary/components/NewMeetingSummary.tsx +++ b/packages/client/modules/summary/components/NewMeetingSummary.tsx @@ -60,7 +60,7 @@ const NewMeetingSummary = (props: Props) => { if (!newMeeting) { return null } - // eslint-disable react-hooks/rules-of-hooks -- return above violates these rules, but is just a safeguard and not normal usage + /* eslint-disable react-hooks/rules-of-hooks */ const {id: meetingId, name: meetingName, team} = newMeeting const {id: teamId, name: teamName} = team const title = `${meetingName} ${MEETING_SUMMARY_LABEL} | ${teamName}` diff --git a/packages/client/modules/team/components/NewTeamOrgPicker.tsx b/packages/client/modules/team/components/NewTeamOrgPicker.tsx index a4f208672b4..1d7a94856fb 100644 --- a/packages/client/modules/team/components/NewTeamOrgPicker.tsx +++ b/packages/client/modules/team/components/NewTeamOrgPicker.tsx @@ -56,17 +56,12 @@ const NewTeamOrgPicker = (props: Props) => { organizationsRef ) const sortedOrgs = useMemo(() => sortByTier(organizations), [organizations]) - useEffect( - () => { - const [firstOrg] = sortedOrgs - if (firstOrg) { - onChange(firstOrg.id) - } - }, - [ - /* eslint-disable-line react-hooks/exhaustive-deps*/ - ] - ) + useEffect(() => { + const [firstOrg] = sortedOrgs + if (firstOrg) { + onChange(firstOrg.id) + } + }, []) const orgIdx = orgId ? sortedOrgs.findIndex((org) => org.id === orgId) : 0 const org = sortedOrgs[orgIdx] const defaultText = org ? org.name : NO_ORGS diff --git a/packages/client/package.json b/packages/client/package.json index 1c7f7f1b5e7..472512e2f5d 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -16,11 +16,11 @@ "format": "yarn typecheck && yarn lint && yarn prettier", "storybook": "start-storybook -p 6006 -c .storybook", "build-storybook": "build-sFtorybook", - "lint": "eslint --fix . --ext .ts,.tsx", - "lint:check": "eslint --fix . --ext .ts,.tsx", + "lint": "yarn lint:check --fix .", + "lint:check": "node --max_old_space_size=8192 ../../node_modules/.bin/eslint .", "precommit": "lint-staged", - "prettier": "prettier --config ../../.prettierrc --ignore-path ./.eslintignore --write \"**/*.{ts,tsx,graphql}\"", - "prettier:check": "prettier --config ../../.prettierrc --ignore-path ./.eslintignore --check \"**/*.{ts,tsx,graphql}\"", + "prettier": "prettier --config ../../.prettierrc --ignore-path ./.prettierignore --write \"**/*.{ts,tsx,graphql}\"", + "prettier:check": "prettier --config ../../.prettierrc --ignore-path ./.prettierignore --check \"**/*.{ts,tsx,graphql}\"", "test": "jest --verbose", "typecheck": "yarn tsc --noEmit -p tsconfig.json" }, diff --git a/packages/client/tsconfig.eslint.json b/packages/client/tsconfig.eslint.json deleted file mode 100644 index c14c8f7bf4c..00000000000 --- a/packages/client/tsconfig.eslint.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["./**/*.ts", "./**/*.tsx"], - "exclude": ["node_modules"] -} diff --git a/packages/client/utils/makeCalendarInvites.ts b/packages/client/utils/makeCalendarInvites.ts index 6d72c6417f3..de7f9258377 100644 --- a/packages/client/utils/makeCalendarInvites.ts +++ b/packages/client/utils/makeCalendarInvites.ts @@ -4,7 +4,6 @@ import ensureDate from './ensureDate' import roundDateToNearestHalfHour from './roundDateToNearestHalfHour' // the ICS doesn't get the 'Add your conference' line because it doesn't accept line breaks. that's cool though because it isn't editable -// eslint-disable-next-line max-len const description = `Our weekly meeting to update each other on our progress, build and process an agenda to unblock one another and track new tasks. Add your conference or dial-in bridge information here.` @@ -29,7 +28,6 @@ export const createGoogleCalendarInviteURL = ( ) => { const createdAt = ensureDate(maybeCreatedAt) const text = `${MEETING_NAME} for ${teamName}` - // eslint-disable-next-line max-len return encodeURI( `http://www.google.com/calendar/render?action=TEMPLATE&text=${text}&details=${description}&dates=${getStartTime( createdAt diff --git a/packages/client/utils/requestIdleCallback.ts b/packages/client/utils/requestIdleCallback.ts index db0ccce870f..d5de8d92aac 100644 --- a/packages/client/utils/requestIdleCallback.ts +++ b/packages/client/utils/requestIdleCallback.ts @@ -1,7 +1,6 @@ const shimRIC: Window['requestIdleCallback'] = (cb) => { const start = Date.now() return window.setTimeout(() => { - // eslint-disable-next-line cb({ didTimeout: false, timeRemaining: () => Math.max(0, 50 - (Date.now() - start)) diff --git a/packages/client/utils/smartGroup/getGroupMatrix.ts b/packages/client/utils/smartGroup/getGroupMatrix.ts index 3b37cd4d9b4..0678a22b4f0 100644 --- a/packages/client/utils/smartGroup/getGroupMatrix.ts +++ b/packages/client/utils/smartGroup/getGroupMatrix.ts @@ -85,12 +85,10 @@ const getGroupMatrix = (distanceMatrix: number[][], groupingOptions: GroupingOpt distancesArr = Array.from(res.distanceSet).sort() const reduction = (distanceMatrix.length - groups.length) / distanceMatrix.length if (reduction < MIN_REDUCTION_PERCENT) { - // eslint-disable-next-line no-loop-func const nextDistance = distancesArr.find((d) => d > thresh) if (!nextDistance || nextDistance >= 1) break thresh = Math.ceil(nextDistance * 100) / 100 } else if (reduction > maxReductionPercent) { - // eslint-disable-next-line no-loop-func const nextDistance = distancesArr.find((d) => d < thresh) if (!nextDistance || nextDistance >= 1) break thresh = Math.floor(nextDistance * 100) / 100 diff --git a/packages/client/utils/smartGroup/groupReflections.ts b/packages/client/utils/smartGroup/groupReflections.ts index ea20495b808..dc2976225df 100644 --- a/packages/client/utils/smartGroup/groupReflections.ts +++ b/packages/client/utils/smartGroup/groupReflections.ts @@ -31,7 +31,7 @@ const groupReflections = { - const allReflectionEntities = reflections.map(({entities}) => entities!) + const allReflectionEntities = reflections.map(({entities}) => entities) const oldReflectionGroupIds = reflections.map(({reflectionGroupId}) => reflectionGroupId) // create a unique array of all entity names mentioned in the meeting's reflect phase diff --git a/packages/client/validation/regex.ts b/packages/client/validation/regex.ts index 043386d1f21..ed2f839eaeb 100644 --- a/packages/client/validation/regex.ts +++ b/packages/client/validation/regex.ts @@ -1,5 +1,4 @@ export const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/ -// eslint-disable-next-line export const urlRegex = /^(https?|ftp):\/\/(((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/(([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(#((([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i export const idRegex = /^[a-zA-Z0-9\-_|:]{5,70}$/ diff --git a/packages/embedder/.eslintrc.js b/packages/embedder/.eslintrc.js deleted file mode 100644 index a6a5d110f1e..00000000000 --- a/packages/embedder/.eslintrc.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - extends: [ - '../../.eslintrc.js' - ], - parserOptions: { - project: './tsconfig.json', - ecmaVersion: 2020, - sourceType: 'module' - }, - "ignorePatterns": ["**/lib", "*.js"] -} diff --git a/packages/embedder/ai_models/helpers/fetchWithRetry.ts b/packages/embedder/ai_models/helpers/fetchWithRetry.ts index 98343ef26e6..15668f95e4c 100644 --- a/packages/embedder/ai_models/helpers/fetchWithRetry.ts +++ b/packages/embedder/ai_models/helpers/fetchWithRetry.ts @@ -22,7 +22,7 @@ export default async (url: RequestInfo, options: FetchWithRetryOptions): Promise attempt++ if (debug) { - console.log(`Attempt ${attempt}: Fetching ${url}`) + console.log(`Attempt ${attempt}: Fetching ${JSON.stringify(url)}`) } const response = await fetch(url, fetchOptions) diff --git a/packages/embedder/lint-staged.config.js b/packages/embedder/lint-staged.config.js index e308623dbac..b3321deffb0 100644 --- a/packages/embedder/lint-staged.config.js +++ b/packages/embedder/lint-staged.config.js @@ -1,5 +1,8 @@ module.exports = { - '*.{ts,tsx}': ['eslint --fix', 'prettier --config ../../.prettierrc --ignore-path ./.eslintignore --write'], - '*.graphql': ['prettier --config ../../.prettierrc --ignore-path ./.eslintignore --write'], + '*.{ts,tsx}': [ + 'eslint --fix', + 'prettier --config ../../.prettierrc --ignore-path ./.prettierignore --write' + ], + '*.graphql': ['prettier --config ../../.prettierrc --ignore-path ./.prettierignore --write'], '**/*.{ts,tsx}': () => 'tsc --noEmit -p tsconfig.json' } diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 91a4d002ed0..d3bbefbcac4 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -11,8 +11,8 @@ }, "scripts": { "precommit": "lint-staged", - "lint": "eslint --fix . --ext .ts,.tsx", - "lint:check": "eslint . --ext .ts,.tsx", + "lint": "yarn lint:check --fix .", + "lint:check": "node --max_old_space_size=8192 ../../node_modules/.bin/eslint .", "prettier": "prettier --config ../../.prettierrc --write \"**/*.{ts,tsx}\"", "prettier:check": "prettier --config ../../.prettierrc --check \"**/*.{ts,tsx}\"", "test": "jest --verbose", diff --git a/packages/gql-executor/.eslintrc.js b/packages/gql-executor/.eslintrc.js deleted file mode 100644 index a6a5d110f1e..00000000000 --- a/packages/gql-executor/.eslintrc.js +++ /dev/null @@ -1,11 +0,0 @@ -module.exports = { - extends: [ - '../../.eslintrc.js' - ], - parserOptions: { - project: './tsconfig.json', - ecmaVersion: 2020, - sourceType: 'module' - }, - "ignorePatterns": ["**/lib", "*.js"] -} diff --git a/packages/integration-tests/.eslintrc.js b/packages/integration-tests/.eslintrc.js deleted file mode 100644 index b90b542d612..00000000000 --- a/packages/integration-tests/.eslintrc.js +++ /dev/null @@ -1,15 +0,0 @@ -module.exports = { - root: true, - parser: '@typescript-eslint/parser', - parserOptions: { - tsconfigRootDir: __dirname, - project: ['./tsconfig.json'] - }, - plugins: ['@typescript-eslint'], - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/recommended', - 'plugin:@typescript-eslint/recommended-requiring-type-checking', - 'prettier' - ] -} diff --git a/packages/integration-tests/.eslintignore b/packages/integration-tests/.prettierignore similarity index 100% rename from packages/integration-tests/.eslintignore rename to packages/integration-tests/.prettierignore diff --git a/packages/server/.eslintrc.js b/packages/server/.eslintrc.js deleted file mode 100644 index 20fc204dd16..00000000000 --- a/packages/server/.eslintrc.js +++ /dev/null @@ -1,9 +0,0 @@ -module.exports = { - extends: ['../../.eslintrc.js'], - parserOptions: { - project: 'tsconfig.eslint.json', - ecmaVersion: 2020, - sourceType: 'module' - }, - ignorePatterns: ['*.js'] -} diff --git a/packages/server/.eslintignore b/packages/server/.prettierignore similarity index 93% rename from packages/server/.eslintignore rename to packages/server/.prettierignore index 0f0f4df4e30..c5b5af599e1 100644 --- a/packages/server/.eslintignore +++ b/packages/server/.prettierignore @@ -7,7 +7,6 @@ githubSchema.graphql gitlabSchema.graphql graphql/private/schema.graphql graphql/public/schema.graphql -_legacy.graphql migrationTemplate.ts billing/debug.ts pg.d.ts diff --git a/packages/server/eslint.config.mjs b/packages/server/eslint.config.mjs new file mode 100644 index 00000000000..80b753f505c --- /dev/null +++ b/packages/server/eslint.config.mjs @@ -0,0 +1,36 @@ +import {FlatCompat} from '@eslint/eslintrc' +import js from '@eslint/js' +import path from 'node:path' +import {fileURLToPath} from 'node:url' +import base from '../../eslint.config.mjs' + +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) +const compat = new FlatCompat({ + baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, + allConfig: js.configs.all +}) + +export default [ + ...base, + { + ignores: [ + 'types/webpackEnv.ts', + 'database/migrations', + 'database/stricterR.ts', + 'postgres/migrations', + '**/generated/', + '**/*/githubTypes.ts', + '**/*/gitlabTypes.ts', + '**/*/resolverTypes.ts', + '**/*/githubSchema.graphql', + '**/*/gitlabSchema.graphql', + 'graphql/private/schema.graphql', + 'graphql/public/schema.graphql', + '**/*/migrationTemplate.ts', + '**/*debug.ts', + '**/*/pg.d.ts' + ] + } +] diff --git a/packages/server/graphql/composeResolvers.ts b/packages/server/graphql/composeResolvers.ts index e3f3d050419..8652c420231 100644 --- a/packages/server/graphql/composeResolvers.ts +++ b/packages/server/graphql/composeResolvers.ts @@ -43,6 +43,7 @@ const wrapResolve = return res } } catch (err) { + console.log(err) throw err } } diff --git a/packages/server/graphql/mutations/helpers/importTasksForPoker.ts b/packages/server/graphql/mutations/helpers/importTasksForPoker.ts index 11fd8a65ad7..3c7d8858474 100644 --- a/packages/server/graphql/mutations/helpers/importTasksForPoker.ts +++ b/packages/server/graphql/mutations/helpers/importTasksForPoker.ts @@ -22,7 +22,6 @@ const importTasksForPoker = async ( additiveUpdates.map((update) => { if (update.service === 'PARABOL') { integrationHashToTaskId[update.serviceTaskId] = update.serviceTaskId - } else { } }) const newIntegrationUpdates = integratedUpdates.filter( diff --git a/packages/server/graphql/private/mutations/processRecurrence.ts b/packages/server/graphql/private/mutations/processRecurrence.ts index 8b1828eadd8..a6311f28343 100644 --- a/packages/server/graphql/private/mutations/processRecurrence.ts +++ b/packages/server/graphql/private/mutations/processRecurrence.ts @@ -115,7 +115,11 @@ const startRecurringMeeting = async ( return undefined } -const processRecurrence: MutationResolvers['processRecurrence'] = async (_source, {}, context) => { +const processRecurrence: MutationResolvers['processRecurrence'] = async ( + _source, + _args, + context +) => { const {dataLoader, socketId: mutatorId} = context const r = await getRethink() const now = new Date() diff --git a/packages/server/graphql/public/mutations/requestToJoinDomain.ts b/packages/server/graphql/public/mutations/requestToJoinDomain.ts index cd2dad2c9f0..da16dd57dee 100644 --- a/packages/server/graphql/public/mutations/requestToJoinDomain.ts +++ b/packages/server/graphql/public/mutations/requestToJoinDomain.ts @@ -14,7 +14,7 @@ const REQUEST_EXPIRATION_DAYS = 30 const requestToJoinDomain: MutationResolvers['requestToJoinDomain'] = async ( _source, - {}, + _args, {authToken, dataLoader} ) => { const r = await getRethink() diff --git a/packages/server/graphql/public/mutations/toggleSummaryEmail.ts b/packages/server/graphql/public/mutations/toggleSummaryEmail.ts index aaeb7652a67..c03976740cb 100644 --- a/packages/server/graphql/public/mutations/toggleSummaryEmail.ts +++ b/packages/server/graphql/public/mutations/toggleSummaryEmail.ts @@ -7,7 +7,7 @@ import {MutationResolvers} from '../resolverTypes' const toggleSummaryEmail: MutationResolvers['toggleSummaryEmail'] = async ( _source, - {}, + _args, {authToken} ) => { const viewerId = getUserId(authToken) diff --git a/packages/server/graphql/public/types/ShareTopicSuccess.ts b/packages/server/graphql/public/types/ShareTopicSuccess.ts index 7d910a6f8eb..65d59fb5d12 100644 --- a/packages/server/graphql/public/types/ShareTopicSuccess.ts +++ b/packages/server/graphql/public/types/ShareTopicSuccess.ts @@ -5,7 +5,7 @@ export type ShareTopicSuccessSource = { } const ShareTopicSuccess: ShareTopicSuccessResolvers = { - meeting: async ({meetingId}, {}, {dataLoader}) => { + meeting: async ({meetingId}, _args, {dataLoader}) => { return dataLoader.get('newMeetings').load(meetingId) } } diff --git a/packages/server/graphql/public/types/TeamPromptMeeting.ts b/packages/server/graphql/public/types/TeamPromptMeeting.ts index 9ade4b0e494..25f29461423 100644 --- a/packages/server/graphql/public/types/TeamPromptMeeting.ts +++ b/packages/server/graphql/public/types/TeamPromptMeeting.ts @@ -61,7 +61,7 @@ const TeamPromptMeeting: TeamPromptMeetingResolvers = { return await dataLoader.get('meetingSettingsByType').load({teamId, meetingType: 'teamPrompt'}) }, - responses: ({id: meetingId}, _args, {}) => { + responses: ({id: meetingId}, _args) => { return getTeamPromptResponsesByMeetingId(meetingId) }, diff --git a/packages/server/graphql/public/types/TeamPromptResponseStage.ts b/packages/server/graphql/public/types/TeamPromptResponseStage.ts index 6015bd369e2..e612c229bd7 100644 --- a/packages/server/graphql/public/types/TeamPromptResponseStage.ts +++ b/packages/server/graphql/public/types/TeamPromptResponseStage.ts @@ -4,7 +4,7 @@ import {TeamPromptResponseStageResolvers} from '../resolverTypes' const TeamPromptResponseStage: TeamPromptResponseStageResolvers = { __isTypeOf: ({phaseType}) => phaseType === 'RESPONSES', - response: async ({meetingId, teamMemberId}, _args, {}) => { + response: async ({meetingId, teamMemberId}, _args) => { // TODO: implement getTeamPromptResponsesByMeetingIdAndUserId const responses = await getTeamPromptResponsesByMeetingId(meetingId) const userId = TeamMemberId.split(teamMemberId).userId diff --git a/packages/server/graphql/types/GraphQLURLType.ts b/packages/server/graphql/types/GraphQLURLType.ts index c91b3bc148e..1d91e1ebb35 100644 --- a/packages/server/graphql/types/GraphQLURLType.ts +++ b/packages/server/graphql/types/GraphQLURLType.ts @@ -7,7 +7,6 @@ const GraphQLURLType = new GraphQLScalarType({ serialize: (value) => String(value), parseValue: (value) => String(value), parseLiteral: (ast: any) => { - // eslint-disable-next-line max-len, no-useless-escape if (!urlRegex.test(ast.value)) { throw new Error('Query error: Not a valid URL') } diff --git a/packages/server/graphql/types/Team.ts b/packages/server/graphql/types/Team.ts index f6bf14bb400..be6246317e1 100644 --- a/packages/server/graphql/types/Team.ts +++ b/packages/server/graphql/types/Team.ts @@ -203,7 +203,7 @@ const Team: GraphQLObjectType = new GraphQLObjectType({ scales: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(TemplateScale))), description: 'The list of scales this team can use', - resolve: async ({id: teamId}: {id: string}, {}, {dataLoader}: GQLContext) => { + resolve: async ({id: teamId}: {id: string}, _args, {dataLoader}: GQLContext) => { const availableScales = await dataLoader .get('scalesByTeamId') .loadMany([teamId, 'aGhostTeam']) diff --git a/packages/server/graphql/uWSAsyncHandler.ts b/packages/server/graphql/uWSAsyncHandler.ts index 922afc91a62..70ca0653f35 100644 --- a/packages/server/graphql/uWSAsyncHandler.ts +++ b/packages/server/graphql/uWSAsyncHandler.ts @@ -3,7 +3,7 @@ import safetyPatchRes from '../safetyPatchRes' import getReqAuth from '../utils/getReqAuth' import sendToSentry from '../utils/sendToSentry' -export type uWSHandler = (res: HttpResponse, req: HttpRequest) => void +export type uWSHandler = (res: HttpResponse, req: HttpRequest) => Promise const uWSAsyncHandler = (handler: uWSHandler, ignoreDone?: boolean) => async (res: HttpResponse, req: HttpRequest) => { safetyPatchRes(res) diff --git a/packages/server/lint-staged.config.js b/packages/server/lint-staged.config.js index e308623dbac..b3321deffb0 100644 --- a/packages/server/lint-staged.config.js +++ b/packages/server/lint-staged.config.js @@ -1,5 +1,8 @@ module.exports = { - '*.{ts,tsx}': ['eslint --fix', 'prettier --config ../../.prettierrc --ignore-path ./.eslintignore --write'], - '*.graphql': ['prettier --config ../../.prettierrc --ignore-path ./.eslintignore --write'], + '*.{ts,tsx}': [ + 'eslint --fix', + 'prettier --config ../../.prettierrc --ignore-path ./.prettierignore --write' + ], + '*.graphql': ['prettier --config ../../.prettierrc --ignore-path ./.prettierignore --write'], '**/*.{ts,tsx}': () => 'tsc --noEmit -p tsconfig.json' } diff --git a/packages/server/package.json b/packages/server/package.json index 8758bfbeaa5..d5de774ea95 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -16,10 +16,10 @@ "format": "yarn typecheck && yarn lint && yarn prettier", "debug:stripe": "node billing/debug.babel.js", "precommit": "lint-staged", - "lint": "eslint --fix . --ext .ts,.tsx", - "lint:check": "eslint . --ext .ts,.tsx", - "prettier": "prettier --config ../../.prettierrc --ignore-path ./.eslintignore --write \"**/*.{ts,tsx,graphql}\"", - "prettier:check": "prettier --config ../../.prettierrc --ignore-path ./.eslintignore --check \"**/*.{ts,tsx,graphql}\"", + "lint": "yarn lint:check --fix .", + "lint:check": "node --max_old_space_size=8192 ../../node_modules/.bin/eslint .", + "prettier": "prettier --config ../../.prettierrc --ignore-path ./.prettierignore --write \"**/*.{ts,tsx,graphql}\"", + "prettier:check": "prettier --config ../../.prettierrc --ignore-path ./.prettierignore --check \"**/*.{ts,tsx,graphql}\"", "test": "jest --verbose --runInBand", "postversion": "git push --follow-tags", "typecheck": "tsc --noEmit -p tsconfig.json" diff --git a/packages/server/safetyPatchRes.ts b/packages/server/safetyPatchRes.ts index aa974ebb88f..9b7d1ef4efa 100644 --- a/packages/server/safetyPatchRes.ts +++ b/packages/server/safetyPatchRes.ts @@ -89,6 +89,7 @@ const safetyPatchRes = (res: HttpResponse) => { res._writeStatus = res.writeStatus res.writeStatus = (status: RecognizedString) => { if (res.done) { + // eslint-disable-next-line @typescript-eslint/no-base-to-string Logger.error(`uWS: Called writeStatus after done ${status}`) } res.status = status diff --git a/packages/server/tsconfig.eslint.json b/packages/server/tsconfig.eslint.json deleted file mode 100644 index c14c8f7bf4c..00000000000 --- a/packages/server/tsconfig.eslint.json +++ /dev/null @@ -1,5 +0,0 @@ -{ - "extends": "./tsconfig.json", - "include": ["./**/*.ts", "./**/*.tsx"], - "exclude": ["node_modules"] -} diff --git a/packages/server/types/webpackEnv.ts b/packages/server/types/webpackEnv.ts index 099cacf58a0..e75e005cb06 100644 --- a/packages/server/types/webpackEnv.ts +++ b/packages/server/types/webpackEnv.ts @@ -1,4 +1,3 @@ -// eslint-disable-next-line @typescript-eslint/ban-ts-comment // @ts-nocheck // Type definitions for webpack (module API) 1.16 // Project: https://github.com/webpack/webpack diff --git a/packages/server/utils/AtlassianServerManager.ts b/packages/server/utils/AtlassianServerManager.ts index d9d1e8e9708..dcbf67bc2ec 100644 --- a/packages/server/utils/AtlassianServerManager.ts +++ b/packages/server/utils/AtlassianServerManager.ts @@ -629,7 +629,7 @@ class AtlassianServerManager extends AtlassianManager { const project = await this.getProject(cloudId, projectKey) if (project instanceof RateLimitError || project instanceof Error) { - throw project + throw project as Error } if (project.simplified) { diff --git a/packages/server/utils/atlassian/jiraIssues.ts b/packages/server/utils/atlassian/jiraIssues.ts index 649f623f798..cd3fe917874 100644 --- a/packages/server/utils/atlassian/jiraIssues.ts +++ b/packages/server/utils/atlassian/jiraIssues.ts @@ -9,7 +9,7 @@ import getRedis from '../getRedis' const ISSUE_TTL_MS = ms('2d') const MAX_ATTEMPT_DURATION = ms('9s') -type StoreAndNetworkRequests = RequestsInit | CacheHit | CacheMiss | CacheError +type StoreAndNetworkRequests = CacheHit | CacheMiss | CacheError interface RequestsInit { firstToResolve: (value: unknown) => void diff --git a/packages/server/utils/publishInternalGQL.ts b/packages/server/utils/publishInternalGQL.ts index 1d2701826c8..d1282f98182 100644 --- a/packages/server/utils/publishInternalGQL.ts +++ b/packages/server/utils/publishInternalGQL.ts @@ -14,7 +14,7 @@ interface Options { const publishInternalGQL = async (options: Options) => { const {socketId, query, ip, authToken, variables} = options try { - return (await getGraphQLExecutor().publish)({ + return await getGraphQLExecutor().publish({ socketId, authToken, query, diff --git a/packages/server/utils/publishWebhookGQL.ts b/packages/server/utils/publishWebhookGQL.ts index 20b43696b78..6dba4aac694 100644 --- a/packages/server/utils/publishWebhookGQL.ts +++ b/packages/server/utils/publishWebhookGQL.ts @@ -13,7 +13,7 @@ const publishWebhookGQL = async ( options?: PublishOptions ) => { try { - return (await getGraphQLExecutor().publish)({ + return await getGraphQLExecutor().publish({ authToken: new ServerAuthToken(), query, variables, diff --git a/packages/server/utils/samlXMLValidator.ts b/packages/server/utils/samlXMLValidator.ts index 5e39800f172..5d728481cb2 100644 --- a/packages/server/utils/samlXMLValidator.ts +++ b/packages/server/utils/samlXMLValidator.ts @@ -6,6 +6,7 @@ export const samlXMLValidator = { allowBooleanAttributes: true }) if (isValid === true) return 'SUCCESS_VALIDATE_XML' + /* eslint-disable-next-line @typescript-eslint/only-throw-error */ throw 'ERR_INVALID_XML' } } diff --git a/yarn.lock b/yarn.lock index 17fed322d80..3ec488bd779 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4041,30 +4041,49 @@ dependencies: eslint-visitor-keys "^3.3.0" -"@eslint-community/regexpp@^4.5.1", "@eslint-community/regexpp@^4.6.1": - version "4.10.0" - resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.10.0.tgz#548f6de556857c8bb73bbee70c35dc82a2e74d63" - integrity sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA== +"@eslint-community/regexpp@^4.10.0", "@eslint-community/regexpp@^4.11.0": + version "4.11.0" + resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.11.0.tgz#b0ffd0312b4a3fd2d6f77237e7248a5ad3a680ae" + integrity sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A== -"@eslint/eslintrc@^2.1.4": - version "2.1.4" - resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.4.tgz#388a269f0f25c1b6adc317b5a2c55714894c70ad" - integrity sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ== +"@eslint/compat@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@eslint/compat/-/compat-1.1.1.tgz#5736523f5105c94dfae5f35e31debc38443722cd" + integrity sha512-lpHyRyplhGPL5mGEh6M9O5nnKk0Gz4bFI+Zu6tKlPpDUN7XshWvH9C/px4UVm87IAANE0W81CEsNGbS1KlzXpA== + +"@eslint/config-array@^0.18.0": + version "0.18.0" + resolved "https://registry.yarnpkg.com/@eslint/config-array/-/config-array-0.18.0.tgz#37d8fe656e0d5e3dbaea7758ea56540867fd074d" + integrity sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw== + dependencies: + "@eslint/object-schema" "^2.1.4" + debug "^4.3.1" + minimatch "^3.1.2" + +"@eslint/eslintrc@^3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-3.1.0.tgz#dbd3482bfd91efa663cbe7aa1f506839868207b6" + integrity sha512-4Bfj15dVJdoy3RfZmmo86RK1Fwzn6SstsvK9JS+BaVKqC6QQQQyXekNaC+g+LKNgkQ+2VhGAzm6hO40AhMR3zQ== dependencies: ajv "^6.12.4" debug "^4.3.2" - espree "^9.6.0" - globals "^13.19.0" + espree "^10.0.1" + globals "^14.0.0" ignore "^5.2.0" import-fresh "^3.2.1" js-yaml "^4.1.0" minimatch "^3.1.2" strip-json-comments "^3.1.1" -"@eslint/js@8.57.0": - version "8.57.0" - resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.57.0.tgz#a5417ae8427873f1dd08b70b3574b453e67b5f7f" - integrity sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g== +"@eslint/js@9.9.1": + version "9.9.1" + resolved "https://registry.yarnpkg.com/@eslint/js/-/js-9.9.1.tgz#4a97e85e982099d6c7ee8410aacb55adaa576f06" + integrity sha512-xIDQRsfg5hNBqHz04H1R3scSVwmI+KUbqjsQKHKQ1DAUSaUjYPReZZmS/5PNiKu1fUvzDd6H7DEDKACSEhu+TQ== + +"@eslint/object-schema@^2.1.4": + version "2.1.4" + resolved "https://registry.yarnpkg.com/@eslint/object-schema/-/object-schema-2.1.4.tgz#9e69f8bb4031e11df79e03db09f9dbbae1740843" + integrity sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ== "@exodus/schemasafe@^1.0.0-rc.2": version "1.0.1" @@ -4777,15 +4796,6 @@ dependencies: client-only "^0.0.1" -"@humanwhocodes/config-array@^0.11.14": - version "0.11.14" - resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.14.tgz#d78e481a039f7566ecc9660b4ea7fe6b1fec442b" - integrity sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg== - dependencies: - "@humanwhocodes/object-schema" "^2.0.2" - debug "^4.3.1" - minimatch "^3.0.5" - "@humanwhocodes/module-importer@^1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c" @@ -4796,10 +4806,10 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/momoa/-/momoa-2.0.4.tgz#8b9e7a629651d15009c3587d07a222deeb829385" integrity sha512-RE815I4arJFtt+FVeU1Tgp9/Xvecacji8w/V6XtXsWWH/wz/eNkNbhb+ny/+PlVZjV0rxQpRSQKNKE3lcktHEA== -"@humanwhocodes/object-schema@^2.0.2": - version "2.0.2" - resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz#d9fae00a2d5cb40f92cfe64b47ad749fbc38f917" - integrity sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw== +"@humanwhocodes/retry@^0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@humanwhocodes/retry/-/retry-0.3.0.tgz#6d86b8cb322660f03d3f0aa94b99bdd8e172d570" + integrity sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew== "@hutson/parse-repository-url@^3.0.0": version "3.0.2" @@ -8868,12 +8878,12 @@ "@types/parse5" "*" "@types/tough-cookie" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.11", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.8", "@types/json-schema@^7.0.9": +"@types/json-schema@*", "@types/json-schema@^7.0.11", "@types/json-schema@^7.0.6", "@types/json-schema@^7.0.9": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== -"@types/json-schema@^7.0.12": +"@types/json-schema@^7.0.8": version "7.0.15" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841" integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA== @@ -9208,11 +9218,6 @@ dependencies: htmlparser2 "^8.0.0" -"@types/semver@^7.5.0": - version "7.5.6" - resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.6.tgz#c65b2bfce1bec346582c07724e3f8c1017a20339" - integrity sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A== - "@types/send@*": version "0.17.1" resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.1.tgz#ed4932b8a2a805f1fe362a70f4e62d0ac994e301" @@ -9381,96 +9386,86 @@ resolved "https://registry.yarnpkg.com/@types/yup/-/yup-0.29.11.tgz#d654a112973f5e004bf8438122bd7e56a8e5cd7e" integrity sha512-9cwk3c87qQKZrT251EDoibiYRILjCmxBvvcb4meofCmx1vdnNcR9gyildy5vOHASpOKMsn42CugxUvcwK5eu1g== -"@typescript-eslint/eslint-plugin@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.4.0.tgz#de61c3083842fc6ac889d2fc83c9a96b55ab8328" - integrity sha512-yHMQ/oFaM7HZdVrVm/M2WHaNPgyuJH4WelkSVEWSSsir34kxW2kDJCxlXRhhGWEsMN0WAW/vLpKfKVcm8k+MPw== - dependencies: - "@eslint-community/regexpp" "^4.5.1" - "@typescript-eslint/scope-manager" "7.4.0" - "@typescript-eslint/type-utils" "7.4.0" - "@typescript-eslint/utils" "7.4.0" - "@typescript-eslint/visitor-keys" "7.4.0" - debug "^4.3.4" +"@typescript-eslint/eslint-plugin@8.3.0", "@typescript-eslint/eslint-plugin@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.3.0.tgz#726627fad16d41d20539637efee8c2329fe6be32" + integrity sha512-FLAIn63G5KH+adZosDYiutqkOkYEx0nvcwNNfJAf+c7Ae/H35qWwTYvPZUKFj5AS+WfHG/WJJfWnDnyNUlp8UA== + dependencies: + "@eslint-community/regexpp" "^4.10.0" + "@typescript-eslint/scope-manager" "8.3.0" + "@typescript-eslint/type-utils" "8.3.0" + "@typescript-eslint/utils" "8.3.0" + "@typescript-eslint/visitor-keys" "8.3.0" graphemer "^1.4.0" - ignore "^5.2.4" + ignore "^5.3.1" natural-compare "^1.4.0" - semver "^7.5.4" - ts-api-utils "^1.0.1" + ts-api-utils "^1.3.0" -"@typescript-eslint/parser@^7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-7.4.0.tgz#540f4321de1e52b886c0fa68628af1459954c1f1" - integrity sha512-ZvKHxHLusweEUVwrGRXXUVzFgnWhigo4JurEj0dGF1tbcGh6buL+ejDdjxOQxv6ytcY1uhun1p2sm8iWStlgLQ== +"@typescript-eslint/parser@8.3.0", "@typescript-eslint/parser@^8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-8.3.0.tgz#3c72c32bc909cb91ce3569e7d11d729ad84deafa" + integrity sha512-h53RhVyLu6AtpUzVCYLPhZGL5jzTD9fZL+SYf/+hYOx2bDkyQXztXSc4tbvKYHzfMXExMLiL9CWqJmVz6+78IQ== dependencies: - "@typescript-eslint/scope-manager" "7.4.0" - "@typescript-eslint/types" "7.4.0" - "@typescript-eslint/typescript-estree" "7.4.0" - "@typescript-eslint/visitor-keys" "7.4.0" + "@typescript-eslint/scope-manager" "8.3.0" + "@typescript-eslint/types" "8.3.0" + "@typescript-eslint/typescript-estree" "8.3.0" + "@typescript-eslint/visitor-keys" "8.3.0" debug "^4.3.4" -"@typescript-eslint/scope-manager@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-7.4.0.tgz#acfc69261f10ece7bf7ece1734f1713392c3655f" - integrity sha512-68VqENG5HK27ypafqLVs8qO+RkNc7TezCduYrx8YJpXq2QGZ30vmNZGJJJC48+MVn4G2dCV8m5ZTVnzRexTVtw== +"@typescript-eslint/scope-manager@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-8.3.0.tgz#834301d2e70baf924c26818b911bdc40086f7468" + integrity sha512-mz2X8WcN2nVu5Hodku+IR8GgCOl4C0G/Z1ruaWN4dgec64kDBabuXyPAr+/RgJtumv8EEkqIzf3X2U5DUKB2eg== dependencies: - "@typescript-eslint/types" "7.4.0" - "@typescript-eslint/visitor-keys" "7.4.0" + "@typescript-eslint/types" "8.3.0" + "@typescript-eslint/visitor-keys" "8.3.0" -"@typescript-eslint/type-utils@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-7.4.0.tgz#cfcaab21bcca441c57da5d3a1153555e39028cbd" - integrity sha512-247ETeHgr9WTRMqHbbQdzwzhuyaJ8dPTuyuUEMANqzMRB1rj/9qFIuIXK7l0FX9i9FXbHeBQl/4uz6mYuCE7Aw== +"@typescript-eslint/type-utils@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-8.3.0.tgz#c1ae6af8c21a27254321016b052af67ddb44a9ac" + integrity sha512-wrV6qh//nLbfXZQoj32EXKmwHf4b7L+xXLrP3FZ0GOUU72gSvLjeWUl5J5Ue5IwRxIV1TfF73j/eaBapxx99Lg== dependencies: - "@typescript-eslint/typescript-estree" "7.4.0" - "@typescript-eslint/utils" "7.4.0" + "@typescript-eslint/typescript-estree" "8.3.0" + "@typescript-eslint/utils" "8.3.0" debug "^4.3.4" - ts-api-utils "^1.0.1" + ts-api-utils "^1.3.0" -"@typescript-eslint/types@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-7.4.0.tgz#ee9dafa75c99eaee49de6dcc9348b45d354419b6" - integrity sha512-mjQopsbffzJskos5B4HmbsadSJQWaRK0UxqQ7GuNA9Ga4bEKeiO6b2DnB6cM6bpc8lemaPseh0H9B/wyg+J7rw== +"@typescript-eslint/types@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-8.3.0.tgz#378e62447c2d7028236e55a81d3391026600563b" + integrity sha512-y6sSEeK+facMaAyixM36dQ5NVXTnKWunfD1Ft4xraYqxP0lC0POJmIaL/mw72CUMqjY9qfyVfXafMeaUj0noWw== -"@typescript-eslint/typescript-estree@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-7.4.0.tgz#12dbcb4624d952f72c10a9f4431284fca24624f4" - integrity sha512-A99j5AYoME/UBQ1ucEbbMEmGkN7SE0BvZFreSnTd1luq7yulcHdyGamZKizU7canpGDWGJ+Q6ZA9SyQobipePg== +"@typescript-eslint/typescript-estree@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-8.3.0.tgz#3e3d38af101ba61a8568f034733b72bfc9f176b9" + integrity sha512-Mq7FTHl0R36EmWlCJWojIC1qn/ZWo2YiWYc1XVtasJ7FIgjo0MVv9rZWXEE7IK2CGrtwe1dVOxWwqXUdNgfRCA== dependencies: - "@typescript-eslint/types" "7.4.0" - "@typescript-eslint/visitor-keys" "7.4.0" + "@typescript-eslint/types" "8.3.0" + "@typescript-eslint/visitor-keys" "8.3.0" debug "^4.3.4" - globby "^11.1.0" + fast-glob "^3.3.2" is-glob "^4.0.3" - minimatch "9.0.3" - semver "^7.5.4" - ts-api-utils "^1.0.1" + minimatch "^9.0.4" + semver "^7.6.0" + ts-api-utils "^1.3.0" -"@typescript-eslint/utils@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-7.4.0.tgz#d889a0630cab88bddedaf7c845c64a00576257bd" - integrity sha512-NQt9QLM4Tt8qrlBVY9lkMYzfYtNz8/6qwZg8pI3cMGlPnj6mOpRxxAm7BMJN9K0AiY+1BwJ5lVC650YJqYOuNg== +"@typescript-eslint/utils@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-8.3.0.tgz#b10972319deac5959c7a7075d0cf2b5e1de7ec08" + integrity sha512-F77WwqxIi/qGkIGOGXNBLV7nykwfjLsdauRB/DOFPdv6LTF3BHHkBpq81/b5iMPSF055oO2BiivDJV4ChvNtXA== dependencies: "@eslint-community/eslint-utils" "^4.4.0" - "@types/json-schema" "^7.0.12" - "@types/semver" "^7.5.0" - "@typescript-eslint/scope-manager" "7.4.0" - "@typescript-eslint/types" "7.4.0" - "@typescript-eslint/typescript-estree" "7.4.0" - semver "^7.5.4" + "@typescript-eslint/scope-manager" "8.3.0" + "@typescript-eslint/types" "8.3.0" + "@typescript-eslint/typescript-estree" "8.3.0" -"@typescript-eslint/visitor-keys@7.4.0": - version "7.4.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-7.4.0.tgz#0c8ff2c1f8a6fe8d7d1a57ebbd4a638e86a60a94" - integrity sha512-0zkC7YM0iX5Y41homUUeW1CHtZR01K3ybjM1l6QczoMuay0XKtrb93kv95AxUGwdjGr64nNqnOCwmEl616N8CA== +"@typescript-eslint/visitor-keys@8.3.0": + version "8.3.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-8.3.0.tgz#320d747d107af1eef1eb43fbc4ccdbddda13068b" + integrity sha512-RmZwrTbQ9QveF15m/Cl28n0LXD6ea2CjkhH5rQ55ewz3H24w+AMCJHPVYaZ8/0HoG8Z3cLLFFycRXxeO2tz9FA== dependencies: - "@typescript-eslint/types" "7.4.0" - eslint-visitor-keys "^3.4.1" - -"@ungap/structured-clone@^1.2.0": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@ungap/structured-clone/-/structured-clone-1.2.0.tgz#756641adb587851b5ccb3e095daf27ae581c8406" - integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ== + "@typescript-eslint/types" "8.3.0" + eslint-visitor-keys "^3.4.3" "@webassemblyjs/ast@1.11.5", "@webassemblyjs/ast@^1.11.5": version "1.11.5" @@ -9781,11 +9776,16 @@ acorn@^7.0.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.0.4, acorn@^8.1.0, acorn@^8.7.1, acorn@^8.8.0, acorn@^8.8.2, acorn@^8.9.0: +acorn@^8.0.4, acorn@^8.1.0, acorn@^8.7.1, acorn@^8.8.0, acorn@^8.8.2: version "8.10.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5" integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw== +acorn@^8.12.0: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + acorn@^8.4.1: version "8.11.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.11.3.tgz#71e0b14e13a4ec160724b38fb7b0f233b1b81d7a" @@ -9861,7 +9861,7 @@ ajv-keywords@^3.5.2: resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d" integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ== -ajv-keywords@^5.0.0, ajv-keywords@^5.1.0: +ajv-keywords@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-5.1.0.tgz#69d4d385a4733cdbeab44964a1170a88f87f0e16" integrity sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw== @@ -9878,7 +9878,7 @@ ajv@^6.12.4, ajv@^6.12.5: json-schema-traverse "^0.4.1" uri-js "^4.2.2" -ajv@^8.0.0, ajv@^8.10.0, ajv@^8.12.0, ajv@^8.6.0, ajv@^8.8.0, ajv@^8.9.0: +ajv@^8.0.0, ajv@^8.10.0, ajv@^8.12.0, ajv@^8.6.0, ajv@^8.9.0: version "8.12.0" resolved "https://registry.yarnpkg.com/ajv/-/ajv-8.12.0.tgz#d1a0527323e22f53562c567c00991577dfbe19d1" integrity sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA== @@ -12388,13 +12388,6 @@ doctrine@^2.1.0: dependencies: esutils "^2.0.2" -doctrine@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" - integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w== - dependencies: - esutils "^2.0.2" - dogapi@2.8.4: version "2.8.4" resolved "https://registry.yarnpkg.com/dogapi/-/dogapi-2.8.4.tgz#ada64f20c6acdea206b9fd9e70df0c96241b6621" @@ -12957,54 +12950,55 @@ eslint-scope@5.1.1: esrecurse "^4.3.0" estraverse "^4.1.1" -eslint-scope@^7.2.2: - version "7.2.2" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.2.tgz#deb4f92563390f32006894af62a22dba1c46423f" - integrity sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg== +eslint-scope@^8.0.2: + version "8.0.2" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-8.0.2.tgz#5cbb33d4384c9136083a71190d548158fe128f94" + integrity sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA== dependencies: esrecurse "^4.3.0" estraverse "^5.2.0" -eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4.3: +eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.3: version "3.4.3" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz#0cd72fe8550e3c2eae156a96a4dddcd1c8ac5800" integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag== -eslint@^8.57.0: - version "8.57.0" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.57.0.tgz#c786a6fd0e0b68941aaf624596fb987089195668" - integrity sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ== +eslint-visitor-keys@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-4.0.0.tgz#e3adc021aa038a2a8e0b2f8b0ce8f66b9483b1fb" + integrity sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw== + +eslint@^9.9.1: + version "9.9.1" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-9.9.1.tgz#147ac9305d56696fb84cf5bdecafd6517ddc77ec" + integrity sha512-dHvhrbfr4xFQ9/dq+jcVneZMyRYLjggWjk6RVsIiHsP8Rz6yZ8LvZ//iU4TrZF+SXWG+JkNF2OyiZRvzgRDqMg== dependencies: "@eslint-community/eslint-utils" "^4.2.0" - "@eslint-community/regexpp" "^4.6.1" - "@eslint/eslintrc" "^2.1.4" - "@eslint/js" "8.57.0" - "@humanwhocodes/config-array" "^0.11.14" + "@eslint-community/regexpp" "^4.11.0" + "@eslint/config-array" "^0.18.0" + "@eslint/eslintrc" "^3.1.0" + "@eslint/js" "9.9.1" "@humanwhocodes/module-importer" "^1.0.1" + "@humanwhocodes/retry" "^0.3.0" "@nodelib/fs.walk" "^1.2.8" - "@ungap/structured-clone" "^1.2.0" ajv "^6.12.4" chalk "^4.0.0" cross-spawn "^7.0.2" debug "^4.3.2" - doctrine "^3.0.0" escape-string-regexp "^4.0.0" - eslint-scope "^7.2.2" - eslint-visitor-keys "^3.4.3" - espree "^9.6.1" - esquery "^1.4.2" + eslint-scope "^8.0.2" + eslint-visitor-keys "^4.0.0" + espree "^10.1.0" + esquery "^1.5.0" esutils "^2.0.2" fast-deep-equal "^3.1.3" - file-entry-cache "^6.0.1" + file-entry-cache "^8.0.0" find-up "^5.0.0" glob-parent "^6.0.2" - globals "^13.19.0" - graphemer "^1.4.0" ignore "^5.2.0" imurmurhash "^0.1.4" is-glob "^4.0.0" is-path-inside "^3.0.3" - js-yaml "^4.1.0" json-stable-stringify-without-jsonify "^1.0.1" levn "^0.4.1" lodash.merge "^4.6.2" @@ -13024,14 +13018,14 @@ esniff@^2.0.1: event-emitter "^0.3.5" type "^2.7.2" -espree@^9.6.0, espree@^9.6.1: - version "9.6.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.1.tgz#a2a17b8e434690a5432f2f8018ce71d331a48c6f" - integrity sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ== +espree@^10.0.1, espree@^10.1.0: + version "10.1.0" + resolved "https://registry.yarnpkg.com/espree/-/espree-10.1.0.tgz#8788dae611574c0f070691f522e4116c5a11fc56" + integrity sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA== dependencies: - acorn "^8.9.0" + acorn "^8.12.0" acorn-jsx "^5.3.2" - eslint-visitor-keys "^3.4.1" + eslint-visitor-keys "^4.0.0" esprima@^4.0.0, esprima@^4.0.1, esprima@~4.0.0: version "4.0.1" @@ -13045,10 +13039,10 @@ esquery@^1.0.1: dependencies: estraverse "^5.1.0" -esquery@^1.4.2: - version "1.5.0" - resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b" - integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg== +esquery@^1.5.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.6.0.tgz#91419234f804d852a82dceec3e16cdc22cf9dae7" + integrity sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg== dependencies: estraverse "^5.1.0" @@ -13302,7 +13296,7 @@ fast-glob@^3.1.1, fast-glob@^3.2.11, fast-glob@^3.2.12, fast-glob@^3.2.4: merge2 "^1.3.0" micromatch "^4.0.4" -fast-glob@^3.2.9: +fast-glob@^3.3.2: version "3.3.2" resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.2.tgz#a904501e57cfdd2ffcded45e99a54fef55e46129" integrity sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow== @@ -13451,12 +13445,12 @@ figures@3.2.0, figures@^3.0.0, figures@^3.2.0: dependencies: escape-string-regexp "^1.0.5" -file-entry-cache@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027" - integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg== +file-entry-cache@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-8.0.0.tgz#7787bddcf1131bffb92636c69457bbc0edd6d81f" + integrity sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ== dependencies: - flat-cache "^3.0.4" + flat-cache "^4.0.0" file-loader@6.2.0: version "6.2.0" @@ -13546,13 +13540,13 @@ find-up@^5.0.0: locate-path "^6.0.0" path-exists "^4.0.0" -flat-cache@^3.0.4: - version "3.0.4" - resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11" - integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg== +flat-cache@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-4.0.1.tgz#0ece39fcb14ee012f4b0410bd33dd9c1f011127c" + integrity sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw== dependencies: - flatted "^3.1.0" - rimraf "^3.0.2" + flatted "^3.2.9" + keyv "^4.5.4" flat@^5.0.2: version "5.0.2" @@ -13564,10 +13558,10 @@ flatted@^2.0.1: resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.2.tgz#4575b21e2bcee7434aa9be662f4b7b5f9c2b5138" integrity sha512-r5wGx7YeOwNWNlCA0wQ86zKyDLMQr+/RB8xy74M4hTphfmjlijTSSXGuH8rnvKZnfT9i+75zmd8jcKdMR4O6jA== -flatted@^3.1.0: - version "3.2.4" - resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.4.tgz#28d9969ea90661b5134259f312ab6aa7929ac5e2" - integrity sha512-8/sOawo8tJ4QOBX8YlQBMxL8+RLZfxMQOif9o0KUKTNTjMYElWPE0r/m5VNFxTRd0NSw8qSy8dajrwX4RYI1Hw== +flatted@^3.2.9: + version "3.3.1" + resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.3.1.tgz#21db470729a6734d4997002f439cb308987f567a" + integrity sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw== flow-parser@0.*: version "0.168.0" @@ -14081,12 +14075,10 @@ globals@^11.1.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== -globals@^13.19.0: - version "13.24.0" - resolved "https://registry.yarnpkg.com/globals/-/globals-13.24.0.tgz#8432a19d78ce0c1e833949c36adb345400bb1171" - integrity sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ== - dependencies: - type-fest "^0.20.2" +globals@^14.0.0: + version "14.0.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-14.0.0.tgz#898d7413c29babcf6bafe56fcadded858ada724e" + integrity sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ== globby@^11.0.1, globby@^11.0.2, globby@^11.0.3: version "11.0.4" @@ -14100,18 +14092,6 @@ globby@^11.0.1, globby@^11.0.2, globby@^11.0.3: merge2 "^1.3.0" slash "^3.0.0" -globby@^11.1.0: - version "11.1.0" - resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b" - integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g== - dependencies: - array-union "^2.1.0" - dir-glob "^3.0.1" - fast-glob "^3.2.9" - ignore "^5.2.0" - merge2 "^1.4.1" - slash "^3.0.0" - globby@^13.1.1: version "13.1.3" resolved "https://registry.yarnpkg.com/globby/-/globby-13.1.3.tgz#f62baf5720bcb2c1330c8d4ef222ee12318563ff" @@ -14802,10 +14782,10 @@ ignore@^5.0.4, ignore@^5.1.4, ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== -ignore@^5.2.4: - version "5.3.1" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.1.tgz#5073e554cd42c5b33b394375f538b8593e34d4ef" - integrity sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw== +ignore@^5.3.1: + version "5.3.2" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" + integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== image-size@1.0.0: version "1.0.0" @@ -16057,6 +16037,11 @@ json-bigint@^1.0.0: dependencies: bignumber.js "^9.0.0" +json-buffer@3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.1.tgz#9338802a30d3b6605fbe0613e094008ca8c05a13" + integrity sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ== + json-loader@^0.5.7: version "0.5.7" resolved "https://registry.yarnpkg.com/json-loader/-/json-loader-0.5.7.tgz#dca14a70235ff82f0ac9a3abeb60d337a365185d" @@ -16294,6 +16279,13 @@ keycode@^2.1.7: resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.1.tgz#09c23b2be0611d26117ea2501c2c391a01f39eff" integrity sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg== +keyv@^4.5.4: + version "4.5.4" + resolved "https://registry.yarnpkg.com/keyv/-/keyv-4.5.4.tgz#a879a99e29452f942439f2a405e3af8b31d4de93" + integrity sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw== + dependencies: + json-buffer "3.0.1" + kind-of@^6.0.2, kind-of@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" @@ -17160,13 +17152,6 @@ minimatch@3.0.5: dependencies: brace-expansion "^1.1.7" -minimatch@9.0.3: - version "9.0.3" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.3.tgz#a6e00c3de44c3a542bfaae70abfc22420a6da825" - integrity sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg== - dependencies: - brace-expansion "^2.0.1" - minimatch@^3.0.2, minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -17202,6 +17187,13 @@ minimatch@^9.0.1: dependencies: brace-expansion "^2.0.1" +minimatch@^9.0.4: + version "9.0.5" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" + integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== + dependencies: + brace-expansion "^2.0.1" + minimist-options@4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/minimist-options/-/minimist-options-4.1.0.tgz#c0655713c53a8a2ebd77ffa247d342c40f010619" @@ -19091,10 +19083,10 @@ prelude-ls@^1.2.1: resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g== -prettier-plugin-organize-imports@^3.2.4: - version "3.2.4" - resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-3.2.4.tgz#77967f69d335e9c8e6e5d224074609309c62845e" - integrity sha512-6m8WBhIp0dfwu0SkgfOxJqh+HpdyfqSSLfKKRZSFbDuEQXDDndb8fTpRWkUrX/uBenkex3MgnVk0J3b3Y5byog== +prettier-plugin-organize-imports@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/prettier-plugin-organize-imports/-/prettier-plugin-organize-imports-4.0.0.tgz#a69acf024ea3c8eb650c81f664693826ca853534" + integrity sha512-vnKSdgv9aOlqKeEFGhf9SCBsTyzDSyScy1k7E0R1Uo4L0cTcOV7c1XQaT7jfXIOc/p08WLBfN2QUQA9zDSZMxA== prettier-plugin-tailwindcss@^0.5.13: version "0.5.13" @@ -20597,17 +20589,7 @@ schema-utils@^3.0.0, schema-utils@^3.1.1, schema-utils@^3.2.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -schema-utils@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.0.0.tgz#60331e9e3ae78ec5d16353c467c34b3a0a1d3df7" - integrity sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg== - dependencies: - "@types/json-schema" "^7.0.9" - ajv "^8.8.0" - ajv-formats "^2.1.1" - ajv-keywords "^5.0.0" - -schema-utils@^4.2.0: +schema-utils@^4.0.0, schema-utils@^4.2.0: version "4.2.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== @@ -20669,7 +20651,7 @@ semver@^6.0.0, semver@^6.1.1, semver@^6.1.2, semver@^6.3.0, semver@^6.3.1: resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.1.tgz#556d2ef8689146e46dcea4bfdd095f3434dffcb4" integrity sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA== -semver@^7.2: +semver@^7.2, semver@^7.6.0: version "7.6.3" resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.3.tgz#980f7b5550bc175fb4dc09403085627f9eb33143" integrity sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A== @@ -22097,10 +22079,10 @@ ts-algebra@^1.2.0: resolved "https://registry.yarnpkg.com/ts-algebra/-/ts-algebra-1.2.0.tgz#f91c481207a770f0d14d055c376cbee040afdfc9" integrity sha512-kMuJJd8B2N/swCvIvn1hIFcIOrLGbWl9m/J6O3kHx9VRaevh00nvgjPiEGaRee7DRaAczMYR2uwWvXU22VFltw== -ts-api-utils@^1.0.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.2.1.tgz#f716c7e027494629485b21c0df6180f4d08f5e8b" - integrity sha512-RIYA36cJn2WiH9Hy77hdF9r7oEwxAtB/TS9/S4Qd90Ap4z5FSiin5zEiTL44OII1Y3IIlEvxwxFUVgrHSZ/UpA== +ts-api-utils@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/ts-api-utils/-/ts-api-utils-1.3.0.tgz#4b490e27129f1e8e686b45cc4ab63714dc60eea1" + integrity sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ== ts-app-env@^1.4.2: version "1.4.2" @@ -22308,11 +22290,6 @@ type-fest@^0.18.0: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.18.1.tgz#db4bc151a4a2cf4eebf9add5db75508db6cc841f" integrity sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw== -type-fest@^0.20.2: - version "0.20.2" - resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4" - integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ== - type-fest@^0.21.3: version "0.21.3" resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37" @@ -22373,6 +22350,15 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= +typescript-eslint@^8.3.0: + version "8.3.0" + resolved "https://registry.yarnpkg.com/typescript-eslint/-/typescript-eslint-8.3.0.tgz#f4d9c5ba71f6bead03ec41ecb2bece1de511e49f" + integrity sha512-EvWjwWLwwKDIJuBjk2I6UkV8KEQcwZ0VM10nR1rIunRDIP67QJTZAHBXTX0HW/oI1H10YESF8yWie8fRQxjvFA== + dependencies: + "@typescript-eslint/eslint-plugin" "8.3.0" + "@typescript-eslint/parser" "8.3.0" + "@typescript-eslint/utils" "8.3.0" + "typescript@^3 || ^4", typescript@^5.3.3, typescript@^5.5.4: version "5.4.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.2.tgz#0ae9cebcfae970718474fe0da2c090cad6577372" From d7128119c320efed44721fe6c4aad500d4617dbf Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 28 Aug 2024 18:52:10 -0700 Subject: [PATCH 432/529] chore(rethinkdb): Comment: Phase 1 (#10166) Signed-off-by: Matt Krick --- codegen.json | 3 + .../components/DiscussionThreadInput.tsx | 4 +- .../helpers/publishSimilarRetroTopics.ts | 13 ++ .../server/dataloader/customLoaderMakers.ts | 24 +-- .../dataloader/foreignKeyLoaderMakers.ts | 10 + .../dataloader/primaryKeyLoaderMakers.ts | 5 + .../server/graphql/mutations/addComment.ts | 183 ------------------ .../server/graphql/mutations/deleteComment.ts | 71 ------- .../server/graphql/mutations/endCheckIn.ts | 11 +- .../graphql/mutations/endSprintPoker.ts | 12 +- .../helpers/addAIGeneratedContentToThreads.ts | 11 +- .../mutations/helpers/safeEndRetrospective.ts | 18 +- .../resetRetroMeetingToGroupStage.ts | 1 + .../graphql/mutations/updateCommentContent.ts | 88 --------- .../graphql/public/mutations/addComment.ts | 170 ++++++++++++++++ .../public/mutations/addReactjiToReactable.ts | 1 - .../graphql/public/mutations/deleteComment.ts | 58 ++++++ .../public/mutations/updateCommentContent.ts | 71 +++++++ .../public/typeDefs/AddCommentInput.graphql | 2 +- .../graphql/public/typeDefs/Comment.graphql | 2 +- .../public/typeDefs/CreatePollInput.graphql | 2 +- .../public/typeDefs/CreateTaskInput.graphql | 2 +- .../graphql/public/typeDefs/Poll.graphql | 2 +- .../graphql/public/typeDefs/Task.graphql | 2 +- .../public/typeDefs/Threadable.graphql | 2 +- .../graphql/public/types/AddCommentSuccess.ts | 14 ++ .../public/types/DeleteCommentSuccess.ts | 13 ++ .../graphql/public/types/TeamPromptMeeting.ts | 14 +- .../server/graphql/public/types/Threadable.ts | 6 +- .../types/UpdateCommentContentSuccess.ts | 13 ++ packages/server/graphql/rootMutation.ts | 6 - packages/server/graphql/rootTypes.ts | 2 - .../server/graphql/types/AddCommentInput.ts | 34 ---- .../server/graphql/types/AddCommentPayload.ts | 25 --- packages/server/graphql/types/Comment.ts | 29 --- .../server/graphql/types/CreatePollInput.ts | 4 +- .../server/graphql/types/CreateTaskInput.ts | 3 +- .../graphql/types/DeleteCommentPayload.ts | 24 --- packages/server/graphql/types/Poll.ts | 3 - packages/server/graphql/types/Task.ts | 3 - packages/server/graphql/types/Threadable.ts | 89 --------- .../types/UpdateCommentContentPayload.ts | 24 --- .../1724780116674_Comment-phase1.ts | 48 +++++ packages/server/postgres/select.ts | 18 ++ 44 files changed, 497 insertions(+), 643 deletions(-) delete mode 100644 packages/server/graphql/mutations/addComment.ts delete mode 100644 packages/server/graphql/mutations/deleteComment.ts delete mode 100644 packages/server/graphql/mutations/updateCommentContent.ts create mode 100644 packages/server/graphql/public/mutations/addComment.ts create mode 100644 packages/server/graphql/public/mutations/deleteComment.ts create mode 100644 packages/server/graphql/public/mutations/updateCommentContent.ts create mode 100644 packages/server/graphql/public/types/AddCommentSuccess.ts create mode 100644 packages/server/graphql/public/types/DeleteCommentSuccess.ts create mode 100644 packages/server/graphql/public/types/UpdateCommentContentSuccess.ts delete mode 100644 packages/server/graphql/types/AddCommentInput.ts delete mode 100644 packages/server/graphql/types/AddCommentPayload.ts delete mode 100644 packages/server/graphql/types/Comment.ts delete mode 100644 packages/server/graphql/types/DeleteCommentPayload.ts delete mode 100644 packages/server/graphql/types/Threadable.ts delete mode 100644 packages/server/graphql/types/UpdateCommentContentPayload.ts create mode 100644 packages/server/postgres/migrations/1724780116674_Comment-phase1.ts diff --git a/codegen.json b/codegen.json index f8e66b6e79c..50e40fa3bbe 100644 --- a/codegen.json +++ b/codegen.json @@ -48,6 +48,9 @@ "mappers": { "SetSlackNotificationPayload": "./types/SetSlackNotificationPayload#SetSlackNotificationPayloadSource", "SetDefaultSlackChannelSuccess": "./types/SetDefaultSlackChannelSuccess#SetDefaultSlackChannelSuccessSource", + "AddCommentSuccess": "./types/AddCommentSuccess#AddCommentSuccessSource", + "DeleteCommentSuccess": "./types/DeleteCommentSuccess#DeleteCommentSuccessSource", + "UpdateCommentContentSuccess": "./types/UpdateCommentContentSuccess#UpdateCommentContentSuccessSource", "AddSlackAuthPayload": "./types/AddSlackAuthPayload#AddSlackAuthPayloadSource", "RemoveAgendaItemPayload": "./types/RemoveAgendaItemPayload#RemoveAgendaItemPayloadSource", "AddAgendaItemPayload": "./types/AddAgendaItemPayload#AddAgendaItemPayloadSource", diff --git a/packages/client/components/DiscussionThreadInput.tsx b/packages/client/components/DiscussionThreadInput.tsx index 5d2e1f0373a..c9ed94f2e90 100644 --- a/packages/client/components/DiscussionThreadInput.tsx +++ b/packages/client/components/DiscussionThreadInput.tsx @@ -196,7 +196,7 @@ const DiscussionThreadInput = forwardRef((props: Props, ref: any) => { isAnonymous: isAnonymousComment, discussionId, threadParentId, - threadSortOrder: getMaxSortOrder() + SORT_STEP + dndNoise() + threadSortOrder: getMaxSortOrder() + SORT_STEP } AddCommentMutation(atmosphere, {comment}, {onError, onCompleted}) // move focus to end is very important! otherwise ghost chars appear @@ -263,7 +263,7 @@ const DiscussionThreadInput = forwardRef((props: Props, ref: any) => { discussionId, meetingId, threadParentId, - threadSortOrder: getMaxSortOrder() + SORT_STEP + dndNoise(), + threadSortOrder: getMaxSortOrder() + SORT_STEP, userId: viewerId, teamId } as const diff --git a/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts b/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts index 0f881ee4621..8490667c5eb 100644 --- a/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts +++ b/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts @@ -8,6 +8,7 @@ import { buildCommentContentBlock, createAIComment } from '../../../server/graphql/mutations/helpers/addAIGeneratedContentToThreads' +import getKysely from '../../../server/postgres/getKysely' import getPhase from '../../../server/utils/getPhase' import publish from '../../../server/utils/publish' @@ -54,6 +55,7 @@ export const publishSimilarRetroTopics = async ( dataLoader: DataLoaderInstance ) => { const r = await getRethink() + const pg = getKysely() const links = await Promise.all( similarEmbeddings.map((se) => makeSimilarDiscussionLink(se, dataLoader)) ) @@ -68,5 +70,16 @@ export const publishSimilarRetroTopics = async ( 2 ) await r.table('Comment').insert(relatedDiscussionsComment).run() + await pg + .insertInto('Comment') + .values({ + id: relatedDiscussionsComment.id, + content: relatedDiscussionsComment.content, + plaintextContent: relatedDiscussionsComment.plaintextContent, + createdBy: relatedDiscussionsComment.createdBy, + threadSortOrder: relatedDiscussionsComment.threadSortOrder, + discussionId: relatedDiscussionsComment.discussionId + }) + .execute() publishComment(meetingId, relatedDiscussionsComment.id) } diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 8a5b883dce3..00ef82e4031 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -83,31 +83,21 @@ export const commentCountByDiscussionId = ( dependsOn('comments') return new DataLoader( async (discussionIds) => { - const r = await getRethink() - const groups = (await ( - r - .table('Comment') - .getAll(r.args(discussionIds as string[]), {index: 'discussionId'}) - .filter((row: RDatum) => - row('isActive').eq(true).and(row('createdBy').ne(PARABOL_AI_USER_ID)) - ) - .group('discussionId') as any + const commentsByDiscussionId = await Promise.all( + discussionIds.map((discussionId) => parent.get('commentsByDiscussionId').load(discussionId)) ) - .count() - .ungroup() - .run()) as {group: string; reduction: number}[] - const lookup: Record = {} - groups.forEach(({group, reduction}) => { - lookup[group] = reduction + return commentsByDiscussionId.map((commentArr) => { + const activeHumanComments = commentArr.filter( + (comment) => comment.isActive && comment.createdBy !== PARABOL_AI_USER_ID + ) + return activeHumanComments.length }) - return discussionIds.map((discussionId) => lookup[discussionId] || 0) }, { ...parent.dataLoaderOptions } ) } - export const latestTaskEstimates = (parent: RootDataLoader) => { return new DataLoader( async (taskIds) => { diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 2ff2918b551..d65ab6b9385 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -2,6 +2,7 @@ import getKysely from '../postgres/getKysely' import {getTeamPromptResponsesByMeetingIds} from '../postgres/queries/getTeamPromptResponsesByMeetingIds' import { selectAgendaItems, + selectComments, selectOrganizations, selectRetroReflections, selectSlackAuths, @@ -205,3 +206,12 @@ export const slackNotificationsByTeamId = foreignKeyLoaderMaker( return selectSlackNotifications().where('teamId', 'in', teamIds).execute() } ) + +export const _pgcommentsByDiscussionId = foreignKeyLoaderMaker( + '_pgcomments', + 'discussionId', + async (discussionIds) => { + // include deleted comments so we can replace them with tombstones + return selectComments().where('discussionId', 'in', discussionIds).execute() + } +) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index d514172dde0..c57d6b9adab 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -7,6 +7,7 @@ import getTemplateRefsByIds from '../postgres/queries/getTemplateRefsByIds' import {getUsersByIds} from '../postgres/queries/getUsersByIds' import { selectAgendaItems, + selectComments, selectMeetingSettings, selectOrganizations, selectRetroReflections, @@ -105,3 +106,7 @@ export const slackAuths = primaryKeyLoaderMaker((ids: readonly string[]) => { export const slackNotifications = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectSlackNotifications().where('id', 'in', ids).execute() }) + +export const _pgcomments = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectComments().where('id', 'in', ids).execute() +}) diff --git a/packages/server/graphql/mutations/addComment.ts b/packages/server/graphql/mutations/addComment.ts deleted file mode 100644 index 1a117aeb780..00000000000 --- a/packages/server/graphql/mutations/addComment.ts +++ /dev/null @@ -1,183 +0,0 @@ -import {GraphQLNonNull} from 'graphql' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' -import MeetingMemberId from '../../../client/shared/gqlIds/MeetingMemberId' -import TeamMemberId from '../../../client/shared/gqlIds/TeamMemberId' -import getTypeFromEntityMap from '../../../client/utils/draftjs/getTypeFromEntityMap' -import getRethink from '../../database/rethinkDriver' -import Comment from '../../database/types/Comment' -import GenericMeetingPhase, { - NewMeetingPhaseTypeEnum -} from '../../database/types/GenericMeetingPhase' -import GenericMeetingStage from '../../database/types/GenericMeetingStage' -import NotificationDiscussionMentioned from '../../database/types/NotificationDiscussionMentioned' -import NotificationResponseReplied from '../../database/types/NotificationResponseReplied' -import {IGetDiscussionsByIdsQueryResult} from '../../postgres/queries/generated/getDiscussionsByIdsQuery' -import {analytics} from '../../utils/analytics/analytics' -import {getUserId} from '../../utils/authorization' -import publish from '../../utils/publish' -import {GQLContext} from '../graphql' -import publishNotification from '../public/mutations/helpers/publishNotification' -import AddCommentInput from '../types/AddCommentInput' -import AddCommentPayload from '../types/AddCommentPayload' -import {IntegrationNotifier} from './helpers/notifications/IntegrationNotifier' - -type AddCommentMutationVariables = { - comment: { - discussionId: string - content: string - threadSortOrder: number - } -} - -const getMentionNotifications = ( - content: string, - viewerId: string, - discussion: IGetDiscussionsByIdsQueryResult, - commentId: string, - meetingId: string -) => { - let parsedContent: any - try { - parsedContent = JSON.parse(content) - } catch { - // If we can't parse the content, assume no new notifications. - return [] - } - - const {entityMap} = parsedContent - return getTypeFromEntityMap('MENTION', entityMap) - .filter((mentionedUserId) => { - if (mentionedUserId === viewerId) { - return false - } - - if (discussion.discussionTopicType === 'teamPromptResponse') { - const {userId: responseUserId} = TeamMemberId.split(discussion.discussionTopicId) - if (responseUserId === mentionedUserId) { - // The mentioned user will already receive a 'RESPONSE_REPLIED' notification for this - // comment - return false - } - } - - // :TODO: (jmtaber129): Consider limiting these to when the mentionee is *not* on the - // relevant page. - return true - }) - .map( - (mentioneeUserId) => - new NotificationDiscussionMentioned({ - userId: mentioneeUserId, - meetingId: meetingId, - authorId: viewerId, - commentId, - discussionId: discussion.id - }) - ) -} - -const addComment = { - type: new GraphQLNonNull(AddCommentPayload), - description: `Add a comment to a discussion`, - args: { - comment: { - type: new GraphQLNonNull(AddCommentInput), - description: 'A partial new comment' - } - }, - resolve: async ( - _source: unknown, - {comment}: AddCommentMutationVariables, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) => { - const r = await getRethink() - const viewerId = getUserId(authToken) - const operationId = dataLoader.share() - const subOptions = {mutatorId, operationId} - - //AUTH - const {discussionId} = comment - const discussion = await dataLoader.get('discussions').load(discussionId) - if (!discussion) { - return {error: {message: 'Invalid discussion thread'}} - } - const {meetingId} = discussion - if (!meetingId) { - return {error: {message: 'Discussion does not take place in a meeting'}} - } - const meetingMemberId = MeetingMemberId.join(meetingId, viewerId) - const [meeting, viewerMeetingMember, viewer] = await Promise.all([ - dataLoader.get('newMeetings').load(meetingId), - dataLoader.get('meetingMembers').load(meetingMemberId), - dataLoader.get('users').loadNonNull(viewerId) - ]) - - if (!viewerMeetingMember) { - return {error: {message: 'Not a member of the meeting'}} - } - - // VALIDATION - const content = normalizeRawDraftJS(comment.content) - - const dbComment = new Comment({...comment, content, createdBy: viewerId}) - const {id: commentId, isAnonymous, threadParentId} = dbComment - await r.table('Comment').insert(dbComment).run() - - if (discussion.discussionTopicType === 'teamPromptResponse') { - const {userId: responseUserId} = TeamMemberId.split(discussion.discussionTopicId) - - if (responseUserId !== viewerId) { - const notification = new NotificationResponseReplied({ - userId: responseUserId, - meetingId: meetingId, - authorId: viewerId, - commentId - }) - - await r.table('Notification').insert(notification).run() - - IntegrationNotifier.sendNotificationToUser?.( - dataLoader, - notification.id, - notification.userId - ) - publishNotification(notification, subOptions) - } - } - - const notificationsToAdd = getMentionNotifications( - content, - viewerId, - discussion, - commentId, - meetingId - ) - - if (notificationsToAdd.length) { - await r.table('Notification').insert(notificationsToAdd).run() - notificationsToAdd.forEach((notification) => { - publishNotification(notification, subOptions) - }) - } - - const data = {commentId, meetingId} - const {phases} = meeting! - const threadablePhases = [ - 'discuss', - 'agendaitems', - 'ESTIMATE', - 'RESPONSES' - ] as NewMeetingPhaseTypeEnum[] - const containsThreadablePhase = phases.find(({phaseType}: GenericMeetingPhase) => - threadablePhases.includes(phaseType) - )! - const {stages} = containsThreadablePhase - const isAsync = stages.some((stage: GenericMeetingStage) => stage.isAsync) - analytics.commentAdded(viewer, meeting, isAnonymous, isAsync, !!threadParentId) - publish(SubscriptionChannel.MEETING, meetingId, 'AddCommentSuccess', data, subOptions) - return data - } -} - -export default addComment diff --git a/packages/server/graphql/mutations/deleteComment.ts b/packages/server/graphql/mutations/deleteComment.ts deleted file mode 100644 index a3910576a4f..00000000000 --- a/packages/server/graphql/mutations/deleteComment.ts +++ /dev/null @@ -1,71 +0,0 @@ -import {GraphQLID, GraphQLNonNull} from 'graphql' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' -import {PARABOL_AI_USER_ID} from '../../../client/utils/constants' -import getRethink from '../../database/rethinkDriver' -import {getUserId} from '../../utils/authorization' -import publish from '../../utils/publish' -import {GQLContext} from '../graphql' -import DeleteCommentPayload from '../types/DeleteCommentPayload' - -type DeleteCommentMutationVariables = { - commentId: string - meetingId: string -} - -const deleteComment = { - type: new GraphQLNonNull(DeleteCommentPayload), - description: `Delete a comment from a discussion`, - args: { - commentId: { - type: new GraphQLNonNull(GraphQLID) - }, - meetingId: { - type: new GraphQLNonNull(GraphQLID) - } - }, - resolve: async ( - _source: unknown, - {commentId, meetingId}: DeleteCommentMutationVariables, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) => { - const r = await getRethink() - const viewerId = getUserId(authToken) - const operationId = dataLoader.share() - const subOptions = {mutatorId, operationId} - const now = new Date() - - //AUTH - const meetingMemberId = toTeamMemberId(meetingId, viewerId) - const [comment, viewerMeetingMember] = await Promise.all([ - r.table('Comment').get(commentId).run(), - dataLoader.get('meetingMembers').load(meetingMemberId), - dataLoader.get('newMeetings').load(meetingId) - ]) - if (!comment || !comment.isActive) { - return {error: {message: 'Comment does not exist'}} - } - if (!viewerMeetingMember) { - return {error: {message: `Not a member of the meeting`}} - } - const {createdBy, discussionId} = comment - const discussion = await dataLoader.get('discussions').loadNonNull(discussionId) - if (discussion.meetingId !== meetingId) { - return {error: {message: `Comment is not from this meeting`}} - } - if (createdBy !== viewerId && createdBy !== PARABOL_AI_USER_ID) { - return {error: {message: 'Can only delete your own comment or Parabol AI comments'}} - } - - await r.table('Comment').get(commentId).update({isActive: false, updatedAt: now}).run() - - const data = {commentId} - - if (meetingId) { - publish(SubscriptionChannel.MEETING, meetingId, 'DeleteCommentSuccess', data, subOptions) - } - return data - } -} - -export default deleteComment diff --git a/packages/server/graphql/mutations/endCheckIn.ts b/packages/server/graphql/mutations/endCheckIn.ts index a3e0c2573da..4ad865c40b0 100644 --- a/packages/server/graphql/mutations/endCheckIn.ts +++ b/packages/server/graphql/mutations/endCheckIn.ts @@ -23,6 +23,7 @@ import getPhase from '../../utils/getPhase' import publish from '../../utils/publish' import standardError from '../../utils/standardError' import {DataLoaderWorker, GQLContext} from '../graphql' +import isValid from '../isValid' import EndCheckInPayload from '../types/EndCheckInPayload' import sendNewMeetingSummary from './helpers/endMeeting/sendNewMeetingSummary' import gatherInsights from './helpers/gatherInsights' @@ -133,6 +134,10 @@ const summarizeCheckInMeeting = async (meeting: MeetingAction, dataLoader: DataL const pinnedAgendaItems = await getPinnedAgendaItems(teamId, dataLoader) const isKill = !!(meetingPhase && ![AGENDA_ITEMS, LAST_CALL].includes(meetingPhase.phaseType)) if (!isKill) await clearAgendaItems(teamId, dataLoader) + const commentCounts = ( + await dataLoader.get('commentCountByDiscussionId').loadMany(discussionIds) + ).filter(isValid) + const commentCount = commentCounts.reduce((cumSum, count) => cumSum + count, 0) await Promise.all([ isKill ? undefined : archiveTasksForDB(doneTasks, meetingId), isKill ? undefined : clonePinnedAgendaItems(pinnedAgendaItems, dataLoader), @@ -143,11 +148,7 @@ const summarizeCheckInMeeting = async (meeting: MeetingAction, dataLoader: DataL .update( { agendaItemCount: activeAgendaItems.length, - commentCount: r - .table('Comment') - .getAll(r.args(discussionIds), {index: 'discussionId'}) - .count() - .default(0) as unknown as number, + commentCount, taskCount: tasks.length }, {nonAtomic: true} diff --git a/packages/server/graphql/mutations/endSprintPoker.ts b/packages/server/graphql/mutations/endSprintPoker.ts index 267aa3eb4cf..00db314a2ab 100644 --- a/packages/server/graphql/mutations/endSprintPoker.ts +++ b/packages/server/graphql/mutations/endSprintPoker.ts @@ -16,6 +16,7 @@ import getRedis from '../../utils/getRedis' import publish from '../../utils/publish' import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' +import isValid from '../isValid' import EndSprintPokerPayload from '../types/EndSprintPokerPayload' import sendNewMeetingSummary from './helpers/endMeeting/sendNewMeetingSummary' import gatherInsights from './helpers/gatherInsights' @@ -77,7 +78,10 @@ export default { ).size const discussionIds = estimateStages.map((stage) => stage.discussionId) const insights = await gatherInsights(meeting, dataLoader) - + const commentCounts = ( + await dataLoader.get('commentCountByDiscussionId').loadMany(discussionIds) + ).filter(isValid) + const commentCount = commentCounts.reduce((cumSum, count) => cumSum + count, 0) const completedMeeting = (await r .table('NewMeeting') .get(meetingId) @@ -85,11 +89,7 @@ export default { { endedAt: now, phases, - commentCount: r - .table('Comment') - .getAll(r.args(discussionIds), {index: 'discussionId'}) - .count() - .default(0) as unknown as number, + commentCount, storyCount, ...insights }, diff --git a/packages/server/graphql/mutations/helpers/addAIGeneratedContentToThreads.ts b/packages/server/graphql/mutations/helpers/addAIGeneratedContentToThreads.ts index fa513232f98..5eae10abdb7 100644 --- a/packages/server/graphql/mutations/helpers/addAIGeneratedContentToThreads.ts +++ b/packages/server/graphql/mutations/helpers/addAIGeneratedContentToThreads.ts @@ -2,6 +2,7 @@ import {PARABOL_AI_USER_ID} from '../../../../client/utils/constants' import getRethink from '../../../database/rethinkDriver' import Comment from '../../../database/types/Comment' import DiscussStage from '../../../database/types/DiscussStage' +import getKysely from '../../../postgres/getKysely' import {convertHtmlToTaskContent} from '../../../utils/draftjs/convertHtmlToTaskContent' import {DataLoaderWorker} from '../../graphql' @@ -45,7 +46,15 @@ const addAIGeneratedContentToThreads = async ( ) comments.push(topicSummaryComment) } - + const pgComments = comments.map((comment) => ({ + id: comment.id, + content: comment.content, + plaintextContent: comment.plaintextContent, + createdBy: comment.createdBy, + threadSortOrder: comment.threadSortOrder, + discussionId: comment.discussionId + })) + await getKysely().insertInto('Comment').values(pgComments).execute() return r.table('Comment').insert(comments).run() }) await Promise.all(commentPromises) diff --git a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts index 3f3f0b4be4f..1af261de253 100644 --- a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts @@ -1,10 +1,9 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import {DISCUSS, PARABOL_AI_USER_ID} from 'parabol-client/utils/constants' +import {DISCUSS} from 'parabol-client/utils/constants' import getMeetingPhase from 'parabol-client/utils/getMeetingPhase' import findStageById from 'parabol-client/utils/meetings/findStageById' import {checkTeamsLimit} from '../../../billing/helpers/teamLimitsCheck' import getRethink from '../../../database/rethinkDriver' -import {RDatum} from '../../../database/stricterR' import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import TimelineEventRetroComplete from '../../../database/types/TimelineEventRetroComplete' import getKysely from '../../../postgres/getKysely' @@ -17,6 +16,7 @@ import getPhase from '../../../utils/getPhase' import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' import {InternalContext} from '../../graphql' +import isValid from '../../isValid' import sendNewMeetingSummary from './endMeeting/sendNewMeetingSummary' import gatherInsights from './gatherInsights' import generateWholeMeetingSentimentScore from './generateWholeMeetingSentimentScore' @@ -51,20 +51,16 @@ const summarizeRetroMeeting = async (meeting: MeetingRetrospective, context: Int generateWholeMeetingSummary(discussionIds, meetingId, teamId, facilitatorUserId, dataLoader), getTranscription(recallBotId) ]) - + const commentCounts = ( + await dataLoader.get('commentCountByDiscussionId').loadMany(discussionIds) + ).filter(isValid) + const commentCount = commentCounts.reduce((cumSum, count) => cumSum + count, 0) await r .table('NewMeeting') .get(meetingId) .update( { - commentCount: r - .table('Comment') - .getAll(r.args(discussionIds), {index: 'discussionId'}) - .filter((row: RDatum) => - row('isActive').eq(true).and(row('createdBy').ne(PARABOL_AI_USER_ID)) - ) - .count() - .default(0) as unknown as number, + commentCount, taskCount: r .table('Task') .getAll(r.args(discussionIds), {index: 'discussionId'}) diff --git a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts index 5e1238fedb9..0e775bc2dc2 100644 --- a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts +++ b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts @@ -109,6 +109,7 @@ const resetRetroMeetingToGroupStage = { .getAll(r.args(discussionIdsToDelete), {index: 'discussionId'}) .delete() .run(), + pg.deleteFrom('Comment').where('discussionId', 'in', discussionIdsToDelete).execute(), r.table('Task').getAll(r.args(discussionIdsToDelete), {index: 'discussionId'}).delete().run(), pg .updateTable('RetroReflectionGroup') diff --git a/packages/server/graphql/mutations/updateCommentContent.ts b/packages/server/graphql/mutations/updateCommentContent.ts deleted file mode 100644 index 362301e9019..00000000000 --- a/packages/server/graphql/mutations/updateCommentContent.ts +++ /dev/null @@ -1,88 +0,0 @@ -import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import extractTextFromDraftString from 'parabol-client/utils/draftjs/extractTextFromDraftString' -import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' -import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' -import {PARABOL_AI_USER_ID} from '../../../client/utils/constants' -import getRethink from '../../database/rethinkDriver' -import {getUserId} from '../../utils/authorization' -import publish from '../../utils/publish' -import standardError from '../../utils/standardError' -import {GQLContext} from '../graphql' -import UpdateCommentContentPayload from '../types/UpdateCommentContentPayload' - -export default { - type: UpdateCommentContentPayload, - description: 'Update the content of a comment', - args: { - commentId: { - type: new GraphQLNonNull(GraphQLID) - }, - content: { - type: new GraphQLNonNull(GraphQLString), - description: 'A stringified draft-js document containing thoughts' - }, - meetingId: { - type: new GraphQLNonNull(GraphQLID) - } - }, - async resolve( - _source: unknown, - {commentId, content, meetingId}: {commentId: string; content: string; meetingId: string}, - {authToken, dataLoader, socketId: mutatorId}: GQLContext - ) { - const r = await getRethink() - const operationId = dataLoader.share() - const now = new Date() - const subOptions = {operationId, mutatorId} - - // AUTH - const viewerId = getUserId(authToken) - const meetingMemberId = toTeamMemberId(meetingId, viewerId) - const [comment, viewerMeetingMember] = await Promise.all([ - r.table('Comment').get(commentId).run(), - dataLoader.get('meetingMembers').load(meetingMemberId), - dataLoader.get('newMeetings').load(meetingId) - ]) - if (!comment || !comment.isActive) { - return standardError(new Error('comment not found'), {userId: viewerId}) - } - if (!viewerMeetingMember) { - return {error: {message: `Not a member of the meeting`}} - } - const {createdBy, discussionId} = comment - const discussion = await dataLoader.get('discussions').loadNonNull(discussionId) - if (discussion.meetingId !== meetingId) { - return {error: {message: `Comment is not from this meeting`}} - } - if (createdBy !== viewerId && createdBy !== PARABOL_AI_USER_ID) { - return {error: {message: 'Can only update your own comment or Parabol AI comments'}} - } - - // VALIDATION - const normalizedContent = normalizeRawDraftJS(content) - - // RESOLUTION - const plaintextContent = extractTextFromDraftString(normalizedContent) - await r - .table('Comment') - .get(commentId) - .update({content: normalizedContent, plaintextContent, updatedAt: now}) - .run() - - // :TODO: (jmtaber129): diff new and old comment content for mentions and handle notifications - // appropriately. - - const data = {commentId} - if (meetingId) { - publish( - SubscriptionChannel.MEETING, - meetingId, - 'UpdateCommentContentSuccess', - data, - subOptions - ) - } - return data - } -} diff --git a/packages/server/graphql/public/mutations/addComment.ts b/packages/server/graphql/public/mutations/addComment.ts new file mode 100644 index 00000000000..2e0392c062e --- /dev/null +++ b/packages/server/graphql/public/mutations/addComment.ts @@ -0,0 +1,170 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' +import MeetingMemberId from '../../../../client/shared/gqlIds/MeetingMemberId' +import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' +import getTypeFromEntityMap from '../../../../client/utils/draftjs/getTypeFromEntityMap' +import getRethink from '../../../database/rethinkDriver' +import Comment from '../../../database/types/Comment' +import GenericMeetingPhase, { + NewMeetingPhaseTypeEnum +} from '../../../database/types/GenericMeetingPhase' +import GenericMeetingStage from '../../../database/types/GenericMeetingStage' +import NotificationDiscussionMentioned from '../../../database/types/NotificationDiscussionMentioned' +import NotificationResponseReplied from '../../../database/types/NotificationResponseReplied' +import getKysely from '../../../postgres/getKysely' +import {IGetDiscussionsByIdsQueryResult} from '../../../postgres/queries/generated/getDiscussionsByIdsQuery' +import {analytics} from '../../../utils/analytics/analytics' +import {getUserId} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import {IntegrationNotifier} from '../../mutations/helpers/notifications/IntegrationNotifier' +import {MutationResolvers} from '../resolverTypes' +import publishNotification from './helpers/publishNotification' + +const getMentionNotifications = ( + content: string, + viewerId: string, + discussion: IGetDiscussionsByIdsQueryResult, + commentId: string, + meetingId: string +) => { + let parsedContent: any + try { + parsedContent = JSON.parse(content) + } catch { + // If we can't parse the content, assume no new notifications. + return [] + } + + const {entityMap} = parsedContent + return getTypeFromEntityMap('MENTION', entityMap) + .filter((mentionedUserId) => { + if (mentionedUserId === viewerId) { + return false + } + + if (discussion.discussionTopicType === 'teamPromptResponse') { + const {userId: responseUserId} = TeamMemberId.split(discussion.discussionTopicId) + if (responseUserId === mentionedUserId) { + // The mentioned user will already receive a 'RESPONSE_REPLIED' notification for this + // comment + return false + } + } + + // :TODO: (jmtaber129): Consider limiting these to when the mentionee is *not* on the + // relevant page. + return true + }) + .map( + (mentioneeUserId) => + new NotificationDiscussionMentioned({ + userId: mentioneeUserId, + meetingId: meetingId, + authorId: viewerId, + commentId, + discussionId: discussion.id + }) + ) +} + +const addComment: MutationResolvers['addComment'] = async ( + _source, + {comment}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const r = await getRethink() + const viewerId = getUserId(authToken) + const operationId = dataLoader.share() + const subOptions = {mutatorId, operationId} + + //AUTH + const {discussionId} = comment + const discussion = await dataLoader.get('discussions').load(discussionId) + if (!discussion) { + return {error: {message: 'Invalid discussion thread'}} + } + const {meetingId} = discussion + if (!meetingId) { + return {error: {message: 'Discussion does not take place in a meeting'}} + } + const meetingMemberId = MeetingMemberId.join(meetingId, viewerId) + const [meeting, viewerMeetingMember, viewer] = await Promise.all([ + dataLoader.get('newMeetings').load(meetingId), + dataLoader.get('meetingMembers').load(meetingMemberId), + dataLoader.get('users').loadNonNull(viewerId) + ]) + + if (!viewerMeetingMember) { + return {error: {message: 'Not a member of the meeting'}} + } + + // VALIDATION + const content = normalizeRawDraftJS(comment.content) + + const dbComment = new Comment({...comment, content, createdBy: viewerId}) + const {id: commentId, isAnonymous, threadParentId} = dbComment + await r.table('Comment').insert(dbComment).run() + await getKysely() + .insertInto('Comment') + .values({ + id: dbComment.id, + content: dbComment.content, + plaintextContent: dbComment.plaintextContent, + createdBy: dbComment.createdBy, + threadSortOrder: dbComment.threadSortOrder, + discussionId: dbComment.discussionId + }) + .execute() + + if (discussion.discussionTopicType === 'teamPromptResponse') { + const {userId: responseUserId} = TeamMemberId.split(discussion.discussionTopicId) + + if (responseUserId !== viewerId) { + const notification = new NotificationResponseReplied({ + userId: responseUserId, + meetingId: meetingId, + authorId: viewerId, + commentId + }) + + await r.table('Notification').insert(notification).run() + + IntegrationNotifier.sendNotificationToUser?.(dataLoader, notification.id, notification.userId) + publishNotification(notification, subOptions) + } + } + + const notificationsToAdd = getMentionNotifications( + content, + viewerId, + discussion, + commentId, + meetingId + ) + + if (notificationsToAdd.length) { + await r.table('Notification').insert(notificationsToAdd).run() + notificationsToAdd.forEach((notification) => { + publishNotification(notification, subOptions) + }) + } + + const data = {commentId, meetingId} + const {phases} = meeting! + const threadablePhases = [ + 'discuss', + 'agendaitems', + 'ESTIMATE', + 'RESPONSES' + ] as NewMeetingPhaseTypeEnum[] + const containsThreadablePhase = phases.find(({phaseType}: GenericMeetingPhase) => + threadablePhases.includes(phaseType) + )! + const {stages} = containsThreadablePhase + const isAsync = stages.some((stage: GenericMeetingStage) => stage.isAsync) + analytics.commentAdded(viewer, meeting, isAnonymous, isAsync, !!threadParentId) + publish(SubscriptionChannel.MEETING, meetingId, 'AddCommentSuccess', data, subOptions) + return data +} + +export default addComment diff --git a/packages/server/graphql/public/mutations/addReactjiToReactable.ts b/packages/server/graphql/public/mutations/addReactjiToReactable.ts index b60543c50ad..93225e8837e 100644 --- a/packages/server/graphql/public/mutations/addReactjiToReactable.ts +++ b/packages/server/graphql/public/mutations/addReactjiToReactable.ts @@ -93,7 +93,6 @@ const addReactjiToReactable: MutationResolvers['addReactjiToReactable'] = async tableName === 'TeamPromptResponse' ? TeamPromptResponseId.split(reactableId) : reactableId const updatePG = async (pgTable: ValueOf) => { - if (pgTable === 'Comment') return if (isRemove) { await pg .updateTable(pgTable) diff --git a/packages/server/graphql/public/mutations/deleteComment.ts b/packages/server/graphql/public/mutations/deleteComment.ts new file mode 100644 index 00000000000..957cbda541a --- /dev/null +++ b/packages/server/graphql/public/mutations/deleteComment.ts @@ -0,0 +1,58 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' +import {PARABOL_AI_USER_ID} from '../../../../client/utils/constants' +import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' +import {getUserId} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import {MutationResolvers} from '../resolverTypes' + +const deleteComment: MutationResolvers['deleteComment'] = async ( + _source, + {commentId, meetingId}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const r = await getRethink() + const viewerId = getUserId(authToken) + const operationId = dataLoader.share() + const subOptions = {mutatorId, operationId} + const now = new Date() + + //AUTH + const meetingMemberId = toTeamMemberId(meetingId, viewerId) + const [comment, viewerMeetingMember] = await Promise.all([ + dataLoader.get('comments').load(commentId), + dataLoader.get('meetingMembers').load(meetingMemberId), + dataLoader.get('newMeetings').load(meetingId) + ]) + if (!comment || !comment.isActive) { + return {error: {message: 'Comment does not exist'}} + } + if (!viewerMeetingMember) { + return {error: {message: `Not a member of the meeting`}} + } + const {createdBy, discussionId} = comment + const discussion = await dataLoader.get('discussions').loadNonNull(discussionId) + if (discussion.meetingId !== meetingId) { + return {error: {message: `Comment is not from this meeting`}} + } + if (createdBy !== viewerId && createdBy !== PARABOL_AI_USER_ID) { + return {error: {message: 'Can only delete your own comment or Parabol AI comments'}} + } + + await r.table('Comment').get(commentId).update({isActive: false, updatedAt: now}).run() + await getKysely() + .updateTable('Comment') + .set({updatedAt: now}) + .where('id', '=', commentId) + .execute() + dataLoader.clearAll('comments') + const data = {commentId} + + if (meetingId) { + publish(SubscriptionChannel.MEETING, meetingId, 'DeleteCommentSuccess', data, subOptions) + } + return data +} + +export default deleteComment diff --git a/packages/server/graphql/public/mutations/updateCommentContent.ts b/packages/server/graphql/public/mutations/updateCommentContent.ts new file mode 100644 index 00000000000..3f11febcc72 --- /dev/null +++ b/packages/server/graphql/public/mutations/updateCommentContent.ts @@ -0,0 +1,71 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import extractTextFromDraftString from 'parabol-client/utils/draftjs/extractTextFromDraftString' +import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' +import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' +import {PARABOL_AI_USER_ID} from '../../../../client/utils/constants' +import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' +import {getUserId} from '../../../utils/authorization' +import publish from '../../../utils/publish' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const updateCommentContent: MutationResolvers['updateCommentContent'] = async ( + _source, + {commentId, content, meetingId}, + {authToken, dataLoader, socketId: mutatorId} +) => { + const r = await getRethink() + const operationId = dataLoader.share() + const now = new Date() + const subOptions = {operationId, mutatorId} + + // AUTH + const viewerId = getUserId(authToken) + const meetingMemberId = toTeamMemberId(meetingId, viewerId) + const [comment, viewerMeetingMember] = await Promise.all([ + dataLoader.get('comments').load(commentId), + dataLoader.get('meetingMembers').load(meetingMemberId) + ]) + if (!comment || !comment.isActive) { + return standardError(new Error('comment not found'), {userId: viewerId}) + } + if (!viewerMeetingMember) { + return {error: {message: `Not a member of the meeting`}} + } + const {createdBy, discussionId} = comment + const discussion = await dataLoader.get('discussions').loadNonNull(discussionId) + if (discussion.meetingId !== meetingId) { + return {error: {message: `Comment is not from this meeting`}} + } + if (createdBy !== viewerId && createdBy !== PARABOL_AI_USER_ID) { + return {error: {message: 'Can only update your own comment or Parabol AI comments'}} + } + + // VALIDATION + const normalizedContent = normalizeRawDraftJS(content) + + // RESOLUTION + const plaintextContent = extractTextFromDraftString(normalizedContent) + await r + .table('Comment') + .get(commentId) + .update({content: normalizedContent, plaintextContent, updatedAt: now}) + .run() + await getKysely() + .updateTable('Comment') + .set({content: normalizedContent, plaintextContent}) + .where('id', '=', commentId) + .execute() + dataLoader.clearAll('comments') + // :TODO: (jmtaber129): diff new and old comment content for mentions and handle notifications + // appropriately. + + const data = {commentId} + if (meetingId) { + publish(SubscriptionChannel.MEETING, meetingId, 'UpdateCommentContentSuccess', data, subOptions) + } + return data +} + +export default updateCommentContent diff --git a/packages/server/graphql/public/typeDefs/AddCommentInput.graphql b/packages/server/graphql/public/typeDefs/AddCommentInput.graphql index d9c2ef2b1ed..9fb75071f5e 100644 --- a/packages/server/graphql/public/typeDefs/AddCommentInput.graphql +++ b/packages/server/graphql/public/typeDefs/AddCommentInput.graphql @@ -13,6 +13,6 @@ input AddCommentInput { foreign key for the discussion this was created in """ discussionId: ID! - threadSortOrder: Float! + threadSortOrder: Int! threadParentId: ID } diff --git a/packages/server/graphql/public/typeDefs/Comment.graphql b/packages/server/graphql/public/typeDefs/Comment.graphql index eadc0fe6fd3..782cb1d5885 100644 --- a/packages/server/graphql/public/typeDefs/Comment.graphql +++ b/packages/server/graphql/public/typeDefs/Comment.graphql @@ -45,7 +45,7 @@ type Comment implements Reactable & Threadable { """ the order of this threadable, relative to threadParentId """ - threadSortOrder: Float + threadSortOrder: Int """ The timestamp the item was updated diff --git a/packages/server/graphql/public/typeDefs/CreatePollInput.graphql b/packages/server/graphql/public/typeDefs/CreatePollInput.graphql index d73109d5924..e12e37221f0 100644 --- a/packages/server/graphql/public/typeDefs/CreatePollInput.graphql +++ b/packages/server/graphql/public/typeDefs/CreatePollInput.graphql @@ -7,7 +7,7 @@ input CreatePollInput { """ The order of this threadable """ - threadSortOrder: Float! + threadSortOrder: Int! """ Poll question diff --git a/packages/server/graphql/public/typeDefs/CreateTaskInput.graphql b/packages/server/graphql/public/typeDefs/CreateTaskInput.graphql index 374821407a8..9a14e61ab8a 100644 --- a/packages/server/graphql/public/typeDefs/CreateTaskInput.graphql +++ b/packages/server/graphql/public/typeDefs/CreateTaskInput.graphql @@ -11,7 +11,7 @@ input CreateTaskInput { foreign key for the thread this was created in """ discussionId: ID - threadSortOrder: Float + threadSortOrder: Int threadParentId: ID sortOrder: Float status: TaskStatusEnum! diff --git a/packages/server/graphql/public/typeDefs/Poll.graphql b/packages/server/graphql/public/typeDefs/Poll.graphql index 62d1a3325d7..0da37ebfb78 100644 --- a/packages/server/graphql/public/typeDefs/Poll.graphql +++ b/packages/server/graphql/public/typeDefs/Poll.graphql @@ -40,7 +40,7 @@ type Poll implements Threadable { """ the order of this threadable, relative to threadParentId """ - threadSortOrder: Float + threadSortOrder: Int """ The timestamp the item was updated diff --git a/packages/server/graphql/public/typeDefs/Task.graphql b/packages/server/graphql/public/typeDefs/Task.graphql index 4ea269155c0..ea3d30bd31f 100644 --- a/packages/server/graphql/public/typeDefs/Task.graphql +++ b/packages/server/graphql/public/typeDefs/Task.graphql @@ -45,7 +45,7 @@ type Task implements Threadable { """ the order of this threadable, relative to threadParentId """ - threadSortOrder: Float + threadSortOrder: Int """ The timestamp the item was updated diff --git a/packages/server/graphql/public/typeDefs/Threadable.graphql b/packages/server/graphql/public/typeDefs/Threadable.graphql index 4f713b5c208..96644234f9b 100644 --- a/packages/server/graphql/public/typeDefs/Threadable.graphql +++ b/packages/server/graphql/public/typeDefs/Threadable.graphql @@ -40,7 +40,7 @@ interface Threadable { """ the order of this threadable, relative to threadParentId """ - threadSortOrder: Float + threadSortOrder: Int """ The timestamp the item was updated diff --git a/packages/server/graphql/public/types/AddCommentSuccess.ts b/packages/server/graphql/public/types/AddCommentSuccess.ts new file mode 100644 index 00000000000..5f946d07872 --- /dev/null +++ b/packages/server/graphql/public/types/AddCommentSuccess.ts @@ -0,0 +1,14 @@ +import {AddCommentSuccessResolvers} from '../resolverTypes' + +export type AddCommentSuccessSource = { + commentId: string + meetingId: string +} + +const AddCommentSuccess: AddCommentSuccessResolvers = { + comment: async ({commentId}, _args, {dataLoader}) => { + return dataLoader.get('comments').load(commentId) + } +} + +export default AddCommentSuccess diff --git a/packages/server/graphql/public/types/DeleteCommentSuccess.ts b/packages/server/graphql/public/types/DeleteCommentSuccess.ts new file mode 100644 index 00000000000..b9f2c86f76d --- /dev/null +++ b/packages/server/graphql/public/types/DeleteCommentSuccess.ts @@ -0,0 +1,13 @@ +import {DeleteCommentSuccessResolvers} from '../resolverTypes' + +export type DeleteCommentSuccessSource = { + commentId: string +} + +const DeleteCommentSuccess: DeleteCommentSuccessResolvers = { + comment: async ({commentId}, _args, {dataLoader}) => { + return dataLoader.get('comments').load(commentId) + } +} + +export default DeleteCommentSuccess diff --git a/packages/server/graphql/public/types/TeamPromptMeeting.ts b/packages/server/graphql/public/types/TeamPromptMeeting.ts index 25f29461423..1bf595b202d 100644 --- a/packages/server/graphql/public/types/TeamPromptMeeting.ts +++ b/packages/server/graphql/public/types/TeamPromptMeeting.ts @@ -5,6 +5,7 @@ import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTe import {getUserId} from '../../../utils/authorization' import filterTasksByMeeting from '../../../utils/filterTasksByMeeting' import getPhase from '../../../utils/getPhase' +import isValid from '../../isValid' import {TeamPromptMeetingResolvers} from '../resolverTypes' const TeamPromptMeeting: TeamPromptMeetingResolvers = { @@ -98,14 +99,11 @@ const TeamPromptMeeting: TeamPromptMeetingResolvers = { const discussPhase = getPhase(phases, 'RESPONSES') const {stages} = discussPhase const discussionIds = stages.map((stage) => stage.discussionId) - const r = await getRethink() - return r - .table('Comment') - .getAll(r.args(discussionIds), {index: 'discussionId'}) - .filter({isActive: true}) - .count() - .default(0) - .run() + const commentCounts = ( + await dataLoader.get('commentCountByDiscussionId').loadMany(discussionIds) + ).filter(isValid) + const commentCount = commentCounts.reduce((cumSum, count) => cumSum + count, 0) + return commentCount } } diff --git a/packages/server/graphql/public/types/Threadable.ts b/packages/server/graphql/public/types/Threadable.ts index 80fa584c253..0964be02ce2 100644 --- a/packages/server/graphql/public/types/Threadable.ts +++ b/packages/server/graphql/public/types/Threadable.ts @@ -15,7 +15,11 @@ const Threadable: ThreadableResolvers = { createdByUser: ({createdBy}, _args, {dataLoader}) => { return createdBy ? dataLoader.get('users').loadNonNull(createdBy) : null }, - replies: ({replies}) => replies || [] + replies: ({replies}) => replies || [], + // Can remove after Comment is in PG + threadSortOrder: ({threadSortOrder}) => { + return isNaN(threadSortOrder) ? 0 : Math.trunc(threadSortOrder) + } } export default Threadable diff --git a/packages/server/graphql/public/types/UpdateCommentContentSuccess.ts b/packages/server/graphql/public/types/UpdateCommentContentSuccess.ts new file mode 100644 index 00000000000..62c7382325b --- /dev/null +++ b/packages/server/graphql/public/types/UpdateCommentContentSuccess.ts @@ -0,0 +1,13 @@ +import {UpdateCommentContentSuccessResolvers} from '../resolverTypes' + +export type UpdateCommentContentSuccessSource = { + commentId: string +} + +const UpdateCommentContentSuccess: UpdateCommentContentSuccessResolvers = { + comment: async ({commentId}, _args, {dataLoader}) => { + return dataLoader.get('comments').load(commentId) + } +} + +export default UpdateCommentContentSuccess diff --git a/packages/server/graphql/rootMutation.ts b/packages/server/graphql/rootMutation.ts index 71fde7a3e06..ab88c2e03ba 100644 --- a/packages/server/graphql/rootMutation.ts +++ b/packages/server/graphql/rootMutation.ts @@ -1,7 +1,6 @@ import {GraphQLObjectType} from 'graphql' import {GQLContext} from './graphql' import addAtlassianAuth from './mutations/addAtlassianAuth' -import addComment from './mutations/addComment' import addGitHubAuth from './mutations/addGitHubAuth' import addIntegrationProvider from './mutations/addIntegrationProvider' import addOrg from './mutations/addOrg' @@ -20,7 +19,6 @@ import createPoll from './mutations/createPoll' import createReflection from './mutations/createReflection' import createTask from './mutations/createTask' import createTaskIntegration from './mutations/createTaskIntegration' -import deleteComment from './mutations/deleteComment' import deleteTask from './mutations/deleteTask' import deleteUser from './mutations/deleteUser' import denyPushInvitation from './mutations/denyPushInvitation' @@ -92,7 +90,6 @@ import startDraggingReflection from './mutations/startDraggingReflection' import startSprintPoker from './mutations/startSprintPoker' import toggleTeamDrawer from './mutations/toggleTeamDrawer' import updateAzureDevOpsDimensionField from './mutations/updateAzureDevOpsDimensionField' -import updateCommentContent from './mutations/updateCommentContent' import updateDragLocation from './mutations/updateDragLocation' import updateGitHubDimensionField from './mutations/updateGitHubDimensionField' import updateNewCheckInQuestion from './mutations/updateNewCheckInQuestion' @@ -114,7 +111,6 @@ export default new GraphQLObjectType({ fields: () => ({ addAtlassianAuth, - addComment, addPokerTemplateDimension, addPokerTemplateScale, addPokerTemplateScaleValue, @@ -132,7 +128,6 @@ export default new GraphQLObjectType({ createOAuth1AuthorizeUrl, createReflection, createTask, - deleteComment, deleteTask, deleteUser, denyPushInvitation, @@ -190,7 +185,6 @@ export default new GraphQLObjectType({ startDraggingReflection, startSprintPoker, setTaskHighlight, - updateCommentContent, oldUpdateCreditCard, updatePokerTemplateDimensionScale, updatePokerTemplateScaleValue, diff --git a/packages/server/graphql/rootTypes.ts b/packages/server/graphql/rootTypes.ts index d2c9f7cfe3e..6ef6af5d374 100644 --- a/packages/server/graphql/rootTypes.ts +++ b/packages/server/graphql/rootTypes.ts @@ -1,4 +1,3 @@ -import Comment from './types/Comment' import IntegrationProviderOAuth1 from './types/IntegrationProviderOAuth1' import IntegrationProviderOAuth2 from './types/IntegrationProviderOAuth2' import IntegrationProviderWebhook from './types/IntegrationProviderWebhook' @@ -22,7 +21,6 @@ const rootTypes = [ TimelineEventCompletedRetroMeeting, TimelineEventCompletedActionMeeting, TimelineEventPokerComplete, - Comment, JiraDimensionField, RenamePokerTemplatePayload, UserTiersCount diff --git a/packages/server/graphql/types/AddCommentInput.ts b/packages/server/graphql/types/AddCommentInput.ts deleted file mode 100644 index ff86e2917bc..00000000000 --- a/packages/server/graphql/types/AddCommentInput.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { - GraphQLBoolean, - GraphQLFloat, - GraphQLID, - GraphQLInputObjectType, - GraphQLNonNull, - GraphQLString -} from 'graphql' - -const AddCommentInput = new GraphQLInputObjectType({ - name: 'AddCommentInput', - fields: () => ({ - content: { - type: new GraphQLNonNull(GraphQLString), - description: 'A stringified draft-js document containing thoughts' - }, - isAnonymous: { - type: GraphQLBoolean, - description: 'true if the comment should be anonymous' - }, - discussionId: { - type: new GraphQLNonNull(GraphQLID), - description: 'foreign key for the discussion this was created in' - }, - threadSortOrder: { - type: new GraphQLNonNull(GraphQLFloat) - }, - threadParentId: { - type: GraphQLID - } - }) -}) - -export default AddCommentInput diff --git a/packages/server/graphql/types/AddCommentPayload.ts b/packages/server/graphql/types/AddCommentPayload.ts deleted file mode 100644 index c0535e1b9fe..00000000000 --- a/packages/server/graphql/types/AddCommentPayload.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {GraphQLID, GraphQLNonNull, GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import Comment from './Comment' -import makeMutationPayload from './makeMutationPayload' - -export const AddCommentSuccess = new GraphQLObjectType({ - name: 'AddCommentSuccess', - fields: () => ({ - comment: { - type: new GraphQLNonNull(Comment), - description: 'the comment just created', - resolve: async ({commentId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('comments').load(commentId) - } - }, - meetingId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The id of the meeting where the comment was added' - } - }) -}) - -const AddCommentPayload = makeMutationPayload('AddCommentPayload', AddCommentSuccess) - -export default AddCommentPayload diff --git a/packages/server/graphql/types/Comment.ts b/packages/server/graphql/types/Comment.ts deleted file mode 100644 index 93b25e00d4f..00000000000 --- a/packages/server/graphql/types/Comment.ts +++ /dev/null @@ -1,29 +0,0 @@ -import {GraphQLObjectType} from 'graphql' -import connectionDefinitions from '../connectionDefinitions' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import PageInfoDateCursor from './PageInfoDateCursor' - -const Comment = new GraphQLObjectType({ - name: 'Comment', - fields: {} -}) - -const {connectionType, edgeType} = connectionDefinitions({ - name: Comment.name, - nodeType: Comment, - edgeFields: () => ({ - cursor: { - type: GraphQLISO8601Type - } - }), - connectionFields: () => ({ - pageInfo: { - type: PageInfoDateCursor, - description: 'Page info with cursors coerced to ISO8601 dates' - } - }) -}) - -export const TaskConnection = connectionType -export const TaskEdge = edgeType -export default Comment diff --git a/packages/server/graphql/types/CreatePollInput.ts b/packages/server/graphql/types/CreatePollInput.ts index ddeb17bf122..70c0bff6deb 100644 --- a/packages/server/graphql/types/CreatePollInput.ts +++ b/packages/server/graphql/types/CreatePollInput.ts @@ -1,7 +1,7 @@ import { - GraphQLFloat, GraphQLID, GraphQLInputObjectType, + GraphQLInt, GraphQLList, GraphQLNonNull, GraphQLString @@ -16,7 +16,7 @@ const CreatePollInput = new GraphQLInputObjectType({ description: 'Foreign key for the discussion this was created in' }, threadSortOrder: { - type: new GraphQLNonNull(GraphQLFloat), + type: new GraphQLNonNull(GraphQLInt), description: 'The order of this threadable' }, title: { diff --git a/packages/server/graphql/types/CreateTaskInput.ts b/packages/server/graphql/types/CreateTaskInput.ts index dc6a7d1999a..6bad42ddbc7 100644 --- a/packages/server/graphql/types/CreateTaskInput.ts +++ b/packages/server/graphql/types/CreateTaskInput.ts @@ -2,6 +2,7 @@ import { GraphQLFloat, GraphQLID, GraphQLInputObjectType, + GraphQLInt, GraphQLNonNull, GraphQLString } from 'graphql' @@ -41,7 +42,7 @@ const CreateTaskInput = new GraphQLInputObjectType({ description: 'foreign key for the thread this was created in' }, threadSortOrder: { - type: GraphQLFloat + type: GraphQLInt }, threadParentId: { type: GraphQLID diff --git a/packages/server/graphql/types/DeleteCommentPayload.ts b/packages/server/graphql/types/DeleteCommentPayload.ts deleted file mode 100644 index 361812d4cb4..00000000000 --- a/packages/server/graphql/types/DeleteCommentPayload.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {GraphQLID, GraphQLNonNull, GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import Comment from './Comment' -import makeMutationPayload from './makeMutationPayload' - -export const DeleteCommentSuccess = new GraphQLObjectType({ - name: 'DeleteCommentSuccess', - fields: () => ({ - commentId: { - type: new GraphQLNonNull(GraphQLID) - }, - comment: { - type: new GraphQLNonNull(Comment), - description: 'the comment just deleted', - resolve: async ({commentId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('comments').load(commentId) - } - } - }) -}) - -const DeleteCommentPayload = makeMutationPayload('DeleteCommentPayload', DeleteCommentSuccess) - -export default DeleteCommentPayload diff --git a/packages/server/graphql/types/Poll.ts b/packages/server/graphql/types/Poll.ts index 98de0976f31..0922b4fac7c 100644 --- a/packages/server/graphql/types/Poll.ts +++ b/packages/server/graphql/types/Poll.ts @@ -3,16 +3,13 @@ import PollId from '../../../client/shared/gqlIds/PollId' import {GQLContext} from './../graphql' import PollOption from './PollOption' import Team from './Team' -import Threadable, {threadableFields} from './Threadable' import User from './User' const Poll: GraphQLObjectType = new GraphQLObjectType({ name: 'Poll', description: 'A poll created during the meeting', - interfaces: () => [Threadable], isTypeOf: ({title}) => !!title, fields: () => ({ - ...(threadableFields() as any), createdByUser: { type: new GraphQLNonNull(User), description: 'The user that created the item', diff --git a/packages/server/graphql/types/Task.ts b/packages/server/graphql/types/Task.ts index fcf13730521..d2a8b1b7ad8 100644 --- a/packages/server/graphql/types/Task.ts +++ b/packages/server/graphql/types/Task.ts @@ -32,15 +32,12 @@ import TaskIntegration from './TaskIntegration' import TaskServiceEnum from './TaskServiceEnum' import TaskStatusEnum from './TaskStatusEnum' import Team from './Team' -import Threadable, {threadableFields} from './Threadable' const Task: GraphQLObjectType = new GraphQLObjectType({ name: 'Task', description: 'A long-term task shared across the team, assigned to a single user ', - interfaces: () => [Threadable], isTypeOf: ({status}) => !!status, fields: () => ({ - ...(threadableFields() as any), agendaItem: { type: AgendaItem, description: 'The agenda item that the task was created in, if any', diff --git a/packages/server/graphql/types/Threadable.ts b/packages/server/graphql/types/Threadable.ts deleted file mode 100644 index 3341b404ba9..00000000000 --- a/packages/server/graphql/types/Threadable.ts +++ /dev/null @@ -1,89 +0,0 @@ -import { - GraphQLFloat, - GraphQLID, - GraphQLInterfaceType, - GraphQLList, - GraphQLNonNull, - GraphQLString -} from 'graphql' -import connectionDefinitions from '../connectionDefinitions' -import {GQLContext} from '../graphql' -import {ThreadableSource as ThreadableDB} from '../public/types/Threadable' -import GraphQLISO8601Type from './GraphQLISO8601Type' -import PageInfo from './PageInfo' - -export const threadableFields = () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'shortid' - }, - createdAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the item was created' - }, - createdBy: { - type: GraphQLID, - description: 'The userId that created the item' - }, - createdByUser: { - type: require('./User').default, - description: 'The user that created the item', - resolve: ({createdBy}: {createdBy: string}, _args: unknown, {dataLoader}: GQLContext) => { - return dataLoader.get('users').load(createdBy) - } - }, - replies: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(Threadable))), - description: 'the replies to this threadable item', - resolve: ({replies}: {replies: ThreadableDB[]}) => replies || [] - }, - discussionId: { - type: GraphQLID, - description: - 'The FK of the discussion this task was created in. Null if task was not created in a discussion', - // can remove the threadId after 2021-07-01 - resolve: ({discussionId, threadId}: {discussionId: string; threadId: string}) => - discussionId || threadId - }, - threadParentId: { - type: GraphQLID, - description: 'the parent, if this threadable is a reply, else null' - }, - threadSortOrder: { - type: GraphQLFloat, - description: 'the order of this threadable, relative to threadParentId' - }, - updatedAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the item was updated' - } -}) - -const Threadable: GraphQLInterfaceType = new GraphQLInterfaceType({ - name: 'Threadable', - fields: {} -}) - -const {connectionType, edgeType} = connectionDefinitions({ - name: Threadable.name, - nodeType: Threadable, - edgeFields: () => ({ - cursor: { - type: GraphQLString - } - }), - connectionFields: () => ({ - error: { - type: GraphQLString, - description: 'Any errors that prevented the query from returning the full results' - }, - pageInfo: { - type: PageInfo, - description: 'Page info with strings (sortOrder) as cursors' - } - }) -}) - -export const ThreadableConnection = connectionType -export const ThreadableEdge = edgeType -export default Threadable diff --git a/packages/server/graphql/types/UpdateCommentContentPayload.ts b/packages/server/graphql/types/UpdateCommentContentPayload.ts deleted file mode 100644 index 9d028791b90..00000000000 --- a/packages/server/graphql/types/UpdateCommentContentPayload.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {GraphQLNonNull, GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import Comment from './Comment' -import makeMutationPayload from './makeMutationPayload' - -export const UpdateCommentContentSuccess = new GraphQLObjectType({ - name: 'UpdateCommentContentSuccess', - fields: () => ({ - comment: { - type: new GraphQLNonNull(Comment), - description: 'the comment with updated content', - resolve: async ({commentId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('comments').load(commentId) - } - } - }) -}) - -const UpdateCommentContentPayload = makeMutationPayload( - 'UpdateCommentContentPayload', - UpdateCommentContentSuccess -) - -export default UpdateCommentContentPayload diff --git a/packages/server/postgres/migrations/1724780116674_Comment-phase1.ts b/packages/server/postgres/migrations/1724780116674_Comment-phase1.ts new file mode 100644 index 00000000000..5090679072f --- /dev/null +++ b/packages/server/postgres/migrations/1724780116674_Comment-phase1.ts @@ -0,0 +1,48 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS "Comment" ( + "id" VARCHAR(100) PRIMARY KEY, + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "isActive" BOOLEAN NOT NULL DEFAULT TRUE, + "isAnonymous" BOOLEAN NOT NULL DEFAULT FALSE, + "threadParentId" VARCHAR(100), + "reactjis" "Reactji"[] NOT NULL DEFAULT array[]::"Reactji"[], + "content" JSONB NOT NULL, + "createdBy" VARCHAR(100), + "plaintextContent" VARCHAR(2000) NOT NULL, + "discussionId" VARCHAR(100) NOT NULL, + "threadSortOrder" INTEGER NOT NULL, + CONSTRAINT "fk_createdBy" + FOREIGN KEY("createdBy") + REFERENCES "User"("id") + ON DELETE SET NULL, + CONSTRAINT "fk_discussionId" + FOREIGN KEY("discussionId") + REFERENCES "Discussion"("id") + ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS "idx_Comment_threadParentId" ON "Comment"("threadParentId"); + CREATE INDEX IF NOT EXISTS "idx_Comment_createdBy" ON "Comment"("createdBy"); + CREATE INDEX IF NOT EXISTS "idx_Comment_discussionId" ON "Comment"("discussionId"); + END $$; +`) + // TODO add constraint threadParentId in phase 2 + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE "Comment"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index 481164d8916..9a610b1de19 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -210,3 +210,21 @@ export const selectSlackAuths = () => getKysely().selectFrom('SlackAuth').select export const selectSlackNotifications = () => getKysely().selectFrom('SlackNotification').selectAll() + +export const selectComments = () => + getKysely() + .selectFrom('Comment') + .select([ + 'id', + 'createdAt', + 'isActive', + 'isAnonymous', + 'threadParentId', + 'updatedAt', + 'content', + 'createdBy', + 'plaintextContent', + 'discussionId', + 'threadSortOrder' + ]) + .select(({fn}) => [fn('to_json', ['reactjis']).as('reactjis')]) From bdfffa429e9ad28c7105606a0f290f8887a3f1eb Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 28 Aug 2024 18:58:16 -0700 Subject: [PATCH 433/529] chore(release): release v7.45.1 (#10171) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 86cb919ad4c..64aaef74b3e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.45.0" + ".": "7.45.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index ccb8674fcd8..55459668d37 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.45.1](https://github.com/ParabolInc/parabol/compare/v7.45.0...v7.45.1) (2024-08-29) + + +### Fixed + +* consolidate org user menus ([#10162](https://github.com/ParabolInc/parabol/issues/10162)) ([b172a2f](https://github.com/ParabolInc/parabol/commit/b172a2fa4e29245a916fe4046b29366b0f0b2b0e)) + + +### Changed + +* bump eslint ([#10170](https://github.com/ParabolInc/parabol/issues/10170)) ([002aac2](https://github.com/ParabolInc/parabol/commit/002aac201159e99d45367f44634e065566f1553f)) +* **rethinkdb:** Comment: Phase 1 ([#10166](https://github.com/ParabolInc/parabol/issues/10166)) ([d712811](https://github.com/ParabolInc/parabol/commit/d7128119c320efed44721fe6c4aad500d4617dbf)) + ## [7.45.0](https://github.com/ParabolInc/parabol/compare/v7.44.0...v7.45.0) (2024-08-28) diff --git a/package.json b/package.json index cdd0f526bd9..37b6f5bc9b2 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.45.0", + "version": "7.45.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 2abd538d32a..82d9aeba043 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.45.0", + "version": "7.45.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.45.0" + "parabol-server": "7.45.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index 472512e2f5d..fbf4e0001a2 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.45.0", + "version": "7.45.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index d3bbefbcac4..91adf677cab 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.45.0", + "version": "7.45.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 8b792c0a608..86691872d9e 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.45.0", + "version": "7.45.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.45.0", - "parabol-server": "7.45.0", + "parabol-client": "7.45.1", + "parabol-server": "7.45.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 6150fa84faf..0b16487975e 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.45.0", + "version": "7.45.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index d5de774ea95..551b1417943 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.45.0", + "version": "7.45.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.45.0", + "parabol-client": "7.45.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 635fb15cb30e0819268256866d1cb5511dc6b9a8 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 28 Aug 2024 19:22:35 -0700 Subject: [PATCH 434/529] chore(rethinkdb): Comment: Phase 2 (#10173) Signed-off-by: Matt Krick --- .../1724884922936_Comment-phase2.ts | 136 ++++++++++++++++++ 1 file changed, 136 insertions(+) create mode 100644 packages/server/postgres/migrations/1724884922936_Comment-phase2.ts diff --git a/packages/server/postgres/migrations/1724884922936_Comment-phase2.ts b/packages/server/postgres/migrations/1724884922936_Comment-phase2.ts new file mode 100644 index 00000000000..9e5f6d3e893 --- /dev/null +++ b/packages/server/postgres/migrations/1724884922936_Comment-phase2.ts @@ -0,0 +1,136 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + try { + console.log('Adding index') + await r + .table('Comment') + .indexCreate('updatedAtId', (row: any) => [row('updatedAt'), row('id')]) + .run() + await r.table('Comment').indexWait().run() + } catch { + // index already exists + } + + console.log('Adding index complete') + const MAX_PG_PARAMS = 65545 + const PG_COLS = [ + 'id', + 'createdAt', + 'updatedAt', + 'isActive', + 'isAnonymous', + 'threadParentId', + 'reactjis', + 'content', + 'createdBy', + 'plaintextContent', + 'discussionId', + 'threadSortOrder' + ] as const + type Comment = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + + let curUpdatedAt = r.minval + let curId = r.minval + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, String(curUpdatedAt), String(curId)) + const rawRowsToInsert = (await r + .table('Comment') + .between([curUpdatedAt, curId], [r.maxval, r.maxval], { + index: 'updatedAtId', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'updatedAtId'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as Comment[] + + const rowsToInsert = rawRowsToInsert + .map((row) => { + const {plaintextContent, threadSortOrder, reactjis, ...rest} = row as any + return { + ...rest, + plaintextContent: plaintextContent.slice(0, 2000), + threadSortOrder: threadSortOrder ? Math.trunc(threadSortOrder) : 0, + reactjis: reactjis?.map((r: any) => `(${r.id},${r.userId})`) ?? [] + } + }) + .filter((row) => row.discussionId) + + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curUpdatedAt = lastRow.updatedAt + curId = lastRow.id + await Promise.all( + rowsToInsert.map(async (row) => { + try { + await pg + .insertInto('Comment') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_createdBy') { + await pg + .insertInto('Comment') + .values({...row, createdBy: null}) + .onConflict((oc) => oc.doNothing()) + .execute() + return + } + if (e.constraint === 'fk_discussionId') { + console.log(`Skipping ${row.id} because it has no discussion`) + return + } + console.log(e, row) + } + }) + ) + } + + // if the threadParentId references an id that does not exist, set it to null + console.log('adding threadParentId constraint') + await pg + .updateTable('Comment') + .set({threadParentId: null}) + .where(({eb, selectFrom}) => + eb( + 'id', + 'in', + selectFrom('Comment as child') + .select('child.id') + .leftJoin('Comment as parent', 'child.threadParentId', 'parent.id') + .where('parent.id', 'is', null) + .where('child.threadParentId', 'is not', null) + ) + ) + .execute() + await pg.schema + .alterTable('Comment') + .addForeignKeyConstraint('fk_threadParentId', ['threadParentId'], 'Comment', ['id']) + .onDelete('set null') + .execute() +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql`TRUNCATE TABLE "Comment" CASCADE`.execute(pg) +} From 911ab904550c9235f34a2bd48606d036d2b229e3 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 29 Aug 2024 10:01:21 -0700 Subject: [PATCH 435/529] fix: add discussion before comments (#10178) Signed-off-by: Matt Krick --- .../mutations/helpers/handleCompletedStage.ts | 3 +- .../graphql/public/mutations/addComment.ts | 1 + .../1724884922936_Comment-phase2.ts | 136 ------------------ 3 files changed, 3 insertions(+), 137 deletions(-) delete mode 100644 packages/server/postgres/migrations/1724884922936_Comment-phase2.ts diff --git a/packages/server/graphql/mutations/helpers/handleCompletedStage.ts b/packages/server/graphql/mutations/helpers/handleCompletedStage.ts index fe160315b2e..7bd65438355 100644 --- a/packages/server/graphql/mutations/helpers/handleCompletedStage.ts +++ b/packages/server/graphql/mutations/helpers/handleCompletedStage.ts @@ -92,8 +92,9 @@ const handleCompletedRetrospectiveStage = async ( discussionTopicType: 'reflectionGroup' as const, discussionTopicId: stage.reflectionGroupId })) + // discussions must exist before we can add comments to them! + await insertDiscussions(discussions) await Promise.all([ - insertDiscussions(discussions), addAIGeneratedContentToThreads(discussPhaseStages, meetingId, dataLoader), publishToEmbedder({jobType: 'relatedDiscussions:start', data: {meetingId}, priority: 0}) ]) diff --git a/packages/server/graphql/public/mutations/addComment.ts b/packages/server/graphql/public/mutations/addComment.ts index 2e0392c062e..99c0aa07a82 100644 --- a/packages/server/graphql/public/mutations/addComment.ts +++ b/packages/server/graphql/public/mutations/addComment.ts @@ -111,6 +111,7 @@ const addComment: MutationResolvers['addComment'] = async ( content: dbComment.content, plaintextContent: dbComment.plaintextContent, createdBy: dbComment.createdBy, + threadParentId: dbComment.threadParentId, threadSortOrder: dbComment.threadSortOrder, discussionId: dbComment.discussionId }) diff --git a/packages/server/postgres/migrations/1724884922936_Comment-phase2.ts b/packages/server/postgres/migrations/1724884922936_Comment-phase2.ts deleted file mode 100644 index 9e5f6d3e893..00000000000 --- a/packages/server/postgres/migrations/1724884922936_Comment-phase2.ts +++ /dev/null @@ -1,136 +0,0 @@ -import {Kysely, PostgresDialect, sql} from 'kysely' -import {r} from 'rethinkdb-ts' -import connectRethinkDB from '../../database/connectRethinkDB' -import getPg from '../getPg' - -export async function up() { - await connectRethinkDB() - const pg = new Kysely({ - dialect: new PostgresDialect({ - pool: getPg() - }) - }) - - try { - console.log('Adding index') - await r - .table('Comment') - .indexCreate('updatedAtId', (row: any) => [row('updatedAt'), row('id')]) - .run() - await r.table('Comment').indexWait().run() - } catch { - // index already exists - } - - console.log('Adding index complete') - const MAX_PG_PARAMS = 65545 - const PG_COLS = [ - 'id', - 'createdAt', - 'updatedAt', - 'isActive', - 'isAnonymous', - 'threadParentId', - 'reactjis', - 'content', - 'createdBy', - 'plaintextContent', - 'discussionId', - 'threadSortOrder' - ] as const - type Comment = { - [K in (typeof PG_COLS)[number]]: any - } - const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) - - let curUpdatedAt = r.minval - let curId = r.minval - for (let i = 0; i < 1e6; i++) { - console.log('inserting row', i * BATCH_SIZE, String(curUpdatedAt), String(curId)) - const rawRowsToInsert = (await r - .table('Comment') - .between([curUpdatedAt, curId], [r.maxval, r.maxval], { - index: 'updatedAtId', - leftBound: 'open', - rightBound: 'closed' - }) - .orderBy({index: 'updatedAtId'}) - .limit(BATCH_SIZE) - .pluck(...PG_COLS) - .run()) as Comment[] - - const rowsToInsert = rawRowsToInsert - .map((row) => { - const {plaintextContent, threadSortOrder, reactjis, ...rest} = row as any - return { - ...rest, - plaintextContent: plaintextContent.slice(0, 2000), - threadSortOrder: threadSortOrder ? Math.trunc(threadSortOrder) : 0, - reactjis: reactjis?.map((r: any) => `(${r.id},${r.userId})`) ?? [] - } - }) - .filter((row) => row.discussionId) - - if (rowsToInsert.length === 0) break - const lastRow = rowsToInsert[rowsToInsert.length - 1] - curUpdatedAt = lastRow.updatedAt - curId = lastRow.id - await Promise.all( - rowsToInsert.map(async (row) => { - try { - await pg - .insertInto('Comment') - .values(row) - .onConflict((oc) => oc.doNothing()) - .execute() - } catch (e) { - if (e.constraint === 'fk_createdBy') { - await pg - .insertInto('Comment') - .values({...row, createdBy: null}) - .onConflict((oc) => oc.doNothing()) - .execute() - return - } - if (e.constraint === 'fk_discussionId') { - console.log(`Skipping ${row.id} because it has no discussion`) - return - } - console.log(e, row) - } - }) - ) - } - - // if the threadParentId references an id that does not exist, set it to null - console.log('adding threadParentId constraint') - await pg - .updateTable('Comment') - .set({threadParentId: null}) - .where(({eb, selectFrom}) => - eb( - 'id', - 'in', - selectFrom('Comment as child') - .select('child.id') - .leftJoin('Comment as parent', 'child.threadParentId', 'parent.id') - .where('parent.id', 'is', null) - .where('child.threadParentId', 'is not', null) - ) - ) - .execute() - await pg.schema - .alterTable('Comment') - .addForeignKeyConstraint('fk_threadParentId', ['threadParentId'], 'Comment', ['id']) - .onDelete('set null') - .execute() -} - -export async function down() { - const pg = new Kysely({ - dialect: new PostgresDialect({ - pool: getPg() - }) - }) - await sql`TRUNCATE TABLE "Comment" CASCADE`.execute(pg) -} From 974532be26e3c55adc7ca897da54abbc555c9116 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Thu, 29 Aug 2024 10:10:33 -0700 Subject: [PATCH 436/529] chore(release): release v7.45.2 (#10175) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 64aaef74b3e..dcfe77c1e9a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.45.1" + ".": "7.45.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 55459668d37..aaa80d4c0cc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.45.2](https://github.com/ParabolInc/parabol/compare/v7.45.1...v7.45.2) (2024-08-29) + + +### Fixed + +* add discussion before comments ([#10178](https://github.com/ParabolInc/parabol/issues/10178)) ([911ab90](https://github.com/ParabolInc/parabol/commit/911ab904550c9235f34a2bd48606d036d2b229e3)) + + +### Changed + +* **rethinkdb:** Comment: Phase 2 ([#10173](https://github.com/ParabolInc/parabol/issues/10173)) ([635fb15](https://github.com/ParabolInc/parabol/commit/635fb15cb30e0819268256866d1cb5511dc6b9a8)) + ## [7.45.1](https://github.com/ParabolInc/parabol/compare/v7.45.0...v7.45.1) (2024-08-29) diff --git a/package.json b/package.json index 37b6f5bc9b2..872a9104293 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.45.1", + "version": "7.45.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 82d9aeba043..83fdd61db90 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.45.1", + "version": "7.45.2", "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.45.1" + "parabol-server": "7.45.2" } } diff --git a/packages/client/package.json b/packages/client/package.json index fbf4e0001a2..0304016588a 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.45.1", + "version": "7.45.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 91adf677cab..4ad30a514da 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.45.1", + "version": "7.45.2", "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 86691872d9e..610357c65da 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.45.1", + "version": "7.45.2", "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.45.1", - "parabol-server": "7.45.1", + "parabol-client": "7.45.2", + "parabol-server": "7.45.2", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 0b16487975e..4b7caae9866 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.45.1", + "version": "7.45.2", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 551b1417943..b12149d0c45 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.45.1", + "version": "7.45.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -124,7 +124,7 @@ "openai": "^4.53.0", "openapi-fetch": "^0.9.7", "oy-vey": "^0.12.1", - "parabol-client": "7.45.1", + "parabol-client": "7.45.2", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 70f69ce039f9550c52f391c0f556919a7fe4589b Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Sep 2024 11:27:25 +0200 Subject: [PATCH 437/529] chore(deps): bump micromatch from 4.0.5 to 4.0.8 (#10164) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 41 +++++++++++++++++++++++++++++++++-------- 1 file changed, 33 insertions(+), 8 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3ec488bd779..96645f4748c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10665,7 +10665,7 @@ brace-expansion@^2.0.1: dependencies: balanced-match "^1.0.0" -braces@^3.0.2, braces@~3.0.2: +braces@^3.0.3, braces@~3.0.2: version "3.0.3" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.3.tgz#490332f40919452272d55a8480adc0c441358789" integrity sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA== @@ -17076,11 +17076,11 @@ methods@^1.1.2, methods@~1.1.2: integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= micromatch@^4.0.0, micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== + version "4.0.8" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.8.tgz#d66fa18f3a47076789320b9b1af32bd86d9fa202" + integrity sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA== dependencies: - braces "^3.0.2" + braces "^3.0.3" picomatch "^2.3.1" mime-db@1.52.0, "mime-db@>= 1.43.0 < 2": @@ -21346,7 +21346,7 @@ string-similarity@^3.0.0: resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-3.0.0.tgz#07b0bc69fae200ad88ceef4983878d03793847c7" integrity sha512-7kS7LyTp56OqOI2BDWQNVnLX/rCxIQn+/5M0op1WV6P8Xx6TZNdajpuqQdiJ7Xx+p1C5CsWMvdiBp9ApMhxzEQ== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -21364,6 +21364,15 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.0.tgz#5ab00980cfb29f43e736b113a120a73a0fb569d3" @@ -21435,7 +21444,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -21449,6 +21458,13 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -23298,7 +23314,7 @@ workbox-window@6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.5.4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -23316,6 +23332,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 97bb948330e1e57a331113e267ec5965a84ea6e4 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Tue, 3 Sep 2024 11:31:12 -0700 Subject: [PATCH 438/529] feat(orgAdmins): Make org members view sortable (#10146) --- .../client/components/OrgAdminActionMenu.tsx | 10 +- .../components/OrgMembers/OrgMembers.tsx | 122 ++++++++++++++---- .../components/OrgUserRow/OrgMemberRow.tsx | 78 ++++++----- 3 files changed, 140 insertions(+), 70 deletions(-) diff --git a/packages/client/components/OrgAdminActionMenu.tsx b/packages/client/components/OrgAdminActionMenu.tsx index 37119d67ba5..c61a4679d15 100644 --- a/packages/client/components/OrgAdminActionMenu.tsx +++ b/packages/client/components/OrgAdminActionMenu.tsx @@ -1,5 +1,4 @@ import {MoreVert} from '@mui/icons-material' -import * as DropdownMenu from '@radix-ui/react-dropdown-menu' import graphql from 'babel-plugin-relay/macro' import React from 'react' import {useFragment} from 'react-relay' @@ -123,9 +122,14 @@ export const OrgAdminActionMenu = (props: Props) => { )} {isSelf && ((isOrgAdmin && isViewerLastOrgAdmin) || (isBillingLeader && isViewerLastRole)) && ( - + { + window.location.href = + 'mailto:support@parabol.co?subject=Request to be removed from organization' + }} + > {'Contact support@parabol.co to be removed'} - + )} diff --git a/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx b/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx index e9534528b3b..0e3e0b19a4f 100644 --- a/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx +++ b/packages/client/modules/userDashboard/components/OrgMembers/OrgMembers.tsx @@ -1,22 +1,16 @@ -import styled from '@emotion/styled' import graphql from 'babel-plugin-relay/macro' import type {Parser as JSON2CSVParser} from 'json2csv' import Parser from 'json2csv/lib/JSON2CSVParser' // only grab the sync parser -import React from 'react' +import React, {useMemo, useState} from 'react' import {PreloadedQuery, usePaginationFragment, usePreloadedQuery} from 'react-relay' import {OrgMembersPaginationQuery} from '~/__generated__/OrgMembersPaginationQuery.graphql' import {OrgMembersQuery} from '~/__generated__/OrgMembersQuery.graphql' import {OrgMembers_viewer$key} from '~/__generated__/OrgMembers_viewer.graphql' +import User from '../../../../../server/database/types/User' import ExportToCSVButton from '../../../../components/ExportToCSVButton' -import Panel from '../../../../components/Panel/Panel' -import {ElementWidth} from '../../../../types/constEnums' import {APP_CORS_OPTIONS} from '../../../../types/cors' import OrgMemberRow from '../OrgUserRow/OrgMemberRow' -const StyledPanel = styled(Panel)({ - maxWidth: ElementWidth.PANEL_WIDTH -}) - interface Props { queryRef: PreloadedQuery } @@ -50,6 +44,7 @@ const OrgMembers = (props: Props) => { user { preferredName email + lastSeenAt } ...OrgMemberRow_organizationUser } @@ -70,6 +65,43 @@ const OrgMembers = (props: Props) => { const {organization} = viewer if (!organization) return null const {organizationUsers, name: orgName, isBillingLeader} = organization + const billingLeaderCount = organizationUsers.edges.reduce( + (count, {node}) => + ['BILLING_LEADER', 'ORG_ADMIN'].includes(node.role ?? '') ? count + 1 : count, + 0 + ) + const orgAdminCount = organizationUsers.edges.reduce( + (count, {node}) => (['ORG_ADMIN'].includes(node.role ?? '') ? count + 1 : count), + 0 + ) + const [sortBy, setSortBy] = useState('lastSeenAt') + const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc') + + const sortedOrganizationUsers = useMemo(() => { + return [...organizationUsers.edges].sort((a, b) => { + if (sortBy === 'lastSeenAt') { + const aDate = a.node.user.lastSeenAt ? new Date(a.node.user.lastSeenAt) : new Date(0) + const bDate = b.node.user.lastSeenAt ? new Date(b.node.user.lastSeenAt) : new Date(0) + return sortDirection === 'asc' + ? aDate.getTime() - bDate.getTime() + : bDate.getTime() - aDate.getTime() + } else if (sortBy === 'preferredName') { + return sortDirection === 'asc' + ? a.node.user.preferredName.localeCompare(b.node.user.preferredName) + : b.node.user.preferredName.localeCompare(a.node.user.preferredName) + } + return 0 + }) + }, [organizationUsers.edges, sortBy, sortDirection]) + + const handleSort = (column: keyof User) => { + if (sortBy === column) { + setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc') + } else { + setSortBy(column) + setSortDirection('asc') + } + } const exportToCSV = async () => { const rows = organizationUsers.edges.map((orgUser, idx) => { @@ -99,24 +131,62 @@ const OrgMembers = (props: Props) => { } return ( - - ) - } - > - {organizationUsers.edges.map(({node: organizationUser}) => { - return ( - - ) - })} - +
+
+
+

Members

+
+
+ {isBillingLeader && ( + + )} +
+
+ +
+
+
+
+ {organizationUsers.edges.length} total +
+
+
+
+
+ {commentLinkLabel} diff --git a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/SummarySheet.tsx b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/SummarySheet.tsx index 138200b6327..c42a46ed20b 100644 --- a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/SummarySheet.tsx +++ b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/SummarySheet.tsx @@ -5,7 +5,7 @@ import {SummarySheet_meeting$key} from 'parabol-client/__generated__/SummaryShee import CreateAccountSection from 'parabol-client/modules/demo/components/CreateAccountSection' import {sheetShadow} from 'parabol-client/styles/elevation' import {ACTION} from 'parabol-client/utils/constants' -import React from 'react' +import React, {useRef} from 'react' import {useFragment} from 'react-relay' import {Link} from 'react-router-dom' import useAtmosphere from '../../../../../hooks/useAtmosphere' @@ -90,12 +90,68 @@ const SummarySheet = (props: Props) => { `, meetingRef ) - const {id: meetingId, meetingType, taskCount} = meeting + const {id: meetingId, meetingType, taskCount, name} = meeting const isDemo = !!props.isDemo + const sheetRef = useRef(null) const downloadPDF = () => { SendClientSideEvent(atmosphere, 'Download PDF Clicked', {meetingId}) - window.print() + + const printStyles = ` + + ` + + const printWindow = window.open('', '_blank') + if (!printWindow) return + printWindow.document.write(` + + + Parabol: ${name} Summary + + + ${printStyles} + ${sheetRef.current?.outerHTML} + + + `) + printWindow.document.close() + printWindow.focus() + printWindow.print() + printWindow.onafterprint = () => { + printWindow.close() + } } return ( @@ -105,6 +161,7 @@ const SummarySheet = (props: Props) => { height='100%' align='center' bgcolor='#FFFFFF' + ref={sheetRef} style={sheetStyle} >
{wholeMeetingSummary}
+ + + + + + + + + {sortedOrganizationUsers.map(({node: organizationUser}) => ( + + ))} + +
handleSort('preferredName')} + > + User + {sortBy === 'preferredName' && (sortDirection === 'asc' ? ' ▲' : ' ▼')} + handleSort('lastSeenAt')} + > + Last Seen + {sortBy === 'lastSeenAt' && (sortDirection === 'asc' ? ' ▲' : ' ▼')} +
+ + + ) } diff --git a/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx b/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx index 3ee6efc2135..0d795ba5f5c 100644 --- a/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx +++ b/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx @@ -1,5 +1,6 @@ import styled from '@emotion/styled' import graphql from 'babel-plugin-relay/macro' +import {format} from 'date-fns' import React from 'react' import {useFragment} from 'react-relay' import { @@ -11,7 +12,6 @@ import { OrgMemberRow_organizationUser$key } from '../../../../__generated__/OrgMemberRow_organizationUser.graphql' import Avatar from '../../../../components/Avatar/Avatar' -import Row from '../../../../components/Row/Row' import RowActions from '../../../../components/Row/RowActions' import RowInfo from '../../../../components/Row/RowInfo' import RowInfoHeader from '../../../../components/Row/RowInfoHeader' @@ -22,28 +22,8 @@ import InactiveTag from '../../../../components/Tag/InactiveTag' import RoleTag from '../../../../components/Tag/RoleTag' import useModal from '../../../../hooks/useModal' import defaultUserAvatar from '../../../../styles/theme/images/avatar-user.svg' -import {Breakpoint} from '../../../../types/constEnums' import lazyPreload from '../../../../utils/lazyPreload' -const AvatarBlock = styled('div')({ - display: 'none', - [`@media screen and (min-width: ${Breakpoint.SIDEBAR_LEFT}px)`]: { - display: 'block', - marginRight: 16 - } -}) - -const StyledRow = styled(Row)({ - padding: '12px 8px 12px 16px', - [`@media screen and (min-width: ${Breakpoint.SIDEBAR_LEFT}px)`]: { - padding: '16px 8px 16px 16px' - } -}) - -const StyledRowInfo = styled(RowInfo)({ - paddingLeft: 0 -}) - const ActionsBlock = styled('div')({ alignItems: 'center', display: 'flex', @@ -51,6 +31,8 @@ const ActionsBlock = styled('div')({ }) interface Props { + billingLeaderCount: number + orgAdminCount: number organizationUser: OrgMemberRow_organizationUser$key organization: OrgMemberRow_organization$key } @@ -74,13 +56,13 @@ interface UserAvatarProps { } const UserAvatar: React.FC = ({picture}) => ( - +
{picture ? ( ) : ( default avatar )} - +
) interface UserInfoProps { @@ -98,7 +80,7 @@ const UserInfo: React.FC = ({ isOrgAdmin, inactive }) => ( - + {preferredName} {isBillingLeader && Billing Leader} @@ -108,7 +90,7 @@ const UserInfo: React.FC = ({ {email} - + ) interface UserActionsProps { @@ -168,6 +150,7 @@ const OrgMemberRow = (props: Props) => { inactive picture preferredName + lastSeenAt } role ...OrgAdminActionMenu_organizationUser @@ -177,28 +160,41 @@ const OrgMemberRow = (props: Props) => { ) const { - user: {email, inactive, picture, preferredName}, + user: {email, inactive, picture, preferredName, lastSeenAt}, role } = organizationUser const isBillingLeader = role === 'BILLING_LEADER' const isOrgAdmin = role === 'ORG_ADMIN' + const formattedLastSeenAt = lastSeenAt ? format(new Date(lastSeenAt), 'yyyy-MM-dd') : 'Never' + return ( - - - - - +
+
+ +
+ +
+
+
+ {formattedLastSeenAt} + + +
- +
) diff --git a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/TaskSummarySection.tsx b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/TaskSummarySection.tsx index 05c56291daa..c2f51081cc7 100644 --- a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/TaskSummarySection.tsx +++ b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/TaskSummarySection.tsx @@ -43,7 +43,7 @@ const TaskSummarySection = (props: Props) => { {grid((task) => ( - + ))} diff --git a/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx b/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx index 0d795ba5f5c..032d6053253 100644 --- a/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx +++ b/packages/client/modules/userDashboard/components/OrgUserRow/OrgMemberRow.tsx @@ -70,7 +70,7 @@ interface UserInfoProps { email: string isBillingLeader: boolean isOrgAdmin: boolean - inactive: boolean | null + inactive: boolean | null | undefined } const UserInfo: React.FC = ({ diff --git a/packages/client/modules/userDashboard/components/UserProfileRoot.tsx b/packages/client/modules/userDashboard/components/UserProfileRoot.tsx index 3a473107b08..487779c6137 100644 --- a/packages/client/modules/userDashboard/components/UserProfileRoot.tsx +++ b/packages/client/modules/userDashboard/components/UserProfileRoot.tsx @@ -15,7 +15,7 @@ const UserProfileRoot = (props: Props) => { } } = props useSubscription('UserProfileRoot', NotificationSubscription) - const queryRef = useQueryLoaderNow(userProfileQuery, {teamId}) + const queryRef = useQueryLoaderNow(userProfileQuery, {}) return ( {queryRef && } diff --git a/packages/client/mutations/LoginWithGoogleMutation.ts b/packages/client/mutations/LoginWithGoogleMutation.ts index b64e6b5ee5e..2adfa6fc24a 100644 --- a/packages/client/mutations/LoginWithGoogleMutation.ts +++ b/packages/client/mutations/LoginWithGoogleMutation.ts @@ -57,7 +57,7 @@ const LoginWithGoogleMutation: StandardMutation { if (!orgUserId) return diff --git a/packages/client/mutations/handlers/handleUpsertTasks.ts b/packages/client/mutations/handlers/handleUpsertTasks.ts index 186fb5bcea1..7f7a59d020d 100644 --- a/packages/client/mutations/handlers/handleUpsertTasks.ts +++ b/packages/client/mutations/handlers/handleUpsertTasks.ts @@ -15,11 +15,11 @@ type Task = RecordProxy<{ readonly id: string readonly teamId: string readonly tags: readonly string[] - readonly discussionId: string | null - readonly threadParentId: string | null - readonly meetingId: string | null - readonly updatedAt: string | null - readonly userId: string | null + readonly discussionId: string | null | undefined + readonly threadParentId: string | null | undefined + readonly meetingId: string | null | undefined + readonly updatedAt: string | null | undefined + readonly userId: string | null | undefined }> const handleUpsertTask = (task: Task | null, store: RecordSourceSelectorProxy) => { diff --git a/packages/client/package.json b/packages/client/package.json index bec5a37c452..7a5cc95a586 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -128,7 +128,7 @@ "react-dom": "^17.0.2", "react-dom-confetti": "^0.2.0", "react-ga4": "^1.4.1", - "react-relay": "^14.1.0", + "react-relay": "^18.0.0", "react-router": "^5.0.1", "react-router-dom": "^5.0.1", "react-swipeable-views": "https://github.com/mattkrick/react-swipeable-views/tarball/4f1d3062d6f8939e9889bc6241bb46aa7bc5332d", @@ -137,7 +137,7 @@ "react-textarea-autosize": "^7.1.0", "react-transition-group": "^4.3.0", "react-virtualized": "^9.21.1", - "relay-runtime": "^14.1.0", + "relay-runtime": "^18.0.0", "resize-observer-polyfill": "^1.5.0", "rrule": "^2.7.2", "sanitize-html": "^2.13.0", diff --git a/packages/client/schemaExtensions/clientSchema.graphql b/packages/client/schemaExtensions/clientSchema.graphql index 915fbf7685a..63541038006 100644 --- a/packages/client/schemaExtensions/clientSchema.graphql +++ b/packages/client/schemaExtensions/clientSchema.graphql @@ -42,9 +42,12 @@ extend type RetrospectiveMeeting { } extend type TeamPromptMeeting { + localPhase: NewMeetingPhase! + localStage: NewMeetingStage! localStageId: String isRightDrawerOpen: Boolean! showWorkSidebar: Boolean! + showSidebar: Boolean! } extend type Team { @@ -120,6 +123,18 @@ extend type AgendaItemsStage { localScheduledEndTime: String } +extend type EstimateStage { + localScheduledEndTime: String +} + +extend type TeamHealthStage { + localScheduledEndTime: String +} + +extend type TeamPromptResponseStage { + localScheduledEndTime: String +} + extend type Discussion { isAnonymousComment: Boolean! replyingToCommentId: String! diff --git a/packages/client/utils/AzureDevOpsClientManager.ts b/packages/client/utils/AzureDevOpsClientManager.ts index 84f3956a6e3..29adab81837 100644 --- a/packages/client/utils/AzureDevOpsClientManager.ts +++ b/packages/client/utils/AzureDevOpsClientManager.ts @@ -32,7 +32,7 @@ class AzureDevOpsClientManager { static async openOAuth( atmosphere: Atmosphere, teamId: string, - provider: {id: string; tenantId: string | null; clientId: string}, + provider: {id: string; tenantId: string | null | undefined; clientId: string}, mutationProps: MenuMutationProps ) { const {id: providerId, tenantId, clientId} = provider diff --git a/packages/client/utils/getNonNullEdges.ts b/packages/client/utils/getNonNullEdges.ts index 711c21a637d..661a9885e7c 100644 --- a/packages/client/utils/getNonNullEdges.ts +++ b/packages/client/utils/getNonNullEdges.ts @@ -1,8 +1,8 @@ interface RelayEdge { - node: Record | null + node: Record | null | undefined } -const getNonNullEdges = (edges: readonly T[]) => { +const getNonNullEdges = (edges: readonly T[]) => { const goodEdges = edges.filter((edge) => edge?.node) return goodEdges as (NonNullable & {node: NonNullable['node']>})[] } diff --git a/relay.config.js b/relay.config.js index 99475b728b9..2540f4f10ea 100644 --- a/relay.config.js +++ b/relay.config.js @@ -9,7 +9,7 @@ module.exports = { }, language: 'typescript', src: path.join(__dirname, 'packages'), - customScalars: { + customScalarTypes: { Email: 'string', DateTime: 'string', URL: 'string', diff --git a/yarn.lock b/yarn.lock index 75581e85db5..89f9873c3f8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3485,6 +3485,13 @@ dependencies: regenerator-runtime "^0.13.11" +"@babel/runtime@^7.25.0": + version "7.25.6" + resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.25.6.tgz#9afc3289f7184d8d7f98b099884c26317b9264d2" + integrity sha512-VBj9MYyDb9tuLq7yzqjgzt6Q+IBQLrGZfdjOekyEirZPHxXWoTSGUTMrpsfi58Up73d13NfYLv8HT9vmznjzhQ== + dependencies: + regenerator-runtime "^0.14.0" + "@babel/template@^7.16.7", "@babel/template@^7.3.3": version "7.16.7" resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.16.7.tgz#8d126c8701fde4d66b264b3eba3d96f07666d155" @@ -9224,9 +9231,9 @@ redux "^4.0.0" "@types/react-relay@^14.1.3": - version "14.1.3" - resolved "https://registry.yarnpkg.com/@types/react-relay/-/react-relay-14.1.3.tgz#d8a64e816c255910be7c94170e20472cafe5a51f" - integrity sha512-tsu+3jN0zeOYKV485fwUy3yMEZWkDVzC2JG0tJgEH6p9tcPQkBAUoXqEZFwSBtHtNo1etfa1Eityg3fC55qDvQ== + version "14.1.7" + resolved "https://registry.yarnpkg.com/@types/react-relay/-/react-relay-14.1.7.tgz#22c747c90c6c28aabc74118dfb81a76cd8bc7067" + integrity sha512-EM5rXp+CbDYEazOgwG0bIzEJFow/m/b4SlaYiGotr37pLm8Egi5wbfR4mNBMOthzBCQuw/ObkSoJv9kEIHT9tw== dependencies: "@types/react" "*" "@types/relay-runtime" "*" @@ -11022,9 +11029,9 @@ camelcase@^6.2.0: integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA== caniuse-lite@^1.0.30001426, caniuse-lite@^1.0.30001646, caniuse-lite@~1.0.0: - version "1.0.30001651" - resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001651.tgz#52de59529e8b02b1aedcaaf5c05d9e23c0c28138" - integrity sha512-9Cf+Xv1jJNe1xPZLGuUXLNkE1BoDkqRqYyFJ9TDYSqhduqA4hu4oR9HluGoWYQC/aj8WHjsGVV+bwkh0+tegRg== + version "1.0.30001663" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001663.tgz#1529a723505e429fdfd49532e9fc42273ba7fed7" + integrity sha512-o9C3X27GLKbLeTYZ6HBOLU1tsAcBZsLis28wrVzddShCS16RujjHp9GDHKZqrB3meE0YjhawvMFsGb/igqiPzA== capital-case@^1.0.4: version "1.0.4" @@ -19766,16 +19773,16 @@ react-refresh@^0.9.0: resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.9.0.tgz#71863337adc3e5c2f8a6bfddd12ae3bfe32aafbf" integrity sha512-Gvzk7OZpiqKSkxsQvO/mbTN1poglhmAV7gR/DdIrRrSMXraRQQlfikRJOr3Nb9GTMPC5kof948Zy6jJZIFtDvQ== -react-relay@^14.1.0: - version "14.1.0" - resolved "https://registry.yarnpkg.com/react-relay/-/react-relay-14.1.0.tgz#2a2349d33ca6558543340a12b363ee2de3db06a6" - integrity sha512-U4oHb5wn1LEi17x3AcZOy66MXs83tnYbsK7/YE1/3cQKCPrXhyVUQbEOC9L7jMX659jtS1NACae+7b03bryMCQ== +react-relay@^18.0.0: + version "18.0.0" + resolved "https://registry.yarnpkg.com/react-relay/-/react-relay-18.0.0.tgz#f35d2c74adc7d6203cccdd0dff6009852332da1f" + integrity sha512-8JD6LFcWmrrRprAdYL0Q/UL8VpIkZwMzTXp/pF7P4s3V6mtmNpnb8PaME+0lVlbzF5dJTmdim4Pr+E+GP+oGqA== dependencies: - "@babel/runtime" "^7.0.0" + "@babel/runtime" "^7.25.0" fbjs "^3.0.2" invariant "^2.2.4" nullthrows "^1.1.1" - relay-runtime "14.1.0" + relay-runtime "18.0.0" react-remove-scroll-bar@^2.3.3: version "2.3.4" @@ -20183,6 +20190,11 @@ regenerator-runtime@^0.13.4: resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.9.tgz#8925742a98ffd90814988d7566ad30ca3b263b52" integrity sha512-p3VT+cOEgxFsRRA9X4lkI1E+k2/CtnKtU4gcxyaCUreilL/vqI6CdZ3wxVUx3UOUg+gnUOQQcRI7BmSI656MYA== +regenerator-runtime@^0.14.0: + version "0.14.1" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz#356ade10263f685dda125100cd862c1db895327f" + integrity sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw== + regenerator-transform@^0.15.0: version "0.15.0" resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.15.0.tgz#cbd9ead5d77fae1a48d957cf889ad0586adb6537" @@ -20253,12 +20265,12 @@ relateurl@^0.2.7: resolved "https://registry.yarnpkg.com/relateurl/-/relateurl-0.2.7.tgz#54dbf377e51440aca90a4cd274600d3ff2d888a9" integrity sha1-VNvzd+UUQKypCkzSdGANP/LYiKk= -relay-compiler@^14.1.0: - version "14.1.0" - resolved "https://registry.yarnpkg.com/relay-compiler/-/relay-compiler-14.1.0.tgz#88e9c531eb14a6a31e6f14663982124d780bd1b6" - integrity sha512-P8+CXm+Hq96z5NNwYl7hyGo5GgvMZDs9mXBRv7txUbJO4Ql9mXio3+D9EX3VfevRWTuE4ahM37i3Ssx1H604vA== +relay-compiler@^18.0.0: + version "18.0.0" + resolved "https://registry.yarnpkg.com/relay-compiler/-/relay-compiler-18.0.0.tgz#3b278d906695ccefbeed965c6923496572473d21" + integrity sha512-sl9g6c/qMJrAc8usIeRp6L86D8kKSJGd+xTgMlZh0zXPO6kB1hGYGjjy1W0rOg+NE/yqNeREJxwvz5VxSGU92g== -relay-config@^12.0.0: +relay-config@^12.0.1: version "12.0.1" resolved "https://registry.yarnpkg.com/relay-config/-/relay-config-12.0.1.tgz#852ddfe22081b837ce4d2282dffd4dbe75f34c67" integrity sha512-vq7GLLsGPpb/tZr9kmfbfe5sLO1cGa67jpCp4Xtk+N98fMInCu9YIH8R/FxxUhKVPnpLQf3ecmz+s+/MjzcnTQ== @@ -20274,7 +20286,16 @@ relay-runtime@12.0.0: fbjs "^3.0.0" invariant "^2.2.4" -relay-runtime@14.1.0, relay-runtime@^14.1.0: +relay-runtime@18.0.0, relay-runtime@^18.0.0: + version "18.0.0" + resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-18.0.0.tgz#527eb9de7e4c804163500235d941fac1561e7c6b" + integrity sha512-LmW6T0Wdm3VQgTB0OcCl6VJX8lx7x9faTIwUqzTYh0olYFQ3FfA2LEuUW/LcjRX13hWQUiWQE/i3hqTyjc9jAw== + dependencies: + "@babel/runtime" "^7.25.0" + fbjs "^3.0.2" + invariant "^2.2.4" + +relay-runtime@^14.1.0: version "14.1.0" resolved "https://registry.yarnpkg.com/relay-runtime/-/relay-runtime-14.1.0.tgz#44b317101f560a16caea2eef8a52e254b03908fa" integrity sha512-t2DR2hZviHrdEQrOvFqwmvRWvZ0SjI/r4bS5f3qvMyXt4g1kw3RTb2RNVmbKGg6zEQhf7Na/brdqE4roApmclQ== From b625d7e51b5ef4fe196251110699d99249d44fc9 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 23 Sep 2024 18:29:13 -0600 Subject: [PATCH 473/529] fix: isPaid flag when moving teams to 0-team org (#10263) Signed-off-by: Matt Krick --- packages/server/graphql/mutations/moveTeamToOrg.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/graphql/mutations/moveTeamToOrg.ts b/packages/server/graphql/mutations/moveTeamToOrg.ts index 84e432021b0..dbae4a63713 100644 --- a/packages/server/graphql/mutations/moveTeamToOrg.ts +++ b/packages/server/graphql/mutations/moveTeamToOrg.ts @@ -75,7 +75,7 @@ const moveToOrg = async ( // RESOLUTION const updates = { orgId, - isPaid: !!isPaidResult?.isPaid, + isPaid: isPaidResult?.isPaid ?? true, tier: org.tier, trialStartDate: org.trialStartDate, updatedAt: new Date() From b72decd56c08239ca990fa23552faf5d33182012 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Mon, 23 Sep 2024 17:44:18 -0700 Subject: [PATCH 474/529] feat(metrics): add mutation to generate usage report (#10236) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .../private/mutations/runOrgActivityReport.ts | 112 ++++++++++++++++++ .../typeDefs/runOrgActivityReport.graphql | 50 ++++++++ 2 files changed, 162 insertions(+) create mode 100644 packages/server/graphql/private/mutations/runOrgActivityReport.ts create mode 100644 packages/server/graphql/private/typeDefs/runOrgActivityReport.graphql diff --git a/packages/server/graphql/private/mutations/runOrgActivityReport.ts b/packages/server/graphql/private/mutations/runOrgActivityReport.ts new file mode 100644 index 00000000000..17c27f8d8c0 --- /dev/null +++ b/packages/server/graphql/private/mutations/runOrgActivityReport.ts @@ -0,0 +1,112 @@ +import {sql} from 'kysely' +import getRethink from '../../../database/rethinkDriver' +import {RDatum, RValue} from '../../../database/stricterR' +import getKysely from '../../../postgres/getKysely' +import {MutationResolvers} from '../resolverTypes' + +const runOrgActivityReport: MutationResolvers['runOrgActivityReport'] = async ( + _source, + {startDate, endDate} +) => { + const pg = getKysely() + const now = new Date() + const queryEndDate = endDate || now + const queryStartDate = startDate || new Date(0) // Unix epoch start if not provided + + const months = pg + .selectFrom( + sql`generate_series( + date_trunc('month', ${queryStartDate.toISOString()}::date), + date_trunc('month', ${queryEndDate.toISOString()}::date), + ${sql`'1 month'::interval`} + )`.as('monthStart') + ) + .selectAll() + + const userSignups = pg + .selectFrom('User') + .select([ + sql`date_trunc('month', "createdAt")`.as('month'), + sql`COUNT(DISTINCT "id")`.as('signup_count') + ]) + .where('createdAt', '>=', queryStartDate) + .where('createdAt', '<', queryEndDate) + .groupBy(sql`date_trunc('month', "createdAt")`) + + const query = pg + .selectFrom(months.as('m')) + .leftJoin(userSignups.as('us'), (join) => + join.onRef(sql`m."monthStart"`, '=', sql`us.month::timestamp`) + ) + .select([ + sql`m."monthStart"`.as('monthStart'), + sql`COALESCE(us.signup_count, 0)`.as('signupCount') + ]) + .orderBy('monthStart') + + const r = await getRethink() + try { + const [pgResults, rethinkResults] = await Promise.all([ + query.execute(), + r + .table('NewMeeting') + .between( + r.epochTime(queryStartDate.getTime() / 1000), + r.epochTime(queryEndDate.getTime() / 1000), + {index: 'createdAt'} + ) + .merge((row: RValue) => ({ + yearMonth: { + year: row('createdAt').year(), + month: row('createdAt').month() + } + })) + .group((row) => row('yearMonth')) + .ungroup() + .map((group: RDatum) => ({ + yearMonth: group('group'), + meetingCount: group('reduction').count(), + participantIds: group('reduction') + .concatMap((row: RDatum) => + r.table('MeetingMember').getAll(row('id'), {index: 'meetingId'})('userId') + ) + .distinct() + })) + .map((row: RDatum) => + row.merge({ + participantCount: row('participantIds').count() + }) + ) + .without('participantIds') + .run() + ]) + + // Combine PostgreSQL and RethinkDB results + const combinedResults = pgResults.map((pgRow: any) => { + const monthStart = new Date(pgRow.monthStart) + const rethinkParticipants = rethinkResults.find( + (r: any) => + r.yearMonth.month === monthStart.getUTCMonth() + 1 && + r.yearMonth.year === monthStart.getUTCFullYear() + ) + const rethinkMeetings = rethinkResults.find( + (r: any) => + r.yearMonth.month === monthStart.getUTCMonth() + 1 && + r.yearMonth.year === monthStart.getUTCFullYear() + ) + + return { + monthStart: pgRow.monthStart, + signupCount: pgRow.signupCount ? pgRow.signupCount : 0, + participantCount: rethinkParticipants ? rethinkParticipants.participantCount : 0, + meetingCount: rethinkMeetings ? rethinkMeetings.meetingCount : 0 + } + }) + return {rows: combinedResults} + } catch (error) { + console.error('Error executing Org Activity Report:', error) + return {error: {message: 'Error executing Org Activity Report'}} + } +} + +export default runOrgActivityReport diff --git a/packages/server/graphql/private/typeDefs/runOrgActivityReport.graphql b/packages/server/graphql/private/typeDefs/runOrgActivityReport.graphql new file mode 100644 index 00000000000..f4a90a913fe --- /dev/null +++ b/packages/server/graphql/private/typeDefs/runOrgActivityReport.graphql @@ -0,0 +1,50 @@ +extend type Mutation { + """ + Get org activity by month + """ + runOrgActivityReport( + """ + the start date for the query + """ + startDate: DateTime + + """ + the end date for the query + """ + endDate: DateTime + ): RunOrgActivityReportPayload! +} + +""" +Return value for runOrgActivityReport, which could be an error +""" +union RunOrgActivityReportPayload = ErrorPayload | RunOrgActivityReportSuccess + +type RunOrgActivityReportSuccess { + """ + The rows of the report + """ + rows: [OrgActivityRow!]! +} + +type OrgActivityRow { + """ + The start date of the month + """ + monthStart: DateTime! + + """ + The number of signups in the month + """ + signupCount: Int! + + """ + The number of unique meeting participants in the month + """ + participantCount: Int! + + """ + The number of meetings run in the month + """ + meetingCount: Int! +} From 477b789c6de0ed131caf98a463ffc56697b19527 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 24 Sep 2024 17:28:17 +0200 Subject: [PATCH 475/529] chore(release): release v7.48.0 (#10255) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 14 ++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 26 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ff2ef7b4f06..419139c591f 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.47.5" + ".": "7.48.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index e8f33aeab75..46dc19fd412 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,20 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.48.0](https://github.com/ParabolInc/parabol/compare/v7.47.5...v7.48.0) (2024-09-24) + + +### Added + +* **metrics:** add mutation to generate usage report ([#10236](https://github.com/ParabolInc/parabol/issues/10236)) ([b72decd](https://github.com/ParabolInc/parabol/commit/b72decd56c08239ca990fa23552faf5d33182012)) + + +### Fixed + +* bump relay so it shares react's scheduler ([#10262](https://github.com/ParabolInc/parabol/issues/10262)) ([5893e38](https://github.com/ParabolInc/parabol/commit/5893e38a008597ec2e659b488ec0e3444338a2ff)) +* isPaid flag when moving teams to 0-team org ([#10263](https://github.com/ParabolInc/parabol/issues/10263)) ([b625d7e](https://github.com/ParabolInc/parabol/commit/b625d7e51b5ef4fe196251110699d99249d44fc9)) +* **misc:** show full length of agenda item text when hovering ([#10251](https://github.com/ParabolInc/parabol/issues/10251)) ([89661a7](https://github.com/ParabolInc/parabol/commit/89661a7425898418461aefb65428179622c70b78)) + ## [7.47.5](https://github.com/ParabolInc/parabol/compare/v7.47.4...v7.47.5) (2024-09-16) diff --git a/package.json b/package.json index bb1decbc61e..b627a19d5f8 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.47.5", + "version": "7.48.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 1124079a529..f6518c68cf3 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.47.5", + "version": "7.48.0", "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.47.5" + "parabol-server": "7.48.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 7a5cc95a586..cf4761c89af 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.47.5", + "version": "7.48.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 1516a4b5a08..6deba5caa56 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.47.5", + "version": "7.48.0", "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 6daabf55bc5..3776b5aeb90 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.47.5", + "version": "7.48.0", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.47.5", - "parabol-server": "7.47.5", + "parabol-client": "7.48.0", + "parabol-server": "7.48.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 97e62da9c62..c074a1defdd 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.47.5", + "version": "7.48.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 08b438aaf01..6591639beee 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.47.5", + "version": "7.48.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.47.5", + "parabol-client": "7.48.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 203835e4296f17f51c8d6f968b41f7fb64e820ab Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 24 Sep 2024 11:41:09 -0600 Subject: [PATCH 476/529] fix: stop series when team is no more (#10268) Signed-off-by: Matt Krick --- .../private/mutations/processRecurrence.ts | 20 ++++++++++++++----- .../mutations/updateRecurrenceSettings.ts | 2 +- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/packages/server/graphql/private/mutations/processRecurrence.ts b/packages/server/graphql/private/mutations/processRecurrence.ts index 32670a43866..e51547e940c 100644 --- a/packages/server/graphql/private/mutations/processRecurrence.ts +++ b/packages/server/graphql/private/mutations/processRecurrence.ts @@ -3,6 +3,7 @@ import tracer from 'dd-trace' import ms from 'ms' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {DateTime, RRuleSet} from 'rrule-rust' +import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' import {fromDateTime, toDateTime} from '../../../../client/shared/rruleUtil' import getRethink from '../../../database/rethinkDriver' import MeetingRetrospective, { @@ -23,6 +24,7 @@ import safeCreateRetrospective from '../../mutations/helpers/safeCreateRetrospec import safeCreateTeamPrompt, {DEFAULT_PROMPT} from '../../mutations/helpers/safeCreateTeamPrompt' import safeEndRetrospective from '../../mutations/helpers/safeEndRetrospective' import safeEndTeamPrompt from '../../mutations/helpers/safeEndTeamPrompt' +import {stopMeetingSeries} from '../../public/mutations/updateRecurrenceSettings' import {MutationResolvers} from '../resolverTypes' const startRecurringMeeting = async ( @@ -156,14 +158,22 @@ const processRecurrence: MutationResolvers['processRecurrence'] = async ( await tracer.trace('processRecurrence.startActiveMeetingSeries', async () => Promise.allSettled( activeMeetingSeries.map(async (meetingSeries) => { - const seriesTeam = await dataLoader.get('teams').loadNonNull(meetingSeries.teamId) - if (seriesTeam.isArchived || !seriesTeam.isPaid) { + const {teamId, id: meetingSeriesId, recurrenceRule, facilitatorId} = meetingSeries + const teamMemberId = TeamMemberId.join(teamId, facilitatorId) + const [seriesTeam, facilitatorTeamMember] = await Promise.all([ + dataLoader.get('teams').loadNonNull(teamId), + dataLoader.get('teamMembers').loadNonNull(teamMemberId) + ]) + if (seriesTeam.isArchived || !facilitatorTeamMember.isNotRemoved) { + return await stopMeetingSeries(meetingSeries) + } + if (!seriesTeam.isPaid) { return } const [seriesOrg, lastMeeting] = await Promise.all([ dataLoader.get('organizations').loadNonNull(seriesTeam.orgId), - dataLoader.get('lastMeetingByMeetingSeriesId').load(meetingSeries.id) + dataLoader.get('lastMeetingByMeetingSeriesId').load(meetingSeriesId) ]) if (seriesOrg.lockedAt) { @@ -172,7 +182,7 @@ const processRecurrence: MutationResolvers['processRecurrence'] = async ( // For meetings that should still be active, start the meeting and set its end time. // Any subscriptions are handled by the shared meeting start code - const rrule = RRuleSet.parse(meetingSeries.recurrenceRule) + const rrule = RRuleSet.parse(recurrenceRule) // Only get meetings that should currently be active, i.e. meetings that should have started // within the last 24 hours, started after the last meeting in the series, and started before @@ -188,7 +198,7 @@ const processRecurrence: MutationResolvers['processRecurrence'] = async ( ) for (const startTime of newMeetingsStartTimes) { const err = await tracer.trace('startRecurringMeeting', async (span) => { - span?.addTags({meetingSeriesId: meetingSeries.id}) + span?.addTags({meetingSeriesId}) return startRecurringMeeting( meetingSeries, fromDateTime(startTime.toString(), rrule.tzid).toDate(), diff --git a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts index a41aea04e18..014a0fccef3 100644 --- a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts +++ b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts @@ -88,7 +88,7 @@ const updateMeetingSeries = async (meetingSeries: MeetingSeries, newRecurrenceRu await Promise.all(updates) } -const stopMeetingSeries = async (meetingSeries: MeetingSeries) => { +export const stopMeetingSeries = async (meetingSeries: MeetingSeries) => { const r = await getRethink() const now = new Date() From 6273411f5c5e7e03dc569b3359a49902b88dc11c Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Fri, 27 Sep 2024 12:03:37 -0700 Subject: [PATCH 477/529] chore(rethinkdb): NewMeeting: Phase 1a (#10216) Signed-off-by: Matt Krick --- codegen.json | 10 +- .../modules/demo/ClientGraphQLServer.ts | 19 +- packages/client/modules/demo/initDB.ts | 2 +- packages/client/types/generics.ts | 4 + packages/client/utils/meetings/lookups.ts | 2 +- .../indexing/retrospectiveDiscussionTopic.ts | 3 +- .../helpers/publishSimilarRetroTopics.ts | 3 +- .../__tests__/processRecurrence.test.ts | 6 +- .../database/types/GenericMeetingStage.ts | 14 +- packages/server/database/types/Meeting.ts | 35 +-- .../server/database/types/MeetingAction.ts | 10 +- .../server/database/types/MeetingPoker.ts | 11 +- .../database/types/MeetingRetrospective.ts | 45 ++-- .../database/types/MeetingTeamPrompt.ts | 9 +- .../email/newMeetingSummaryEmailCreator.tsx | 4 +- .../server/graphql/meetingTypePredicates.ts | 13 -- .../server/graphql/mutations/endCheckIn.ts | 22 +- .../graphql/mutations/endRetrospective.ts | 10 +- .../graphql/mutations/endSprintPoker.ts | 18 +- .../addAgendaItemToActiveActionMeeting.ts | 3 +- .../mutations/helpers/addDiscussionTopics.ts | 4 +- .../mutations/helpers/calculateEngagement.ts | 7 +- .../mutations/helpers/collectReactjis.ts | 7 +- .../helpers/createNewMeetingPhases.ts | 5 +- .../endMeeting/sendNewMeetingSummary.ts | 4 +- .../mutations/helpers/gatherInsights.ts | 4 +- .../helpers/generateDiscussionSummary.ts | 6 +- .../mutations/helpers/generateGroups.ts | 3 +- .../helpers/generateStandupMeetingSummary.ts | 6 +- .../mutations/helpers/handleCompletedStage.ts | 9 +- .../helpers/notifications/MSTeamsNotifier.ts | 5 +- .../notifications/MattermostNotifier.ts | 15 +- .../NotificationIntegrationHelper.ts | 14 +- .../helpers/notifications/Notifier.ts | 12 +- .../helpers/notifications/SlackNotifier.ts | 29 ++- .../helpers/notifications/getSummaryText.ts | 31 +-- .../mutations/helpers/pushEstimateToGitHub.ts | 6 +- .../helpers/removeEmptyReflections.ts | 4 +- .../helpers/removeStagesFromMeetings.ts | 2 +- .../helpers/safeCreateRetrospective.ts | 7 +- .../mutations/helpers/safeCreateTeamPrompt.ts | 4 +- .../mutations/helpers/safeEndRetrospective.ts | 20 +- .../mutations/helpers/safeEndTeamPrompt.ts | 14 +- .../helpers/sendPokerMeetingRevoteEvent.ts | 4 +- .../removeReflectionFromGroup.ts | 13 +- .../server/graphql/mutations/joinMeeting.ts | 7 +- .../mutations/pokerAnnounceDeckHover.ts | 6 +- .../graphql/mutations/pokerResetDimension.ts | 6 +- .../graphql/mutations/pokerRevealVotes.ts | 6 +- .../resetRetroMeetingToGroupStage.ts | 6 +- .../graphql/mutations/setTaskEstimate.ts | 5 +- .../graphql/mutations/startSprintPoker.ts | 7 +- .../updateAzureDevOpsDimensionField.ts | 6 +- .../mutations/updateGitHubDimensionField.ts | 6 +- .../graphql/mutations/updatePokerScope.ts | 13 +- .../graphql/mutations/updateRetroMaxVotes.ts | 6 +- .../graphql/mutations/voteForPokerStory.ts | 11 +- .../mutations/voteForReflectionGroup.ts | 6 +- .../mutations/generateMeetingSummary.ts | 9 +- .../private/mutations/processRecurrence.ts | 13 +- .../private/mutations/runScheduledJobs.ts | 4 +- .../types/GenerateMeetingSummarySuccess.ts | 9 +- .../public/mutations/addTranscriptionBot.ts | 6 +- .../graphql/public/mutations/endTeamPrompt.ts | 6 +- .../public/mutations/helpers/getSummaries.ts | 5 +- .../public/mutations/helpers/getTopics.ts | 12 +- .../public/mutations/resetReflectionGroups.ts | 2 +- .../public/mutations/setTeamHealthVote.ts | 32 +++ .../graphql/public/mutations/startCheckIn.ts | 7 +- .../mutations/updateGitLabDimensionField.ts | 6 +- .../mutations/updateJiraDimensionField.ts | 6 +- .../updateJiraServerDimensionField.ts | 6 +- .../public/mutations/updateMeetingTemplate.ts | 4 +- .../mutations/updateRecurrenceSettings.ts | 6 +- .../types/AddTranscriptionBotSuccess.ts | 5 +- .../graphql/public/types/AutogroupSuccess.ts | 4 +- .../server/graphql/public/types/Discussion.ts | 8 +- .../public/types/EndTeamPromptSuccess.ts | 5 +- .../graphql/public/types/EstimateStage.ts | 10 +- .../public/types/GenerateGroupsSuccess.ts | 5 +- .../public/types/GenerateInsightSuccess.ts | 8 +- .../server/graphql/public/types/NewMeeting.ts | 2 +- .../public/types/NotifyResponseMentioned.ts | 4 +- .../public/types/NotifyResponseReplied.ts | 4 +- .../graphql/public/types/ReflectPhase.ts | 4 +- .../types/ResetReflectionGroupsSuccess.ts | 4 +- .../graphql/public/types/RetroDiscussStage.ts | 4 +- .../graphql/public/types/RetroReflection.ts | 4 +- .../public/types/RetroReflectionGroup.ts | 4 +- .../public/types/StartCheckInSuccess.ts | 7 +- .../public/types/StartRetrospectiveSuccess.ts | 7 +- .../public/types/StartTeamPromptSuccess.ts | 5 +- .../graphql/public/types/TeamPromptMeeting.ts | 6 +- .../types/UpdateDimensionFieldSuccess.ts | 4 +- .../types/UpdateMeetingPromptSuccess.ts | 5 +- .../types/UpdateRecurrenceSettingsSuccess.ts | 5 +- packages/server/graphql/resolvers.ts | 3 +- .../graphql/types/SetPhaseFocusPayload.ts | 4 +- .../1726174453131_NewMeeting-phase1.ts | 99 +++++++++ packages/server/postgres/select.ts | 54 ++++- packages/server/postgres/types/Meeting.d.ts | 95 +++++++- .../postgres/types/NewMeetingPhase.d.ts | 204 ++++++++++++++++++ packages/server/postgres/types/index.d.ts | 14 ++ .../server/utils/RecallAIServerManager.ts | 2 +- packages/server/utils/analytics/analytics.ts | 20 +- packages/server/utils/analytics/helpers.ts | 9 +- packages/server/utils/getPhase.ts | 31 +-- 107 files changed, 905 insertions(+), 446 deletions(-) create mode 100644 packages/server/postgres/migrations/1726174453131_NewMeeting-phase1.ts create mode 100644 packages/server/postgres/types/NewMeetingPhase.d.ts diff --git a/codegen.json b/codegen.json index 39b03e6f474..14b74d7dc35 100644 --- a/codegen.json +++ b/codegen.json @@ -26,7 +26,7 @@ "PingableServices": "./types/PingableServices#PingableServicesSource", "ProcessRecurrenceSuccess": "./types/ProcessRecurrenceSuccess#ProcessRecurrenceSuccessSource", "RemoveAuthIdentitySuccess": "./types/RemoveAuthIdentitySuccess#RemoveAuthIdentitySuccessSource", - "RetrospectiveMeeting": "../../database/types/MeetingRetrospective#default", + "RetrospectiveMeeting": "../../postgres/types/Meeting#RetrospectiveMeeting", "SAML": "./types/SAML#SAMLSource", "SetIsFreeMeetingTemplateSuccess": "./types/SetIsFreeMeetingTemplateSuccess#SetIsFreeMeetingTemplateSuccessSource", "SignupsPayload": "./types/SignupsPayload#SignupsPayloadSource", @@ -73,7 +73,7 @@ "EndTeamPromptSuccess": "./types/EndTeamPromptSuccess#EndTeamPromptSuccessSource", "AcceptRequestToJoinDomainSuccess": "./types/AcceptRequestToJoinDomainSuccess#AcceptRequestToJoinDomainSuccessSource", "AcceptTeamInvitationPayload": "./types/AcceptTeamInvitationPayload#AcceptTeamInvitationPayloadSource", - "ActionMeeting": "../../database/types/MeetingAction#default", + "ActionMeeting": "../../postgres/types/Meeting#CheckInMeeting", "ActionMeetingMember": "../../database/types/ActionMeetingMember#default as ActionMeetingMemberDB", "AddApprovedOrganizationDomainsSuccess": "./types/AddApprovedOrganizationDomainsSuccess#AddApprovedOrganizationDomainsSuccessSource", "AddPokerTemplateSuccess": "./types/AddPokerTemplateSuccess#AddPokerTemplateSuccessSource", @@ -143,7 +143,7 @@ "Threadable": "./types/Threadable#ThreadableSource", "OrgIntegrationProviders": "./types/OrgIntegrationProviders#OrgIntegrationProvidersSource", "OrganizationUser": "../../postgres/types/index#OrganizationUser as OrganizationUserDB", - "PokerMeeting": "../../database/types/MeetingPoker#default as MeetingPoker", + "PokerMeeting": "../../postgres/types/Meeting#PokerMeeting", "PokerMeetingMember": "../../database/types/MeetingPokerMeetingMember#default as PokerMeetingMemberDB", "PokerTemplate": "../../database/types/PokerTemplate#default as PokerTemplateDB", "RRule": "rrule-rust#RRuleSet", @@ -159,7 +159,7 @@ "ResetReflectionGroupsSuccess": "./types/ResetReflectionGroupsSuccess#ResetReflectionGroupsSuccessSource", "RetroReflection": "../../postgres/types/index#RetroReflection as RetroReflectionDB", "RetroReflectionGroup": "./types/RetroReflectionGroup#RetroReflectionGroupSource", - "RetrospectiveMeeting": "../../database/types/MeetingRetrospective#default", + "RetrospectiveMeeting": "../../postgres/types/Meeting#RetrospectiveMeeting", "RetrospectiveMeetingMember": "../../database/types/RetroMeetingMember#default", "SAML": "./types/SAML#SAMLSource", "SetMeetingSettingsPayload": "../types/SetMeetingSettingsPayload#SetMeetingSettingsPayloadSource", @@ -182,7 +182,7 @@ "TeamMemberIntegrationAuthOAuth1": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", "TeamMemberIntegrationAuthOAuth2": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", "TeamMemberIntegrations": "./types/TeamMemberIntegrations#TeamMemberIntegrationsSource", - "TeamPromptMeeting": "../../database/types/MeetingTeamPrompt#default as MeetingTeamPromptDB", + "TeamPromptMeeting": "../../postgres/types/Meeting#TeamPromptMeeting", "TeamPromptMeetingMember": "../../database/types/TeamPromptMeetingMember#default as TeamPromptMeetingMemberDB", "TeamPromptResponse": "../../postgres/types/index#TeamPromptResponse as TeamPromptResponseDB", "TemplateDimension": "../../postgres/types/index#TemplateDimension as TemplateDimensionDB", diff --git a/packages/client/modules/demo/ClientGraphQLServer.ts b/packages/client/modules/demo/ClientGraphQLServer.ts index e7ea44c8170..4be77ecd626 100644 --- a/packages/client/modules/demo/ClientGraphQLServer.ts +++ b/packages/client/modules/demo/ClientGraphQLServer.ts @@ -8,13 +8,15 @@ import stringSimilarity from 'string-similarity' import {ReactableEnum} from '~/__generated__/AddReactjiToReactableMutation.graphql' import {DragReflectionDropTargetTypeEnum} from '~/__generated__/EndDraggingReflectionMutation.graphql' import {PALETTE} from '~/styles/paletteV3' -import DiscussPhase from '../../../server/database/types/DiscussPhase' -import DiscussStage from '../../../server/database/types/DiscussStage' -import NewMeetingPhase from '../../../server/database/types/GenericMeetingPhase' -import NewMeetingStage from '../../../server/database/types/GenericMeetingStage' import GoogleAnalyzedEntity from '../../../server/database/types/GoogleAnalyzedEntity' import ReflectPhase from '../../../server/database/types/ReflectPhase' import ITask from '../../../server/database/types/Task' +import {NewMeetingStage} from '../../../server/graphql/private/resolverTypes' +import { + DiscussPhase, + DiscussStage, + NewMeetingPhase +} from '../../../server/postgres/types/NewMeetingPhase' import { ExternalLinks, MeetingSettingsThreshold, @@ -100,11 +102,7 @@ export type DemoReflectionGroup = { voterIds: string[] } -export type IDiscussPhase = Omit & { - readyToAdvance: any - startAt: string | Date - endAt: string | Date -} +export type IDiscussPhase = DiscussPhase export type IReflectPhase = Omit & { startAt: string | Date @@ -1048,7 +1046,6 @@ class ClientGraphQLServer extends (EventEmitter as GQLDemoEmitter) { reflectionGroupId: newReflectionGroupId, updatedAt: now }) - this.db.newMeeting.nextAutoGroupThreshold = null const nextTitle = getGroupSmartTitle([reflection as DemoReflection]) newReflectionGroup.smartTitle = nextTitle newReflectionGroup.title = nextTitle @@ -1523,7 +1520,7 @@ class ClientGraphQLServer extends (EventEmitter as GQLDemoEmitter) { }, EndRetrospectiveMutation: ({meetingId}: {meetingId: string}, userId: string) => { const phases = this.db.newMeeting.phases as INewMeetingPhase[] - const lastPhase = phases[phases.length - 1] as IDiscussPhase + const lastPhase = phases[phases.length - 1]! const currentStage = lastPhase.stages.find( (stage) => stage.startAt && !stage.endAt ) as IDiscussStage diff --git a/packages/client/modules/demo/initDB.ts b/packages/client/modules/demo/initDB.ts index aa9dfc85899..23e400d75f8 100644 --- a/packages/client/modules/demo/initDB.ts +++ b/packages/client/modules/demo/initDB.ts @@ -1,7 +1,7 @@ import {SlackNotificationEventEnum} from '~/__generated__/SlackNotificationList_viewer.graphql' import {PALETTE} from '~/styles/paletteV3' -import RetrospectiveMeeting from '../../../server/database/types/MeetingRetrospective' import ITask from '../../../server/database/types/Task' +import {RetrospectiveMeeting} from '../../../server/postgres/types/Meeting' import JiraProjectId from '../../shared/gqlIds/JiraProjectId' import demoUserAvatar from '../../styles/theme/images/avatar-user.svg' import {ExternalLinks, MeetingSettingsThreshold, RetroDemo} from '../../types/constEnums' diff --git a/packages/client/types/generics.ts b/packages/client/types/generics.ts index 755054dfa68..0148c9b35be 100644 --- a/packages/client/types/generics.ts +++ b/packages/client/types/generics.ts @@ -32,6 +32,10 @@ type DeepNonNullableObject = { [P in keyof T]-?: DeepNonNullable> } +export type NonNullableProps = { + [K in keyof T]: NonNullable +} + // export type DeepNullableObject = { // [P in keyof T]: T[P] extends Array // ? Array> | null diff --git a/packages/client/utils/meetings/lookups.ts b/packages/client/utils/meetings/lookups.ts index aa327d72d5b..b0a59f46588 100644 --- a/packages/client/utils/meetings/lookups.ts +++ b/packages/client/utils/meetings/lookups.ts @@ -1,6 +1,6 @@ import React from 'react' -import {MeetingTypeEnum} from '~/../server/postgres/types/Meeting' import {NewMeetingPhaseTypeEnum} from '~/__generated__/ActionMeetingSidebar_meeting.graphql' +import {MeetingTypeEnum} from '../../__generated__/SummarySheet_meeting.graphql' import CardsSVG from '../../components/CardsSVG' import {ACTION, POKER, RETROSPECTIVE, TEAM_PROMPT} from '../constants' diff --git a/packages/embedder/indexing/retrospectiveDiscussionTopic.ts b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts index 36eca506754..c962d3bcbdf 100644 --- a/packages/embedder/indexing/retrospectiveDiscussionTopic.ts +++ b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts @@ -1,4 +1,3 @@ -import {isMeetingRetrospective} from 'parabol-server/database/types/MeetingRetrospective' import {DataLoaderInstance} from 'parabol-server/dataloader/RootDataLoader' import prettier from 'prettier' import {Comment} from '../../server/postgres/types' @@ -73,7 +72,7 @@ export const createTextFromRetrospectiveDiscussionTopic = async ( dataLoader.get('retroReflectionGroups').load(reflectionGroupId), dataLoader.get('retroReflectionsByGroupId').load(reflectionGroupId) ]) - if (!isMeetingRetrospective(newMeeting)) throw new Error('Meeting is not a retro') + if (newMeeting.meetingType !== 'retrospective') throw new Error('Meeting is not a retro') // It should never be undefined, but our data integrity in RethinkDB is bad const templateId = newMeeting?.templateId ?? '' diff --git a/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts b/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts index 217ee63073b..9e43696e9f3 100644 --- a/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts +++ b/packages/embedder/workflows/helpers/publishSimilarRetroTopics.ts @@ -2,7 +2,6 @@ import {SubscriptionChannel} from '../../../client/types/constEnums' import makeAppURL from '../../../client/utils/makeAppURL' import appOrigin from '../../../server/appOrigin' import {DataLoaderInstance} from '../../../server/dataloader/RootDataLoader' -import {isRetroMeeting} from '../../../server/graphql/meetingTypePredicates' import { buildCommentContentBlock, createAIComment @@ -25,7 +24,7 @@ const makeSimilarDiscussionLink = async ( dataLoader.get('retroReflectionGroups').loadNonNull(reflectionGroupId) ]) - if (!meeting || !isRetroMeeting(meeting)) throw new Error('invalid meeting type') + if (!meeting || meeting.meetingType !== 'retrospective') throw new Error('invalid meeting type') const {phases, name: meetingName} = meeting const {title: topic} = reflectionGroup const discussPhase = getPhase(phases, 'discuss') diff --git a/packages/server/__tests__/processRecurrence.test.ts b/packages/server/__tests__/processRecurrence.test.ts index bcbf2ab7fbd..86edc1b87fb 100644 --- a/packages/server/__tests__/processRecurrence.test.ts +++ b/packages/server/__tests__/processRecurrence.test.ts @@ -10,6 +10,7 @@ import ReflectPhase from '../database/types/ReflectPhase' import TeamPromptResponsesPhase from '../database/types/TeamPromptResponsesPhase' import generateUID from '../generateUID' import {insertMeetingSeries as insertMeetingSeriesQuery} from '../postgres/queries/insertMeetingSeries' +import {RetroMeetingPhase} from '../postgres/types/NewMeetingPhase' import {getUserTeams, sendIntranet, signUp} from './common' const PROCESS_RECURRENCE = ` @@ -273,7 +274,10 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` id: meetingId, teamId, meetingCount: 0, - phases: [new ReflectPhase(teamId, []), new DiscussPhase(undefined)], + phases: [ + new ReflectPhase(teamId, []) as RetroMeetingPhase, + new DiscussPhase(undefined) as RetroMeetingPhase + ], facilitatorUserId: userId, scheduledEndTime: new Date(Date.now() - ms('5m')), meetingSeriesId, diff --git a/packages/server/database/types/GenericMeetingStage.ts b/packages/server/database/types/GenericMeetingStage.ts index 016e37d6b93..bfe7e2ae23d 100644 --- a/packages/server/database/types/GenericMeetingStage.ts +++ b/packages/server/database/types/GenericMeetingStage.ts @@ -36,17 +36,17 @@ export interface GenericMeetingStageInput { export default class GenericMeetingStage { id: string - isAsync: boolean | undefined | null + isAsync?: boolean | undefined | null isComplete = false isNavigable: boolean isNavigableByFacilitator: boolean - startAt: Date | undefined - endAt: Date | undefined = undefined - scheduledEndTime: Date | undefined | null - suggestedEndTime: Date | undefined - suggestedTimeLimit: number | undefined + startAt?: Date | undefined + endAt?: Date | undefined = undefined + scheduledEndTime?: Date | undefined | null + suggestedEndTime?: Date | undefined + suggestedTimeLimit?: number | undefined viewCount: number - readyToAdvance: string[] | undefined = [] + readyToAdvance?: string[] | undefined = [] phaseType: string constructor(input: GenericMeetingStageInput) { const {durations, phaseType, id, isNavigable, isNavigableByFacilitator, startAt, viewCount} = diff --git a/packages/server/database/types/Meeting.ts b/packages/server/database/types/Meeting.ts index c9ed6cbf778..503b945c153 100644 --- a/packages/server/database/types/Meeting.ts +++ b/packages/server/database/types/Meeting.ts @@ -1,21 +1,22 @@ import generateUID from '../../generateUID' import {MeetingTypeEnum} from '../../postgres/types/Meeting' +import {NewMeetingPhase} from '../../postgres/types/NewMeetingPhase' import GenericMeetingPhase from './GenericMeetingPhase' interface Input { - id?: string + id?: string | null teamId: string meetingType: MeetingTypeEnum meetingCount: number - name?: string + name?: string | null // Every meeting has at least one phase - phases: [GenericMeetingPhase, ...GenericMeetingPhase[]] + phases: [NewMeetingPhase, ...NewMeetingPhase[]] facilitatorUserId: string - showConversionModal?: boolean - meetingSeriesId?: number + showConversionModal?: boolean | null + meetingSeriesId?: number | null scheduledEndTime?: Date | null - summary?: string - sentimentScore?: number + summary?: string | null + sentimentScore?: number | null } const namePrefix = { @@ -29,23 +30,23 @@ export default abstract class Meeting { updatedAt = new Date() createdBy: string | null endedAt: Date | undefined | null = undefined - facilitatorStageId: string | undefined - facilitatorUserId: string + facilitatorStageId: string + facilitatorUserId: string | null meetingCount: number meetingNumber: number name: string - summarySentAt: Date | undefined = undefined + summarySentAt: Date | undefined | null = undefined teamId: string meetingType: MeetingTypeEnum phases: GenericMeetingPhase[] - showConversionModal?: boolean - meetingSeriesId?: number + showConversionModal?: boolean | null + meetingSeriesId?: number | null scheduledEndTime?: Date | null - summary?: string - sentimentScore?: number - usedReactjis?: Record - slackTs?: string - engagement?: number + summary?: string | null + sentimentScore?: number | null + usedReactjis?: Record | null + slackTs?: string | number | null + engagement?: number | null constructor(input: Input) { const { diff --git a/packages/server/database/types/MeetingAction.ts b/packages/server/database/types/MeetingAction.ts index fee9b580b8d..745df2cb0b3 100644 --- a/packages/server/database/types/MeetingAction.ts +++ b/packages/server/database/types/MeetingAction.ts @@ -1,10 +1,6 @@ -import AgendaItemsPhase from './AgendaItemsPhase' -import CheckInPhase from './CheckInPhase' -import GenericMeetingPhase from './GenericMeetingPhase' +import {CheckInMeetingPhase} from '../../postgres/types/NewMeetingPhase' import Meeting from './Meeting' -import UpdatesPhase from './UpdatesPhase' -type CheckInMeetingPhase = CheckInPhase | UpdatesPhase | GenericMeetingPhase | AgendaItemsPhase interface Input { id?: string teamId: string @@ -14,10 +10,6 @@ interface Input { facilitatorUserId: string } -export function isMeetingAction(meeting: Meeting): meeting is MeetingAction { - return meeting.meetingType === 'action' -} - export default class MeetingAction extends Meeting { meetingType!: 'action' taskCount?: number diff --git a/packages/server/database/types/MeetingPoker.ts b/packages/server/database/types/MeetingPoker.ts index 5883fd6d8ac..f8c4a19cea6 100644 --- a/packages/server/database/types/MeetingPoker.ts +++ b/packages/server/database/types/MeetingPoker.ts @@ -1,24 +1,17 @@ -import CheckInPhase from './CheckInPhase' -import EstimatePhase from './EstimatePhase' -import GenericMeetingPhase from './GenericMeetingPhase' +import {PokerMeetingPhase} from '../../postgres/types/NewMeetingPhase' import Meeting from './Meeting' -type PokerPhase = CheckInPhase | EstimatePhase | GenericMeetingPhase interface Input { id: string teamId: string meetingCount: number name: string - phases: [PokerPhase, ...PokerPhase[]] + phases: [PokerMeetingPhase, ...PokerMeetingPhase[]] facilitatorUserId: string templateId: string templateRefId: string } -export function isMeetingPoker(meeting: Meeting): meeting is MeetingPoker { - return meeting.meetingType === 'poker' -} - export default class MeetingPoker extends Meeting { meetingType!: 'poker' templateId: string diff --git a/packages/server/database/types/MeetingRetrospective.ts b/packages/server/database/types/MeetingRetrospective.ts index d727149bb78..4ee8d4e376d 100644 --- a/packages/server/database/types/MeetingRetrospective.ts +++ b/packages/server/database/types/MeetingRetrospective.ts @@ -1,44 +1,31 @@ -import GenericMeetingPhase from './GenericMeetingPhase' +import {AutogroupReflectionGroupType, TranscriptBlock} from '../../postgres/types' +import {RetroMeetingPhase} from '../../postgres/types/NewMeetingPhase' import Meeting from './Meeting' -export type AutogroupReflectionGroupType = { - groupTitle: string - reflectionIds: string[] -} - -export type TranscriptBlock = { - speaker: string - words: string -} - interface Input { - id?: string + id?: string | null teamId: string meetingCount: number name: string - phases: [GenericMeetingPhase, ...GenericMeetingPhase[]] + phases: [RetroMeetingPhase, ...RetroMeetingPhase[]] facilitatorUserId: string - showConversionModal?: boolean + showConversionModal?: boolean | null templateId: string totalVotes: number maxVotesPerGroup: number disableAnonymity: boolean - transcription?: TranscriptBlock[] - autogroupReflectionGroups?: AutogroupReflectionGroupType[] - resetReflectionGroups?: AutogroupReflectionGroupType[] + transcription?: TranscriptBlock[] | null + autogroupReflectionGroups?: AutogroupReflectionGroupType[] | null + resetReflectionGroups?: AutogroupReflectionGroupType[] | null recallBotId?: string - videoMeetingURL?: string - meetingSeriesId?: number + videoMeetingURL?: string | null + meetingSeriesId?: number | null scheduledEndTime?: Date | null } -export function isMeetingRetrospective(meeting: Meeting): meeting is MeetingRetrospective { - return meeting.meetingType === 'retrospective' -} - export default class MeetingRetrospective extends Meeting { meetingType!: 'retrospective' - showConversionModal?: boolean + showConversionModal?: boolean | null autoGroupThreshold?: number | null nextAutoGroupThreshold?: number | null totalVotes: number @@ -50,11 +37,11 @@ export default class MeetingRetrospective extends Meeting { templateId: string topicCount?: number reflectionCount?: number - transcription?: TranscriptBlock[] - recallBotId?: string - videoMeetingURL?: string - autogroupReflectionGroups?: AutogroupReflectionGroupType[] - resetReflectionGroups?: AutogroupReflectionGroupType[] + transcription?: TranscriptBlock[] | null + recallBotId?: string | null + videoMeetingURL?: string | null + autogroupReflectionGroups?: AutogroupReflectionGroupType[] | null + resetReflectionGroups?: AutogroupReflectionGroupType[] | null constructor(input: Input) { const { diff --git a/packages/server/database/types/MeetingTeamPrompt.ts b/packages/server/database/types/MeetingTeamPrompt.ts index bf57eb4ebaf..b781fbb2f55 100644 --- a/packages/server/database/types/MeetingTeamPrompt.ts +++ b/packages/server/database/types/MeetingTeamPrompt.ts @@ -1,8 +1,5 @@ -import GenericMeetingPhase from './GenericMeetingPhase' +import {TeamPromptPhase} from '../../postgres/types/NewMeetingPhase' import Meeting from './Meeting' -import TeamPromptResponsesPhase from './TeamPromptResponsesPhase' - -type TeamPromptPhase = TeamPromptResponsesPhase | GenericMeetingPhase interface Input { id?: string @@ -16,10 +13,6 @@ interface Input { scheduledEndTime?: Date } -export function isMeetingTeamPrompt(meeting: Meeting): meeting is MeetingTeamPrompt { - return meeting.meetingType === 'teamPrompt' -} - export default class MeetingTeamPrompt extends Meeting { meetingType!: 'teamPrompt' meetingPrompt: string diff --git a/packages/server/email/newMeetingSummaryEmailCreator.tsx b/packages/server/email/newMeetingSummaryEmailCreator.tsx index 8387534b811..f0e065a2f33 100644 --- a/packages/server/email/newMeetingSummaryEmailCreator.tsx +++ b/packages/server/email/newMeetingSummaryEmailCreator.tsx @@ -19,9 +19,9 @@ const newMeetingSummaryEmailCreator = async (props: Props) => { const dataLoaderId = dataLoader.share() const newMeeting = await dataLoader.get('newMeetings').load(meetingId) - const facilitator = await dataLoader.get('users').loadNonNull(newMeeting.facilitatorUserId) + const facilitator = await dataLoader.get('users').loadNonNull(newMeeting.facilitatorUserId!) const {tms} = facilitator - const authToken = new AuthToken({sub: newMeeting.facilitatorUserId, tms, rol: 'impersonate'}) + const authToken = new AuthToken({sub: newMeeting.facilitatorUserId!, tms, rol: 'impersonate'}) const environment = new ServerEnvironment(authToken, dataLoaderId) // this depends on types, and those types are generated by created the schema, which must crawl the endMeeting file diff --git a/packages/server/graphql/meetingTypePredicates.ts b/packages/server/graphql/meetingTypePredicates.ts index 332acd71a87..bb4ed35985d 100644 --- a/packages/server/graphql/meetingTypePredicates.ts +++ b/packages/server/graphql/meetingTypePredicates.ts @@ -5,20 +5,7 @@ import EstimatePhase from '../database/types/EstimatePhase' import EstimateStage from '../database/types/EstimateStage' import GenericMeetingPhase from '../database/types/GenericMeetingPhase' import GenericMeetingStage from '../database/types/GenericMeetingStage' -import MeetingAction from '../database/types/MeetingAction' -import MeetingPoker from '../database/types/MeetingPoker' -import MeetingRetrospective from '../database/types/MeetingRetrospective' import TeamPromptResponsesPhase from '../database/types/TeamPromptResponsesPhase' -import {AnyMeeting} from '../postgres/types/Meeting' - -export const isRetroMeeting = (meeting: AnyMeeting): meeting is MeetingRetrospective => - meeting.meetingType === 'retrospective' - -export const isPokerMeeting = (meeting: AnyMeeting): meeting is MeetingPoker => - meeting.meetingType === 'poker' - -export const isActionMeeting = (meeting: AnyMeeting): meeting is MeetingAction => - meeting.meetingType === 'action' export const isEstimateStage = (stage: GenericMeetingStage): stage is EstimateStage => stage.phaseType === 'ESTIMATE' diff --git a/packages/server/graphql/mutations/endCheckIn.ts b/packages/server/graphql/mutations/endCheckIn.ts index 4ad865c40b0..c3ea3419772 100644 --- a/packages/server/graphql/mutations/endCheckIn.ts +++ b/packages/server/graphql/mutations/endCheckIn.ts @@ -7,13 +7,13 @@ import {positionAfter} from '../../../client/shared/sortOrder' import {checkTeamsLimit} from '../../billing/helpers/teamLimitsCheck' import getRethink from '../../database/rethinkDriver' import {RDatum} from '../../database/stricterR' -import MeetingAction from '../../database/types/MeetingAction' import Task from '../../database/types/Task' import TimelineEventCheckinComplete from '../../database/types/TimelineEventCheckinComplete' import {DataLoaderInstance} from '../../dataloader/RootDataLoader' import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' import {AgendaItem} from '../../postgres/types' +import {CheckInMeeting} from '../../postgres/types/Meeting' import archiveTasksForDB from '../../safeMutations/archiveTasksForDB' import removeSuggestedAction from '../../safeMutations/removeSuggestedAction' import {Logger} from '../../utils/Logger' @@ -101,7 +101,7 @@ const clonePinnedAgendaItems = async ( dataLoader.clearAll('agendaItems') } -const summarizeCheckInMeeting = async (meeting: MeetingAction, dataLoader: DataLoaderWorker) => { +const summarizeCheckInMeeting = async (meeting: CheckInMeeting, dataLoader: DataLoaderWorker) => { /* If isKill, no agenda items were processed so clear none of them. * Similarly, don't clone pins. the original ones will show up again. */ @@ -177,12 +177,11 @@ export default { const viewerId = getUserId(authToken) // AUTH - const meeting = (await r - .table('NewMeeting') - .get(meetingId) - .default(null) - .run()) as MeetingAction | null + const meeting = await r.table('NewMeeting').get(meetingId).default(null).run() if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) + if (meeting.meetingType !== 'action') { + return standardError(new Error('Not a check-in meeting'), {userId: viewerId}) + } const {endedAt, facilitatorStageId, phases, teamId} = meeting // VALIDATION @@ -201,7 +200,7 @@ export default { const phase = getMeetingPhase(phases) const insights = await gatherInsights(meeting, dataLoader) - const completedCheckIn = (await r + const completedCheckIn = await r .table('NewMeeting') .get(meetingId) .update( @@ -213,7 +212,7 @@ export default { {returnChanges: true} )('changes')(0)('new_val') .default(null) - .run()) as unknown as MeetingAction + .run() if (!completedCheckIn) { return standardError(new Error('Completed check-in meeting does not exist'), { @@ -221,6 +220,11 @@ export default { }) } + if (completedCheckIn.meetingType !== 'action') { + return standardError(new Error('Completed check-in meeting is not an action'), { + userId: viewerId + }) + } // remove any empty tasks const [meetingMembers, team, teamMembers, removedTaskIds] = await Promise.all([ dataLoader.get('meetingMembersByMeetingId').load(meetingId), diff --git a/packages/server/graphql/mutations/endRetrospective.ts b/packages/server/graphql/mutations/endRetrospective.ts index 82136fa8f9d..322a6ee1c67 100644 --- a/packages/server/graphql/mutations/endRetrospective.ts +++ b/packages/server/graphql/mutations/endRetrospective.ts @@ -1,6 +1,5 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import getRethink from '../../database/rethinkDriver' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' import {getUserId, isTeamMember} from '../../utils/authorization' import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' @@ -23,12 +22,11 @@ export default { const viewerId = getUserId(authToken) // AUTH - const meeting = (await r - .table('NewMeeting') - .get(meetingId) - .default(null) - .run()) as MeetingRetrospective | null + const meeting = await r.table('NewMeeting').get(meetingId).default(null).run() if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) + if (meeting.meetingType !== 'retrospective') { + return standardError(new Error('Meeting not found'), {userId: viewerId}) + } const {endedAt, teamId} = meeting // VALIDATION diff --git a/packages/server/graphql/mutations/endSprintPoker.ts b/packages/server/graphql/mutations/endSprintPoker.ts index 00db314a2ab..5927fcd34f4 100644 --- a/packages/server/graphql/mutations/endSprintPoker.ts +++ b/packages/server/graphql/mutations/endSprintPoker.ts @@ -4,8 +4,6 @@ import getMeetingPhase from 'parabol-client/utils/getMeetingPhase' import findStageById from 'parabol-client/utils/meetings/findStageById' import {checkTeamsLimit} from '../../billing/helpers/teamLimitsCheck' import getRethink from '../../database/rethinkDriver' -import Meeting from '../../database/types/Meeting' -import MeetingPoker from '../../database/types/MeetingPoker' import TimelineEventPokerComplete from '../../database/types/TimelineEventPokerComplete' import getKysely from '../../postgres/getKysely' import {Logger} from '../../utils/Logger' @@ -42,12 +40,11 @@ export default { const viewerId = getUserId(authToken) // AUTH - const meeting = (await r - .table('NewMeeting') - .get(meetingId) - .default(null) - .run()) as Meeting | null + const meeting = await r.table('NewMeeting').get(meetingId).default(null).run() if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) + if (meeting.meetingType !== 'poker') { + return standardError(new Error('Meeting is not a poker meeting'), {userId: viewerId}) + } const {endedAt, facilitatorStageId, phases, teamId} = meeting // VALIDATION @@ -82,7 +79,7 @@ export default { await dataLoader.get('commentCountByDiscussionId').loadMany(discussionIds) ).filter(isValid) const commentCount = commentCounts.reduce((cumSum, count) => cumSum + count, 0) - const completedMeeting = (await r + const completedMeeting = await r .table('NewMeeting') .get(meetingId) .update( @@ -96,12 +93,15 @@ export default { {returnChanges: true, nonAtomic: true} )('changes')(0)('new_val') .default(null) - .run()) as unknown as MeetingPoker + .run() if (!completedMeeting) { return standardError(new Error('Completed poker meeting does not exist'), { userId: viewerId }) } + if (completedMeeting.meetingType !== 'poker') { + return standardError(new Error('Meeting is not a poker meeting'), {userId: viewerId}) + } const {templateId} = completedMeeting const [meetingMembers, team, teamMembers, removedTaskIds, template] = await Promise.all([ dataLoader.get('meetingMembersByMeetingId').load(meetingId), diff --git a/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts b/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts index 735ba4be831..69c0e8bca7e 100644 --- a/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts +++ b/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts @@ -1,6 +1,5 @@ import getRethink from '../../../database/rethinkDriver' import AgendaItemsStage from '../../../database/types/AgendaItemsStage' -import MeetingAction from '../../../database/types/MeetingAction' import getKysely from '../../../postgres/getKysely' import getPhase from '../../../utils/getPhase' import {DataLoaderWorker} from '../../graphql' @@ -18,7 +17,7 @@ const addAgendaItemToActiveActionMeeting = async ( const activeMeetings = await dataLoader.get('activeMeetingsByTeamId').load(teamId) const actionMeeting = activeMeetings.find( (activeMeeting) => activeMeeting.meetingType === 'action' - ) as MeetingAction | undefined + ) if (!actionMeeting) return undefined const {id: meetingId, phases} = actionMeeting const agendaItemPhase = getPhase(phases, 'agendaitems') diff --git a/packages/server/graphql/mutations/helpers/addDiscussionTopics.ts b/packages/server/graphql/mutations/helpers/addDiscussionTopics.ts index ef05b73ae6d..a703837a169 100644 --- a/packages/server/graphql/mutations/helpers/addDiscussionTopics.ts +++ b/packages/server/graphql/mutations/helpers/addDiscussionTopics.ts @@ -1,11 +1,11 @@ import mapGroupsToStages from 'parabol-client/utils/makeGroupsToStages' import DiscussStage from '../../../database/types/DiscussStage' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import generateUID from '../../../generateUID' +import {RetrospectiveMeeting} from '../../../postgres/types/Meeting' import getPhase from '../../../utils/getPhase' import {DataLoaderWorker} from '../../graphql' -const addDiscussionTopics = async (meeting: MeetingRetrospective, dataLoader: DataLoaderWorker) => { +const addDiscussionTopics = async (meeting: RetrospectiveMeeting, dataLoader: DataLoaderWorker) => { const {id: meetingId, phases} = meeting const discussPhase = getPhase(phases, 'discuss') if (!discussPhase) return {discussPhaseStages: [], meetingId} diff --git a/packages/server/graphql/mutations/helpers/calculateEngagement.ts b/packages/server/graphql/mutations/helpers/calculateEngagement.ts index bed551a8130..78ebd67ddfc 100644 --- a/packages/server/graphql/mutations/helpers/calculateEngagement.ts +++ b/packages/server/graphql/mutations/helpers/calculateEngagement.ts @@ -1,6 +1,7 @@ import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' import EstimatePhase from '../../../database/types/EstimatePhase' -import Meeting from '../../../database/types/Meeting' +import {AnyMeeting} from '../../../postgres/types/Meeting' +import {NewMeetingStages} from '../../../postgres/types/NewMeetingPhase' import getPhase from '../../../utils/getPhase' import {DataLoaderWorker} from '../../graphql' import isValid from '../../isValid' @@ -13,7 +14,7 @@ import isValid from '../../isValid' * **sprint poker**: meeting members facilitated, voted discussed or reacted / total meeting members * **standup**: replied, commented or reacted / all members */ -const calculateEngagement = async (meeting: Meeting, dataLoader: DataLoaderWorker) => { +const calculateEngagement = async (meeting: AnyMeeting, dataLoader: DataLoaderWorker) => { const {id: meetingId, phases, meetingType, facilitatorUserId} = meeting if (meetingType === 'action') return undefined @@ -78,7 +79,7 @@ const calculateEngagement = async (meeting: Meeting, dataLoader: DataLoaderWorke } // Discussions can happen in many different stage types: discuss, ESTIMATE, reflect, RESPONSES - const stages = phases.flatMap(({stages}) => stages) + const stages = phases.flatMap(({stages}) => stages as NewMeetingStages[]) const discussionIds = stages .map((stage) => 'discussionId' in stage && stage.discussionId) .filter(isValid) as string[] diff --git a/packages/server/graphql/mutations/helpers/collectReactjis.ts b/packages/server/graphql/mutations/helpers/collectReactjis.ts index 99c3206b63f..5e51b14d1a4 100644 --- a/packages/server/graphql/mutations/helpers/collectReactjis.ts +++ b/packages/server/graphql/mutations/helpers/collectReactjis.ts @@ -1,15 +1,16 @@ -import Meeting from '../../../database/types/Meeting' import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {AnyMeeting} from '../../../postgres/types/Meeting' +import {NewMeetingStages} from '../../../postgres/types/NewMeetingPhase' import {DataLoaderWorker} from '../../graphql' import isValid from '../../isValid' -const collectReactjis = async (meeting: Meeting, dataLoader: DataLoaderWorker) => { +const collectReactjis = async (meeting: AnyMeeting, dataLoader: DataLoaderWorker) => { const {id: meetingId, phases} = meeting const usedReactjis: Record = {} // Discussions can happen in many different stage types: discuss, ESTIMATE, reflect, RESPONSES - const stages = phases.flatMap(({stages}) => stages) + const stages = phases.flatMap(({stages}) => stages as NewMeetingStages[]) const discussionIds = stages .map((stage) => 'discussionId' in stage && stage.discussionId) .filter(isValid) as string[] diff --git a/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts b/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts index 098392fc6a6..44846ed791f 100644 --- a/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts +++ b/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts @@ -25,6 +25,7 @@ import UpdatesPhase from '../../../database/types/UpdatesPhase' import UpdatesStage from '../../../database/types/UpdatesStage' import getKysely from '../../../postgres/getKysely' import {MeetingTypeEnum} from '../../../postgres/types/Meeting' +import {NewMeetingPhase} from '../../../postgres/types/NewMeetingPhase' import isPhaseAvailable from '../../../utils/isPhaseAvailable' import {DataLoaderWorker} from '../../graphql' import {getFeatureTier} from '../../types/helpers/getFeatureTier' @@ -69,7 +70,7 @@ const getPastStageDurations = async (teamId: string) => { ) } -const createNewMeetingPhases = async ( +const createNewMeetingPhases = async ( facilitatorUserId: string, teamId: string, meetingId: string, @@ -162,7 +163,7 @@ const createNewMeetingPhases = async ( throw new Error(`Unhandled phaseType: ${phaseType}`) } }) - )) as [GenericMeetingPhase, ...GenericMeetingPhase[]] + )) as [T, ...T[]] primePhases(phases) await Promise.all(asyncSideEffects) return phases diff --git a/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts b/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts index 1b22cefe1b4..570f456e361 100644 --- a/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts +++ b/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts @@ -1,12 +1,12 @@ import getRethink from '../../../../database/rethinkDriver' -import Meeting from '../../../../database/types/Meeting' import getMailManager from '../../../../email/getMailManager' import newMeetingSummaryEmailCreator from '../../../../email/newMeetingSummaryEmailCreator' +import {AnyMeeting} from '../../../../postgres/types/Meeting' import {GQLContext} from '../../../graphql' import isValid from '../../../isValid' export default async function sendNewMeetingSummary( - newMeeting: Meeting, + newMeeting: AnyMeeting, context: Pick ) { const {id: meetingId, teamId, summarySentAt} = newMeeting diff --git a/packages/server/graphql/mutations/helpers/gatherInsights.ts b/packages/server/graphql/mutations/helpers/gatherInsights.ts index 159a7f75533..fed38a5f008 100644 --- a/packages/server/graphql/mutations/helpers/gatherInsights.ts +++ b/packages/server/graphql/mutations/helpers/gatherInsights.ts @@ -1,9 +1,9 @@ -import Meeting from '../../../database/types/Meeting' +import {AnyMeeting} from '../../../postgres/types/Meeting' import {DataLoaderWorker} from '../../graphql' import calculateEngagement from './calculateEngagement' import collectReactjis from './collectReactjis' -const gatherInsights = async (meeting: Meeting, dataLoader: DataLoaderWorker) => { +const gatherInsights = async (meeting: AnyMeeting, dataLoader: DataLoaderWorker) => { const [usedReactjis, engagement] = await Promise.all([ collectReactjis(meeting, dataLoader), calculateEngagement(meeting, dataLoader) diff --git a/packages/server/graphql/mutations/helpers/generateDiscussionSummary.ts b/packages/server/graphql/mutations/helpers/generateDiscussionSummary.ts index fe88bac549e..0c4943e6a7e 100644 --- a/packages/server/graphql/mutations/helpers/generateDiscussionSummary.ts +++ b/packages/server/graphql/mutations/helpers/generateDiscussionSummary.ts @@ -1,7 +1,7 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' import {PARABOL_AI_USER_ID} from '../../../../client/utils/constants' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import updateDiscussions from '../../../postgres/queries/updateDiscussions' +import {RetrospectiveMeeting} from '../../../postgres/types/Meeting' import OpenAIServerManager from '../../../utils/OpenAIServerManager' import publish from '../../../utils/publish' import {DataLoaderWorker} from '../../graphql' @@ -9,12 +9,12 @@ import canAccessAISummary from './canAccessAISummary' const generateDiscussionSummary = async ( discussionId: string, - meeting: MeetingRetrospective, + meeting: RetrospectiveMeeting, dataLoader: DataLoaderWorker ) => { const {id: meetingId, endedAt, facilitatorUserId, teamId} = meeting const [facilitator, team] = await Promise.all([ - dataLoader.get('users').loadNonNull(facilitatorUserId), + dataLoader.get('users').loadNonNull(facilitatorUserId!), dataLoader.get('teams').loadNonNull(teamId) ]) const isAISummaryAccessible = await canAccessAISummary( diff --git a/packages/server/graphql/mutations/helpers/generateGroups.ts b/packages/server/graphql/mutations/helpers/generateGroups.ts index a37536bb97c..068e3b176d0 100644 --- a/packages/server/graphql/mutations/helpers/generateGroups.ts +++ b/packages/server/graphql/mutations/helpers/generateGroups.ts @@ -1,7 +1,6 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' import getRethink from '../../../database/rethinkDriver' -import {AutogroupReflectionGroupType} from '../../../database/types/MeetingRetrospective' -import {RetroReflection} from '../../../postgres/types' +import {AutogroupReflectionGroupType, RetroReflection} from '../../../postgres/types' import {Logger} from '../../../utils/Logger' import OpenAIServerManager from '../../../utils/OpenAIServerManager' import {analytics} from '../../../utils/analytics/analytics' diff --git a/packages/server/graphql/mutations/helpers/generateStandupMeetingSummary.ts b/packages/server/graphql/mutations/helpers/generateStandupMeetingSummary.ts index ac20e9b106c..7b84ec54f00 100644 --- a/packages/server/graphql/mutations/helpers/generateStandupMeetingSummary.ts +++ b/packages/server/graphql/mutations/helpers/generateStandupMeetingSummary.ts @@ -1,15 +1,15 @@ -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {TeamPromptMeeting} from '../../../postgres/types/Meeting' import OpenAIServerManager from '../../../utils/OpenAIServerManager' import {DataLoaderWorker} from '../../graphql' import canAccessAISummary from './canAccessAISummary' const generateStandupMeetingSummary = async ( - meeting: MeetingTeamPrompt, + meeting: TeamPromptMeeting, dataLoader: DataLoaderWorker ) => { const [facilitator, team] = await Promise.all([ - dataLoader.get('users').loadNonNull(meeting.facilitatorUserId), + dataLoader.get('users').loadNonNull(meeting.facilitatorUserId!), dataLoader.get('teams').loadNonNull(meeting.teamId) ]) const isAISummaryAccessible = await canAccessAISummary( diff --git a/packages/server/graphql/mutations/helpers/handleCompletedStage.ts b/packages/server/graphql/mutations/helpers/handleCompletedStage.ts index a6aa7267f88..0fec6c43d1d 100644 --- a/packages/server/graphql/mutations/helpers/handleCompletedStage.ts +++ b/packages/server/graphql/mutations/helpers/handleCompletedStage.ts @@ -4,9 +4,8 @@ import {r} from 'rethinkdb-ts' import groupReflections from '../../../../client/utils/smartGroup/groupReflections' import DiscussStage from '../../../database/types/DiscussStage' import GenericMeetingStage from '../../../database/types/GenericMeetingStage' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import getKysely from '../../../postgres/getKysely' -import {AnyMeeting} from '../../../postgres/types/Meeting' +import {AnyMeeting, RetrospectiveMeeting} from '../../../postgres/types/Meeting' import {DataLoaderWorker} from '../../graphql' import addAIGeneratedContentToThreads from './addAIGeneratedContentToThreads' import addDiscussionTopics from './addDiscussionTopics' @@ -24,7 +23,7 @@ import removeEmptyReflections from './removeEmptyReflections' */ const handleCompletedRetrospectiveStage = async ( stage: GenericMeetingStage, - meeting: MeetingRetrospective, + meeting: RetrospectiveMeeting, dataLoader: DataLoaderWorker ) => { const pg = getKysely() @@ -72,7 +71,7 @@ const handleCompletedRetrospectiveStage = async ( .run() data.meeting = meeting // dont await for the OpenAI API response - generateDiscussionPrompt(meeting.id, teamId, dataLoader, facilitatorUserId) + generateDiscussionPrompt(meeting.id, teamId, dataLoader, facilitatorUserId!) } return {[stage.phaseType]: data} @@ -116,7 +115,7 @@ const handleCompletedStage = async ( dataLoader: DataLoaderWorker ) => { if (meeting.meetingType === 'retrospective') { - return handleCompletedRetrospectiveStage(stage, meeting as MeetingRetrospective, dataLoader) + return handleCompletedRetrospectiveStage(stage, meeting, dataLoader) } return {} } diff --git a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts index b96aec374fc..45459007256 100644 --- a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts @@ -3,11 +3,10 @@ import makeAppURL from 'parabol-client/utils/makeAppURL' import findStageById from 'parabol-client/utils/meetings/findStageById' import {phaseLabelLookup} from 'parabol-client/utils/meetings/lookups' import appOrigin from '../../../../appOrigin' -import Meeting from '../../../../database/types/Meeting' import {IntegrationProviderMSTeams as IIntegrationProviderMSTeams} from '../../../../postgres/queries/getIntegrationProvidersByIds' import {SlackNotification, Team} from '../../../../postgres/types' import IUser from '../../../../postgres/types/IUser' -import {MeetingTypeEnum} from '../../../../postgres/types/Meeting' +import {AnyMeeting, MeetingTypeEnum} from '../../../../postgres/types/Meeting' import MSTeamsServerManager from '../../../../utils/MSTeamsServerManager' import {analytics} from '../../../../utils/analytics/analytics' import sendToSentry from '../../../../utils/sendToSentry' @@ -360,7 +359,7 @@ function GenerateACMeetingTitle(meetingTitle: string) { return titleTextBlock } -function GenerateACMeetingAndTeamsDetails(team: Team, meeting: Meeting) { +function GenerateACMeetingAndTeamsDetails(team: Team, meeting: AnyMeeting) { const meetingDetailColumnSet = new AdaptiveCards.ColumnSet() const teamDetailColumn = new AdaptiveCards.Column() teamDetailColumn.width = 'stretch' diff --git a/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts index 62eb866722c..857316161ff 100644 --- a/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts @@ -4,11 +4,10 @@ import makeAppURL from 'parabol-client/utils/makeAppURL' import findStageById from 'parabol-client/utils/meetings/findStageById' import {phaseLabelLookup} from 'parabol-client/utils/meetings/lookups' import appOrigin from '../../../../appOrigin' -import Meeting from '../../../../database/types/Meeting' import {IntegrationProviderMattermost as IIntegrationProviderMattermost} from '../../../../postgres/queries/getIntegrationProvidersByIds' import {SlackNotification, Team} from '../../../../postgres/types' import IUser from '../../../../postgres/types/IUser' -import {MeetingTypeEnum} from '../../../../postgres/types/Meeting' +import {AnyMeeting, MeetingTypeEnum} from '../../../../postgres/types/Meeting' import MattermostServerManager from '../../../../utils/MattermostServerManager' import {analytics} from '../../../../utils/analytics/analytics' import {toEpochSeconds} from '../../../../utils/epochTime' @@ -47,7 +46,7 @@ const notifyMattermost = async ( return 'success' } -const makeEndMeetingButtons = (meeting: Meeting) => { +const makeEndMeetingButtons = (meeting: AnyMeeting) => { const {id: meetingId} = meeting const searchParams = { utm_source: 'mattermost summary', @@ -94,7 +93,7 @@ type MattermostNotificationAuth = IntegrationProviderMattermost & {userId: strin const makeTeamPromptStartMeetingNotification = ( team: Team, - meeting: Meeting, + meeting: AnyMeeting, meetingUrl: string ) => { return [ @@ -119,7 +118,11 @@ const makeTeamPromptStartMeetingNotification = ( ] } -const makeGenericStartMeetingNotification = (team: Team, meeting: Meeting, meetingUrl: string) => { +const makeGenericStartMeetingNotification = ( + team: Team, + meeting: AnyMeeting, + meetingUrl: string +) => { return [ makeFieldsAttachment( [ @@ -149,7 +152,7 @@ const makeGenericStartMeetingNotification = (team: Team, meeting: Meeting, meeti const makeStartMeetingNotificationLookup: Record< MeetingTypeEnum, - (team: Team, meeting: Meeting, meetingUrl: string) => ReturnType[] + (team: Team, meeting: AnyMeeting, meetingUrl: string) => ReturnType[] > = { teamPrompt: makeTeamPromptStartMeetingNotification, action: makeGenericStartMeetingNotification, diff --git a/packages/server/graphql/mutations/helpers/notifications/NotificationIntegrationHelper.ts b/packages/server/graphql/mutations/helpers/notifications/NotificationIntegrationHelper.ts index e1bd2c9287c..6223b769ba8 100644 --- a/packages/server/graphql/mutations/helpers/notifications/NotificationIntegrationHelper.ts +++ b/packages/server/graphql/mutations/helpers/notifications/NotificationIntegrationHelper.ts @@ -1,6 +1,6 @@ -import Meeting from '../../../../database/types/Meeting' import {Team, TeamPromptResponse} from '../../../../postgres/types' import User from '../../../../postgres/types/IUser' +import {AnyMeeting} from '../../../../postgres/types/Meeting' export type NotifyResponse = | 'success' | { @@ -10,24 +10,24 @@ export type NotifyResponse = } export type NotificationIntegration = { - startMeeting(meeting: Meeting, team: Team, user: User): Promise - updateMeeting?(meeting: Meeting, team: Team, user: User): Promise + startMeeting(meeting: AnyMeeting, team: Team, user: User): Promise + updateMeeting?(meeting: AnyMeeting, team: Team, user: User): Promise endMeeting( - meeting: Meeting, + meeting: AnyMeeting, team: Team, user: User, standupResponses: {user: User; response: TeamPromptResponse}[] | null ): Promise startTimeLimit( scheduledEndTime: Date, - meeting: Meeting, + meeting: AnyMeeting, team: Team, user: User ): Promise - endTimeLimit(meeting: Meeting, team: Team, user: User): Promise + endTimeLimit(meeting: AnyMeeting, team: Team, user: User): Promise integrationUpdated(user: User): Promise standupResponseSubmitted( - meeting: Meeting, + meeting: AnyMeeting, team: Team, user: User, response: TeamPromptResponse diff --git a/packages/server/graphql/mutations/helpers/notifications/Notifier.ts b/packages/server/graphql/mutations/helpers/notifications/Notifier.ts index 6ba69226bed..e01c3e88709 100644 --- a/packages/server/graphql/mutations/helpers/notifications/Notifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/Notifier.ts @@ -61,14 +61,14 @@ export const createNotifier = (loader: NotificationIntegrationLoader): Notifier async startMeeting(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { const {meeting, team, user} = await loadMeetingTeam(dataLoader, meetingId, teamId) if (!meeting || !team || !user) return - const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId, 'meetingStart') + const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId!, 'meetingStart') notifiers.forEach((notifier) => notifier.startMeeting(meeting, team, user)) }, async updateMeeting(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { const {meeting, team, user} = await loadMeetingTeam(dataLoader, meetingId, teamId) if (!meeting || !team || !user) return - const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId, 'meetingStart') + const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId!, 'meetingStart') notifiers.forEach((notifier) => notifier.updateMeeting?.(meeting, team, user)) }, @@ -85,7 +85,7 @@ export const createNotifier = (loader: NotificationIntegrationLoader): Notifier } }) ) - const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId, 'meetingEnd') + const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId!, 'meetingEnd') notifiers.forEach((notifier) => notifier.endMeeting(meeting, team, user, standupResponses)) }, @@ -100,7 +100,7 @@ export const createNotifier = (loader: NotificationIntegrationLoader): Notifier const notifiers = await loader( dataLoader, team.id, - meeting.facilitatorUserId, + meeting.facilitatorUserId!, 'MEETING_STAGE_TIME_LIMIT_START' ) notifiers.forEach((notifier) => notifier.startTimeLimit(scheduledEndTime, meeting, team, user)) @@ -112,7 +112,7 @@ export const createNotifier = (loader: NotificationIntegrationLoader): Notifier const notifiers = await loader( dataLoader, team.id, - meeting.facilitatorUserId, + meeting.facilitatorUserId!, 'MEETING_STAGE_TIME_LIMIT_END' ) notifiers.forEach((notifier) => notifier.endTimeLimit(meeting, team, user)) @@ -141,7 +141,7 @@ export const createNotifier = (loader: NotificationIntegrationLoader): Notifier const notifiers = await loader( dataLoader, team.id, - meeting.facilitatorUserId, + meeting.facilitatorUserId!, 'STANDUP_RESPONSE_SUBMITTED' ) notifiers.forEach((notifier) => diff --git a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts index 457220ef98a..5a10f159e0e 100644 --- a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts @@ -7,7 +7,6 @@ import TeamPromptResponseId from '../../../../../client/shared/gqlIds/TeamPrompt import {ErrorResponse, PostMessageResponse} from '../../../../../client/utils/SlackManager' import appOrigin from '../../../../appOrigin' import getRethink, {RethinkSchema} from '../../../../database/rethinkDriver' -import Meeting from '../../../../database/types/Meeting' import SlackAuth from '../../../../database/types/SlackAuth' import {SlackNotificationAuth} from '../../../../dataloader/integrationAuthLoaders' import getKysely from '../../../../postgres/getKysely' @@ -91,7 +90,7 @@ const notifySlack = async ( return res } -const makeEndMeetingButtons = (meeting: Meeting) => { +const makeEndMeetingButtons = (meeting: AnyMeeting) => { const {id: meetingId} = meeting const searchParams = { utm_source: 'slack summary', @@ -136,11 +135,11 @@ const makeEndMeetingButtons = (meeting: Meeting) => { const createTeamSectionContent = (team: Team) => `*Team:*\n${team.name}` -const createMeetingSectionContent = (meeting: Meeting) => `*Meeting:*\n${meeting.name}` +const createMeetingSectionContent = (meeting: AnyMeeting) => `*Meeting:*\n${meeting.name}` const makeTeamPromptStartMeetingNotification = ( team: Team, - meeting: Meeting, + meeting: AnyMeeting, meetingUrl: string ): SlackNotificationMessage => { const title = `*${meeting.name}* is open :speech_balloon: ` @@ -155,7 +154,7 @@ const makeTeamPromptStartMeetingNotification = ( const makeGenericStartMeetingNotification = ( team: Team, - meeting: Meeting, + meeting: AnyMeeting, meetingUrl: string ): SlackNotificationMessage => { const title = 'Meeting started :wave: ' @@ -170,7 +169,7 @@ const makeGenericStartMeetingNotification = ( const makeStartMeetingNotificationLookup: Record< MeetingTypeEnum, - (team: Team, meeting: Meeting, meetingUrl: string) => SlackNotificationMessage + (team: Team, meeting: AnyMeeting, meetingUrl: string) => SlackNotificationMessage > = { teamPrompt: makeTeamPromptStartMeetingNotification, action: makeGenericStartMeetingNotification, @@ -183,7 +182,7 @@ const addStandupResponsesToThread = async ( standupResponses: Array<{user: User; response: TeamPromptResponse}> | null, team: Team, user: User, - meeting: Meeting, + meeting: AnyMeeting, notificationChannel: NotificationChannel ) => { if (!standupResponses || standupResponses.length === 0) { @@ -361,7 +360,11 @@ export const SlackSingleChannelNotifier: NotificationIntegrationHelper { // Order of slack auth is: diff --git a/packages/server/graphql/mutations/helpers/notifications/getSummaryText.ts b/packages/server/graphql/mutations/helpers/notifications/getSummaryText.ts index d3522fce222..49ea47b1c85 100644 --- a/packages/server/graphql/mutations/helpers/notifications/getSummaryText.ts +++ b/packages/server/graphql/mutations/helpers/notifications/getSummaryText.ts @@ -1,16 +1,15 @@ import relativeDate from 'parabol-client/utils/date/relativeDate' import plural from 'parabol-client/utils/plural' -import Meeting from '../../../../database/types/Meeting' -import {isMeetingAction} from '../../../../database/types/MeetingAction' -import {isMeetingPoker} from '../../../../database/types/MeetingPoker' -import {isMeetingRetrospective} from '../../../../database/types/MeetingRetrospective' -import {isMeetingTeamPrompt} from '../../../../database/types/MeetingTeamPrompt' import {getTeamPromptResponsesByMeetingId} from '../../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {AnyMeeting} from '../../../../postgres/types/Meeting' import sendToSentry from '../../../../utils/sendToSentry' -const getSummaryText = async (meeting: Meeting) => { - if (isMeetingRetrospective(meeting)) { - const {commentCount = 0, reflectionCount = 0, topicCount = 0, taskCount = 0} = meeting +const getSummaryText = async (meeting: AnyMeeting) => { + if (meeting.meetingType === 'retrospective') { + const commentCount = meeting.commentCount || 0 + const reflectionCount = meeting.reflectionCount || 0 + const topicCount = meeting.topicCount || 0 + const taskCount = meeting.taskCount || 0 const hasNonZeroStat = commentCount || reflectionCount || topicCount || taskCount if (!hasNonZeroStat && meeting.summary) { sendToSentry(new Error('No stats found for meeting'), { @@ -24,8 +23,11 @@ const getSummaryText = async (meeting: Meeting) => { commentCount, 'comment' )} and created ${taskCount} ${plural(taskCount, 'task')}.` - } else if (isMeetingAction(meeting)) { - const {createdAt, endedAt, agendaItemCount = 0, commentCount = 0, taskCount = 0} = meeting + } else if (meeting.meetingType === 'action') { + const agendaItemCount = meeting.agendaItemCount || 0 + const commentCount = meeting.commentCount || 0 + const taskCount = meeting.taskCount || 0 + const {createdAt, endedAt} = meeting const meetingDuration = relativeDate(createdAt, { now: endedAt, max: 2, @@ -39,21 +41,22 @@ const getSummaryText = async (meeting: Meeting) => { commentCount, 'comment' )}.` - } else if (isMeetingTeamPrompt(meeting)) { + } else if (meeting.meetingType === 'teamPrompt') { const responseCount = (await getTeamPromptResponsesByMeetingId(meeting.id)).filter( (response) => !!response.plaintextContent ).length // :TODO: (jmtaber129): Add additional stats here. return `Your team shared ${responseCount} ${plural(responseCount, 'response', 'responses')}.` - } else if (isMeetingPoker(meeting)) { - const {storyCount = 0, commentCount = 0} = meeting + } else if (meeting.meetingType === 'poker') { + const storyCount = meeting.storyCount || 0 + const commentCount = meeting.commentCount || 0 return `You voted on ${storyCount} ${plural( storyCount, 'story', 'stories' )} and added ${commentCount} ${plural(commentCount, 'comment')}.` } else { - throw new Error(`Meeting type not supported ${meeting.meetingType}`) + throw new Error(`Meeting type not supported ${(meeting as any).meetingType}`) } } diff --git a/packages/server/graphql/mutations/helpers/pushEstimateToGitHub.ts b/packages/server/graphql/mutations/helpers/pushEstimateToGitHub.ts index 039c216bd81..891895d7a71 100644 --- a/packages/server/graphql/mutations/helpers/pushEstimateToGitHub.ts +++ b/packages/server/graphql/mutations/helpers/pushEstimateToGitHub.ts @@ -6,7 +6,6 @@ import {SprintPokerDefaults} from 'parabol-client/types/constEnums' import makeAppURL from 'parabol-client/utils/makeAppURL' import {isNotNull} from 'parabol-client/utils/predicates' import appOrigin from '../../../appOrigin' -import MeetingPoker from '../../../database/types/MeetingPoker' import { AddCommentMutation, AddCommentMutationVariables, @@ -50,6 +49,9 @@ const pushEstimateToGitHub = async ( return new Error('Meeting does not exist') } + if (meeting.meetingType !== 'poker') { + return new Error('Not a poker meeting') + } const githubIntegration = task.integration as Extract< typeof task.integration, {service: 'github'} @@ -150,7 +152,7 @@ const pushEstimateToGitHub = async ( if (!matchingLabel) { let color = PALETTE.GRAPE_500.slice(1) if (meeting) { - const {templateRefId} = meeting as MeetingPoker + const {templateRefId} = meeting const templateRef = await dataLoader.get('templateRefs').loadNonNull(templateRefId) const {dimensions} = templateRef const dimensionRef = dimensions.find((dimension) => dimension.name === dimensionName) diff --git a/packages/server/graphql/mutations/helpers/removeEmptyReflections.ts b/packages/server/graphql/mutations/helpers/removeEmptyReflections.ts index e11080a9bb6..cdccfb40f76 100644 --- a/packages/server/graphql/mutations/helpers/removeEmptyReflections.ts +++ b/packages/server/graphql/mutations/helpers/removeEmptyReflections.ts @@ -1,9 +1,9 @@ import extractTextFromDraftString from 'parabol-client/utils/draftjs/extractTextFromDraftString' -import Meeting from '../../../database/types/Meeting' import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' import getKysely from '../../../postgres/getKysely' +import {AnyMeeting} from '../../../postgres/types/Meeting' -const removeEmptyReflections = async (meeting: Meeting, dataLoader: DataLoaderInstance) => { +const removeEmptyReflections = async (meeting: AnyMeeting, dataLoader: DataLoaderInstance) => { const pg = getKysely() const {id: meetingId} = meeting const reflections = await dataLoader.get('retroReflectionsByMeetingId').load(meetingId) diff --git a/packages/server/graphql/mutations/helpers/removeStagesFromMeetings.ts b/packages/server/graphql/mutations/helpers/removeStagesFromMeetings.ts index 64f1dda66a0..39c5546998e 100644 --- a/packages/server/graphql/mutations/helpers/removeStagesFromMeetings.ts +++ b/packages/server/graphql/mutations/helpers/removeStagesFromMeetings.ts @@ -40,7 +40,7 @@ const removeStagesFromMeetings = async ( nextStage.viewCount = nextStage.viewCount ? nextStage.viewCount + 1 : 1 nextStage.isNavigable = true } - const stageIdx = stages.indexOf(stage) + const stageIdx = (stages as any).indexOf(stage) stages.splice(stageIdx, 1) } } diff --git a/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts b/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts index fa01659f21f..8a9f2e8ef0e 100644 --- a/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeCreateRetrospective.ts @@ -1,6 +1,7 @@ import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import generateUID from '../../../generateUID' -import {MeetingTypeEnum} from '../../../postgres/types/Meeting' +import {MeetingTypeEnum, RetrospectiveMeeting} from '../../../postgres/types/Meeting' +import {RetroMeetingPhase} from '../../../postgres/types/NewMeetingPhase' import {DataLoaderWorker} from '../../graphql' import createNewMeetingPhases from './createNewMeetingPhases' @@ -30,7 +31,7 @@ const safeCreateRetrospective = async ( const {showConversionModal} = organization const meetingId = generateUID() - const phases = await createNewMeetingPhases( + const phases = await createNewMeetingPhases( facilitatorUserId, teamId, meetingId, @@ -46,7 +47,7 @@ const safeCreateRetrospective = async ( showConversionModal, ...meetingSettings, name - }) + }) as RetrospectiveMeeting } export default safeCreateRetrospective diff --git a/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts b/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts index 7416d9d78dc..7c702494396 100644 --- a/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts +++ b/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts @@ -3,7 +3,7 @@ import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import TeamPromptResponsesPhase from '../../../database/types/TeamPromptResponsesPhase' import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' -import {MeetingTypeEnum} from '../../../postgres/types/Meeting' +import {MeetingTypeEnum, TeamPromptMeeting} from '../../../postgres/types/Meeting' import {DataLoaderWorker} from '../../graphql' import {primePhases} from './createNewMeetingPhases' @@ -52,7 +52,7 @@ const safeCreateTeamPrompt = async ( facilitatorUserId: facilitatorId, meetingPrompt: DEFAULT_PROMPT, // :TODO: (jmtaber129): Get this from meeting settings. ...meetingOverrideProps - }) + }) as TeamPromptMeeting } export default safeCreateTeamPrompt diff --git a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts index 1af261de253..58ca5c03b34 100644 --- a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts @@ -4,9 +4,9 @@ import getMeetingPhase from 'parabol-client/utils/getMeetingPhase' import findStageById from 'parabol-client/utils/meetings/findStageById' import {checkTeamsLimit} from '../../../billing/helpers/teamLimitsCheck' import getRethink from '../../../database/rethinkDriver' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import TimelineEventRetroComplete from '../../../database/types/TimelineEventRetroComplete' import getKysely from '../../../postgres/getKysely' +import {RetrospectiveMeeting} from '../../../postgres/types/Meeting' import removeSuggestedAction from '../../../safeMutations/removeSuggestedAction' import {Logger} from '../../../utils/Logger' import RecallAIServerManager from '../../../utils/RecallAIServerManager' @@ -33,14 +33,14 @@ const getTranscription = async (recallBotId?: string | null) => { return await manager.getBotTranscript(recallBotId) } -const summarizeRetroMeeting = async (meeting: MeetingRetrospective, context: InternalContext) => { +const summarizeRetroMeeting = async (meeting: RetrospectiveMeeting, context: InternalContext) => { const {dataLoader} = context const {id: meetingId, phases, facilitatorUserId, teamId, recallBotId} = meeting const r = await getRethink() const [reflectionGroups, reflections, sentimentScore] = await Promise.all([ dataLoader.get('retroReflectionGroupsByMeetingId').load(meetingId), dataLoader.get('retroReflectionsByMeetingId').load(meetingId), - generateWholeMeetingSentimentScore(meetingId, facilitatorUserId, dataLoader) + generateWholeMeetingSentimentScore(meetingId, facilitatorUserId!, dataLoader) ]) const discussPhase = getPhase(phases, 'discuss') const {stages} = discussPhase @@ -48,7 +48,7 @@ const summarizeRetroMeeting = async (meeting: MeetingRetrospective, context: Int const reflectionGroupIds = reflectionGroups.map(({id}) => id) const [summary, transcription] = await Promise.all([ - generateWholeMeetingSummary(discussionIds, meetingId, teamId, facilitatorUserId, dataLoader), + generateWholeMeetingSummary(discussionIds, meetingId, teamId, facilitatorUserId!, dataLoader), getTranscription(recallBotId) ]) const commentCounts = ( @@ -93,7 +93,7 @@ const safeEndRetrospective = async ({ context, now }: { - meeting: MeetingRetrospective + meeting: RetrospectiveMeeting context: InternalContext now: Date }) => { @@ -115,7 +115,7 @@ const safeEndRetrospective = async ({ const phase = getMeetingPhase(phases) const insights = await gatherInsights(meeting, dataLoader) - const completedRetrospective = (await r + const completedRetrospective = await r .table('NewMeeting') .get(meetingId) .update( @@ -127,14 +127,18 @@ const safeEndRetrospective = async ({ {returnChanges: true} )('changes')(0)('new_val') .default(null) - .run()) as unknown as MeetingRetrospective + .run() if (!completedRetrospective) { return standardError(new Error('Completed retrospective meeting does not exist'), { userId: viewerId }) } - + if (completedRetrospective.meetingType !== 'retrospective') { + return standardError(new Error('Meeting type is not retrospective'), { + userId: viewerId + }) + } // remove any empty tasks const {templateId} = completedRetrospective const [meetingMembers, team, teamMembers, removedTaskIds, template] = await Promise.all([ diff --git a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts index e88ed41daf1..e44d2ee81d2 100644 --- a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts +++ b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts @@ -1,10 +1,10 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {checkTeamsLimit} from '../../../billing/helpers/teamLimitsCheck' import getRethink, {ParabolR} from '../../../database/rethinkDriver' -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import TimelineEventTeamPromptComplete from '../../../database/types/TimelineEventTeamPromptComplete' import getKysely from '../../../postgres/getKysely' import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {TeamPromptMeeting} from '../../../postgres/types/Meeting' import {Logger} from '../../../utils/Logger' import {analytics} from '../../../utils/analytics/analytics' import publish, {SubOptions} from '../../../utils/publish' @@ -17,7 +17,7 @@ import {IntegrationNotifier} from './notifications/IntegrationNotifier' import updateQualAIMeetingsCount from './updateQualAIMeetingsCount' import updateTeamInsights from './updateTeamInsights' -const summarizeTeamPrompt = async (meeting: MeetingTeamPrompt, context: InternalContext) => { +const summarizeTeamPrompt = async (meeting: TeamPromptMeeting, context: InternalContext) => { const {dataLoader} = context const r = await getRethink() @@ -51,7 +51,7 @@ const safeEndTeamPrompt = async ({ context, subOptions }: { - meeting: MeetingTeamPrompt + meeting: TeamPromptMeeting now: Date viewerId?: string r: ParabolR @@ -66,7 +66,7 @@ const safeEndTeamPrompt = async ({ // RESOLUTION const insights = await gatherInsights(meeting, dataLoader) - const completedTeamPrompt = (await r + const completedTeamPrompt = await r .table('NewMeeting') .get(meetingId) .update( @@ -77,7 +77,7 @@ const safeEndTeamPrompt = async ({ {returnChanges: true} )('changes')(0)('new_val') .default(null) - .run()) as unknown as MeetingTeamPrompt + .run() if (!completedTeamPrompt) { return standardError(new Error('Completed team prompt meeting does not exist'), { @@ -85,6 +85,10 @@ const safeEndTeamPrompt = async ({ }) } + if (completedTeamPrompt.meetingType !== 'teamPrompt') { + return standardError(new Error('Meeting is not a team prompt'), {userId: viewerId}) + } + const [meetingMembers, team, teamMembers, responses] = await Promise.all([ dataLoader.get('meetingMembersByMeetingId').load(meetingId), dataLoader.get('teams').loadNonNull(teamId), diff --git a/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts b/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts index 9db1d61ab85..88c74280788 100644 --- a/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts +++ b/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts @@ -1,11 +1,11 @@ -import Meeting from '../../../database/types/Meeting' import MeetingMember from '../../../database/types/MeetingMember' import {TeamMember} from '../../../postgres/types' +import {AnyMeeting} from '../../../postgres/types/Meeting' import {analytics} from '../../../utils/analytics/analytics' import {DataLoaderWorker} from '../../graphql' const sendPokerMeetingRevoteEvent = async ( - meeting: Meeting, + meeting: AnyMeeting, teamMembers: TeamMember[], meetingMembers: MeetingMember[], dataLoader: DataLoaderWorker diff --git a/packages/server/graphql/mutations/helpers/updateReflectionLocation/removeReflectionFromGroup.ts b/packages/server/graphql/mutations/helpers/updateReflectionLocation/removeReflectionFromGroup.ts index 389ea399d3d..d82fb1c99f0 100644 --- a/packages/server/graphql/mutations/helpers/updateReflectionLocation/removeReflectionFromGroup.ts +++ b/packages/server/graphql/mutations/helpers/updateReflectionLocation/removeReflectionFromGroup.ts @@ -1,21 +1,17 @@ import getGroupSmartTitle from 'parabol-client/utils/smartGroup/getGroupSmartTitle' import dndNoise from '../../../../../client/utils/dndNoise' -import getRethink from '../../../../database/rethinkDriver' -import MeetingRetrospective from '../../../../database/types/MeetingRetrospective' import ReflectionGroup from '../../../../database/types/ReflectionGroup' import getKysely from '../../../../postgres/getKysely' import {GQLContext} from '../../../graphql' import updateSmartGroupTitle from './updateSmartGroupTitle' const removeReflectionFromGroup = async (reflectionId: string, {dataLoader}: GQLContext) => { - const r = await getRethink() const pg = getKysely() const reflection = await dataLoader.get('retroReflections').load(reflectionId) if (!reflection) throw new Error('Reflection not found') const {reflectionGroupId: oldReflectionGroupId, meetingId, promptId} = reflection - const [meetingReflectionGroups, meeting] = await Promise.all([ - dataLoader.get('retroReflectionGroupsByMeetingId').load(meetingId), - dataLoader.get('newMeetings').load(meetingId) + const [meetingReflectionGroups] = await Promise.all([ + dataLoader.get('retroReflectionGroupsByMeetingId').load(meetingId) ]) dataLoader.get('retroReflectionGroupsByMeetingId').clear(meetingId) dataLoader.get('retroReflectionGroups').clearAll() @@ -52,14 +48,11 @@ const removeReflectionFromGroup = async (reflectionId: string, {dataLoader}: GQL reflectionGroupId }) .where('id', '=', reflectionId) - .execute(), - r.table('NewMeeting').get(meetingId).update({nextAutoGroupThreshold: null}).run() + .execute() ]) // mutates the dataloader response reflection.sortOrder = 0 reflection.reflectionGroupId = reflectionGroupId - const retroMeeting = meeting as MeetingRetrospective - retroMeeting.nextAutoGroupThreshold = null const oldReflections = await dataLoader .get('retroReflectionsByGroupId') .load(oldReflectionGroupId) diff --git a/packages/server/graphql/mutations/joinMeeting.ts b/packages/server/graphql/mutations/joinMeeting.ts index c95582aeea1..99f2e3a61ab 100644 --- a/packages/server/graphql/mutations/joinMeeting.ts +++ b/packages/server/graphql/mutations/joinMeeting.ts @@ -6,8 +6,6 @@ import getRethink from '../../database/rethinkDriver' import ActionMeetingMember from '../../database/types/ActionMeetingMember' import CheckInStage from '../../database/types/CheckInStage' import {NewMeetingPhaseTypeEnum} from '../../database/types/GenericMeetingPhase' -import Meeting from '../../database/types/Meeting' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' import PokerMeetingMember from '../../database/types/PokerMeetingMember' import RetroMeetingMember from '../../database/types/RetroMeetingMember' import TeamPromptMeetingMember from '../../database/types/TeamPromptMeetingMember' @@ -15,6 +13,7 @@ import TeamPromptResponseStage from '../../database/types/TeamPromptResponseStag import UpdatesStage from '../../database/types/UpdatesStage' import getKysely from '../../postgres/getKysely' import {TeamMember} from '../../postgres/types' +import {AnyMeeting} from '../../postgres/types/Meeting' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' @@ -22,13 +21,13 @@ import publish from '../../utils/publish' import {GQLContext} from '../graphql' import JoinMeetingPayload from '../types/JoinMeetingPayload' -const createMeetingMember = (meeting: Meeting, teamMember: TeamMember) => { +const createMeetingMember = (meeting: AnyMeeting, teamMember: TeamMember) => { const {userId, teamId, isSpectatingPoker} = teamMember switch (meeting.meetingType) { case 'action': return new ActionMeetingMember({teamId, userId, meetingId: meeting.id}) case 'retrospective': - const {id: meetingId, totalVotes} = meeting as MeetingRetrospective + const {id: meetingId, totalVotes} = meeting return new RetroMeetingMember({ teamId, userId, diff --git a/packages/server/graphql/mutations/pokerAnnounceDeckHover.ts b/packages/server/graphql/mutations/pokerAnnounceDeckHover.ts index d4f09a698a6..e0b4dcbc794 100644 --- a/packages/server/graphql/mutations/pokerAnnounceDeckHover.ts +++ b/packages/server/graphql/mutations/pokerAnnounceDeckHover.ts @@ -2,7 +2,6 @@ import {GraphQLBoolean, GraphQLID, GraphQLNonNull} from 'graphql' import ms from 'ms' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' -import MeetingPoker from '../../database/types/MeetingPoker' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' import getRedis, {RedisPipelineResponse} from '../../utils/getRedis' @@ -35,10 +34,13 @@ const pokerAnnounceDeckHover = { const subOptions = {mutatorId, operationId} // AUTH - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingPoker + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) { return {error: {message: 'Meeting not found'}} } + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } const {endedAt, phases, meetingType, teamId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on the team'}} diff --git a/packages/server/graphql/mutations/pokerResetDimension.ts b/packages/server/graphql/mutations/pokerResetDimension.ts index d728eaa4b2f..c25a460db2c 100644 --- a/packages/server/graphql/mutations/pokerResetDimension.ts +++ b/packages/server/graphql/mutations/pokerResetDimension.ts @@ -2,7 +2,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' import {RValue} from '../../database/stricterR' -import MeetingPoker from '../../database/types/MeetingPoker' import updateStage from '../../database/updateStage' import removeMeetingTaskEstimates from '../../postgres/queries/removeMeetingTaskEstimates' import {getUserId, isTeamMember} from '../../utils/authorization' @@ -33,10 +32,13 @@ const pokerResetDimension = { const subOptions = {mutatorId, operationId} //AUTH - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingPoker + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) { return {error: {message: 'Meeting not found'}} } + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } const {endedAt, phases, meetingType, teamId, createdBy, facilitatorUserId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on the team'}} diff --git a/packages/server/graphql/mutations/pokerRevealVotes.ts b/packages/server/graphql/mutations/pokerRevealVotes.ts index c640ae4964b..50c9620df76 100644 --- a/packages/server/graphql/mutations/pokerRevealVotes.ts +++ b/packages/server/graphql/mutations/pokerRevealVotes.ts @@ -2,7 +2,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {PokerCards, SubscriptionChannel} from 'parabol-client/types/constEnums' import {RValue} from '../../database/stricterR' import EstimateUserScore from '../../database/types/EstimateUserScore' -import MeetingPoker from '../../database/types/MeetingPoker' import PokerMeetingMember from '../../database/types/PokerMeetingMember' import updateStage from '../../database/updateStage' import {getUserId, isTeamMember} from '../../utils/authorization' @@ -36,12 +35,15 @@ const pokerRevealVotes = { // fetch meetingMembers up here to reduce chance of race condition that a vote gets cast in between now & when we update the scores const [meetingMembers, meeting] = await Promise.all([ dataLoader.get('meetingMembersByMeetingId').load(meetingId), - dataLoader.get('newMeetings').load(meetingId) as Promise + dataLoader.get('newMeetings').load(meetingId) ]) if (!meeting) { return {error: {message: 'Meeting not found'}} } + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } const {endedAt, phases, meetingType, teamId, createdBy, facilitatorUserId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on the team'}} diff --git a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts index 259a838413a..f9c202d981d 100644 --- a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts +++ b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts @@ -4,8 +4,8 @@ import {CHECKIN, DISCUSS, GROUP, REFLECT, VOTE} from '../../../client/utils/cons import getRethink from '../../database/rethinkDriver' import DiscussPhase from '../../database/types/DiscussPhase' import GenericMeetingPhase from '../../database/types/GenericMeetingPhase' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' import getKysely from '../../postgres/getKysely' +import {RetroMeetingPhase} from '../../postgres/types/NewMeetingPhase' import {getUserId} from '../../utils/authorization' import getPhase from '../../utils/getPhase' import publish from '../../utils/publish' @@ -34,7 +34,7 @@ const resetRetroMeetingToGroupStage = { // AUTH const viewerId = getUserId(authToken) - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingRetrospective + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) const {createdBy, facilitatorUserId, phases, meetingType} = meeting if (meetingType !== 'retrospective') { @@ -91,7 +91,7 @@ const resetRetroMeetingToGroupStage = { default: throw new Error(`Unhandled phaseType: ${phase.phaseType}`) } - }) + }) as RetroMeetingPhase[] primePhases(newPhases, resetToPhaseIndex) meeting.phases = newPhases diff --git a/packages/server/graphql/mutations/setTaskEstimate.ts b/packages/server/graphql/mutations/setTaskEstimate.ts index 08c2fa49895..1f2bdaea1fa 100644 --- a/packages/server/graphql/mutations/setTaskEstimate.ts +++ b/packages/server/graphql/mutations/setTaskEstimate.ts @@ -3,7 +3,6 @@ import {SprintPokerDefaults, SubscriptionChannel, Threshold} from 'parabol-clien import makeAppURL from 'parabol-client/utils/makeAppURL' import JiraProjectKeyId from '../../../client/shared/gqlIds/JiraProjectKeyId' import appOrigin from '../../appOrigin' -import MeetingPoker from '../../database/types/MeetingPoker' import TaskIntegrationJiraServer from '../../database/types/TaskIntegrationJiraServer' import JiraServerRestManager from '../../integrations/jiraServer/JiraServerRestManager' import {IntegrationProviderJiraServer} from '../../postgres/queries/getIntegrationProvidersByIds' @@ -69,10 +68,10 @@ const setTaskEstimate = { return {error: {message: 'Invalid dimension name'}} } - const {phases, meetingType, templateRefId, name: meetingName} = meeting as MeetingPoker - if (meetingType !== 'poker') { + if (meeting.meetingType !== 'poker') { return {error: {message: 'Invalid poker meeting'}} } + const {phases, templateRefId, name: meetingName} = meeting const templateRef = await dataLoader.get('templateRefs').loadNonNull(templateRefId) const {dimensions} = templateRef const dimensionRefIdx = dimensions.findIndex((dimension) => dimension.name === dimensionName) diff --git a/packages/server/graphql/mutations/startSprintPoker.ts b/packages/server/graphql/mutations/startSprintPoker.ts index 0fd4ecd04f9..ece86c98986 100644 --- a/packages/server/graphql/mutations/startSprintPoker.ts +++ b/packages/server/graphql/mutations/startSprintPoker.ts @@ -8,7 +8,8 @@ import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' import updateMeetingTemplateLastUsedAt from '../../postgres/queries/updateMeetingTemplateLastUsedAt' import updateTeamByTeamId from '../../postgres/queries/updateTeamByTeamId' -import {MeetingTypeEnum} from '../../postgres/types/Meeting' +import {MeetingTypeEnum, PokerMeeting} from '../../postgres/types/Meeting' +import {PokerMeetingPhase} from '../../postgres/types/NewMeetingPhase' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' import getHashAndJSON from '../../utils/getHashAndJSON' @@ -123,7 +124,7 @@ export default { .default(0) .run() - const phases = await createNewMeetingPhases( + const phases = await createNewMeetingPhases( viewerId, teamId, meetingId, @@ -149,7 +150,7 @@ export default { facilitatorUserId: viewerId, templateId: selectedTemplateId, templateRefId - }) + }) as PokerMeeting const template = await dataLoader.get('meetingTemplates').load(selectedTemplateId) await Promise.all([ diff --git a/packages/server/graphql/mutations/updateAzureDevOpsDimensionField.ts b/packages/server/graphql/mutations/updateAzureDevOpsDimensionField.ts index b8a5fd2245c..fda78fffe3c 100644 --- a/packages/server/graphql/mutations/updateAzureDevOpsDimensionField.ts +++ b/packages/server/graphql/mutations/updateAzureDevOpsDimensionField.ts @@ -1,6 +1,5 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import MeetingPoker from '../../database/types/MeetingPoker' import upsertAzureDevOpsDimensionFieldMap, { AzureDevOpsFieldMapProps } from '../../postgres/queries/upsertAzureDevOpsDimensionFieldMap' @@ -67,7 +66,10 @@ const updateAzureDevOpsDimensionField = { if (!meeting) { return {error: {message: 'Invalid meetingId'}} } - const {teamId, templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } + const {teamId, templateRefId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on team'}} } diff --git a/packages/server/graphql/mutations/updateGitHubDimensionField.ts b/packages/server/graphql/mutations/updateGitHubDimensionField.ts index ded249edf3f..f6c18e70f53 100644 --- a/packages/server/graphql/mutations/updateGitHubDimensionField.ts +++ b/packages/server/graphql/mutations/updateGitHubDimensionField.ts @@ -1,6 +1,5 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import MeetingPoker from '../../database/types/MeetingPoker' import upsertGitHubDimensionFieldMap from '../../postgres/queries/upsertGitHubDimensionFieldMap' import {Logger} from '../../utils/Logger' import {isTeamMember} from '../../utils/authorization' @@ -49,7 +48,10 @@ const updateGitHubDimensionField = { if (!meeting) { return {error: {message: 'Invalid meetingId'}} } - const {teamId, templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } + const {teamId, templateRefId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on team'}} } diff --git a/packages/server/graphql/mutations/updatePokerScope.ts b/packages/server/graphql/mutations/updatePokerScope.ts index 823a6381383..0e59ab72161 100644 --- a/packages/server/graphql/mutations/updatePokerScope.ts +++ b/packages/server/graphql/mutations/updatePokerScope.ts @@ -4,7 +4,6 @@ import {SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' import {ESTIMATE_TASK_SORT_ORDER} from '../../../client/utils/constants' import getRethink from '../../database/rethinkDriver' import EstimateStage from '../../database/types/EstimateStage' -import MeetingPoker from '../../database/types/MeetingPoker' import {TaskServiceEnum} from '../../database/types/Task' import getKysely from '../../postgres/getKysely' import {Discussion} from '../../postgres/pg' @@ -56,12 +55,14 @@ const updatePokerScope = { // Wrap everything in try catch to ensure the lock is released try { //AUTH - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingPoker + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) { return {error: {message: `Meeting not found`}} } - - const {endedAt, teamId, phases, meetingType, templateRefId, facilitatorStageId} = meeting + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } + const {endedAt, teamId, phases, templateRefId, facilitatorStageId} = meeting if (!isTeamMember(authToken, teamId)) { // bad actors could be naughty & just lock meetings that they don't own. Limit bad actors to team members return {error: {message: `Not on team`}} @@ -70,10 +71,6 @@ const updatePokerScope = { return {error: {message: `Meeting already ended`}} } - if (meetingType !== 'poker') { - return {error: {message: 'Not a poker meeting'}} - } - // RESOLUTION const estimatePhase = getPhase(phases, 'ESTIMATE') diff --git a/packages/server/graphql/mutations/updateRetroMaxVotes.ts b/packages/server/graphql/mutations/updateRetroMaxVotes.ts index db5c0beca24..be2c7749cb6 100644 --- a/packages/server/graphql/mutations/updateRetroMaxVotes.ts +++ b/packages/server/graphql/mutations/updateRetroMaxVotes.ts @@ -4,7 +4,6 @@ import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' import mode from 'parabol-client/utils/mode' import getRethink from '../../database/rethinkDriver' import {RValue} from '../../database/stricterR' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -44,12 +43,15 @@ const updateRetroMaxVotes = { const subOptions = {mutatorId, operationId} //AUTH - const meeting = (await r.table('NewMeeting').get(meetingId).run()) as MeetingRetrospective + const meeting = await r.table('NewMeeting').get(meetingId).run() if (!meeting) { return {error: {message: 'Meeting not found'}} } + if (meeting.meetingType !== 'retrospective') { + return {error: {message: `Meeting not retrospective`}} + } const { endedAt, meetingType, diff --git a/packages/server/graphql/mutations/voteForPokerStory.ts b/packages/server/graphql/mutations/voteForPokerStory.ts index 76330da4d8a..15705e31292 100644 --- a/packages/server/graphql/mutations/voteForPokerStory.ts +++ b/packages/server/graphql/mutations/voteForPokerStory.ts @@ -3,7 +3,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' import {RValue} from '../../database/stricterR' import EstimateUserScore from '../../database/types/EstimateUserScore' -import MeetingPoker from '../../database/types/MeetingPoker' import updateStage from '../../database/updateStage' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' @@ -71,20 +70,20 @@ const voteForPokerStory = { const subOptions = {mutatorId, operationId} //AUTH - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingPoker + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) { return {error: {message: 'Meeting not found'}} } - const {endedAt, phases, meetingType, teamId, templateRefId} = meeting + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } + const {endedAt, phases, teamId, templateRefId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on the team'}} } if (endedAt) { return {error: {message: 'Meeting has ended'}} } - if (meetingType !== 'poker') { - return {error: {message: 'Not a poker meeting'}} - } // No need to check for now (https://github.com/ParabolInc/parabol/issues/7191) // if (isPhaseComplete('ESTIMATE', phases)) { // return {error: {message: 'Estimate phase is already complete'}} diff --git a/packages/server/graphql/mutations/voteForReflectionGroup.ts b/packages/server/graphql/mutations/voteForReflectionGroup.ts index fe193c618c9..171cf1f451e 100644 --- a/packages/server/graphql/mutations/voteForReflectionGroup.ts +++ b/packages/server/graphql/mutations/voteForReflectionGroup.ts @@ -3,7 +3,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {VOTE} from 'parabol-client/utils/constants' import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' import getRethink from '../../database/rethinkDriver' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -43,7 +42,10 @@ export default { }) } const {meetingId} = reflectionGroup - const meeting = (await r.table('NewMeeting').get(meetingId).run()) as MeetingRetrospective + const meeting = await r.table('NewMeeting').get(meetingId).run() + if (meeting.meetingType !== 'retrospective') { + return {error: {message: 'Meeting type is not retrospective'}} + } const {endedAt, phases, maxVotesPerGroup, teamId} = meeting if (!isTeamMember(authToken, teamId)) { return standardError(new Error('Team not found'), {userId: viewerId}) diff --git a/packages/server/graphql/private/mutations/generateMeetingSummary.ts b/packages/server/graphql/private/mutations/generateMeetingSummary.ts index 8dbb9b5a071..3640fe0541e 100644 --- a/packages/server/graphql/private/mutations/generateMeetingSummary.ts +++ b/packages/server/graphql/private/mutations/generateMeetingSummary.ts @@ -1,7 +1,7 @@ import yaml from 'js-yaml' import getRethink from '../../../database/rethinkDriver' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import getKysely from '../../../postgres/getKysely' +import {RetrospectiveMeeting} from '../../../postgres/types/Meeting' import OpenAIServerManager from '../../../utils/OpenAIServerManager' import getPhase from '../../../utils/getPhase' import {MutationResolvers} from '../resolverTypes' @@ -20,7 +20,7 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn const twoYearsAgo = new Date() twoYearsAgo.setFullYear(endDate.getFullYear() - 2) - const rawMeetings = (await r + const rawMeetings = await r .table('NewMeeting') .getAll(r.args(teamIds), {index: 'teamId'}) .filter((row: any) => @@ -32,7 +32,7 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn .and(r.table('MeetingMember').getAll(row('id'), {index: 'meetingId'}).count().gt(1)) .and(row('endedAt').sub(row('createdAt')).gt(MIN_MILLISECONDS)) ) - .run()) as MeetingRetrospective[] + .run() const getComments = async (reflectionGroupId: string) => { const IGNORE_COMMENT_USER_IDS = ['parabolAIUser'] @@ -87,7 +87,7 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn return comments } - const getMeetingsContent = async (meeting: MeetingRetrospective) => { + const getMeetingsContent = async (meeting: RetrospectiveMeeting) => { const pg = getKysely() const {id: meetingId, disableAnonymity, name: meetingName, createdAt: meetingDate} = meeting const rawReflectionGroups = await dataLoader @@ -157,6 +157,7 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn const updatedMeetingIds = await Promise.all( rawMeetings.map(async (meeting) => { + if (meeting.meetingType !== 'retrospective') return null const meetingsContent = await getMeetingsContent(meeting) if (!meetingsContent || meetingsContent.length === 0) { return null diff --git a/packages/server/graphql/private/mutations/processRecurrence.ts b/packages/server/graphql/private/mutations/processRecurrence.ts index e51547e940c..f27134e03e1 100644 --- a/packages/server/graphql/private/mutations/processRecurrence.ts +++ b/packages/server/graphql/private/mutations/processRecurrence.ts @@ -6,11 +6,8 @@ import {DateTime, RRuleSet} from 'rrule-rust' import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' import {fromDateTime, toDateTime} from '../../../../client/shared/rruleUtil' import getRethink from '../../../database/rethinkDriver' -import MeetingRetrospective, { - isMeetingRetrospective -} from '../../../database/types/MeetingRetrospective' -import MeetingTeamPrompt, {isMeetingTeamPrompt} from '../../../database/types/MeetingTeamPrompt' import {getActiveMeetingSeries} from '../../../postgres/queries/getActiveMeetingSeries' +import {RetrospectiveMeeting, TeamPromptMeeting} from '../../../postgres/types/Meeting' import {MeetingSeries} from '../../../postgres/types/MeetingSeries' import {analytics} from '../../../utils/analytics/analytics' import {getNextRRuleDate} from '../../../utils/getNextRRuleDate' @@ -54,7 +51,7 @@ const startRecurringMeeting = async ( const meetingName = createMeetingSeriesTitle(meetingSeries.title, startTime, rrule.tzid) const meeting = await (async () => { if (meetingSeries.meetingType === 'teamPrompt') { - const teamPromptMeeting = lastMeeting as MeetingTeamPrompt | null + const teamPromptMeeting = lastMeeting as TeamPromptMeeting | null const meeting = await safeCreateTeamPrompt( meetingName, teamId, @@ -73,7 +70,7 @@ const startRecurringMeeting = async ( return meeting } else if (meetingSeries.meetingType === 'retrospective') { const {totalVotes, maxVotesPerGroup, disableAnonymity, templateId} = - (lastMeeting as MeetingRetrospective) ?? { + (lastMeeting as RetrospectiveMeeting) ?? { templateId: meetingSettings.selectedTemplateId, ...meetingSettings } @@ -132,9 +129,9 @@ const processRecurrence: MutationResolvers['processRecurrence'] = async ( const res = await tracer.trace('processRecurrence.endMeetings', async () => Promise.all( meetingsToEnd.map((meeting) => { - if (isMeetingTeamPrompt(meeting)) { + if (meeting.meetingType === 'teamPrompt') { return safeEndTeamPrompt({meeting, now, context, r, subOptions}) - } else if (isMeetingRetrospective(meeting)) { + } else if (meeting.meetingType === 'retrospective') { return safeEndRetrospective({meeting, now, context}) } else { return standardError(new Error('Unhandled recurring meeting type'), { diff --git a/packages/server/graphql/private/mutations/runScheduledJobs.ts b/packages/server/graphql/private/mutations/runScheduledJobs.ts index 2b1c3b70575..bc93f4f84f8 100644 --- a/packages/server/graphql/private/mutations/runScheduledJobs.ts +++ b/packages/server/graphql/private/mutations/runScheduledJobs.ts @@ -31,11 +31,11 @@ const processMeetingStageTimeLimits = async ( const notification = new NotificationMeetingStageTimeLimitEnd({ meetingId, - userId: facilitatorUserId + userId: facilitatorUserId! }) const r = await getRethink() await r.table('Notification').insert(notification).run() - publish(SubscriptionChannel.NOTIFICATION, facilitatorUserId, 'MeetingStageTimeLimitPayload', { + publish(SubscriptionChannel.NOTIFICATION, facilitatorUserId!, 'MeetingStageTimeLimitPayload', { notification }) } diff --git a/packages/server/graphql/private/types/GenerateMeetingSummarySuccess.ts b/packages/server/graphql/private/types/GenerateMeetingSummarySuccess.ts index 8a572dec1e0..ad29fab70d5 100644 --- a/packages/server/graphql/private/types/GenerateMeetingSummarySuccess.ts +++ b/packages/server/graphql/private/types/GenerateMeetingSummarySuccess.ts @@ -1,4 +1,4 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' +import isValid from '../../isValid' import {GenerateMeetingSummarySuccessResolvers} from '../resolverTypes' export type GenerateMeetingSummarySuccessSource = { @@ -7,10 +7,9 @@ export type GenerateMeetingSummarySuccessSource = { const GenerateMeetingSummarySuccess: GenerateMeetingSummarySuccessResolvers = { meetings: async ({meetingIds}, _args, {dataLoader}) => { - const meetings = (await dataLoader - .get('newMeetings') - .loadMany(meetingIds)) as MeetingRetrospective[] - return meetings + return (await dataLoader.get('newMeetings').loadMany(meetingIds)) + .filter(isValid) + .filter((m) => m.meetingType === 'retrospective') } } diff --git a/packages/server/graphql/public/mutations/addTranscriptionBot.ts b/packages/server/graphql/public/mutations/addTranscriptionBot.ts index 6862a934f00..6b78c0bace5 100644 --- a/packages/server/graphql/public/mutations/addTranscriptionBot.ts +++ b/packages/server/graphql/public/mutations/addTranscriptionBot.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {getUserId, isTeamMember} from '../../../utils/authorization' import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' @@ -14,10 +13,13 @@ const addTranscriptionBot: MutationResolvers['addTranscriptionBot'] = async ( const viewerId = getUserId(authToken) const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingRetrospective + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) { return standardError(new Error('Meeting not found'), {userId: viewerId}) } + if (meeting.meetingType !== 'retrospective') { + return {error: {message: 'Meeting type is not retrospective'}} + } const {teamId} = meeting if (!isTeamMember(authToken, teamId)) { const error = new Error('Not on team') diff --git a/packages/server/graphql/public/mutations/endTeamPrompt.ts b/packages/server/graphql/public/mutations/endTeamPrompt.ts index 692ee81d339..b98a70f1261 100644 --- a/packages/server/graphql/public/mutations/endTeamPrompt.ts +++ b/packages/server/graphql/public/mutations/endTeamPrompt.ts @@ -1,5 +1,4 @@ import getRethink from '../../../database/rethinkDriver' -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {getUserId, isTeamMember} from '../../../utils/authorization' import standardError from '../../../utils/standardError' import safeEndTeamPrompt from '../../mutations/helpers/safeEndTeamPrompt' @@ -14,8 +13,11 @@ const endTeamPrompt: MutationResolvers['endTeamPrompt'] = async (_source, {meeti const subOptions = {mutatorId, operationId} // AUTH - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingTeamPrompt | null + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) + if (meeting.meetingType !== 'teamPrompt') { + return {error: {message: 'Meeting type is not teamPrompt'}} + } const {teamId} = meeting // VALIDATION diff --git a/packages/server/graphql/public/mutations/helpers/getSummaries.ts b/packages/server/graphql/public/mutations/helpers/getSummaries.ts index c80339a30f9..30ff97c9f9d 100644 --- a/packages/server/graphql/public/mutations/helpers/getSummaries.ts +++ b/packages/server/graphql/public/mutations/helpers/getSummaries.ts @@ -1,6 +1,5 @@ import yaml from 'js-yaml' import getRethink from '../../../../database/rethinkDriver' -import MeetingRetrospective from '../../../../database/types/MeetingRetrospective' import OpenAIServerManager from '../../../../utils/OpenAIServerManager' import standardError from '../../../../utils/standardError' @@ -14,7 +13,7 @@ export const getSummaries = async ( const MIN_MILLISECONDS = 60 * 1000 // 1 minute const MIN_REFLECTION_COUNT = 3 - const rawMeetings = (await r + const rawMeetings = await r .table('NewMeeting') .getAll(teamId, {index: 'teamId'}) .filter((row: any) => @@ -27,7 +26,7 @@ export const getSummaries = async ( .and(row('endedAt').sub(row('createdAt')).gt(MIN_MILLISECONDS)) .and(row.hasFields('summary')) ) - .run()) as MeetingRetrospective[] + .run() if (!rawMeetings.length) { return standardError(new Error('No meetings found')) diff --git a/packages/server/graphql/public/mutations/helpers/getTopics.ts b/packages/server/graphql/public/mutations/helpers/getTopics.ts index 4cb74174c5b..47782e177f9 100644 --- a/packages/server/graphql/public/mutations/helpers/getTopics.ts +++ b/packages/server/graphql/public/mutations/helpers/getTopics.ts @@ -1,6 +1,5 @@ import yaml from 'js-yaml' import getRethink from '../../../../database/rethinkDriver' -import MeetingRetrospective from '../../../../database/types/MeetingRetrospective' import getKysely from '../../../../postgres/getKysely' import OpenAIServerManager from '../../../../utils/OpenAIServerManager' import sendToSentry from '../../../../utils/sendToSentry' @@ -113,7 +112,7 @@ export const getTopics = async ( const r = await getRethink() const MIN_REFLECTION_COUNT = 3 const MIN_MILLISECONDS = 60 * 1000 // 1 minute - const rawMeetings = await r + const rawAnyMeetings = await r .table('NewMeeting') .getAll(teamId, {index: 'teamId'}) .filter((row: any) => @@ -126,15 +125,10 @@ export const getTopics = async ( .and(row('endedAt').sub(row('createdAt')).gt(MIN_MILLISECONDS)) ) .run() - + const rawMeetings = rawAnyMeetings.filter((m) => m.meetingType === 'retrospective') const meetings = await Promise.all( rawMeetings.map(async (meeting) => { - const { - id: meetingId, - disableAnonymity, - name: meetingName, - createdAt: meetingDate - } = meeting as MeetingRetrospective + const {id: meetingId, disableAnonymity, name: meetingName, createdAt: meetingDate} = meeting const rawReflectionGroups = await dataLoader .get('retroReflectionGroupsByMeetingId') .load(meetingId) diff --git a/packages/server/graphql/public/mutations/resetReflectionGroups.ts b/packages/server/graphql/public/mutations/resetReflectionGroups.ts index c52fc4361a3..4f574390d0f 100644 --- a/packages/server/graphql/public/mutations/resetReflectionGroups.ts +++ b/packages/server/graphql/public/mutations/resetReflectionGroups.ts @@ -75,7 +75,7 @@ const resetReflectionGroups: MutationResolvers['resetReflectionGroups'] = async .get(meetingId) .replace(r.row.without('resetReflectionGroups') as any) .run() - meeting.resetReflectionGroups = undefined + meeting.resetReflectionGroups = null analytics.resetGroupsClicked(viewer, meetingId, teamId) const data = {meetingId} publish(SubscriptionChannel.MEETING, meetingId, 'ResetReflectionGroupsSuccess', data, subOptions) diff --git a/packages/server/graphql/public/mutations/setTeamHealthVote.ts b/packages/server/graphql/public/mutations/setTeamHealthVote.ts index d186c7940af..7f3fe36af53 100644 --- a/packages/server/graphql/public/mutations/setTeamHealthVote.ts +++ b/packages/server/graphql/public/mutations/setTeamHealthVote.ts @@ -3,12 +3,44 @@ import getRethink from '../../../database/rethinkDriver' import {RValue} from '../../../database/stricterR' import TeamHealthVote from '../../../database/types/TeamHealthVote' import updateStage from '../../../database/updateStage' +import getKysely from '../../../postgres/getKysely' +import {NewMeetingPhase} from '../../../postgres/types/NewMeetingPhase.d' import {getUserId, isTeamMember} from '../../../utils/authorization' import getPhase from '../../../utils/getPhase' import publish from '../../../utils/publish' import {MutationResolvers} from '../resolverTypes' const upsertVote = async (meetingId: string, stageId: string, newVote: TeamHealthVote) => { + const pg = getKysely() + await pg.transaction().execute(async (trx) => { + // console.log('start transaction', newVote) + const meeting = await trx + .selectFrom('NewMeeting') + .select(({fn}) => fn('to_json', ['phases']).as('phases')) + .where('id', '=', meetingId) + .forUpdate() + // NewMeeting: add OrThrow in phase 3 + .executeTakeFirst() + if (!meeting) return + // console.log('got lock', newVote) + const {phases} = meeting + const phase = getPhase(phases, 'TEAM_HEALTH') + const {stages} = phase + const [stage] = stages + const {votes} = stage + const existingVote = votes.find((vote) => vote.userId === newVote.userId) + if (existingVote) { + existingVote.vote = newVote.vote + } else { + votes.push(newVote) + } + await trx + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() + // console.log('wrote update, commit', newVote) + }) const r = await getRethink() const updater = (stage: RValue) => stage.merge({ diff --git a/packages/server/graphql/public/mutations/startCheckIn.ts b/packages/server/graphql/public/mutations/startCheckIn.ts index b90ffd146a7..62086ac17ba 100644 --- a/packages/server/graphql/public/mutations/startCheckIn.ts +++ b/packages/server/graphql/public/mutations/startCheckIn.ts @@ -5,7 +5,8 @@ import MeetingAction from '../../../database/types/MeetingAction' import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' import updateTeamByTeamId from '../../../postgres/queries/updateTeamByTeamId' -import {MeetingTypeEnum} from '../../../postgres/types/Meeting' +import {CheckInMeeting, MeetingTypeEnum} from '../../../postgres/types/Meeting' +import {CheckInPhase} from '../../../postgres/types/NewMeetingPhase' import {analytics} from '../../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -48,7 +49,7 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( .run() const meetingId = generateUID() - const phases = await createNewMeetingPhases( + const phases = await createNewMeetingPhases( viewerId, teamId, meetingId, @@ -64,7 +65,7 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( meetingCount, phases, facilitatorUserId: viewerId - }) + }) as CheckInMeeting await r.table('NewMeeting').insert(meeting).run() // Disallow 2 active check-in meetings diff --git a/packages/server/graphql/public/mutations/updateGitLabDimensionField.ts b/packages/server/graphql/public/mutations/updateGitLabDimensionField.ts index 63540c4507b..217bde46903 100644 --- a/packages/server/graphql/public/mutations/updateGitLabDimensionField.ts +++ b/packages/server/graphql/public/mutations/updateGitLabDimensionField.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import MeetingPoker from '../../../database/types/MeetingPoker' import upsertGitLabDimensionFieldMap from '../../../postgres/queries/upsertGitLabDimensionFieldMap' import {Logger} from '../../../utils/Logger' import {isTeamMember} from '../../../utils/authorization' @@ -20,7 +19,10 @@ const updateGitLabDimensionField: MutationResolvers['updateGitLabDimensionField' if (!meeting) { return {error: {message: 'Invalid meetingId'}} } - const {teamId, templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } + const {teamId, templateRefId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on team'}} } diff --git a/packages/server/graphql/public/mutations/updateJiraDimensionField.ts b/packages/server/graphql/public/mutations/updateJiraDimensionField.ts index fd750b7c978..b99f44c49be 100644 --- a/packages/server/graphql/public/mutations/updateJiraDimensionField.ts +++ b/packages/server/graphql/public/mutations/updateJiraDimensionField.ts @@ -1,6 +1,5 @@ import {SprintPokerDefaults, SubscriptionChannel} from 'parabol-client/types/constEnums' import JiraProjectKeyId from '../../../../client/shared/gqlIds/JiraProjectKeyId' -import MeetingPoker from '../../../database/types/MeetingPoker' import {JiraIssue} from '../../../dataloader/atlassianLoaders' import upsertJiraDimensionFieldMap from '../../../postgres/queries/upsertJiraDimensionFieldMap' import {getUserId, isTeamMember} from '../../../utils/authorization' @@ -38,7 +37,10 @@ const updateJiraDimensionField: MutationResolvers['updateJiraDimensionField'] = if (!meeting) { return {error: {message: 'Invalid meetingId'}} } - const {teamId, templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } + const {teamId, templateRefId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on team'}} } diff --git a/packages/server/graphql/public/mutations/updateJiraServerDimensionField.ts b/packages/server/graphql/public/mutations/updateJiraServerDimensionField.ts index e37cde5efb5..2443adc0121 100644 --- a/packages/server/graphql/public/mutations/updateJiraServerDimensionField.ts +++ b/packages/server/graphql/public/mutations/updateJiraServerDimensionField.ts @@ -1,5 +1,4 @@ import {SprintPokerDefaults, SubscriptionChannel} from 'parabol-client/types/constEnums' -import MeetingPoker from '../../../database/types/MeetingPoker' import JiraServerRestManager from '../../../integrations/jiraServer/JiraServerRestManager' import {IntegrationProviderJiraServer} from '../../../postgres/queries/getIntegrationProvidersByIds' import upsertJiraServerDimensionFieldMap from '../../../postgres/queries/upsertJiraServerDimensionFieldMap' @@ -21,7 +20,10 @@ const updateJiraServerDimensionField: MutationResolvers['updateJiraServerDimensi if (!meeting) { return {error: {message: 'Invalid meetingId'}} } - const {teamId, templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } + const {teamId, templateRefId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on team'}} } diff --git a/packages/server/graphql/public/mutations/updateMeetingTemplate.ts b/packages/server/graphql/public/mutations/updateMeetingTemplate.ts index a0be265f8de..d355a6062a2 100644 --- a/packages/server/graphql/public/mutations/updateMeetingTemplate.ts +++ b/packages/server/graphql/public/mutations/updateMeetingTemplate.ts @@ -1,6 +1,5 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' import getRethink from '../../../database/rethinkDriver' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {getUserId, isTeamMember} from '../../../utils/authorization' import getPhase from '../../../utils/getPhase' import publish from '../../../utils/publish' @@ -16,8 +15,9 @@ const updateMeetingTemplate: MutationResolvers['updateMeetingTemplate'] = async const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingRetrospective + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) + if (!('templateId' in meeting)) return {error: {message: 'Meeting has no template'}} if (!isTeamMember(authToken, meeting.teamId)) { return standardError(new Error('Team not found'), {userId: viewerId}) } diff --git a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts index 014a0fccef3..39a814265d0 100644 --- a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts +++ b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts @@ -22,7 +22,7 @@ export const startNewMeetingSeries = async ( teamId: string meetingType: MeetingTypeEnum name: string - facilitatorUserId: string + facilitatorUserId: string | null }, recurrenceRule: RRuleSet, meetingSeriesName?: string | null @@ -35,7 +35,9 @@ export const startNewMeetingSeries = async ( facilitatorUserId: facilitatorId } = meeting const r = await getRethink() - + if (!facilitatorId) { + throw new Error('No facilitatorId') + } const newMeetingSeriesParams = { meetingType, title: meetingSeriesName || meetingName.split('-')[0]!.trim(), // if no name is provided, we use the name of the first meeting without the date diff --git a/packages/server/graphql/public/types/AddTranscriptionBotSuccess.ts b/packages/server/graphql/public/types/AddTranscriptionBotSuccess.ts index 880fb7e8538..44839c75df1 100644 --- a/packages/server/graphql/public/types/AddTranscriptionBotSuccess.ts +++ b/packages/server/graphql/public/types/AddTranscriptionBotSuccess.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {AddTranscriptionBotSuccessResolvers} from '../resolverTypes' export type AddTranscriptionBotSuccessSource = { @@ -8,7 +7,9 @@ export type AddTranscriptionBotSuccessSource = { const AddTranscriptionBotSuccess: AddTranscriptionBotSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingRetrospective + if (meeting.meetingType !== 'retrospective') + throw new Error('Meeting type is not retrospective') + return meeting } } diff --git a/packages/server/graphql/public/types/AutogroupSuccess.ts b/packages/server/graphql/public/types/AutogroupSuccess.ts index 9c62aced1b3..1c86eb2efce 100644 --- a/packages/server/graphql/public/types/AutogroupSuccess.ts +++ b/packages/server/graphql/public/types/AutogroupSuccess.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {AutogroupSuccessResolvers} from '../resolverTypes' export type AutogroupSuccessSource = { @@ -8,7 +7,8 @@ export type AutogroupSuccessSource = { const AutogroupSuccess: AutogroupSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingRetrospective + if (meeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') + return meeting } } diff --git a/packages/server/graphql/public/types/Discussion.ts b/packages/server/graphql/public/types/Discussion.ts index 765d795215d..bf3f5908851 100644 --- a/packages/server/graphql/public/types/Discussion.ts +++ b/packages/server/graphql/public/types/Discussion.ts @@ -46,7 +46,9 @@ const Discussion: DiscussionResolvers = { return null } const {stages} = phase - const dbStage = stages.find((stage) => stage.reflectionGroupId === discussionTopicId) + const dbStage = stages.find( + (stage) => 'reflectionGroupId' in stage && stage.reflectionGroupId === discussionTopicId + ) return dbStage ? augmentDBStage(dbStage, meetingId, DISCUSS, teamId) : null } @@ -56,7 +58,9 @@ const Discussion: DiscussionResolvers = { return null } const {stages} = phase - const dbStage = stages.find((stage) => stage.taskId === discussionTopicId) + const dbStage = stages.find( + (stage) => 'taskId' in stage && stage.taskId === discussionTopicId + ) return dbStage ? augmentDBStage(dbStage, meetingId, 'ESTIMATE', teamId) : null } diff --git a/packages/server/graphql/public/types/EndTeamPromptSuccess.ts b/packages/server/graphql/public/types/EndTeamPromptSuccess.ts index a802a25f5aa..2452403289d 100644 --- a/packages/server/graphql/public/types/EndTeamPromptSuccess.ts +++ b/packages/server/graphql/public/types/EndTeamPromptSuccess.ts @@ -1,4 +1,3 @@ -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {EndTeamPromptSuccessResolvers} from '../resolverTypes' export type EndTeamPromptSuccessSource = { @@ -9,7 +8,9 @@ export type EndTeamPromptSuccessSource = { const EndTeamPromptSuccess: EndTeamPromptSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - return (await dataLoader.get('newMeetings').load(meetingId)) as MeetingTeamPrompt + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (meeting.meetingType !== 'teamPrompt') throw new Error('Meeting is not a team prompt') + return meeting }, team: async ({teamId}, _args, {dataLoader}) => { return await dataLoader.get('teams').loadNonNull(teamId) diff --git a/packages/server/graphql/public/types/EstimateStage.ts b/packages/server/graphql/public/types/EstimateStage.ts index 112e2c91bc5..9e949ac0de7 100644 --- a/packages/server/graphql/public/types/EstimateStage.ts +++ b/packages/server/graphql/public/types/EstimateStage.ts @@ -1,6 +1,5 @@ import JiraProjectKeyId from '../../../../client/shared/gqlIds/JiraProjectKeyId' import {SprintPokerDefaults} from '../../../../client/types/constEnums' -import MeetingPoker from '../../../database/types/MeetingPoker' import TaskIntegrationAzureDevOps from '../../../database/types/TaskIntegrationAzureDevOps' import TaskIntegrationJiraServer from '../../../database/types/TaskIntegrationJiraServer' import GitLabServerManager from '../../../integrations/gitlab/GitLabServerManager' @@ -23,7 +22,8 @@ const EstimateStage: EstimateStageResolvers = { const {service} = integration const getDimensionName = async (meetingId: string) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - const {templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') throw new Error('Meeting is not a poker meeting') + const {templateRefId} = meeting const templateRef = await dataLoader.get('templateRefs').loadNonNull(templateRefId) const {dimensions} = templateRef const dimensionRef = dimensions[dimensionRefIdx]! @@ -176,7 +176,8 @@ const EstimateStage: EstimateStageResolvers = { dimensionRef: async ({meetingId, dimensionRefIdx}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - const {templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') return null + const {templateRefId} = meeting const templateRef = await dataLoader.get('templateRefs').loadNonNull(templateRefId) const {dimensions} = templateRef const {name, scaleRefId} = dimensions[dimensionRefIdx]! @@ -193,7 +194,8 @@ const EstimateStage: EstimateStageResolvers = { dataLoader.get('newMeetings').load(meetingId), dataLoader.get('meetingTaskEstimates').load({taskId, meetingId}) ]) - const {templateRefId} = meeting as MeetingPoker + if (meeting.meetingType !== 'poker') return null + const {templateRefId} = meeting const templateRef = await dataLoader.get('templateRefs').loadNonNull(templateRefId) const {dimensions} = templateRef const dimensionRef = dimensions[dimensionRefIdx]! diff --git a/packages/server/graphql/public/types/GenerateGroupsSuccess.ts b/packages/server/graphql/public/types/GenerateGroupsSuccess.ts index 421d0142de3..a15db891039 100644 --- a/packages/server/graphql/public/types/GenerateGroupsSuccess.ts +++ b/packages/server/graphql/public/types/GenerateGroupsSuccess.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {GenerateGroupsSuccessResolvers} from '../resolverTypes' export type GenerateGroupsSuccessSource = { @@ -8,7 +7,9 @@ export type GenerateGroupsSuccessSource = { const GenerateGroupsSuccess: GenerateGroupsSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingRetrospective + if (meeting.meetingType !== 'retrospective') + throw new Error('Meeting type is not retrospective') + return meeting } } diff --git a/packages/server/graphql/public/types/GenerateInsightSuccess.ts b/packages/server/graphql/public/types/GenerateInsightSuccess.ts index ac099aa2295..a22c5a7e1cd 100644 --- a/packages/server/graphql/public/types/GenerateInsightSuccess.ts +++ b/packages/server/graphql/public/types/GenerateInsightSuccess.ts @@ -1,4 +1,4 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' +import isValid from '../../isValid' import {GenerateInsightSuccessResolvers} from '../resolverTypes' export type GenerateInsightSuccessSource = { @@ -11,10 +11,8 @@ const GenerateInsightSuccess: GenerateInsightSuccessResolvers = { wins: ({wins}) => wins, challenges: ({challenges}) => challenges, meetings: async ({meetingIds}, _args, {dataLoader}) => { - const meetings = (await dataLoader - .get('newMeetings') - .loadMany(meetingIds)) as MeetingRetrospective[] - return meetings + const meetings = await dataLoader.get('newMeetings').loadMany(meetingIds) + return meetings.filter(isValid).filter((m) => m.meetingType === 'retrospective') } } diff --git a/packages/server/graphql/public/types/NewMeeting.ts b/packages/server/graphql/public/types/NewMeeting.ts index d75bc19bceb..941809c0297 100644 --- a/packages/server/graphql/public/types/NewMeeting.ts +++ b/packages/server/graphql/public/types/NewMeeting.ts @@ -19,7 +19,7 @@ const NewMeeting: NewMeetingResolvers = { return dataLoader.get('users').loadNonNull(createdBy) }, facilitator: ({facilitatorUserId, teamId}, _args, {dataLoader}) => { - const teamMemberId = toTeamMemberId(teamId, facilitatorUserId) + const teamMemberId = toTeamMemberId(teamId, facilitatorUserId!) return dataLoader.get('teamMembers').loadNonNull(teamMemberId) }, locked: async ({endedAt, teamId}, _args, {authToken, dataLoader}) => { diff --git a/packages/server/graphql/public/types/NotifyResponseMentioned.ts b/packages/server/graphql/public/types/NotifyResponseMentioned.ts index 0a8b86906a5..a9bc5d31501 100644 --- a/packages/server/graphql/public/types/NotifyResponseMentioned.ts +++ b/packages/server/graphql/public/types/NotifyResponseMentioned.ts @@ -1,12 +1,12 @@ import TeamPromptResponseId from '../../../../client/shared/gqlIds/TeamPromptResponseId' -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {NotifyResponseMentionedResolvers} from '../resolverTypes' const NotifyResponseMentioned: NotifyResponseMentionedResolvers = { __isTypeOf: ({type}) => type === 'RESPONSE_MENTIONED', meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingTeamPrompt + if (meeting.meetingType !== 'teamPrompt') throw new Error('Meeting is not a team prompt') + return meeting }, response: ({responseId}, _args, {dataLoader}) => { // Hack, in a perfect world, this notification would have the numeric DB ID saved on it diff --git a/packages/server/graphql/public/types/NotifyResponseReplied.ts b/packages/server/graphql/public/types/NotifyResponseReplied.ts index 883a71d93bb..87ff6af7198 100644 --- a/packages/server/graphql/public/types/NotifyResponseReplied.ts +++ b/packages/server/graphql/public/types/NotifyResponseReplied.ts @@ -1,4 +1,3 @@ -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTeamPromptResponsesByMeetingIds' import {NotifyResponseRepliedResolvers} from '../resolverTypes' @@ -6,7 +5,8 @@ const NotifyResponseReplied: NotifyResponseRepliedResolvers = { __isTypeOf: ({type}) => type === 'RESPONSE_REPLIED', meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingTeamPrompt + if (meeting.meetingType !== 'teamPrompt') throw new Error('Meeting is not a team prompt') + return meeting }, response: async ({userId, meetingId}) => { // TODO: implement getTeamPromptResponsesByMeetingIdAndUserId diff --git a/packages/server/graphql/public/types/ReflectPhase.ts b/packages/server/graphql/public/types/ReflectPhase.ts index 8714fad14a0..e11232d3cc6 100644 --- a/packages/server/graphql/public/types/ReflectPhase.ts +++ b/packages/server/graphql/public/types/ReflectPhase.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {ReflectPhaseResolvers} from '../resolverTypes' const ReflectPhase: ReflectPhaseResolvers = { @@ -9,7 +8,8 @@ const ReflectPhase: ReflectPhaseResolvers = { }, reflectPrompts: async ({meetingId}, _args, {dataLoader}) => { - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingRetrospective + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (!('templateId' in meeting)) return [] const prompts = await dataLoader.get('reflectPromptsByTemplateId').load(meeting.templateId) // only show prompts that were created before the meeting and // either have not been removed or they were removed after the meeting was created diff --git a/packages/server/graphql/public/types/ResetReflectionGroupsSuccess.ts b/packages/server/graphql/public/types/ResetReflectionGroupsSuccess.ts index e15430efc9d..be7d98e9b8c 100644 --- a/packages/server/graphql/public/types/ResetReflectionGroupsSuccess.ts +++ b/packages/server/graphql/public/types/ResetReflectionGroupsSuccess.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {ResetReflectionGroupsSuccessResolvers} from '../resolverTypes' export type ResetReflectionGroupsSuccessSource = { @@ -8,7 +7,8 @@ export type ResetReflectionGroupsSuccessSource = { const ResetReflectionGroupsSuccess: ResetReflectionGroupsSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingRetrospective + if (meeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') + return meeting } } diff --git a/packages/server/graphql/public/types/RetroDiscussStage.ts b/packages/server/graphql/public/types/RetroDiscussStage.ts index cd98f071fb3..3d4aa611c20 100644 --- a/packages/server/graphql/public/types/RetroDiscussStage.ts +++ b/packages/server/graphql/public/types/RetroDiscussStage.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import ReflectionGroup from '../../../database/types/ReflectionGroup' import {RetroDiscussStageResolvers} from '../resolverTypes' @@ -27,7 +26,8 @@ const RetroDiscussStage: RetroDiscussStageResolvers = { reflectionGroup: async ({reflectionGroupId, meetingId}, _args, {dataLoader}) => { if (!reflectionGroupId) { - const meeting = (await dataLoader.get('newMeetings').load(meetingId)) as MeetingRetrospective + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (!('templateId' in meeting)) throw new Error('Meeting has no template') const prompts = await dataLoader.get('reflectPromptsByTemplateId').load(meeting.templateId) return new ReflectionGroup({ id: `${meetingId}:dummyGroup`, diff --git a/packages/server/graphql/public/types/RetroReflection.ts b/packages/server/graphql/public/types/RetroReflection.ts index 80c967c6797..1443aba8d35 100644 --- a/packages/server/graphql/public/types/RetroReflection.ts +++ b/packages/server/graphql/public/types/RetroReflection.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {getUserId, isSuperUser} from '../../../utils/authorization' import getGroupedReactjis from '../../../utils/getGroupedReactjis' import {RetroReflectionResolvers} from '../resolverTypes' @@ -35,7 +34,8 @@ const RetroReflection: RetroReflectionResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingRetrospective + if (meeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') + return meeting }, prompt: ({promptId}, _args, {dataLoader}) => { diff --git a/packages/server/graphql/public/types/RetroReflectionGroup.ts b/packages/server/graphql/public/types/RetroReflectionGroup.ts index 2c0834b7635..c5435b3a00e 100644 --- a/packages/server/graphql/public/types/RetroReflectionGroup.ts +++ b/packages/server/graphql/public/types/RetroReflectionGroup.ts @@ -1,5 +1,4 @@ import {Selectable} from 'kysely' -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {RetroReflectionGroup as TRetroReflectionGroup} from '../../../postgres/pg' import {getUserId} from '../../../utils/authorization' import {RetroReflectionGroupResolvers} from '../resolverTypes' @@ -9,7 +8,8 @@ export interface RetroReflectionGroupSource extends Selectable { const retroMeeting = await dataLoader.get('newMeetings').load(meetingId) - return retroMeeting as MeetingRetrospective + if (retroMeeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') + return retroMeeting }, prompt: ({promptId}, _args, {dataLoader}) => { return dataLoader.get('reflectPrompts').loadNonNull(promptId) diff --git a/packages/server/graphql/public/types/StartCheckInSuccess.ts b/packages/server/graphql/public/types/StartCheckInSuccess.ts index 0dedaf9905c..7b44a4004ce 100644 --- a/packages/server/graphql/public/types/StartCheckInSuccess.ts +++ b/packages/server/graphql/public/types/StartCheckInSuccess.ts @@ -1,4 +1,3 @@ -import MeetingAction from '../../../database/types/MeetingAction' import {StartCheckInSuccessResolvers} from '../resolverTypes' export type StartCheckInSuccessSource = { @@ -7,8 +6,10 @@ export type StartCheckInSuccessSource = { } const StartCheckInSuccess: StartCheckInSuccessResolvers = { - meeting: ({meetingId}, _args, {dataLoader}) => { - return dataLoader.get('newMeetings').load(meetingId) as Promise + meeting: async ({meetingId}, _args, {dataLoader}) => { + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (meeting.meetingType !== 'action') throw new Error('Not a check-in meeting') + return meeting }, team: ({teamId}, _args, {dataLoader}) => { return dataLoader.get('teams').loadNonNull(teamId) diff --git a/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts b/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts index 4ba4baef9be..261edb42fec 100644 --- a/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts +++ b/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts @@ -1,4 +1,3 @@ -import MeetingRetrospective from '../../../database/types/MeetingRetrospective' import {StartRetrospectiveSuccessResolvers} from '../resolverTypes' export type StartRetrospectiveSuccessSource = { @@ -8,8 +7,10 @@ export type StartRetrospectiveSuccessSource = { } const StartRetrospectiveSuccess: StartRetrospectiveSuccessResolvers = { - meeting: ({meetingId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('newMeetings').load(meetingId) as Promise + meeting: async ({meetingId}, _args: unknown, {dataLoader}) => { + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (meeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') + return meeting }, team: ({teamId}, _args: unknown, {dataLoader}) => { return dataLoader.get('teams').loadNonNull(teamId) diff --git a/packages/server/graphql/public/types/StartTeamPromptSuccess.ts b/packages/server/graphql/public/types/StartTeamPromptSuccess.ts index 15f9fcff241..017c7b63180 100644 --- a/packages/server/graphql/public/types/StartTeamPromptSuccess.ts +++ b/packages/server/graphql/public/types/StartTeamPromptSuccess.ts @@ -1,4 +1,3 @@ -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {StartTeamPromptSuccessResolvers} from '../resolverTypes' export type StartTeamPromptSuccessSource = { @@ -8,7 +7,9 @@ export type StartTeamPromptSuccessSource = { const StartTeamPromptSuccess: StartTeamPromptSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - return dataLoader.get('newMeetings').load(meetingId) as Promise + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (meeting.meetingType !== 'teamPrompt') throw new Error('Not a team prompt meeting') + return meeting }, team: async ({teamId}, _args, {dataLoader}) => { return dataLoader.get('teams').loadNonNull(teamId) diff --git a/packages/server/graphql/public/types/TeamPromptMeeting.ts b/packages/server/graphql/public/types/TeamPromptMeeting.ts index 1bf595b202d..870dd09843b 100644 --- a/packages/server/graphql/public/types/TeamPromptMeeting.ts +++ b/packages/server/graphql/public/types/TeamPromptMeeting.ts @@ -1,7 +1,7 @@ import getRethink from '../../../database/rethinkDriver' import {RValue} from '../../../database/stricterR' -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {TeamPromptMeeting as TeamPromptMeetingSource} from '../../../postgres/types/Meeting' import {getUserId} from '../../../utils/authorization' import filterTasksByMeeting from '../../../utils/filterTasksByMeeting' import getPhase from '../../../utils/getPhase' @@ -28,7 +28,7 @@ const TeamPromptMeeting: TeamPromptMeetingResolvers = { .limit(1) .run() - return meetings[0] as MeetingTeamPrompt + return meetings[0] as TeamPromptMeetingSource }, nextMeeting: async ({meetingSeriesId, createdAt}, _args, {dataLoader}) => { if (!meetingSeriesId) return null @@ -48,7 +48,7 @@ const TeamPromptMeeting: TeamPromptMeetingResolvers = { .limit(1) .run() - return meetings[0] as MeetingTeamPrompt + return meetings[0] as TeamPromptMeetingSource }, tasks: async ({id: meetingId}, _args: unknown, {authToken, dataLoader}) => { const viewerId = getUserId(authToken) diff --git a/packages/server/graphql/public/types/UpdateDimensionFieldSuccess.ts b/packages/server/graphql/public/types/UpdateDimensionFieldSuccess.ts index d74d4f47c02..a6e28d189a1 100644 --- a/packages/server/graphql/public/types/UpdateDimensionFieldSuccess.ts +++ b/packages/server/graphql/public/types/UpdateDimensionFieldSuccess.ts @@ -1,4 +1,3 @@ -import MeetingPoker from '../../../database/types/MeetingPoker' import {UpdateDimensionFieldSuccessResolvers} from '../resolverTypes' export type UpdateDimensionFieldSuccessSource = { @@ -10,7 +9,8 @@ const UpdateDimensionFieldSuccess: UpdateDimensionFieldSuccessResolvers = { team: ({teamId}, _args, {dataLoader}) => dataLoader.get('teams').loadNonNull(teamId), meeting: async ({meetingId}, _args, {dataLoader}) => { const meeting = await dataLoader.get('newMeetings').load(meetingId) - return meeting as MeetingPoker + if (meeting.meetingType !== 'poker') throw new Error('Not a poker meeting') + return meeting } } diff --git a/packages/server/graphql/public/types/UpdateMeetingPromptSuccess.ts b/packages/server/graphql/public/types/UpdateMeetingPromptSuccess.ts index 50d348e1e46..0873d892c7d 100644 --- a/packages/server/graphql/public/types/UpdateMeetingPromptSuccess.ts +++ b/packages/server/graphql/public/types/UpdateMeetingPromptSuccess.ts @@ -1,4 +1,3 @@ -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {UpdateMeetingPromptSuccessResolvers} from '../resolverTypes' export type UpdateMeetingPromptSuccessSource = { @@ -8,7 +7,9 @@ export type UpdateMeetingPromptSuccessSource = { const UpdateMeetingPromptSuccess: UpdateMeetingPromptSuccessResolvers = { meeting: async (source, _args, {dataLoader}) => { const {meetingId} = source - return dataLoader.get('newMeetings').load(meetingId) as Promise + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (meeting.meetingType !== 'teamPrompt') throw new Error('Not a team prompt meeting') + return meeting } } diff --git a/packages/server/graphql/public/types/UpdateRecurrenceSettingsSuccess.ts b/packages/server/graphql/public/types/UpdateRecurrenceSettingsSuccess.ts index b94a914ca27..d9e4f566501 100644 --- a/packages/server/graphql/public/types/UpdateRecurrenceSettingsSuccess.ts +++ b/packages/server/graphql/public/types/UpdateRecurrenceSettingsSuccess.ts @@ -1,4 +1,3 @@ -import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import {UpdateRecurrenceSettingsSuccessResolvers} from '../resolverTypes' export type UpdateRecurrenceSettingsSuccessSource = { @@ -7,7 +6,9 @@ export type UpdateRecurrenceSettingsSuccessSource = { const UpdateRecurrenceSettingsSuccess: UpdateRecurrenceSettingsSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - return dataLoader.get('newMeetings').load(meetingId) as Promise + const meeting = await dataLoader.get('newMeetings').load(meetingId) + if (meeting.meetingType !== 'teamPrompt') throw new Error('Not a team prompt') + return meeting } } diff --git a/packages/server/graphql/resolvers.ts b/packages/server/graphql/resolvers.ts index b02819208d0..09c762ab07a 100644 --- a/packages/server/graphql/resolvers.ts +++ b/packages/server/graphql/resolvers.ts @@ -3,7 +3,6 @@ import nullIfEmpty from 'parabol-client/utils/nullIfEmpty' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import {NewMeetingPhaseTypeEnum} from '../database/types/GenericMeetingPhase' import GenericMeetingStage from '../database/types/GenericMeetingStage' -import Meeting from '../database/types/Meeting' import Organization from '../database/types/Organization' import Task from '../database/types/Task' import User from '../database/types/User' @@ -116,7 +115,7 @@ export const resolveTeamMembers = ( : teamMembers } -export const resolveGQLStageFromId = (stageId: string | undefined, meeting: Meeting) => { +export const resolveGQLStageFromId = (stageId: string | undefined, meeting: AnyMeeting) => { const {id: meetingId, phases} = meeting const stageRes = findStageById(phases, stageId) if (!stageRes) return undefined diff --git a/packages/server/graphql/types/SetPhaseFocusPayload.ts b/packages/server/graphql/types/SetPhaseFocusPayload.ts index 3bb578e72fc..7f6147a522d 100644 --- a/packages/server/graphql/types/SetPhaseFocusPayload.ts +++ b/packages/server/graphql/types/SetPhaseFocusPayload.ts @@ -1,6 +1,6 @@ import {GraphQLNonNull, GraphQLObjectType} from 'graphql' import {REFLECT} from 'parabol-client/utils/constants' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' +import {RetrospectiveMeeting as RetrospectiveMeetingSource} from '../../postgres/types/Meeting' import {GQLContext} from '../graphql' import {resolveNewMeeting} from '../resolvers' import ReflectPhase from './ReflectPhase' @@ -26,7 +26,7 @@ const SetPhaseFocusPayload = new GraphQLObjectType({ ) => { const meeting = (await dataLoader .get('newMeetings') - .load(meetingId)) as MeetingRetrospective + .load(meetingId)) as RetrospectiveMeetingSource return meeting.phases.find((phase) => phase.phaseType === REFLECT) } } diff --git a/packages/server/postgres/migrations/1726174453131_NewMeeting-phase1.ts b/packages/server/postgres/migrations/1726174453131_NewMeeting-phase1.ts new file mode 100644 index 00000000000..87318cabb7a --- /dev/null +++ b/packages/server/postgres/migrations/1726174453131_NewMeeting-phase1.ts @@ -0,0 +1,99 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + + // Notable changes + // - SlackTs is now a double precision + // - facilitatorUserId is nullable in the case of a user hard delete + // - hasScheduledEndTime index changed to scheduledEndTime + + await client.query(` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS "NewMeeting" ( + "id" VARCHAR(100) PRIMARY KEY, + "isLegacy" BOOLEAN NOT NULL DEFAULT FALSE, + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "createdBy" VARCHAR(100), + "endedAt" TIMESTAMP WITH TIME ZONE, + "facilitatorStageId" VARCHAR(100) NOT NULL, + "facilitatorUserId" VARCHAR(100), + "meetingCount" INT NOT NULL, + "meetingNumber" INT NOT NULL, + "name" VARCHAR(100) NOT NULL, + "summarySentAt" TIMESTAMP WITH TIME ZONE, + "teamId" VARCHAR(100) NOT NULL, + "meetingType" "MeetingTypeEnum" NOT NULL, + "phases" JSONB NOT NULL, + "showConversionModal" BOOLEAN NOT NULL DEFAULT FALSE, + "meetingSeriesId" INT, + "scheduledEndTime" TIMESTAMP WITH TIME ZONE, + "summary" VARCHAR(10000), + "sentimentScore" DOUBLE PRECISION, + "usedReactjis" JSONB, + "slackTs" DOUBLE PRECISION, + "engagement" DOUBLE PRECISION, + "totalVotes" INT, + "maxVotesPerGroup" SMALLINT, + "disableAnonymity" BOOLEAN, + "commentCount" INT, + "taskCount" INT, + "agendaItemCount" INT, + "storyCount" INT, + "templateId" VARCHAR(100), + "topicCount" INT, + "reflectionCount" INT, + "transcription" JSONB, + "recallBotId" VARCHAR(255), + "videoMeetingURL" VARCHAR(2048), + "autogroupReflectionGroups" JSONB, + "resetReflectionGroups" JSONB, + "templateRefId" VARCHAR(25), + "meetingPrompt" VARCHAR(255), + CONSTRAINT "fk_createdBy" + FOREIGN KEY("createdBy") + REFERENCES "User"("id") + ON DELETE SET NULL, + CONSTRAINT "fk_facilitatorUserId" + FOREIGN KEY("facilitatorUserId") + REFERENCES "User"("id") + ON DELETE SET NULL, + CONSTRAINT "fk_teamId" + FOREIGN KEY("teamId") + REFERENCES "Team"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_meetingSeriesId" + FOREIGN KEY("meetingSeriesId") + REFERENCES "MeetingSeries"("id") + ON DELETE SET NULL, + CONSTRAINT "fk_templateId" + FOREIGN KEY("templateId") + REFERENCES "MeetingTemplate"("id") + ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS "idx_NewMeeting_createdAt" ON "NewMeeting"("createdAt"); + CREATE INDEX IF NOT EXISTS "idx_NewMeeting_facilitatorUserId" ON "NewMeeting"("facilitatorUserId"); + CREATE INDEX IF NOT EXISTS "idx_NewMeeting_scheduledEndTime" ON "NewMeeting"("scheduledEndTime") WHERE "scheduledEndTime" IS NOT NULL AND "endedAt" IS NULL; + CREATE INDEX IF NOT EXISTS "idx_NewMeeting_meetingSeriesId" ON "NewMeeting"("meetingSeriesId") WHERE "meetingSeriesId" IS NOT NULL; + CREATE INDEX IF NOT EXISTS "idx_NewMeeting_teamId" ON "NewMeeting"("teamId"); + CREATE INDEX IF NOT EXISTS "idx_NewMeeting_templateId" ON "NewMeeting"("templateId") WHERE "templateId" IS NOT NULL; + DROP TRIGGER IF EXISTS "update_NewMeeting_updatedAt" ON "NewMeeting"; + CREATE TRIGGER "update_NewMeeting_updatedAt" BEFORE UPDATE ON "NewMeeting" 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 IF EXISTS "NewMeeting"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index affc2e06e00..d0eacbffb2e 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -2,8 +2,8 @@ import type {JSONContent} from '@tiptap/core' import {NotNull, sql} from 'kysely' import {NewMeetingPhaseTypeEnum} from '../graphql/public/resolverTypes' import getKysely from './getKysely' -import {ReactjiDB} from './types' - +import {AutogroupReflectionGroupType, ReactjiDB, TranscriptBlock, UsedReactjis} from './types' +import type {NewMeetingPhase} from './types/NewMeetingPhase' export const selectTimelineEvent = () => { return getKysely().selectFrom('TimelineEvent').selectAll().$narrowType< | { @@ -230,3 +230,53 @@ export const selectComments = () => .select(({fn}) => [fn('to_json', ['reactjis']).as('reactjis')]) export const selectReflectPrompts = () => getKysely().selectFrom('ReflectPrompt').selectAll() + +export const selectNewMeetings = () => + getKysely() + .selectFrom('NewMeeting') + .select(({fn}) => [ + 'id', + 'isLegacy', + 'createdAt', + 'updatedAt', + 'createdBy', + 'endedAt', + 'facilitatorStageId', + 'facilitatorUserId', + 'meetingCount', + 'meetingNumber', + 'name', + 'summarySentAt', + 'teamId', + 'meetingType', + 'showConversionModal', + 'meetingSeriesId', + 'scheduledEndTime', + 'summary', + 'sentimentScore', + 'slackTs', + 'engagement', + 'totalVotes', + 'maxVotesPerGroup', + 'disableAnonymity', + 'commentCount', + 'taskCount', + 'agendaItemCount', + 'storyCount', + 'templateId', + 'topicCount', + 'reflectionCount', + 'recallBotId', + 'videoMeetingURL', + 'templateRefId', + 'meetingPrompt', + fn('to_json', ['phases']).as('phases'), + fn('to_json', ['usedReactjis']).as('usedReactjis'), + fn('to_json', ['transcription']).as('transcription'), + fn('to_json', ['autogroupReflectionGroups']).as( + 'autogroupReflectionGroups' + ), + fn('to_json', ['resetReflectionGroups']).as( + 'resetReflectionGroups' + ) + ]) diff --git a/packages/server/postgres/types/Meeting.d.ts b/packages/server/postgres/types/Meeting.d.ts index 65d7995ce18..b4d38c02e0e 100644 --- a/packages/server/postgres/types/Meeting.d.ts +++ b/packages/server/postgres/types/Meeting.d.ts @@ -1,16 +1,97 @@ +import {NonNullableProps} from '../../../client/types/generics' import ActionMeetingMember from '../../database/types/ActionMeetingMember' -import MeetingAction from '../../database/types/MeetingAction' -import MeetingPoker from '../../database/types/MeetingPoker' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' -import MeetingTeamPrompt from '../../database/types/MeetingTeamPrompt' import PokerMeetingMember from '../../database/types/PokerMeetingMember' import RetroMeetingMember from '../../database/types/RetroMeetingMember' import TeamPromptMeetingMember from '../../database/types/TeamPromptMeetingMember' -import {MeetingTypeEnum} from '../queries/generated/insertTeamQuery' +import {NewMeeting as NewMeetingDB} from '../pg' +import {NewMeeting} from './index.d' -export {MeetingTypeEnum} +import {Insertable} from 'kysely' +import { + CheckInMeetingPhase, + NewMeetingPhase, + PokerMeetingPhase, + RetroMeetingPhase, + TeamPromptPhase +} from './NewMeetingPhase' -export type AnyMeeting = MeetingRetrospective | MeetingPoker | MeetingAction | MeetingTeamPrompt +export type MeetingTypeEnum = NewMeeting['meetingType'] + +type BaseNewMeeting = Pick< + NewMeeting, + | 'id' + | 'isLegacy' + | 'createdAt' + | 'updatedAt' + | 'createdBy' + | 'endedAt' + | 'facilitatorStageId' + | 'facilitatorUserId' + | 'meetingCount' + | 'meetingNumber' + | 'name' + | 'summarySentAt' + | 'teamId' + | 'meetingType' + | 'showConversionModal' + | 'meetingSeriesId' + | 'scheduledEndTime' + | 'summary' + | 'sentimentScore' + | 'usedReactjis' + | 'slackTs' + | 'engagement' +> & {phases: NewMeetingPhase[]} + +type InsertableRetrospectiveMeeting = Insertable & { + meetingType: 'retrospective' + phases: RetroMeetingPhase[] + totalVotes: number + maxVotesPerGroup: number + disableAnonymity: boolean + templateId: string +} + +export type RetrospectiveMeeting = BaseNewMeeting & + NonNullableProps< + Pick + > & + Pick< + NewMeeting, + | 'commentCount' + | 'taskCount' + | 'topicCount' + | 'reflectionCount' + | 'transcription' + | 'recallBotId' + | 'videoMeetingURL' + | 'autogroupReflectionGroups' + | 'resetReflectionGroups' + > & { + meetingType: 'retrospective' + phases: RetroMeetingPhase[] + } + +export type PokerMeeting = BaseNewMeeting & + NonNullableProps> & + Pick & { + meetingType: 'poker' + phases: PokerMeetingPhase[] + } + +export type CheckInMeeting = BaseNewMeeting & + Pick & { + meetingType: 'action' + phases: CheckInMeetingPhase[] + } + +export type TeamPromptMeeting = BaseNewMeeting & + NonNullableProps> & { + meetingType: 'teamPrompt' + phases: TeamPromptPhase[] + } + +export type AnyMeeting = RetrospectiveMeeting | PokerMeeting | CheckInMeeting | TeamPromptMeeting export type AnyMeetingTeamMember = | PokerMeetingMember diff --git a/packages/server/postgres/types/NewMeetingPhase.d.ts b/packages/server/postgres/types/NewMeetingPhase.d.ts new file mode 100644 index 00000000000..6569deba9db --- /dev/null +++ b/packages/server/postgres/types/NewMeetingPhase.d.ts @@ -0,0 +1,204 @@ +interface GenericMeetingStage { + id: string + isAsync?: boolean | null + isComplete: boolean + isNavigable: boolean + isNavigableByFacilitator: boolean + startAt?: Date + endAt?: Date + scheduledEndTime?: Date | null + suggestedEndTime?: Date + suggestedTimeLimit?: number + viewCount: number + readyToAdvance?: string[] + phaseType: string +} + +interface AgendaItemStage extends GenericMeetingStage { + phaseType: 'agendaitems' + agendaItemId: string + discussionId: string +} + +interface CheckInStage extends GenericMeetingStage { + phaseType: 'checkin' + teamMemberId: string + durations?: number[] +} + +interface DiscussStage extends GenericMeetingStage { + phaseType: 'discuss' + reflectionGroupId: string + discussionId: string + sortOrder: number +} + +interface EstimateStage extends GenericMeetingStage { + phaseType: 'ESTIMATE' + creatorUserId: string + serviceTaskId: string + taskId: string + sortOrder: number + dimensionRefIdx: number + finalScore?: number + scores: { + userId: string + label: string + }[] + isVoting: boolean + discussionId: string +} + +interface ReflectStage extends GenericMeetingStage { + phaseType: 'reflect' +} + +interface TeamHealthStage extends GenericMeetingStage { + phaseType: 'TEAM_HEALTH' + votes: { + userId: string + vote: number + }[] + isRevealed: boolean + question: string + labels: string[] + durations?: number[] +} + +interface TeamPromptResponseStage extends GenericMeetingStage { + phaseType: 'RESPONSES' + teamMemberId: string + discussionId: string +} + +interface UpdatesStage extends GenericMeetingStage { + phaseType: 'updates' + teamMemberId: string + durations?: number[] +} + +interface FirstCallStage extends GenericMeetingStage { + phaseType: 'firstcall' +} + +interface LastCallStage extends GenericMeetingStage { + phaseType: 'lastcall' +} + +interface GroupStage extends GenericMeetingStage { + phaseType: 'group' +} + +interface VoteStage extends GenericMeetingStage { + phaseType: 'vote' +} + +interface ScopeStage extends GenericMeetingStage { + phaseType: 'SCOPE' +} + +interface GenericMeetingPhase { + id: string +} + +interface FirstCallPhase extends GenericMeetingPhase { + phaseType: 'firstcall' + stages: [FirstCallStage] +} + +interface LastCallPhase extends GenericMeetingPhase { + phaseType: 'lastcall' + stages: [LastCallStage] +} + +interface GroupPhase extends GenericMeetingPhase { + phaseType: 'group' + stages: [GroupStage] +} + +interface VotePhase extends GenericMeetingPhase { + phaseType: 'vote' + stages: [VoteStage] +} + +interface ScopePhase extends GenericMeetingPhase { + phaseType: 'SCOPE' + stages: [ScopeStage] +} + +interface AgendaItemPhase extends GenericMeetingPhase { + phaseType: 'agendaitems' + stages: AgendaItemStage[] +} + +const a: AgendaItemPhase + +interface CheckInPhase extends GenericMeetingPhase { + phaseType: 'checkin' + stages: [CheckInStage, ...CheckInStage[]] + checkInGreeting: {content: string; language: string} + checkInQuestion: string +} + +interface DiscussPhase extends GenericMeetingPhase { + phaseType: 'discuss' + stages: [DiscussStage, ...DiscussStage[]] +} + +interface EstimatePhase extends GenericMeetingPhase { + phaseType: 'ESTIMATE' + stages: EstimateStage[] +} + +interface ReflectPhase extends GenericMeetingPhase { + phaseType: 'reflect' + stages: [ReflectStage] + teamId: string + focusedPromptId?: string +} + +interface TeamHealthPhase extends GenericMeetingPhase { + phaseType: 'TEAM_HEALTH' + isRevealed: boolean + stages: [TeamHealthStage] +} + +interface TeamPromptResponsesPhase extends GenericMeetingPhase { + phaseType: 'RESPONSES' + stages: [TeamPromptResponseStage, ...TeamPromptResponseStage[]] +} + +interface UpdatesPhase extends GenericMeetingPhase { + phaseType: 'updates' + + stages: [UpdatesStage, ...UpdatesStage[]] +} + +export type RetroMeetingPhase = + | CheckInPhase + | TeamHealthPhase + | ReflectPhase + | GroupPhase + | VotePhase + | DiscussPhase + +export type PokerMeetingPhase = CheckInPhase | TeamHealthPhase | ScopePhase | EstimatePhase + +export type CheckInMeetingPhase = + | CheckInPhase + | TeamHealthPhase + | UpdatesPhase + | FirstCallPhase + | LastCallPhase + | AgendaItemPhase + +export type TeamPromptPhase = TeamPromptResponsesPhase + +export type NewMeetingPhase = + | RetroMeetingPhase + | PokerMeetingPhase + | CheckInMeetingPhase + | TeamPromptPhase + +type TupleToArray = T extends (infer U)[] ? U : never +export type NewMeetingStages = TupleToArray diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index affb35215d4..2186bdc5ee3 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -8,6 +8,7 @@ import { selectAgendaItems, selectComments, selectMeetingSettings, + selectNewMeetings, selectOrganizations, selectReflectPrompts, selectRetroReflections, @@ -26,6 +27,17 @@ type ExtractTypeFromQueryBuilderSelect any> = export type Discussion = Selectable export type ReactjiDB = {id: string; userId: string} +export type UsedReactjis = Record +export type TranscriptBlock = { + speaker: string + words: string +} + +export type AutogroupReflectionGroupType = { + groupTitle: string + reflectionIds: string[] +} + export interface Organization extends ExtractTypeFromQueryBuilderSelect {} export type OrganizationUser = Selectable @@ -58,3 +70,5 @@ export type SlackNotification = ExtractTypeFromQueryBuilderSelect export type ReflectPrompt = ExtractTypeFromQueryBuilderSelect + +export type NewMeeting = ExtractTypeFromQueryBuilderSelect diff --git a/packages/server/utils/RecallAIServerManager.ts b/packages/server/utils/RecallAIServerManager.ts index 920337d662e..87d5857ec7f 100644 --- a/packages/server/utils/RecallAIServerManager.ts +++ b/packages/server/utils/RecallAIServerManager.ts @@ -2,7 +2,7 @@ import api from 'api' import axios from 'axios' import {ExternalLinks} from '../../client/types/constEnums' import appOrigin from '../appOrigin' -import {TranscriptBlock} from '../database/types/MeetingRetrospective' +import {TranscriptBlock} from '../postgres/types' import {Logger} from './Logger' import sendToSentry from './sendToSentry' diff --git a/packages/server/utils/analytics/analytics.ts b/packages/server/utils/analytics/analytics.ts index 00b37a3fbc3..568cda427e8 100644 --- a/packages/server/utils/analytics/analytics.ts +++ b/packages/server/utils/analytics/analytics.ts @@ -3,16 +3,14 @@ import type {UpgradeCTALocationEnumType} from '../../../client/shared/UpgradeCTA import TeamPromptResponseId from '../../../client/shared/gqlIds/TeamPromptResponseId' import {PARABOL_AI_USER_ID} from '../../../client/utils/constants' import {TeamLimitsEmailType} from '../../billing/helpers/sendTeamsLimitEmail' -import Meeting from '../../database/types/Meeting' import MeetingMember from '../../database/types/MeetingMember' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' import MeetingTemplate from '../../database/types/MeetingTemplate' import {TaskServiceEnum} from '../../database/types/Task' import {DataLoaderWorker} from '../../graphql/graphql' import {ModifyType, ReactableEnum} from '../../graphql/public/resolverTypes' import {IntegrationProviderServiceEnumType} from '../../graphql/types/IntegrationProviderServiceEnum' import {SlackNotification, TeamPromptResponse, TemplateScale} from '../../postgres/types' -import {MeetingTypeEnum} from '../../postgres/types/Meeting' +import {AnyMeeting, MeetingTypeEnum, RetrospectiveMeeting} from '../../postgres/types/Meeting' import {MeetingSeries} from '../../postgres/types/MeetingSeries' import {AmplitudeAnalytics} from './amplitude/AmplitudeAnalytics' import {createMeetingProperties} from './helpers' @@ -193,7 +191,7 @@ class Analytics { // meeting teamPromptEnd = async ( - completedMeeting: Meeting, + completedMeeting: AnyMeeting, meetingMembers: MeetingMember[], responses: TeamPromptResponse[], dataLoader: DataLoaderWorker @@ -220,7 +218,7 @@ class Analytics { } checkInEnd = async ( - completedMeeting: Meeting, + completedMeeting: AnyMeeting, meetingMembers: MeetingMember[], dataLoader: DataLoaderWorker ) => @@ -238,7 +236,7 @@ class Analytics { ) retrospectiveEnd = async ( - completedMeeting: MeetingRetrospective, + completedMeeting: RetrospectiveMeeting, meetingMembers: MeetingMember[], template: MeetingTemplate, dataLoader: DataLoaderWorker @@ -261,7 +259,7 @@ class Analytics { } sprintPokerEnd = ( - completedMeeting: Meeting, + completedMeeting: AnyMeeting, meetingMembers: MeetingMember[], template: MeetingTemplate, dataLoader: DataLoaderWorker @@ -282,7 +280,7 @@ class Analytics { private meetingEnd = async ( dataloader: DataLoaderWorker, userId: string, - completedMeeting: Meeting, + completedMeeting: AnyMeeting, meetingMembers: MeetingMember[], template?: MeetingTemplate, meetingSpecificProperties?: any @@ -295,7 +293,7 @@ class Analytics { }) } - meetingStarted = (user: AnalyticsUser, meeting: Meeting, template?: MeetingTemplate) => { + meetingStarted = (user: AnalyticsUser, meeting: AnyMeeting, template?: MeetingTemplate) => { this.track(user, 'Meeting Started', createMeetingProperties(meeting, undefined, template)) } @@ -307,7 +305,7 @@ class Analytics { this.track(user, 'Meeting Recurrence Stopped', meetingSeries) } - meetingJoined = (user: AnalyticsUser, meeting: Meeting) => { + meetingJoined = (user: AnalyticsUser, meeting: AnyMeeting) => { this.track(user, 'Meeting Joined', createMeetingProperties(meeting, undefined, undefined)) } @@ -326,7 +324,7 @@ class Analytics { commentAdded = ( user: AnalyticsUser, - meeting: Meeting, + meeting: AnyMeeting, isAnonymous: boolean, isAsync: boolean, isReply: boolean diff --git a/packages/server/utils/analytics/helpers.ts b/packages/server/utils/analytics/helpers.ts index fb3b90cddeb..1e9f965b50c 100644 --- a/packages/server/utils/analytics/helpers.ts +++ b/packages/server/utils/analytics/helpers.ts @@ -1,11 +1,10 @@ import {CHECKIN} from '../../../client/utils/constants' -import Meeting from '../../database/types/Meeting' import MeetingMember from '../../database/types/MeetingMember' -import MeetingRetrospective from '../../database/types/MeetingRetrospective' import MeetingTemplate from '../../database/types/MeetingTemplate' +import {AnyMeeting} from '../../postgres/types/Meeting' export const createMeetingProperties = ( - meeting: Meeting, + meeting: AnyMeeting, meetingMembers?: MeetingMember[], template?: MeetingTemplate ) => { @@ -28,8 +27,6 @@ export const createMeetingProperties = ( meetingTemplateCategory: template?.mainCategory, meetingSeriesId: meeting.meetingSeriesId, disableAnonymity: - meetingType === 'retrospective' - ? (meeting as MeetingRetrospective).disableAnonymity ?? false - : undefined + meetingType === 'retrospective' ? meeting.disableAnonymity ?? false : undefined } } diff --git a/packages/server/utils/getPhase.ts b/packages/server/utils/getPhase.ts index 94a825bac70..8c7936e36ab 100644 --- a/packages/server/utils/getPhase.ts +++ b/packages/server/utils/getPhase.ts @@ -1,26 +1,13 @@ -import AgendaItemsPhase from '../database/types/AgendaItemsPhase' -import CheckInPhase from '../database/types/CheckInPhase' -import DiscussPhase from '../database/types/DiscussPhase' -import EstimatePhase from '../database/types/EstimatePhase' -import GenericMeetingPhase from '../database/types/GenericMeetingPhase' -import ReflectPhase from '../database/types/ReflectPhase' -import TeamHealthPhase from '../database/types/TeamHealthPhase' -import TeamPromptResponsesPhase from '../database/types/TeamPromptResponsesPhase' -import UpdatesPhase from '../database/types/UpdatesPhase' +import {NewMeetingPhase} from '../postgres/types/NewMeetingPhase' -interface PhaseTypeLookup { - agendaitems: AgendaItemsPhase - checkin: CheckInPhase - discuss: DiscussPhase - ESTIMATE: EstimatePhase - reflect: ReflectPhase - updates: UpdatesPhase - RESPONSES: TeamPromptResponsesPhase - TEAM_HEALTH: TeamHealthPhase -} - -const getPhase = (phases: GenericMeetingPhase[], phaseType: T) => { - return phases.find((phase) => phase.phaseType === phaseType) as unknown as PhaseTypeLookup[T] +const getPhase = ( + phases: NewMeetingPhase[], + phaseType: T +) => { + return phases.find((phase) => phase.phaseType === phaseType) as Extract< + NewMeetingPhase, + {phaseType: T} + > } export default getPhase From 8070a7e82d156d7b587742f1bde8279419ea85db Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Fri, 27 Sep 2024 12:13:23 -0700 Subject: [PATCH 478/529] chore(rethinkdb): NewMeeting: Phase 1b (#10250) Signed-off-by: Matt Krick --- .../server/dataloader/customLoaderMakers.ts | 101 +++++++++++++++++- .../dataloader/foreignKeyLoaderMakers.ts | 24 +++++ .../dataloader/primaryKeyLoaderMakers.ts | 5 + .../graphql/mutations/createReflection.ts | 8 +- .../graphql/mutations/dragDiscussionTopic.ts | 11 +- .../graphql/mutations/dragEstimatingTask.ts | 11 +- .../server/graphql/mutations/endCheckIn.ts | 30 +++++- .../graphql/mutations/endRetrospective.ts | 6 +- .../graphql/mutations/endSprintPoker.ts | 18 +++- .../graphql/mutations/flagReadyToAdvance.ts | 10 +- .../addAgendaItemToActiveActionMeeting.ts | 6 ++ .../graphql/mutations/helpers/addRecallBot.ts | 6 ++ .../endMeeting/sendNewMeetingSummary.ts | 10 +- .../mutations/helpers/generateGroups.ts | 6 ++ .../mutations/helpers/handleCompletedStage.ts | 5 + .../mutations/helpers/hideConversionModal.ts | 8 +- .../helpers/notifications/SlackNotifier.ts | 10 ++ .../mutations/helpers/removeEmptyTasks.ts | 3 +- .../helpers/removeStagesFromMeetings.ts | 9 +- .../mutations/helpers/removeTeamMember.ts | 5 + .../helpers/removeUserFromMeetingStages.ts | 9 +- .../mutations/helpers/safeCreateTeamPrompt.ts | 10 +- .../mutations/helpers/safeEndRetrospective.ts | 43 ++++++-- .../mutations/helpers/safeEndTeamPrompt.ts | 16 ++- .../server/graphql/mutations/joinMeeting.ts | 35 +++++- .../graphql/mutations/navigateMeeting.ts | 13 ++- packages/server/graphql/mutations/payLater.ts | 9 +- .../graphql/mutations/pokerResetDimension.ts | 19 +++- .../graphql/mutations/pokerRevealVotes.ts | 18 +++- .../mutations/promoteNewMeetingFacilitator.ts | 10 +- .../graphql/mutations/removeReflection.ts | 5 + .../server/graphql/mutations/renameMeeting.ts | 3 +- .../resetRetroMeetingToGroupStage.ts | 25 ++++- .../server/graphql/mutations/setPhaseFocus.ts | 9 +- .../server/graphql/mutations/setStageTimer.ts | 7 +- .../graphql/mutations/startSprintPoker.ts | 17 ++- .../mutations/updateNewCheckInQuestion.ts | 11 +- .../graphql/mutations/updatePokerScope.ts | 11 ++ .../graphql/mutations/updateRetroMaxVotes.ts | 10 +- .../graphql/mutations/voteForPokerStory.ts | 49 +++++++++ .../mutations/voteForReflectionGroup.ts | 2 +- .../mutations/generateMeetingSummary.ts | 5 + .../private/mutations/hardDeleteUser.ts | 56 ++++++---- .../private/mutations/processRecurrence.ts | 27 ++--- .../graphql/public/mutations/autogroup.ts | 9 +- .../public/mutations/modifyCheckInQuestion.ts | 4 +- .../public/mutations/resetReflectionGroups.ts | 7 ++ .../public/mutations/revealTeamHealthVotes.ts | 15 ++- .../public/mutations/setTeamHealthVote.ts | 3 - .../graphql/public/mutations/startCheckIn.ts | 15 ++- .../public/mutations/startRetrospective.ts | 7 +- .../public/mutations/startTeamPrompt.ts | 18 ++-- .../public/mutations/updateAgendaItem.ts | 5 + .../public/mutations/updateMeetingPrompt.ts | 7 ++ .../public/mutations/updateMeetingTemplate.ts | 4 +- .../mutations/updateRecurrenceSettings.ts | 67 ++++++++---- .../1726251201860_NewMeeting-uniq.ts | 45 ++++++++ .../src/updateMeetingSeriesByIdQuery.sql | 10 -- .../postgres/queries/updateMeetingSeries.ts | 20 ---- 59 files changed, 764 insertions(+), 183 deletions(-) create mode 100644 packages/server/postgres/migrations/1726251201860_NewMeeting-uniq.ts delete mode 100644 packages/server/postgres/queries/src/updateMeetingSeriesByIdQuery.sql delete mode 100644 packages/server/postgres/queries/updateMeetingSeries.ts diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 00ef82e4031..a809dae9d0a 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -25,7 +25,7 @@ import getLatestTaskEstimates from '../postgres/queries/getLatestTaskEstimates' import getMeetingTaskEstimates, { MeetingTaskEstimatesResult } from '../postgres/queries/getMeetingTaskEstimates' -import {selectMeetingSettings, selectTeams} from '../postgres/select' +import {selectMeetingSettings, selectNewMeetings, selectTeams} from '../postgres/select' import {MeetingSettings, OrganizationUser, Team} from '../postgres/types' import {AnyMeeting, MeetingTypeEnum} from '../postgres/types/Meeting' import {Logger} from '../utils/Logger' @@ -510,6 +510,36 @@ export const meetingStatsByOrgId = (parent: RootDataLoader, dependsOn: RegisterD ) } +export const _pgmeetingStatsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('newMeetings') + return new DataLoader( + async (orgIds) => { + const pg = getKysely() + const meetingStatsByOrgId = await Promise.all( + orgIds.map(async (orgId) => { + // note: does not include archived teams! + const teams = await parent.get('teamsByOrgIds').load(orgId) + const teamIds = teams.map(({id}) => id) + const stats = await pg + .selectFrom('NewMeeting') + .select(['createdAt', 'meetingType']) + .where('teamId', 'in', teamIds) + .execute() + return stats.map((stat) => ({ + createdAt: stat.createdAt, + meetingType: stat.meetingType, + id: `ms${stat.createdAt.getTime()}` + })) + }) + ) + return meetingStatsByOrgId + }, + { + ...parent.dataLoaderOptions + } + ) +} + export const teamStatsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { dependsOn('teams') return new DataLoader( @@ -595,6 +625,27 @@ export const activeMeetingsByMeetingSeriesId = ( ) } +export const _pgactiveMeetingsByMeetingSeriesId = ( + parent: RootDataLoader, + dependsOn: RegisterDependsOn +) => { + dependsOn('newMeetings') + return new DataLoader( + async (keys) => { + const res = await selectNewMeetings() + .where('meetingSeriesId', 'in', keys) + .where('endedAt', 'is', null) + .orderBy('createdAt') + .$narrowType() + .execute() + return normalizeArrayResults(keys, res, 'meetingSeriesId') + }, + { + ...parent.dataLoaderOptions + } + ) +} + export const lastMeetingByMeetingSeriesId = ( parent: RootDataLoader, dependsOn: RegisterDependsOn @@ -623,6 +674,31 @@ export const lastMeetingByMeetingSeriesId = ( ) } +export const _pglastMeetingByMeetingSeriesId = ( + parent: RootDataLoader, + dependsOn: RegisterDependsOn +) => { + dependsOn('newMeetings') + return new DataLoader( + async (keys) => { + return await Promise.all( + keys.map(async (key) => { + const latestMeeting = await selectNewMeetings() + .where('meetingSeriesId', '=', key) + .orderBy('createdAt desc') + .limit(1) + .$narrowType() + .executeTakeFirst() + return latestMeeting || null + }) + ) + }, + { + ...parent.dataLoaderOptions + } + ) +} + export const billingLeadersIdsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { dependsOn('organizationUsers') return new DataLoader( @@ -842,3 +918,26 @@ export const meetingCount = (parent: RootDataLoader, dependsOn: RegisterDependsO } ) } + +export const _pgmeetingCount = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { + dependsOn('newMeetings') + return new DataLoader<{teamId: string; meetingType: MeetingTypeEnum}, number, string>( + async (keys) => { + return await Promise.all( + keys.map(async ({teamId, meetingType}) => { + const row = await getKysely() + .selectFrom('NewMeeting') + .select(({fn}) => fn.count('id').as('count')) + .where('teamId', '=', teamId) + .where('meetingType', '=', meetingType) + .executeTakeFirstOrThrow() + return Number(row.count) + }) + ) + }, + { + ...parent.dataLoaderOptions, + cacheKeyFn: (key) => `${key.teamId}:${key.meetingType}` + } + ) +} diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 77b010bc58e..5b0e9d2eb2e 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -3,6 +3,7 @@ import {getTeamPromptResponsesByMeetingIds} from '../postgres/queries/getTeamPro import { selectAgendaItems, selectComments, + selectNewMeetings, selectOrganizations, selectReflectPrompts, selectRetroReflections, @@ -227,3 +228,26 @@ export const reflectPromptsByTemplateId = foreignKeyLoaderMaker( .execute() } ) + +export const _pgactiveMeetingsByTeamId = foreignKeyLoaderMaker( + '_pgnewMeetings', + 'teamId', + async (teamIds) => { + return selectNewMeetings() + .where('teamId', 'in', teamIds) + .where('endedAt', 'is', null) + .orderBy('createdAt desc') + .execute() + } +) +export const _pgcompletedMeetingsByTeamId = foreignKeyLoaderMaker( + '_pgnewMeetings', + 'teamId', + async (teamIds) => { + return selectNewMeetings() + .where('teamId', 'in', teamIds) + .where('endedAt', 'is not', null) + .orderBy('endedAt desc') + .execute() + } +) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 24a45d0b660..842397eb089 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -9,6 +9,7 @@ import { selectAgendaItems, selectComments, selectMeetingSettings, + selectNewMeetings, selectOrganizations, selectReflectPrompts, selectRetroReflections, @@ -115,3 +116,7 @@ export const comments = primaryKeyLoaderMaker((ids: readonly string[]) => { export const reflectPrompts = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectReflectPrompts().where('id', 'in', ids).execute() }) + +export const _pgnewMeetings = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectNewMeetings().where('id', 'in', ids).execute() +}) diff --git a/packages/server/graphql/mutations/createReflection.ts b/packages/server/graphql/mutations/createReflection.ts index de7171d3705..7a20208c72d 100644 --- a/packages/server/graphql/mutations/createReflection.ts +++ b/packages/server/graphql/mutations/createReflection.ts @@ -43,7 +43,7 @@ export default { const viewerId = getUserId(authToken) const [reflectPrompt, meeting, viewer] = await Promise.all([ dataLoader.get('reflectPrompts').load(promptId), - r.table('NewMeeting').get(meetingId).default(null).run(), + dataLoader.get('newMeetings').load(meetingId), dataLoader.get('users').loadNonNull(viewerId) ]) if (!reflectPrompt) { @@ -119,6 +119,12 @@ export default { phases }) .run() + await pg + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() + dataLoader.clearAll('newMeetings') } analytics.reflectionAdded(viewer, teamId, meetingId) const data = { diff --git a/packages/server/graphql/mutations/dragDiscussionTopic.ts b/packages/server/graphql/mutations/dragDiscussionTopic.ts index 3ed3f0acd65..472c314197c 100644 --- a/packages/server/graphql/mutations/dragDiscussionTopic.ts +++ b/packages/server/graphql/mutations/dragDiscussionTopic.ts @@ -1,6 +1,7 @@ import {GraphQLFloat, GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' import publish from '../../utils/publish' @@ -27,13 +28,14 @@ export default { {meetingId, stageId, sortOrder}: {meetingId: string; stageId: string; sortOrder: number}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { + const pg = getKysely() const r = await getRethink() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const viewerId = getUserId(authToken) // AUTH - const meeting = await r.table('NewMeeting').get(meetingId).run() + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) const {endedAt, phases, teamId} = meeting if (!isTeamMember(authToken, teamId)) { @@ -63,7 +65,12 @@ export default { phases }) .run() - + await pg + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() + dataLoader.clearAll('newMeetings') const data = { meetingId, stageId diff --git a/packages/server/graphql/mutations/dragEstimatingTask.ts b/packages/server/graphql/mutations/dragEstimatingTask.ts index 4034619b38c..5539ab1667b 100644 --- a/packages/server/graphql/mutations/dragEstimatingTask.ts +++ b/packages/server/graphql/mutations/dragEstimatingTask.ts @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLInt, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {ESTIMATE_TASK_SORT_ORDER} from '../../../client/utils/constants' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' import publish from '../../utils/publish' @@ -34,13 +35,14 @@ export default { }: {meetingId: string; taskId: string; newPositionIndex: number}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { + const pg = getKysely() const r = await getRethink() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const viewerId = getUserId(authToken) // AUTH - const meeting = await r.table('NewMeeting').get(meetingId).run() + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) const {endedAt, phases, teamId} = meeting if (!isTeamMember(authToken, teamId)) { @@ -92,7 +94,12 @@ export default { phases }) .run() - + await pg + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() + dataLoader.clearAll('newMeetings') const data = { meetingId, stageIds diff --git a/packages/server/graphql/mutations/endCheckIn.ts b/packages/server/graphql/mutations/endCheckIn.ts index c3ea3419772..dd11494d64d 100644 --- a/packages/server/graphql/mutations/endCheckIn.ts +++ b/packages/server/graphql/mutations/endCheckIn.ts @@ -107,6 +107,7 @@ const summarizeCheckInMeeting = async (meeting: CheckInMeeting, dataLoader: Data */ const {id: meetingId, teamId, phases} = meeting + const pg = getKysely() const r = await getRethink() const [meetingMembers, tasks, doneTasks, activeAgendaItems] = await Promise.all([ dataLoader.get('meetingMembersByMeetingId').load(meetingId), @@ -142,6 +143,15 @@ const summarizeCheckInMeeting = async (meeting: CheckInMeeting, dataLoader: Data isKill ? undefined : archiveTasksForDB(doneTasks, meetingId), isKill ? undefined : clonePinnedAgendaItems(pinnedAgendaItems, dataLoader), updateTaskSortOrders(userIds, tasks), + pg + .updateTable('NewMeeting') + .set({ + agendaItemCount: activeAgendaItems.length, + commentCount, + taskCount: tasks.length + }) + .where('id', '=', meetingId) + .execute(), r .table('NewMeeting') .get(meetingId) @@ -155,7 +165,7 @@ const summarizeCheckInMeeting = async (meeting: CheckInMeeting, dataLoader: Data ) .run() ]) - + dataLoader.clearAll('newMeetings') return {updatedTaskIds: [...tasks, ...doneTasks].map(({id}) => id)} } @@ -170,6 +180,7 @@ export default { }, async resolve(_source: unknown, {meetingId}: {meetingId: string}, context: GQLContext) { const {authToken, socketId: mutatorId, dataLoader} = context + const pg = getKysely() const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -177,7 +188,7 @@ export default { const viewerId = getUserId(authToken) // AUTH - const meeting = await r.table('NewMeeting').get(meetingId).default(null).run() + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) if (meeting.meetingType !== 'action') { return standardError(new Error('Not a check-in meeting'), {userId: viewerId}) @@ -213,7 +224,17 @@ export default { )('changes')(0)('new_val') .default(null) .run() - + await pg + .updateTable('NewMeeting') + .set({ + endedAt: now, + phases: JSON.stringify(phases), + usedReactjis: JSON.stringify(insights.usedReactjis), + engagement: insights.engagement + }) + .where('id', '=', meetingId) + .execute() + dataLoader.clearAll('newMeetings') if (!completedCheckIn) { return standardError(new Error('Completed check-in meeting does not exist'), { userId: viewerId @@ -230,7 +251,7 @@ export default { dataLoader.get('meetingMembersByMeetingId').load(meetingId), dataLoader.get('teams').loadNonNull(teamId), dataLoader.get('teamMembersByTeamId').load(teamId), - removeEmptyTasks(meetingId), + removeEmptyTasks(meetingId, teamId), updateTeamInsights(teamId, dataLoader) ]) // need to wait for removeEmptyTasks before finishing the meeting @@ -252,7 +273,6 @@ export default { }) ) const timelineEventId = events[0]!.id - const pg = getKysely() await pg.insertInto('TimelineEvent').values(events).execute() if (team.isOnboardTeam) { const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) diff --git a/packages/server/graphql/mutations/endRetrospective.ts b/packages/server/graphql/mutations/endRetrospective.ts index 322a6ee1c67..4d2e36bfd99 100644 --- a/packages/server/graphql/mutations/endRetrospective.ts +++ b/packages/server/graphql/mutations/endRetrospective.ts @@ -1,5 +1,4 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' -import getRethink from '../../database/rethinkDriver' import {getUserId, isTeamMember} from '../../utils/authorization' import standardError from '../../utils/standardError' import {GQLContext} from '../graphql' @@ -16,13 +15,12 @@ export default { } }, async resolve(_source: unknown, {meetingId}: {meetingId: string}, context: GQLContext) { - const {authToken} = context - const r = await getRethink() + const {authToken, dataLoader} = context const now = new Date() const viewerId = getUserId(authToken) // AUTH - const meeting = await r.table('NewMeeting').get(meetingId).default(null).run() + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) if (meeting.meetingType !== 'retrospective') { return standardError(new Error('Meeting not found'), {userId: viewerId}) diff --git a/packages/server/graphql/mutations/endSprintPoker.ts b/packages/server/graphql/mutations/endSprintPoker.ts index 5927fcd34f4..f057e37a57c 100644 --- a/packages/server/graphql/mutations/endSprintPoker.ts +++ b/packages/server/graphql/mutations/endSprintPoker.ts @@ -1,4 +1,5 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' +import {sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getMeetingPhase from 'parabol-client/utils/getMeetingPhase' import findStageById from 'parabol-client/utils/meetings/findStageById' @@ -40,7 +41,7 @@ export default { const viewerId = getUserId(authToken) // AUTH - const meeting = await r.table('NewMeeting').get(meetingId).default(null).run() + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) if (meeting.meetingType !== 'poker') { return standardError(new Error('Meeting is not a poker meeting'), {userId: viewerId}) @@ -94,6 +95,19 @@ export default { )('changes')(0)('new_val') .default(null) .run() + await getKysely() + .updateTable('NewMeeting') + .set({ + endedAt: sql`CURRENT_TIMESTAMP`, + phases: JSON.stringify(phases), + commentCount, + storyCount, + usedReactjis: JSON.stringify(insights.usedReactjis), + engagement: insights.engagement + }) + .where('id', '=', meetingId) + .executeTakeFirst() + dataLoader.clearAll('newMeetings') if (!completedMeeting) { return standardError(new Error('Completed poker meeting does not exist'), { userId: viewerId @@ -107,7 +121,7 @@ export default { dataLoader.get('meetingMembersByMeetingId').load(meetingId), dataLoader.get('teams').loadNonNull(teamId), dataLoader.get('teamMembersByTeamId').load(teamId), - removeEmptyTasks(meetingId), + removeEmptyTasks(meetingId, teamId), // technically, this template could have mutated while the meeting was going on. but in practice, probably not dataLoader.get('meetingTemplates').loadNonNull(templateId), updateTeamInsights(teamId, dataLoader) diff --git a/packages/server/graphql/mutations/flagReadyToAdvance.ts b/packages/server/graphql/mutations/flagReadyToAdvance.ts index da1ba3c08af..24a07512bf5 100644 --- a/packages/server/graphql/mutations/flagReadyToAdvance.ts +++ b/packages/server/graphql/mutations/flagReadyToAdvance.ts @@ -3,6 +3,7 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import findStageById from 'parabol-client/utils/meetings/findStageById' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId} from '../../utils/authorization' import publish from '../../utils/publish' import {GQLContext} from '../graphql' @@ -29,6 +30,7 @@ const flagReadyToAdvance = { {meetingId, stageId, isReady}: {meetingId: string; stageId: string; isReady: boolean}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { + const pg = getKysely() const r = await getRethink() const viewerId = getUserId(authToken) const now = new Date() @@ -38,7 +40,7 @@ const flagReadyToAdvance = { //AUTH const meetingMemberId = toTeamMemberId(meetingId, viewerId) const [meeting, viewerMeetingMember] = await Promise.all([ - r.table('NewMeeting').get(meetingId).run(), + dataLoader.get('newMeetings').load(meetingId), dataLoader.get('meetingMembers').load(meetingMemberId) ]) if (!meeting) { @@ -81,6 +83,12 @@ const flagReadyToAdvance = { // RESOLUTION // TODO there's enough evidence showing that we should probably worry about atomicity await r.table('NewMeeting').get(meetingId).update({phases, updatedAt: now}).run() + await pg + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() + dataLoader.clearAll('newMeetings') const data = {meetingId, stageId} publish(SubscriptionChannel.MEETING, meetingId, 'FlagReadyToAdvanceSuccess', data, subOptions) return data diff --git a/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts b/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts index 69c0e8bca7e..b7a34bc7b6f 100644 --- a/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts +++ b/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts @@ -46,6 +46,12 @@ const addAgendaItemToActiveActionMeeting = async ( }) .run(), getKysely() + .with('UpdatePhases', (qb) => + qb + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + ) .with('InsertDiscussion', (qb) => qb.insertInto('Discussion').values({ id: discussionId, diff --git a/packages/server/graphql/mutations/helpers/addRecallBot.ts b/packages/server/graphql/mutations/helpers/addRecallBot.ts index fde576b504a..3a16b97c0d2 100644 --- a/packages/server/graphql/mutations/helpers/addRecallBot.ts +++ b/packages/server/graphql/mutations/helpers/addRecallBot.ts @@ -1,4 +1,5 @@ import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import RecallAIServerManager from '../../../utils/RecallAIServerManager' const getBotId = async (videoMeetingURL: string) => { @@ -11,6 +12,11 @@ const addRecallBot = async (meetingId: string, videoMeetingURL: string) => { const r = await getRethink() const recallBotId = (await getBotId(videoMeetingURL)) ?? undefined await r.table('NewMeeting').get(meetingId).update({recallBotId, videoMeetingURL}).run() + await getKysely() + .updateTable('NewMeeting') + .set({recallBotId, videoMeetingURL}) + .where('id', '=', meetingId) + .execute() } export default addRecallBot diff --git a/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts b/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts index 570f456e361..bc4aa19f9a4 100644 --- a/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts +++ b/packages/server/graphql/mutations/helpers/endMeeting/sendNewMeetingSummary.ts @@ -1,6 +1,8 @@ +import {sql} from 'kysely' import getRethink from '../../../../database/rethinkDriver' import getMailManager from '../../../../email/getMailManager' import newMeetingSummaryEmailCreator from '../../../../email/newMeetingSummaryEmailCreator' +import getKysely from '../../../../postgres/getKysely' import {AnyMeeting} from '../../../../postgres/types/Meeting' import {GQLContext} from '../../../graphql' import isValid from '../../../isValid' @@ -11,13 +13,19 @@ export default async function sendNewMeetingSummary( ) { const {id: meetingId, teamId, summarySentAt} = newMeeting if (summarySentAt) return + const pg = getKysely() const now = new Date() const r = await getRethink() const {dataLoader} = context const [teamMembers, team] = await Promise.all([ dataLoader.get('teamMembersByTeamId').load(teamId), dataLoader.get('teams').loadNonNull(teamId), - r.table('NewMeeting').get(meetingId).update({summarySentAt: now}).run() + r.table('NewMeeting').get(meetingId).update({summarySentAt: now}).run(), + pg + .updateTable('NewMeeting') + .set({summarySentAt: sql`CURRENT_TIMESTAMP`}) + .where('id', '=', meetingId) + .execute() ]) const {name: teamName, orgId} = team const userIds = teamMembers.map(({userId}) => userId) diff --git a/packages/server/graphql/mutations/helpers/generateGroups.ts b/packages/server/graphql/mutations/helpers/generateGroups.ts index 068e3b176d0..df1d1ea0427 100644 --- a/packages/server/graphql/mutations/helpers/generateGroups.ts +++ b/packages/server/graphql/mutations/helpers/generateGroups.ts @@ -1,5 +1,6 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {AutogroupReflectionGroupType, RetroReflection} from '../../../postgres/types' import {Logger} from '../../../utils/Logger' import OpenAIServerManager from '../../../utils/OpenAIServerManager' @@ -53,6 +54,11 @@ const generateGroups = async ( } const r = await getRethink() + await getKysely() + .updateTable('NewMeeting') + .set({autogroupReflectionGroups: JSON.stringify(autogroupReflectionGroups)}) + .where('id', '=', meetingId) + .execute() const meetingRes = await r .table('NewMeeting') .get(meetingId) diff --git a/packages/server/graphql/mutations/helpers/handleCompletedStage.ts b/packages/server/graphql/mutations/helpers/handleCompletedStage.ts index 0fec6c43d1d..5a88154ce8b 100644 --- a/packages/server/graphql/mutations/helpers/handleCompletedStage.ts +++ b/packages/server/graphql/mutations/helpers/handleCompletedStage.ts @@ -62,6 +62,11 @@ const handleCompletedRetrospectiveStage = async ( } else if (stage.phaseType === GROUP) { const {facilitatorUserId, phases, teamId} = meeting unlockAllStagesForPhase(phases, 'discuss', true) + await pg + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meeting.id) + .execute() await r .table('NewMeeting') .get(meeting.id) diff --git a/packages/server/graphql/mutations/helpers/hideConversionModal.ts b/packages/server/graphql/mutations/helpers/hideConversionModal.ts index df578c85a2c..b36acb05a09 100644 --- a/packages/server/graphql/mutations/helpers/hideConversionModal.ts +++ b/packages/server/graphql/mutations/helpers/hideConversionModal.ts @@ -8,7 +8,8 @@ const hideConversionModal = async (orgId: string, dataLoader: DataLoaderWorker) const {showConversionModal} = organization if (showConversionModal) { const r = await getRethink() - await getKysely() + const pg = getKysely() + await pg .updateTable('Organization') .set({showConversionModal: false}) .where('id', '=', orgId) @@ -25,6 +26,11 @@ const hideConversionModal = async (orgId: string, dataLoader: DataLoaderWorker) meeting.showConversionModal = false }) const meetingIds = activeMeetings.map(({id}) => id) + await pg + .updateTable('NewMeeting') + .set({showConversionModal: false}) + .where('id', 'in', meetingIds) + .execute() await r .table('NewMeeting') .getAll(r.args(meetingIds)) diff --git a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts index 5a10f159e0e..d38ed8dffc4 100644 --- a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts @@ -360,6 +360,11 @@ export const SlackSingleChannelNotifier: NotificationIntegrationHelper { +const removeEmptyTasks = async (meetingId: string, teamId: string) => { const r = await getRethink() - const teamId = await r.table('NewMeeting').get(meetingId)('teamId').run() const createdTasks = await r .table('Task') .getAll(teamId, {index: 'teamId'}) diff --git a/packages/server/graphql/mutations/helpers/removeStagesFromMeetings.ts b/packages/server/graphql/mutations/helpers/removeStagesFromMeetings.ts index 39c5546998e..1fa3d45a48c 100644 --- a/packages/server/graphql/mutations/helpers/removeStagesFromMeetings.ts +++ b/packages/server/graphql/mutations/helpers/removeStagesFromMeetings.ts @@ -1,4 +1,5 @@ import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {DataLoaderWorker} from '../../graphql' import getNextFacilitatorStageAfterStageRemoved from './getNextFacilitatorStageAfterStageRemoved' @@ -11,6 +12,7 @@ const removeStagesFromMeetings = async ( teamId: string, dataLoader: DataLoaderWorker ) => { + const pg = getKysely() const now = new Date() const r = await getRethink() const [activeMeetings, completedMeetings] = await Promise.all([ @@ -20,7 +22,7 @@ const removeStagesFromMeetings = async ( const meetings = activeMeetings.concat(completedMeetings) await Promise.all( - meetings.map((meeting) => { + meetings.map(async (meeting) => { const {id: meetingId, phases} = meeting phases.forEach((phase) => { // do this inside the loop since it's mutative @@ -45,6 +47,11 @@ const removeStagesFromMeetings = async ( } } }) + await pg + .updateTable('NewMeeting') + .set({facilitatorStageId: meeting.facilitatorStageId, phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() return r .table('NewMeeting') .get(meetingId) diff --git a/packages/server/graphql/mutations/helpers/removeTeamMember.ts b/packages/server/graphql/mutations/helpers/removeTeamMember.ts index 8088ee60491..9a9452ba27e 100644 --- a/packages/server/graphql/mutations/helpers/removeTeamMember.ts +++ b/packages/server/graphql/mutations/helpers/removeTeamMember.ts @@ -166,6 +166,11 @@ const removeTeamMember = async ( // member. return } + await pg + .updateTable('NewMeeting') + .set({facilitatorUserId: newFacilitator.userId}) + .where('id', '=', newFacilitator.meetingId) + .execute() await r .table('NewMeeting') .get(newFacilitator.meetingId) diff --git a/packages/server/graphql/mutations/helpers/removeUserFromMeetingStages.ts b/packages/server/graphql/mutations/helpers/removeUserFromMeetingStages.ts index db8324e6865..dff4349cf30 100644 --- a/packages/server/graphql/mutations/helpers/removeUserFromMeetingStages.ts +++ b/packages/server/graphql/mutations/helpers/removeUserFromMeetingStages.ts @@ -1,4 +1,5 @@ import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {DataLoaderWorker} from '../../graphql' import {isEstimateStage} from '../../meetingTypePredicates' @@ -12,6 +13,7 @@ const removeUserFromMeetingStages = async ( teamId: string, dataLoader: DataLoaderWorker ) => { + const pg = getKysely() const now = new Date() const r = await getRethink() const [activeMeetings, completedMeetings] = await Promise.all([ @@ -21,7 +23,7 @@ const removeUserFromMeetingStages = async ( const meetings = activeMeetings.concat(completedMeetings) await Promise.all( - meetings.map((meeting) => { + meetings.map(async (meeting) => { const {id: meetingId, phases} = meeting let isChanged = false phases.forEach((phase) => { @@ -45,6 +47,11 @@ const removeUserFromMeetingStages = async ( } }) if (!isChanged) return Promise.resolve(undefined) + await pg + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() return r .table('NewMeeting') .get(meetingId) diff --git a/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts b/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts index 7c702494396..7bb25288232 100644 --- a/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts +++ b/packages/server/graphql/mutations/helpers/safeCreateTeamPrompt.ts @@ -1,4 +1,3 @@ -import {ParabolR} from '../../../database/rethinkDriver' import MeetingTeamPrompt from '../../../database/types/MeetingTeamPrompt' import TeamPromptResponsesPhase from '../../../database/types/TeamPromptResponsesPhase' import generateUID from '../../../generateUID' @@ -13,18 +12,11 @@ const safeCreateTeamPrompt = async ( name: string, teamId: string, facilitatorId: string, - r: ParabolR, dataLoader: DataLoaderWorker, meetingOverrideProps = {} ) => { const meetingType: MeetingTypeEnum = 'teamPrompt' - const meetingCount = await r - .table('NewMeeting') - .getAll(teamId, {index: 'teamId'}) - .filter({meetingType}) - .count() - .default(0) - .run() + const meetingCount = await dataLoader.get('meetingCount').load({teamId, meetingType}) const meetingId = generateUID() const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) const teamMemberIds = teamMembers.map(({id}) => id) diff --git a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts index 58ca5c03b34..6a0b1e21689 100644 --- a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts @@ -1,3 +1,4 @@ +import {sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {DISCUSS} from 'parabol-client/utils/constants' import getMeetingPhase from 'parabol-client/utils/getMeetingPhase' @@ -36,6 +37,7 @@ const getTranscription = async (recallBotId?: string | null) => { const summarizeRetroMeeting = async (meeting: RetrospectiveMeeting, context: InternalContext) => { const {dataLoader} = context const {id: meetingId, phases, facilitatorUserId, teamId, recallBotId} = meeting + const pg = getKysely() const r = await getRethink() const [reflectionGroups, reflections, sentimentScore] = await Promise.all([ dataLoader.get('retroReflectionGroupsByMeetingId').load(meetingId), @@ -55,17 +57,32 @@ const summarizeRetroMeeting = async (meeting: RetrospectiveMeeting, context: Int await dataLoader.get('commentCountByDiscussionId').loadMany(discussionIds) ).filter(isValid) const commentCount = commentCounts.reduce((cumSum, count) => cumSum + count, 0) + const taskCount = await r + .table('Task') + .getAll(r.args(discussionIds), {index: 'discussionId'}) + .count() + .default(0) + .run() + await pg + .updateTable('NewMeeting') + .set({ + commentCount, + taskCount, + topicCount: reflectionGroupIds.length, + reflectionCount: reflections.length, + sentimentScore, + summary, + transcription + }) + .where('id', '=', meetingId) + .execute() await r .table('NewMeeting') .get(meetingId) .update( { commentCount, - taskCount: r - .table('Task') - .getAll(r.args(discussionIds), {index: 'discussionId'}) - .count() - .default(0) as unknown as number, + taskCount, topicCount: reflectionGroupIds.length, reflectionCount: reflections.length, sentimentScore, @@ -76,7 +93,7 @@ const summarizeRetroMeeting = async (meeting: RetrospectiveMeeting, context: Int ) .run() - dataLoader.get('newMeetings').clear(meetingId) + dataLoader.clearAll('newMeetings') // wait for whole meeting summary to be generated before sending summary email and updating qualAIMeetingCount sendNewMeetingSummary(meeting, context).catch(Logger.log) updateQualAIMeetingsCount(meetingId, teamId, dataLoader) @@ -128,7 +145,17 @@ const safeEndRetrospective = async ({ )('changes')(0)('new_val') .default(null) .run() - + await getKysely() + .updateTable('NewMeeting') + .set({ + endedAt: sql`CURRENT_TIMESTAMP`, + phases: JSON.stringify(phases), + usedReactjis: JSON.stringify(insights.usedReactjis), + engagement: insights.engagement + }) + .where('id', '=', meetingId) + .executeTakeFirst() + dataLoader.clearAll('newMeetings') if (!completedRetrospective) { return standardError(new Error('Completed retrospective meeting does not exist'), { userId: viewerId @@ -145,7 +172,7 @@ const safeEndRetrospective = async ({ dataLoader.get('meetingMembersByMeetingId').load(meetingId), dataLoader.get('teams').loadNonNull(teamId), dataLoader.get('teamMembersByTeamId').load(teamId), - removeEmptyTasks(meetingId), + removeEmptyTasks(meetingId, teamId), dataLoader.get('meetingTemplates').loadNonNull(templateId), updateTeamInsights(teamId, dataLoader) ]) diff --git a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts index e44d2ee81d2..33364edf2fb 100644 --- a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts +++ b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts @@ -1,3 +1,4 @@ +import {sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {checkTeamsLimit} from '../../../billing/helpers/teamLimitsCheck' import getRethink, {ParabolR} from '../../../database/rethinkDriver' @@ -19,10 +20,11 @@ import updateTeamInsights from './updateTeamInsights' const summarizeTeamPrompt = async (meeting: TeamPromptMeeting, context: InternalContext) => { const {dataLoader} = context + const pg = getKysely() const r = await getRethink() const summary = await generateStandupMeetingSummary(meeting, dataLoader) - + await pg.updateTable('NewMeeting').set({summary}).where('id', '=', meeting.id).execute() await r .table('NewMeeting') .get(meeting.id) @@ -31,7 +33,7 @@ const summarizeTeamPrompt = async (meeting: TeamPromptMeeting, context: Internal }) .run() - dataLoader.get('newMeetings').clear(meeting.id) + dataLoader.clearAll('newMeetings') // wait for whole meeting summary to be generated before sending summary email and updating qualAIMeetingCount sendNewMeetingSummary(meeting, context).catch(Logger.log) updateQualAIMeetingsCount(meeting.id, meeting.teamId, dataLoader) @@ -58,6 +60,7 @@ const safeEndTeamPrompt = async ({ context: InternalContext subOptions: SubOptions }) => { + const pg = getKysely() const {dataLoader} = context const {endedAt, id: meetingId, teamId} = meeting @@ -66,6 +69,14 @@ const safeEndTeamPrompt = async ({ // RESOLUTION const insights = await gatherInsights(meeting, dataLoader) + await pg + .updateTable('NewMeeting') + .set({ + endedAt: sql`CURRENT_TIMESTAMP`, + usedReactjis: JSON.stringify(insights.usedReactjis), + engagement: insights.engagement + }) + .execute() const completedTeamPrompt = await r .table('NewMeeting') .get(meetingId) @@ -107,7 +118,6 @@ const safeEndTeamPrompt = async ({ }) ) const timelineEventId = events[0]!.id - const pg = getKysely() await pg.insertInto('TimelineEvent').values(events).execute() summarizeTeamPrompt(meeting, context) analytics.teamPromptEnd(completedTeamPrompt, meetingMembers, responses, dataLoader) diff --git a/packages/server/graphql/mutations/joinMeeting.ts b/packages/server/graphql/mutations/joinMeeting.ts index 99f2e3a61ab..e830e298608 100644 --- a/packages/server/graphql/mutations/joinMeeting.ts +++ b/packages/server/graphql/mutations/joinMeeting.ts @@ -5,7 +5,6 @@ import rMapIf from '../../database/rMapIf' import getRethink from '../../database/rethinkDriver' import ActionMeetingMember from '../../database/types/ActionMeetingMember' import CheckInStage from '../../database/types/CheckInStage' -import {NewMeetingPhaseTypeEnum} from '../../database/types/GenericMeetingPhase' import PokerMeetingMember from '../../database/types/PokerMeetingMember' import RetroMeetingMember from '../../database/types/RetroMeetingMember' import TeamPromptMeetingMember from '../../database/types/TeamPromptMeetingMember' @@ -14,6 +13,7 @@ import UpdatesStage from '../../database/types/UpdatesStage' import getKysely from '../../postgres/getKysely' import {TeamMember} from '../../postgres/types' import {AnyMeeting} from '../../postgres/types/Meeting' +import {NewMeetingPhase, NewMeetingStages} from '../../postgres/types/NewMeetingPhase' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' @@ -91,10 +91,37 @@ const joinMeeting = { const mapIf = rMapIf(r) - const addStageToPhase = ( + const addStageToPhase = async ( stage: CheckInStage | UpdatesStage | TeamPromptResponseStage, - phaseType: NewMeetingPhaseTypeEnum + phaseType: NewMeetingPhase['phaseType'] ) => { + await getKysely() + .transaction() + .execute(async (trx) => { + const meeting = await trx + .selectFrom('NewMeeting') + .select(({fn}) => fn('to_json', ['phases']).as('phases')) + .where('id', '=', meetingId) + .forUpdate() + // NewMeeting: add OrThrow in phase 3 + .executeTakeFirst() + if (!meeting) return + const {phases} = meeting + const phase = getPhase(phases, phaseType) + const stages = phase.stages as NewMeetingStages[] + stages.push({ + ...stage, + isNavigable: true, + isNavigableByFacilitator: true, + // the stage is complete if all other stages are complete & there's at least 1 + isComplete: stages.length >= 1 && stages.every((stage) => stage.isComplete) + }) + await trx + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() + }) return r .table('NewMeeting') .get(meetingId) @@ -160,7 +187,7 @@ const joinMeeting = { // effort is taken here to run both at the same time // so e.g.the 5th person in check-in is the 5th person in updates await Promise.all([appendToCheckin(), appendToUpdate(), appendToTeamPromptResponses()]) - dataLoader.get('newMeetings').clear(meetingId) + dataLoader.clearAll('newMeetings') const data = {meetingId} publish(SubscriptionChannel.MEETING, meetingId, 'JoinMeetingSuccess', data, subOptions) diff --git a/packages/server/graphql/mutations/navigateMeeting.ts b/packages/server/graphql/mutations/navigateMeeting.ts index be704dedc4a..8563af871b1 100644 --- a/packages/server/graphql/mutations/navigateMeeting.ts +++ b/packages/server/graphql/mutations/navigateMeeting.ts @@ -4,6 +4,7 @@ import findStageById from 'parabol-client/utils/meetings/findStageById' import startStage_ from 'parabol-client/utils/startStage_' import unlockNextStages from 'parabol-client/utils/unlockNextStages' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {Logger} from '../../utils/Logger' import {getUserId} from '../../utils/authorization' import publish from '../../utils/publish' @@ -46,7 +47,7 @@ export default { // AUTH const viewerId = getUserId(authToken) - const meeting = await r.table('NewMeeting').get(meetingId).default(null).run() + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) const {createdBy, endedAt, facilitatorUserId, phases, teamId, meetingType} = meeting if (endedAt) { @@ -123,7 +124,15 @@ export default { )('changes')(0)('old_val')('facilitatorStageId') .default(null) .run() - + await getKysely() + .updateTable('NewMeeting') + .set({ + facilitatorStageId: facilitatorStageId ?? undefined, + phases: JSON.stringify(phases) + }) + .where('id', '=', meetingId) + .execute() + dataLoader.clearAll('newMeetings') if (!oldFacilitatorStageId) { return {error: {message: 'Stage already advanced'}} } diff --git a/packages/server/graphql/mutations/payLater.ts b/packages/server/graphql/mutations/payLater.ts index 18ec12d6af9..fa4c0189a89 100644 --- a/packages/server/graphql/mutations/payLater.ts +++ b/packages/server/graphql/mutations/payLater.ts @@ -32,7 +32,7 @@ export default { // AUTH const viewerId = getUserId(authToken) const [meeting, viewer] = await Promise.all([ - r.table('NewMeeting').get(meetingId).run(), + dataLoader.get('newMeetings').load(meetingId), dataLoader.get('users').loadNonNull(viewerId) ]) if (!meeting) { @@ -63,7 +63,12 @@ export default { showConversionModal: false }) .run() - + await getKysely() + .updateTable('NewMeeting') + .set({showConversionModal: false}) + .where('id', '=', meetingId) + .execute() + dataLoader.clearAll('newMeetings') await incrementUserPayLaterClickCountQuery.run({id: viewerId}, getPg()) analytics.conversionModalPayLaterClicked(viewer) diff --git a/packages/server/graphql/mutations/pokerResetDimension.ts b/packages/server/graphql/mutations/pokerResetDimension.ts index c25a460db2c..5dafb36786b 100644 --- a/packages/server/graphql/mutations/pokerResetDimension.ts +++ b/packages/server/graphql/mutations/pokerResetDimension.ts @@ -1,8 +1,10 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' +import {sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' import {RValue} from '../../database/stricterR' import updateStage from '../../database/updateStage' +import getKysely from '../../postgres/getKysely' import removeMeetingTaskEstimates from '../../postgres/queries/removeMeetingTaskEstimates' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' @@ -27,6 +29,7 @@ const pokerResetDimension = { {meetingId, stageId}: {meetingId: string; stageId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { + const pg = getKysely() const viewerId = getUserId(authToken) const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -65,8 +68,10 @@ const pokerResetDimension = { // VALIDATION const estimatePhase = getPhase(phases, 'ESTIMATE') + const estimatePhaseIdx = phases.indexOf(estimatePhase) const {stages} = estimatePhase - const stage = stages.find((stage) => stage.id === stageId) + const stageIdx = stages.findIndex((stage) => stage.id === stageId) + const stage = stages[stageIdx] if (!stage) { return {error: {message: 'Invalid stageId provided'}} } @@ -77,14 +82,26 @@ const pokerResetDimension = { scores: [] } // mutate the cached meeting + Object.assign(stage, updates) const updater = (estimateStage: RValue) => estimateStage.merge(updates) const [meetingMembers, teamMembers] = await Promise.all([ dataLoader.get('meetingMembersByMeetingId').load(meetingId), dataLoader.get('teamMembersByTeamId').load(teamId), updateStage(meetingId, stageId, 'ESTIMATE', updater), + pg + .updateTable('NewMeeting') + .set({ + phases: sql`jsonb_set( + jsonb_set(phases, ${sql.lit(`{${estimatePhaseIdx},stages,${stageIdx},"scores"}`)}, '[]'::jsonb, false), + ${sql.lit(`{${estimatePhaseIdx},stages,${stageIdx},"isVoting"}`)}, 'true'::jsonb, false + )` + }) + .where('id', '=', meetingId) + .execute(), removeMeetingTaskEstimates(meetingId, stageId) ]) + dataLoader.clearAll('newMeetings') const data = {meetingId, stageId} sendPokerMeetingRevoteEvent(meeting, teamMembers, meetingMembers, dataLoader) diff --git a/packages/server/graphql/mutations/pokerRevealVotes.ts b/packages/server/graphql/mutations/pokerRevealVotes.ts index 50c9620df76..1c0f22fc51d 100644 --- a/packages/server/graphql/mutations/pokerRevealVotes.ts +++ b/packages/server/graphql/mutations/pokerRevealVotes.ts @@ -1,9 +1,11 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' +import {sql} from 'kysely' import {PokerCards, SubscriptionChannel} from 'parabol-client/types/constEnums' import {RValue} from '../../database/stricterR' import EstimateUserScore from '../../database/types/EstimateUserScore' import PokerMeetingMember from '../../database/types/PokerMeetingMember' import updateStage from '../../database/updateStage' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' import publish from '../../utils/publish' @@ -26,6 +28,7 @@ const pokerRevealVotes = { {meetingId, stageId}: {meetingId: string; stageId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { + const pg = getKysely() const viewerId = getUserId(authToken) const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -67,8 +70,10 @@ const pokerRevealVotes = { // VALIDATION const estimatePhase = getPhase(phases, 'ESTIMATE') + const estimatePhaseIdx = phases.indexOf(estimatePhase) const {stages} = estimatePhase - const stage = stages.find((stage) => stage.id === stageId) + const stageIdx = stages.findIndex((stage) => stage.id === stageId) + const stage = stages[stageIdx] if (!stage) { return {error: {message: 'Invalid stageId provided'}} } @@ -93,7 +98,18 @@ const pokerRevealVotes = { // note that a race condition exists here. it's possible that i cast my vote after the meeting is fetched but before this update & that'll be overwritten scores }) + await pg + .updateTable('NewMeeting') + .set({ + phases: sql`jsonb_set( + jsonb_set(phases, ${sql.lit(`{${estimatePhaseIdx},stages,${stageIdx},"scores"}`)}, ${JSON.stringify(scores)}::jsonb, false), + ${sql.lit(`{${estimatePhaseIdx},stages,${stageIdx},"isVoting"}`)}, 'false'::jsonb, false + )` + }) + .where('id', '=', meetingId) + .execute() await updateStage(meetingId, stageId, 'ESTIMATE', updater) + dataLoader.clearAll('newMeetings') const data = {meetingId, stageId} publish(SubscriptionChannel.MEETING, meetingId, 'PokerRevealVotesSuccess', data, subOptions) return data diff --git a/packages/server/graphql/mutations/promoteNewMeetingFacilitator.ts b/packages/server/graphql/mutations/promoteNewMeetingFacilitator.ts index 1d93c486ad1..ce3a30827f9 100644 --- a/packages/server/graphql/mutations/promoteNewMeetingFacilitator.ts +++ b/packages/server/graphql/mutations/promoteNewMeetingFacilitator.ts @@ -1,6 +1,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -31,7 +32,7 @@ export default { const viewerId = getUserId(authToken) // AUTH - const meeting = await r.table('NewMeeting').get(meetingId).default(null).run() + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) const {facilitatorUserId: oldFacilitatorUserId, teamId, endedAt} = meeting if (!isTeamMember(authToken, teamId)) { @@ -59,7 +60,12 @@ export default { updatedAt: now }) .run() - + await getKysely() + .updateTable('NewMeeting') + .set({facilitatorUserId}) + .where('id', '=', meetingId) + .execute() + dataLoader.clearAll('newMeetings') const data = {meetingId, oldFacilitatorUserId} publish( SubscriptionChannel.MEETING, diff --git a/packages/server/graphql/mutations/removeReflection.ts b/packages/server/graphql/mutations/removeReflection.ts index 83460fccc5d..b864cfff51a 100644 --- a/packages/server/graphql/mutations/removeReflection.ts +++ b/packages/server/graphql/mutations/removeReflection.ts @@ -68,6 +68,11 @@ export default { phases }) .run() + await getKysely() + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() } const data = {meetingId, reflectionId, unlockedStageIds} publish(SubscriptionChannel.MEETING, meetingId, 'RemoveReflectionPayload', data, subOptions) diff --git a/packages/server/graphql/mutations/renameMeeting.ts b/packages/server/graphql/mutations/renameMeeting.ts index 09c207b70f2..a4d2263145b 100644 --- a/packages/server/graphql/mutations/renameMeeting.ts +++ b/packages/server/graphql/mutations/renameMeeting.ts @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import linkify from 'parabol-client/utils/linkify' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -61,7 +62,7 @@ const renameMeeting = { name }) .run() - + await getKysely().updateTable('NewMeeting').set({name}).where('id', '=', meetingId).execute() const data = {meetingId} IntegrationNotifier.updateMeeting?.(dataLoader, meetingId, teamId) publish(SubscriptionChannel.TEAM, teamId, 'RenameMeetingSuccess', data, subOptions) diff --git a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts index f9c202d981d..6985db0c274 100644 --- a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts +++ b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts @@ -104,18 +104,33 @@ const resetRetroMeetingToGroupStage = { reflectionGroups.forEach((rg) => (rg.voterIds = [])) await Promise.all([ - pg.deleteFrom('Comment').where('discussionId', 'in', discussionIdsToDelete).execute(), - r.table('Task').getAll(r.args(discussionIdsToDelete), {index: 'discussionId'}).delete().run(), pg - .updateTable('RetroReflectionGroup') - .set({voterIds: [], discussionPromptQuestion: null}) - .where('id', 'in', reflectionGroupIds) + .with('DeleteComments', (qb) => + qb.deleteFrom('Comment').where('discussionId', 'in', discussionIdsToDelete) + ) + .with('ResetGroups', (qb) => + qb + .updateTable('RetroReflectionGroup') + .set({voterIds: [], discussionPromptQuestion: null}) + .where('id', 'in', reflectionGroupIds) + ) + .updateTable('NewMeeting') + .set({phases: JSON.stringify(newPhases)}) + .where('id', '=', meetingId) .execute(), + r.table('Task').getAll(r.args(discussionIdsToDelete), {index: 'discussionId'}).delete().run(), r.table('NewMeeting').get(meetingId).update({phases: newPhases}).run(), (r.table('MeetingMember').getAll(meetingId, {index: 'meetingId'}) as any) .update({votesRemaining: meeting.totalVotes}) .run() ]) + dataLoader.clearAll([ + 'newMeetings', + 'comments', + 'retroReflectionGroups', + 'tasks', + 'meetingMembers' + ]) const data = { meetingId } diff --git a/packages/server/graphql/mutations/setPhaseFocus.ts b/packages/server/graphql/mutations/setPhaseFocus.ts index dcd45ac0b33..301502c2aa6 100644 --- a/packages/server/graphql/mutations/setPhaseFocus.ts +++ b/packages/server/graphql/mutations/setPhaseFocus.ts @@ -3,6 +3,7 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {GROUP} from 'parabol-client/utils/constants' import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId} from '../../utils/authorization' import getPhase from '../../utils/getPhase' import publish from '../../utils/publish' @@ -33,7 +34,7 @@ const setPhaseFocus = { // AUTH const viewerId = getUserId(authToken) - const meeting = await r.table('NewMeeting').get(meetingId).default(null).run() + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) const {endedAt, facilitatorUserId, phases} = meeting if (endedAt) return standardError(new Error('Meeting already completed'), {userId: viewerId}) @@ -52,6 +53,12 @@ const setPhaseFocus = { // mutative reflectPhase.focusedPromptId = focusedPromptId ?? undefined await r.table('NewMeeting').get(meetingId).update(meeting).run() + await getKysely() + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() + dataLoader.clearAll('newMeetings') const data = {meetingId} publish(SubscriptionChannel.MEETING, meetingId, 'SetPhaseFocusPayload', data, subOptions) return data diff --git a/packages/server/graphql/mutations/setStageTimer.ts b/packages/server/graphql/mutations/setStageTimer.ts index 79c8bfc1593..6e93cdd39ff 100644 --- a/packages/server/graphql/mutations/setStageTimer.ts +++ b/packages/server/graphql/mutations/setStageTimer.ts @@ -45,6 +45,7 @@ export default { }: {scheduledEndTime: Date | null; meetingId: string; timeRemaining: number | null}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { + const pg = getKysely() const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -115,7 +116,11 @@ export default { updatedAt: now }) .run() - + await pg + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() const data = {meetingId, stageId: facilitatorStageId} const {isAsync, phaseType, startAt, viewCount} = stage const stoppedOrStarted = newScheduledEndTime ? `Meeting Timer Started` : `Meeting Timer Stopped` diff --git a/packages/server/graphql/mutations/startSprintPoker.ts b/packages/server/graphql/mutations/startSprintPoker.ts index ece86c98986..abb727757e7 100644 --- a/packages/server/graphql/mutations/startSprintPoker.ts +++ b/packages/server/graphql/mutations/startSprintPoker.ts @@ -97,6 +97,7 @@ export default { }: {teamId: string; name: string | null | undefined; gcalInput?: CreateGcalEventInputType}, {authToken, socketId: mutatorId, dataLoader}: GQLContext ) { + const pg = getKysely() const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -116,13 +117,7 @@ export default { // RESOLUTION const meetingId = generateUID() - const meetingCount = await r - .table('NewMeeting') - .getAll(teamId, {index: 'teamId'}) - .filter({meetingType}) - .count() - .default(0) - .run() + const meetingCount = await dataLoader.get('meetingCount').load({teamId, meetingType}) const phases = await createNewMeetingPhases( viewerId, @@ -153,11 +148,15 @@ export default { }) as PokerMeeting const template = await dataLoader.get('meetingTemplates').load(selectedTemplateId) - await Promise.all([ + await Promise.allSettled([ + pg + .insertInto('NewMeeting') + .values({...meeting, phases: JSON.stringify(phases)}) + .execute(), r.table('NewMeeting').insert(meeting).run(), updateMeetingTemplateLastUsedAt(selectedTemplateId, teamId) ]) - + dataLoader.clearAll('newMeetings') // Disallow accidental starts (2 meetings within 2 seconds) const newActiveMeetings = await dataLoader.get('activeMeetingsByTeamId').load(teamId) const otherActiveMeeting = newActiveMeetings.find((activeMeeting) => { diff --git a/packages/server/graphql/mutations/updateNewCheckInQuestion.ts b/packages/server/graphql/mutations/updateNewCheckInQuestion.ts index 5a5935be3f8..0446229d527 100644 --- a/packages/server/graphql/mutations/updateNewCheckInQuestion.ts +++ b/packages/server/graphql/mutations/updateNewCheckInQuestion.ts @@ -4,6 +4,7 @@ import convertToTaskContent from 'parabol-client/utils/draftjs/convertToTaskCont import {makeCheckinQuestion} from 'parabol-client/utils/makeCheckinGreeting' import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' import publish from '../../utils/publish' @@ -29,6 +30,7 @@ export default { {meetingId, checkInQuestion}: {meetingId: string; checkInQuestion: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { + const pg = getKysely() const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -36,7 +38,7 @@ export default { const viewerId = getUserId(authToken) // AUTH - const meeting = await r.table('NewMeeting').get(meetingId).run() + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) const {endedAt, phases, teamId} = meeting if (!isTeamMember(authToken, teamId)) { @@ -64,7 +66,12 @@ export default { updatedAt: now }) .run() - + await pg + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() + dataLoader.clearAll('newMeetings') const data = {meetingId} publish( SubscriptionChannel.MEETING, diff --git a/packages/server/graphql/mutations/updatePokerScope.ts b/packages/server/graphql/mutations/updatePokerScope.ts index 0e59ab72161..f104dc07282 100644 --- a/packages/server/graphql/mutations/updatePokerScope.ts +++ b/packages/server/graphql/mutations/updatePokerScope.ts @@ -42,6 +42,7 @@ const updatePokerScope = { {meetingId, updates}: {meetingId: string; updates: TUpdatePokerScopeItemInput[]}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { + const pg = getKysely() const r = await getRethink() const redis = getRedis() const viewerId = getUserId(authToken) @@ -156,6 +157,15 @@ const updatePokerScope = { if (stages.length > Threshold.MAX_POKER_STORIES * dimensions.length) { return {error: {message: 'Story limit reached'}} } + + await pg + .updateTable('NewMeeting') + .set({ + facilitatorStageId: meeting.facilitatorStageId, + phases: JSON.stringify(phases) + }) + .where('id', '=', meetingId) + .execute() await r .table('NewMeeting') .get(meetingId) @@ -168,6 +178,7 @@ const updatePokerScope = { if (newDiscussions.length > 0) { await getKysely().insertInto('Discussion').values(newDiscussions).execute() } + dataLoader.clearAll(['newMeetings']) const data = {meetingId, newStageIds} publish(SubscriptionChannel.MEETING, meetingId, 'UpdatePokerScopeSuccess', data, subOptions) return data diff --git a/packages/server/graphql/mutations/updateRetroMaxVotes.ts b/packages/server/graphql/mutations/updateRetroMaxVotes.ts index be2c7749cb6..329ce6646fd 100644 --- a/packages/server/graphql/mutations/updateRetroMaxVotes.ts +++ b/packages/server/graphql/mutations/updateRetroMaxVotes.ts @@ -43,7 +43,7 @@ const updateRetroMaxVotes = { const subOptions = {mutatorId, operationId} //AUTH - const meeting = await r.table('NewMeeting').get(meetingId).run() + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (!meeting) { return {error: {message: 'Meeting not found'}} @@ -140,6 +140,12 @@ const updateRetroMaxVotes = { // RESOLUTION await Promise.all([ getKysely() + .with('MeetingUpdates', (qb) => + qb + .updateTable('NewMeeting') + .set({totalVotes, maxVotesPerGroup}) + .where('id', '=', meetingId) + ) .updateTable('MeetingSettings') .set({ totalVotes, @@ -157,7 +163,7 @@ const updateRetroMaxVotes = { }) .run() ]) - + dataLoader.get('newMeetings').clear(meetingId) const data = {meetingId} publish(SubscriptionChannel.MEETING, meetingId, 'UpdateRetroMaxVotesSuccess', data, subOptions) return data diff --git a/packages/server/graphql/mutations/voteForPokerStory.ts b/packages/server/graphql/mutations/voteForPokerStory.ts index 15705e31292..79e4012c8a4 100644 --- a/packages/server/graphql/mutations/voteForPokerStory.ts +++ b/packages/server/graphql/mutations/voteForPokerStory.ts @@ -4,6 +4,8 @@ import getRethink from '../../database/rethinkDriver' import {RValue} from '../../database/stricterR' import EstimateUserScore from '../../database/types/EstimateUserScore' import updateStage from '../../database/updateStage' +import getKysely from '../../postgres/getKysely' +import {NewMeetingPhase} from '../../postgres/types/NewMeetingPhase' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' import publish from '../../utils/publish' @@ -11,6 +13,29 @@ import {GQLContext} from '../graphql' import VoteForPokerStoryPayload from '../types/VoteForPokerStoryPayload' export const removeVoteForUserId = async (userId: string, stageId: string, meetingId: string) => { + await getKysely() + .transaction() + .execute(async (trx) => { + const meeting = await trx + .selectFrom('NewMeeting') + .select(({fn}) => fn('to_json', ['phases']).as('phases')) + .where('id', '=', meetingId) + .forUpdate() + // NewMeeting: add OrThrow in phase 3 + .executeTakeFirst() + if (!meeting) return + const {phases} = meeting + const phase = getPhase(phases, 'ESTIMATE') + const {stages} = phase + const stage = stages.find((stage) => stage.id === stageId)! + const {scores} = stage + stage.scores = scores.filter((score) => score.userId !== userId) + await trx + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() + }) const updater = (estimateStage: RValue) => estimateStage.merge({ scores: estimateStage('scores').deleteAt( @@ -23,6 +48,30 @@ export const removeVoteForUserId = async (userId: string, stageId: string, meeti } const upsertVote = async (vote: EstimateUserScore, stageId: string, meetingId: string) => { + await getKysely() + .transaction() + .execute(async (trx) => { + const meeting = await trx + .selectFrom('NewMeeting') + .select(({fn}) => fn('to_json', ['phases']).as('phases')) + .where('id', '=', meetingId) + .forUpdate() + // NewMeeting: add OrThrow in phase 3 + .executeTakeFirst() + if (!meeting) return + const {phases} = meeting + const phase = getPhase(phases, 'ESTIMATE') + const {stages} = phase + const stage = stages.find((stage) => stage.id === stageId)! + const {scores} = stage + stage.scores = [...scores.filter((score) => score.userId !== vote.userId), vote] + await trx + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() + }) + const r = await getRethink() const updater = (estimateStage: RValue) => estimateStage.merge({ diff --git a/packages/server/graphql/mutations/voteForReflectionGroup.ts b/packages/server/graphql/mutations/voteForReflectionGroup.ts index 171cf1f451e..6afe994dee6 100644 --- a/packages/server/graphql/mutations/voteForReflectionGroup.ts +++ b/packages/server/graphql/mutations/voteForReflectionGroup.ts @@ -42,7 +42,7 @@ export default { }) } const {meetingId} = reflectionGroup - const meeting = await r.table('NewMeeting').get(meetingId).run() + const meeting = await dataLoader.get('newMeetings').load(meetingId) if (meeting.meetingType !== 'retrospective') { return {error: {message: 'Meeting type is not retrospective'}} } diff --git a/packages/server/graphql/private/mutations/generateMeetingSummary.ts b/packages/server/graphql/private/mutations/generateMeetingSummary.ts index 3640fe0541e..0d5d21ede25 100644 --- a/packages/server/graphql/private/mutations/generateMeetingSummary.ts +++ b/packages/server/graphql/private/mutations/generateMeetingSummary.ts @@ -169,6 +169,11 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn if (!newSummary) return null const now = new Date() + await getKysely() + .updateTable('NewMeeting') + .set({summary: newSummary}) + .where('id', '=', meeting.id) + .execute() await r .table('NewMeeting') .get(meeting.id) diff --git a/packages/server/graphql/private/mutations/hardDeleteUser.ts b/packages/server/graphql/private/mutations/hardDeleteUser.ts index 2c9bd2e58c0..27995849eea 100644 --- a/packages/server/graphql/private/mutations/hardDeleteUser.ts +++ b/packages/server/graphql/private/mutations/hardDeleteUser.ts @@ -1,7 +1,7 @@ import getRethink from '../../../database/rethinkDriver' import {RValue} from '../../../database/stricterR' import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' -import getPg from '../../../postgres/getPg' +import getKysely from '../../../postgres/getKysely' import {getUserByEmail} from '../../../postgres/queries/getUsersByEmails' import {getUserById} from '../../../postgres/queries/getUsersByIds' import blacklistJWT from '../../../utils/blacklistJWT' @@ -15,6 +15,7 @@ const setFacilitatedUserIdOrDelete = async ( teamIds: string[], dataLoader: DataLoaderInstance ) => { + const pg = getKysely() const r = await getRethink() const facilitatedMeetings = await r .table('NewMeeting') @@ -26,6 +27,11 @@ const setFacilitatedUserIdOrDelete = async ( const meetingMembers = await dataLoader.get('meetingMembersByMeetingId').load(meetingId) const otherMember = meetingMembers.find(({userId}) => userId !== userIdToDelete) if (otherMember) { + await pg + .updateTable('NewMeeting') + .set({facilitatorUserId: otherMember.userId}) + .where('id', '=', meetingId) + .execute() await r .table('NewMeeting') .get(meetingId) @@ -34,6 +40,7 @@ const setFacilitatedUserIdOrDelete = async ( }) .run() } else { + await pg.deleteFrom('NewMeeting').where('id', '=', meetingId).execute() // single-person meeting must be deleted because facilitatorUserId must be non-null await r.table('NewMeeting').get(meetingId).delete().run() } @@ -53,7 +60,7 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( return {error: {message: 'Provide a userId or email'}} } const r = await getRethink() - const pg = getPg() + const pg = getKysely() const user = userId ? await getUserById(userId) : email ? await getUserByEmail(email) : null if (!user) { @@ -69,16 +76,24 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( const teamIds = teamMembers.map(({teamId}) => teamId) const meetingIds = meetingMembers.map(({meetingId}) => meetingId) - const discussions = await pg.query(`SELECT "id" FROM "Discussion" WHERE "teamId" = ANY ($1);`, [ - teamIds - ]) - const teamDiscussionIds = discussions.rows.map(({id}) => id) + const discussions = await pg + .selectFrom('Discussion') + .select('id') + .where('id', 'in', teamIds) + .execute() + const teamDiscussionIds = discussions.map(({id}) => id) // soft delete first for side effects await softDeleteUser(userIdToDelete, dataLoader) // all other writes await setFacilitatedUserIdOrDelete(userIdToDelete, teamIds, dataLoader) + await pg + .updateTable('NewMeeting') + .set({createdBy: null}) + .where('teamId', 'in', teamIds) + .where('createdBy', '=', userIdToDelete) + .execute() await r({ nullifyCreatedBy: r .table('NewMeeting') @@ -107,24 +122,29 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( // now postgres, after FKs are added then triggers should take care of children // TODO when we're done migrating to PG, these should have constraints that ON DELETE CASCADE - await Promise.all([ - pg.query(`DELETE FROM "AtlassianAuth" WHERE "userId" = $1`, [userIdToDelete]), - pg.query(`DELETE FROM "GitHubAuth" WHERE "userId" = $1`, [userIdToDelete]), - pg.query( - `DELETE FROM "TaskEstimate" WHERE "meetingId" = ANY($1::varchar[]) AND "userId" = $2`, - [meetingIds, userIdToDelete] - ), - pg.query( - `DELETE FROM "Poll" WHERE "discussionId" = ANY($1::varchar[]) AND "createdById" = $2`, - [teamDiscussionIds, userIdToDelete] + await pg + .with('AtlassianAuthDelete', (qb) => + qb.deleteFrom('AtlassianAuth').where('userId', '=', userIdToDelete) ) - ]) + .with('GitHubAuthDelete', (qb) => + qb.deleteFrom('GitHubAuth').where('userId', '=', userIdToDelete) + ) + .with('TaskEstimateDelete', (qb) => + qb + .deleteFrom('TaskEstimate') + .where('userId', '=', userIdToDelete) + .where('meetingId', 'in', meetingIds) + ) + .deleteFrom('Poll') + .where('discussionId', 'in', teamDiscussionIds) + .where('createdById', '=', userIdToDelete) + .execute() // Send metrics to HubSpot before the user is really deleted in DB await sendAccountRemovedEvent(userIdToDelete, user.email, reasonText ?? '') // User needs to be deleted after children - await pg.query(`DELETE FROM "User" WHERE "id" = $1`, [userIdToDelete]) + await pg.deleteFrom('User').where('id', '=', userIdToDelete).execute() await blacklistJWT(userIdToDelete, toEpochSeconds(new Date())) return {} diff --git a/packages/server/graphql/private/mutations/processRecurrence.ts b/packages/server/graphql/private/mutations/processRecurrence.ts index f27134e03e1..6d0b2099e3c 100644 --- a/packages/server/graphql/private/mutations/processRecurrence.ts +++ b/packages/server/graphql/private/mutations/processRecurrence.ts @@ -6,6 +6,7 @@ import {DateTime, RRuleSet} from 'rrule-rust' import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' import {fromDateTime, toDateTime} from '../../../../client/shared/rruleUtil' import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {getActiveMeetingSeries} from '../../../postgres/queries/getActiveMeetingSeries' import {RetrospectiveMeeting, TeamPromptMeeting} from '../../../postgres/types/Meeting' import {MeetingSeries} from '../../../postgres/types/MeetingSeries' @@ -30,6 +31,7 @@ const startRecurringMeeting = async ( dataLoader: DataLoaderWorker, subOptions: SubOptions ) => { + const pg = getKysely() const r = await getRethink() const {id: meetingSeriesId, teamId, facilitatorId, meetingType} = meetingSeries @@ -52,18 +54,15 @@ const startRecurringMeeting = async ( const meeting = await (async () => { if (meetingSeries.meetingType === 'teamPrompt') { const teamPromptMeeting = lastMeeting as TeamPromptMeeting | null - const meeting = await safeCreateTeamPrompt( - meetingName, - teamId, - facilitatorId, - r, - dataLoader, - { - scheduledEndTime, - meetingSeriesId: meetingSeries.id, - meetingPrompt: teamPromptMeeting?.meetingPrompt ?? DEFAULT_PROMPT - } - ) + const meeting = await safeCreateTeamPrompt(meetingName, teamId, facilitatorId, dataLoader, { + scheduledEndTime, + meetingSeriesId: meetingSeries.id, + meetingPrompt: teamPromptMeeting?.meetingPrompt ?? DEFAULT_PROMPT + }) + await pg + .insertInto('NewMeeting') + .values({...meeting, phases: JSON.stringify(meeting.phases)}) + .execute() await r.table('NewMeeting').insert(meeting).run() const data = {teamId, meetingId: meeting.id} publish(SubscriptionChannel.TEAM, teamId, 'StartTeamPromptSuccess', data, subOptions) @@ -90,6 +89,10 @@ const startRecurringMeeting = async ( dataLoader ) await r.table('NewMeeting').insert(meeting).run() + await pg + .insertInto('NewMeeting') + .values({...meeting, phases: JSON.stringify(meeting.phases)}) + .execute() const data = {teamId, meetingId: meeting.id} publish(SubscriptionChannel.TEAM, teamId, 'StartRetrospectiveSuccess', data, subOptions) return meeting diff --git a/packages/server/graphql/public/mutations/autogroup.ts b/packages/server/graphql/public/mutations/autogroup.ts index 182b6910563..21db1e150a7 100644 --- a/packages/server/graphql/public/mutations/autogroup.ts +++ b/packages/server/graphql/public/mutations/autogroup.ts @@ -1,5 +1,6 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {analytics} from '../../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -13,6 +14,7 @@ const autogroup: MutationResolvers['autogroup'] = async ( {meetingId}: {meetingId: string}, context: GQLContext ) => { + const pg = getKysely() const r = await getRethink() const {authToken, dataLoader, socketId: mutatorId} = context const viewerId = getUserId(authToken) @@ -70,7 +72,12 @@ const autogroup: MutationResolvers['autogroup'] = async ( ) ) }), - r.table('NewMeeting').get(meetingId).update({resetReflectionGroups}).run() + r.table('NewMeeting').get(meetingId).update({resetReflectionGroups}).run(), + pg + .updateTable('NewMeeting') + .set({resetReflectionGroups: JSON.stringify(resetReflectionGroups)}) + .where('id', '=', meetingId) + .execute() ]) meeting.resetReflectionGroups = resetReflectionGroups analytics.suggestGroupsClicked(viewer, meetingId, teamId) diff --git a/packages/server/graphql/public/mutations/modifyCheckInQuestion.ts b/packages/server/graphql/public/mutations/modifyCheckInQuestion.ts index 18149f77431..ec00936901a 100644 --- a/packages/server/graphql/public/mutations/modifyCheckInQuestion.ts +++ b/packages/server/graphql/public/mutations/modifyCheckInQuestion.ts @@ -3,7 +3,6 @@ import {getUserId, isTeamMember} from '../../../utils/authorization' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import publish from '../../../utils/publish' -import getRethink from '../../../database/rethinkDriver' import OpenAIServerManager from '../../../utils/OpenAIServerManager' import {analytics} from '../../../utils/analytics/analytics' import standardError from '../../../utils/standardError' @@ -14,14 +13,13 @@ const modifyCheckInQuestion: MutationResolvers['modifyCheckInQuestion'] = async {meetingId, checkInQuestion, modifyType}, {authToken, dataLoader, socketId: mutatorId} ) => { - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const viewerId = getUserId(authToken) // AUTH const [meeting, viewer] = await Promise.all([ - r.table('NewMeeting').get(meetingId).run(), + dataLoader.get('newMeetings').load(meetingId), dataLoader.get('users').loadNonNull(viewerId) ]) if (!meeting) return standardError(new Error('Meeting not found'), {userId: viewerId}) diff --git a/packages/server/graphql/public/mutations/resetReflectionGroups.ts b/packages/server/graphql/public/mutations/resetReflectionGroups.ts index 4f574390d0f..991eea4ddda 100644 --- a/packages/server/graphql/public/mutations/resetReflectionGroups.ts +++ b/packages/server/graphql/public/mutations/resetReflectionGroups.ts @@ -1,5 +1,6 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {analytics} from '../../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -21,6 +22,7 @@ const resetReflectionGroups: MutationResolvers['resetReflectionGroups'] = async {meetingId}: {meetingId: string}, context: GQLContext ) => { + const pg = getKysely() const {authToken, dataLoader, socketId: mutatorId} = context const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -75,6 +77,11 @@ const resetReflectionGroups: MutationResolvers['resetReflectionGroups'] = async .get(meetingId) .replace(r.row.without('resetReflectionGroups') as any) .run() + await pg + .updateTable('NewMeeting') + .set({resetReflectionGroups: null}) + .where('id', '=', meetingId) + .execute() meeting.resetReflectionGroups = null analytics.resetGroupsClicked(viewer, meetingId, teamId) const data = {meetingId} diff --git a/packages/server/graphql/public/mutations/revealTeamHealthVotes.ts b/packages/server/graphql/public/mutations/revealTeamHealthVotes.ts index ba88c567f35..86bbbb99744 100644 --- a/packages/server/graphql/public/mutations/revealTeamHealthVotes.ts +++ b/packages/server/graphql/public/mutations/revealTeamHealthVotes.ts @@ -1,5 +1,7 @@ +import {sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import updateStage from '../../../database/updateStage' +import getKysely from '../../../postgres/getKysely' import {getUserId, isTeamMember} from '../../../utils/authorization' import getPhase from '../../../utils/getPhase' import publish from '../../../utils/publish' @@ -10,6 +12,7 @@ const revealTeamHealthVotes: MutationResolvers['revealTeamHealthVotes'] = async {meetingId, stageId}, {authToken, dataLoader, socketId: mutatorId} ) => { + const pg = getKysely() const viewerId = getUserId(authToken) const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -41,7 +44,10 @@ const revealTeamHealthVotes: MutationResolvers['revealTeamHealthVotes'] = async // VALIDATION const teamHealthPhase = getPhase(phases, 'TEAM_HEALTH') const {stages} = teamHealthPhase - const stage = stages.find((stage) => stage.id === stageId) + const stageIdx = stages.findIndex((stage) => stage.id === stageId) + const phaseIdx = phases.indexOf(teamHealthPhase) + const stage = stages[stageIdx] + if (!stage || stage.phaseType !== 'TEAM_HEALTH') { return {error: {message: 'Invalid stageId provided'}} } @@ -49,6 +55,13 @@ const revealTeamHealthVotes: MutationResolvers['revealTeamHealthVotes'] = async return {error: {message: 'Votes are already revealed'}} } + await pg + .updateTable('NewMeeting') + .set({ + phases: sql`jsonb_set(phases, ${sql.lit(`{${phaseIdx},stages,${stageIdx},"isRevealed"}`)}, 'true'::jsonb, false)` + }) + .where('id', '=', meetingId) + .execute() updateStage(meetingId, stageId, 'TEAM_HEALTH', (stage) => stage.merge({isRevealed: true})) stage.isRevealed = true diff --git a/packages/server/graphql/public/mutations/setTeamHealthVote.ts b/packages/server/graphql/public/mutations/setTeamHealthVote.ts index 7f3fe36af53..68198ba59cf 100644 --- a/packages/server/graphql/public/mutations/setTeamHealthVote.ts +++ b/packages/server/graphql/public/mutations/setTeamHealthVote.ts @@ -13,7 +13,6 @@ import {MutationResolvers} from '../resolverTypes' const upsertVote = async (meetingId: string, stageId: string, newVote: TeamHealthVote) => { const pg = getKysely() await pg.transaction().execute(async (trx) => { - // console.log('start transaction', newVote) const meeting = await trx .selectFrom('NewMeeting') .select(({fn}) => fn('to_json', ['phases']).as('phases')) @@ -22,7 +21,6 @@ const upsertVote = async (meetingId: string, stageId: string, newVote: TeamHealt // NewMeeting: add OrThrow in phase 3 .executeTakeFirst() if (!meeting) return - // console.log('got lock', newVote) const {phases} = meeting const phase = getPhase(phases, 'TEAM_HEALTH') const {stages} = phase @@ -39,7 +37,6 @@ const upsertVote = async (meetingId: string, stageId: string, newVote: TeamHealt .set({phases: JSON.stringify(phases)}) .where('id', '=', meetingId) .execute() - // console.log('wrote update, commit', newVote) }) const r = await getRethink() const updater = (stage: RValue) => diff --git a/packages/server/graphql/public/mutations/startCheckIn.ts b/packages/server/graphql/public/mutations/startCheckIn.ts index 62086ac17ba..3714e6a2042 100644 --- a/packages/server/graphql/public/mutations/startCheckIn.ts +++ b/packages/server/graphql/public/mutations/startCheckIn.ts @@ -22,6 +22,7 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( {teamId, name, gcalInput}, context ) => { + const pg = getKysely() const r = await getRethink() const {authToken, socketId: mutatorId, dataLoader} = context const operationId = dataLoader.share() @@ -40,13 +41,7 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( const meetingType: MeetingTypeEnum = 'action' // RESOLUTION - const meetingCount = await r - .table('NewMeeting') - .getAll(teamId, {index: 'teamId'}) - .filter({meetingType}) - .count() - .default(0) - .run() + const meetingCount = await dataLoader.get('meetingCount').load({teamId, meetingType}) const meetingId = generateUID() const phases = await createNewMeetingPhases( @@ -67,7 +62,10 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( facilitatorUserId: viewerId }) as CheckInMeeting await r.table('NewMeeting').insert(meeting).run() - + await pg + .insertInto('NewMeeting') + .values({...meeting, phases: JSON.stringify(phases)}) + .execute() // Disallow 2 active check-in meetings const newActiveMeetings = await dataLoader.get('activeMeetingsByTeamId').load(teamId) const otherActiveMeeting = newActiveMeetings.find((activeMeeting) => { @@ -79,6 +77,7 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( await r.table('NewMeeting').get(meetingId).delete().run() return {error: {message: 'Meeting already started'}} } + dataLoader.clearAll('newMeetings') const agendaItems = await dataLoader.get('agendaItemsByTeamId').load(teamId) const agendaItemIds = agendaItems.map(({id}) => id) diff --git a/packages/server/graphql/public/mutations/startRetrospective.ts b/packages/server/graphql/public/mutations/startRetrospective.ts index 541e983f79b..a27d4cb4b0d 100644 --- a/packages/server/graphql/public/mutations/startRetrospective.ts +++ b/packages/server/graphql/public/mutations/startRetrospective.ts @@ -74,8 +74,12 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( const meetingId = meeting.id const template = await dataLoader.get('meetingTemplates').load(selectedTemplateId) - await Promise.all([ + await Promise.allSettled([ r.table('NewMeeting').insert(meeting).run(), + pg + .insertInto('NewMeeting') + .values({...meeting, phases: JSON.stringify(meeting.phases)}) + .execute(), updateMeetingTemplateLastUsedAt(selectedTemplateId, teamId) ]) @@ -87,6 +91,7 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( return createdAt.getTime() > Date.now() - DUPLICATE_THRESHOLD }) if (otherActiveMeeting) { + // trigger exists in PG to prevent this await r.table('NewMeeting').get(meetingId).delete().run() return {error: {message: 'Meeting already started'}} } diff --git a/packages/server/graphql/public/mutations/startTeamPrompt.ts b/packages/server/graphql/public/mutations/startTeamPrompt.ts index 322ee844986..0f22ea40ab6 100644 --- a/packages/server/graphql/public/mutations/startTeamPrompt.ts +++ b/packages/server/graphql/public/mutations/startTeamPrompt.ts @@ -1,7 +1,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' -import updateTeamByTeamId from '../../../postgres/queries/updateTeamByTeamId' import RedisLockQueue from '../../../utils/RedisLockQueue' import {analytics} from '../../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../../utils/authorization' @@ -22,6 +21,7 @@ const startTeamPrompt: MutationResolvers['startTeamPrompt'] = async ( {teamId, name, rrule, gcalInput}, {authToken, dataLoader, socketId: mutatorId} ) => { + const pg = getKysely() const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -49,16 +49,18 @@ const startTeamPrompt: MutationResolvers['startTeamPrompt'] = async ( //TODO: use client timezone here (requires sending it from the client and passing it via gql context most likely) const meetingName = createMeetingSeriesTitle(name || 'Standup', new Date(), 'UTC') const eventName = rrule ? name || 'Standup' : meetingName - const meeting = await safeCreateTeamPrompt(meetingName, teamId, viewerId, r, dataLoader) + const meeting = await safeCreateTeamPrompt(meetingName, teamId, viewerId, dataLoader) await Promise.all([ r.table('NewMeeting').insert(meeting).run(), - updateTeamByTeamId( - { - lastMeetingType: 'teamPrompt' - }, - teamId - ) + pg + .with('NewMeetingInsert', (qb) => + qb.insertInto('NewMeeting').values({...meeting, phases: JSON.stringify(meeting.phases)}) + ) + .updateTable('Team') + .set({lastMeetingType: 'teamPrompt'}) + .where('id', '=', teamId) + .execute() ]) const {id: meetingId} = meeting diff --git a/packages/server/graphql/public/mutations/updateAgendaItem.ts b/packages/server/graphql/public/mutations/updateAgendaItem.ts index 51c506e8fdb..6acc7e5efd2 100644 --- a/packages/server/graphql/public/mutations/updateAgendaItem.ts +++ b/packages/server/graphql/public/mutations/updateAgendaItem.ts @@ -57,6 +57,11 @@ const updateAgendaItem: MutationResolvers['updateAgendaItem'] = async ( return (agendaItem && agendaItem.sortOrder) || 0 } stages.sort((a, b) => (getSortOrder(a) > getSortOrder(b) ? 1 : -1)) + await pg + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() await r .table('NewMeeting') .get(meetingId) diff --git a/packages/server/graphql/public/mutations/updateMeetingPrompt.ts b/packages/server/graphql/public/mutations/updateMeetingPrompt.ts index 98d2986eb96..edc59a97d9c 100644 --- a/packages/server/graphql/public/mutations/updateMeetingPrompt.ts +++ b/packages/server/graphql/public/mutations/updateMeetingPrompt.ts @@ -1,5 +1,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {getUserId} from '../../../utils/authorization' import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' @@ -10,6 +11,7 @@ const updateMeetingPrompt: MutationResolvers['updateMeetingPrompt'] = async ( {meetingId, newPrompt}, {authToken, dataLoader, socketId: mutatorId} ) => { + const pg = getKysely() const r = await getRethink() const viewerId = getUserId(authToken) const operationId = dataLoader.share() @@ -36,6 +38,11 @@ const updateMeetingPrompt: MutationResolvers['updateMeetingPrompt'] = async ( } // RESOLUTION + await pg + .updateTable('NewMeeting') + .set({meetingPrompt: newPrompt}) + .where('id', '=', meetingId) + .execute() await r .table('NewMeeting') .get(meetingId) diff --git a/packages/server/graphql/public/mutations/updateMeetingTemplate.ts b/packages/server/graphql/public/mutations/updateMeetingTemplate.ts index d355a6062a2..de34566210a 100644 --- a/packages/server/graphql/public/mutations/updateMeetingTemplate.ts +++ b/packages/server/graphql/public/mutations/updateMeetingTemplate.ts @@ -1,5 +1,6 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {getUserId, isTeamMember} from '../../../utils/authorization' import getPhase from '../../../utils/getPhase' import publish from '../../../utils/publish' @@ -11,6 +12,7 @@ const updateMeetingTemplate: MutationResolvers['updateMeetingTemplate'] = async {meetingId, templateId}, {authToken, dataLoader, socketId: mutatorId} ) => { + const pg = getKysely() const viewerId = getUserId(authToken) const r = await getRethink() const operationId = dataLoader.share() @@ -37,7 +39,7 @@ const updateMeetingTemplate: MutationResolvers['updateMeetingTemplate'] = async } ) } - + await pg.updateTable('NewMeeting').set({templateId}).where('id', '=', meetingId).execute() await r.table('NewMeeting').get(meetingId).update({templateId}).run() meeting.templateId = templateId diff --git a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts index 39a814265d0..2692015636f 100644 --- a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts +++ b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts @@ -1,11 +1,12 @@ import dayjs from 'dayjs' +import {sql} from 'kysely' import {toDateTime} from 'parabol-client/shared/rruleUtil' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {DateTime, RRuleSet} from 'rrule-rust' import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {insertMeetingSeries as insertMeetingSeriesQuery} from '../../../postgres/queries/insertMeetingSeries' import restartMeetingSeries from '../../../postgres/queries/restartMeetingSeries' -import updateMeetingSeriesQuery from '../../../postgres/queries/updateMeetingSeries' import {MeetingTypeEnum} from '../../../postgres/types/Meeting' import {MeetingSeries} from '../../../postgres/types/MeetingSeries' import {analytics} from '../../../utils/analytics/analytics' @@ -34,6 +35,7 @@ export const startNewMeetingSeries = async ( name: meetingName, facilitatorUserId: facilitatorId } = meeting + const pg = getKysely() const r = await getRethink() if (!facilitatorId) { throw new Error('No facilitatorId') @@ -58,7 +60,11 @@ export const startNewMeetingSeries = async ( scheduledEndTime: nextMeetingStartDate }) .run() - + await pg + .updateTable('NewMeeting') + .set({meetingSeriesId: newMeetingSeriesId, scheduledEndTime: nextMeetingStartDate}) + .where('id', '=', meetingId) + .execute() return { id: newMeetingSeriesId, ...newMeetingSeriesParams @@ -66,6 +72,7 @@ export const startNewMeetingSeries = async ( } const updateMeetingSeries = async (meetingSeries: MeetingSeries, newRecurrenceRule: RRuleSet) => { + const pg = getKysely() const r = await getRethink() const {id: meetingSeriesId} = meetingSeries @@ -78,23 +85,42 @@ const updateMeetingSeries = async (meetingSeries: MeetingSeries, newRecurrenceRu .getAll(meetingSeriesId, {index: 'meetingSeriesId'}) .filter({endedAt: null}, {default: true}) .run() - const updates = activeMeetings.map((meeting) => - r - .table('NewMeeting') - .get(meeting.id) - .update({ - scheduledEndTime: getNextRRuleDate(newRecurrenceRule) - }) - .run() - ) - await Promise.all(updates) + if (activeMeetings.length > 0) { + const meetingIds = activeMeetings.map(({id}) => id) + const scheduledEndTime = getNextRRuleDate(newRecurrenceRule) + await pg + .updateTable('NewMeeting') + .set({scheduledEndTime}) + .where('id', 'in', meetingIds) + .execute() + const updates = activeMeetings.map((meeting) => + r + .table('NewMeeting') + .get(meeting.id) + .update({ + scheduledEndTime + }) + .run() + ) + await Promise.all(updates) + } } export const stopMeetingSeries = async (meetingSeries: MeetingSeries) => { + const pg = getKysely() const r = await getRethink() - const now = new Date() - - await updateMeetingSeriesQuery({cancelledAt: now}, meetingSeries.id) + await pg + .with('NewMeetingUpdateEnd', (qb) => + qb + .updateTable('NewMeeting') + .set({scheduledEndTime: null}) + .where('meetingSeriesId', '=', meetingSeries.id) + .where('endedAt', 'is', null) + ) + .updateTable('MeetingSeries') + .set({cancelledAt: sql`CURRENT_TIMESTAMP`}) + .where('id', '=', meetingSeries.id) + .execute() await r .table('NewMeeting') .getAll(meetingSeries.id, {index: 'meetingSeriesId'}) @@ -119,6 +145,7 @@ const updateRecurrenceSettings: MutationResolvers['updateRecurrenceSettings'] = {meetingId, name, rrule}, {authToken, dataLoader, socketId: mutatorId} ) => { + const pg = getKysely() const viewerId = getUserId(authToken) const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -164,10 +191,12 @@ const updateRecurrenceSettings: MutationResolvers['updateRecurrenceSettings'] = } if (name) { - await updateMeetingSeriesQuery({title: name}, meetingSeries.id) + await pg + .updateTable('MeetingSeries') + .set({title: name}) + .where('id', '=', meetingSeries.id) + .execute() } - - dataLoader.get('meetingSeries').clear(meetingSeries.id) } else { if (!rrule) { return standardError( @@ -180,7 +209,7 @@ const updateRecurrenceSettings: MutationResolvers['updateRecurrenceSettings'] = analytics.recurrenceStarted(viewer, newMeetingSeries) } - dataLoader.get('newMeetings').clear(meetingId) + dataLoader.clearAll(['newMeetings', 'meetingSeries']) // RESOLUTION const data = {meetingId} diff --git a/packages/server/postgres/migrations/1726251201860_NewMeeting-uniq.ts b/packages/server/postgres/migrations/1726251201860_NewMeeting-uniq.ts new file mode 100644 index 00000000000..5bdf1e5a27d --- /dev/null +++ b/packages/server/postgres/migrations/1726251201860_NewMeeting-uniq.ts @@ -0,0 +1,45 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + // Doing this as a trigger instead of making unique teamId/meetingType/createdAt because rounding createdAt to the nearest 5 seconds felt bad + sql` + CREATE OR REPLACE FUNCTION prevent_meeting_overlap() + RETURNS TRIGGER AS $$ + BEGIN + -- Check if a meeting exists within a 2-second window of the new createdAt + IF EXISTS ( + SELECT 1 FROM "NewMeeting" + WHERE "teamId" = NEW."teamId" + AND "meetingType" = NEW."meetingType" + AND ABS(EXTRACT(EPOCH FROM (NEW."createdAt" - "createdAt"))) < 2 + ) THEN + RAISE EXCEPTION 'Cannot insert meeting. A meeting exists within a 2-second window.'; + END IF; + -- If no conflict, allow the insert + RETURN NEW; + END; + $$ LANGUAGE plpgsql; + DROP TRIGGER IF EXISTS "check_meeting_overlap" ON "NewMeeting"; + CREATE TRIGGER "check_meeting_overlap" + BEFORE INSERT ON "NewMeeting" + FOR EACH ROW + EXECUTE FUNCTION prevent_meeting_overlap(); + `.execute(pg) +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql`DROP TRIGGER IF EXISTS "check_meeting_overlap" ON "NewMeeting";`.execute(pg) +} diff --git a/packages/server/postgres/queries/src/updateMeetingSeriesByIdQuery.sql b/packages/server/postgres/queries/src/updateMeetingSeriesByIdQuery.sql deleted file mode 100644 index bc8bd35dde6..00000000000 --- a/packages/server/postgres/queries/src/updateMeetingSeriesByIdQuery.sql +++ /dev/null @@ -1,10 +0,0 @@ -/* - @name updateMeetingSeriesByIdQuery -*/ -UPDATE "MeetingSeries" SET - "meetingType" = COALESCE(:meetingType, "meetingType"), - "title" = COALESCE(:title, "title"), - "recurrenceRule" = COALESCE(:recurrenceRule, "recurrenceRule"), - "duration" = COALESCE(:duration, "duration"), - "cancelledAt" = COALESCE(:cancelledAt, "cancelledAt") -WHERE id = :id; diff --git a/packages/server/postgres/queries/updateMeetingSeries.ts b/packages/server/postgres/queries/updateMeetingSeries.ts deleted file mode 100644 index e3ff1f91435..00000000000 --- a/packages/server/postgres/queries/updateMeetingSeries.ts +++ /dev/null @@ -1,20 +0,0 @@ -import getPg from '../getPg' -import { - IUpdateMeetingSeriesByIdQueryParams, - updateMeetingSeriesByIdQuery -} from './generated/updateMeetingSeriesByIdQuery' - -const updateMeetingSeries = async ( - update: Partial, - id: number -) => { - return updateMeetingSeriesByIdQuery.run( - { - ...update, - id - } as any, - getPg() - ) -} - -export default updateMeetingSeries From 48b66abdf4d4619be8eccc8a295fa06491610fab Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Fri, 27 Sep 2024 12:23:28 -0700 Subject: [PATCH 479/529] chore(release): release v7.48.1 (#10269) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 419139c591f..208d8d55836 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.48.0" + ".": "7.48.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 46dc19fd412..c9d71c846bf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.48.1](https://github.com/ParabolInc/parabol/compare/v7.48.0...v7.48.1) (2024-09-27) + + +### Fixed + +* stop series when team is no more ([#10268](https://github.com/ParabolInc/parabol/issues/10268)) ([203835e](https://github.com/ParabolInc/parabol/commit/203835e4296f17f51c8d6f968b41f7fb64e820ab)) + + +### Changed + +* **rethinkdb:** NewMeeting: Phase 1a ([#10216](https://github.com/ParabolInc/parabol/issues/10216)) ([6273411](https://github.com/ParabolInc/parabol/commit/6273411f5c5e7e03dc569b3359a49902b88dc11c)) +* **rethinkdb:** NewMeeting: Phase 1b ([#10250](https://github.com/ParabolInc/parabol/issues/10250)) ([8070a7e](https://github.com/ParabolInc/parabol/commit/8070a7e82d156d7b587742f1bde8279419ea85db)) + ## [7.48.0](https://github.com/ParabolInc/parabol/compare/v7.47.5...v7.48.0) (2024-09-24) diff --git a/package.json b/package.json index b627a19d5f8..d01cbb8008f 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.48.0", + "version": "7.48.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index f6518c68cf3..9161d0b3e7b 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.48.0", + "version": "7.48.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.48.0" + "parabol-server": "7.48.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index cf4761c89af..1f1002e9280 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.48.0", + "version": "7.48.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 6deba5caa56..de7e56e253b 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.48.0", + "version": "7.48.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 3776b5aeb90..2fe0e45de08 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.48.0", + "version": "7.48.1", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.48.0", - "parabol-server": "7.48.0", + "parabol-client": "7.48.1", + "parabol-server": "7.48.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index c074a1defdd..bb59e09999f 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.48.0", + "version": "7.48.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 6591639beee..fdf9c945831 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.48.0", + "version": "7.48.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.48.0", + "parabol-client": "7.48.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From b8e99253757735212b0e6979d5a7a47ab8417a99 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 12:56:25 +0200 Subject: [PATCH 480/529] chore(deps): bump express from 4.19.2 to 4.20.0 (#10212) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 202 +++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 176 insertions(+), 26 deletions(-) diff --git a/yarn.lock b/yarn.lock index 89f9873c3f8..23fa16e2ab5 100644 --- a/yarn.lock +++ b/yarn.lock @@ -10732,10 +10732,10 @@ bodec@^0.1.0: resolved "https://registry.yarnpkg.com/bodec/-/bodec-0.1.0.tgz#bc851555430f23c9f7650a75ef64c6a94c3418cc" integrity sha512-Ylo+MAo5BDUq1KA3f3R/MFhh+g8cnHmo8bz3YPGhI1znrMaf77ol1sfvYJzsw3nTE+Y2GryfDxBaR+AqpAkEHQ== -body-parser@1.20.2: - version "1.20.2" - resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" - integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== +body-parser@1.20.3: + version "1.20.3" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.3.tgz#1953431221c6fb5cd63c4b36d53fab0928e548c6" + integrity sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g== dependencies: bytes "3.1.2" content-type "~1.0.5" @@ -10745,7 +10745,7 @@ body-parser@1.20.2: http-errors "2.0.0" iconv-lite "0.4.24" on-finished "2.4.1" - qs "6.11.0" + qs "6.13.0" raw-body "2.5.2" type-is "~1.6.18" unpipe "1.0.0" @@ -10957,6 +10957,17 @@ call-bind@^1.0.0, call-bind@^1.0.2: function-bind "^1.1.1" get-intrinsic "^1.0.2" +call-bind@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.7.tgz#06016599c40c56498c18769d2730be242b6fa3b9" + integrity sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + set-function-length "^1.2.1" + call-me-maybe@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.2.tgz#03f964f19522ba643b1b0693acb9152fe2074baa" @@ -12294,6 +12305,15 @@ defaults@^1.0.3: dependencies: clone "^1.0.2" +define-data-property@^1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/define-data-property/-/define-data-property-1.1.4.tgz#894dc141bb7d3060ae4366f6a0107e68fbe48c5e" + integrity sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A== + dependencies: + es-define-property "^1.0.0" + es-errors "^1.3.0" + gopd "^1.0.1" + define-lazy-prop@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" @@ -12784,6 +12804,11 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w== +encodeurl@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-2.0.0.tgz#7b8ea898077d7e409d3ac45474ea38eaf0857a58" + integrity sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg== + encoding@^0.1.12, encoding@^0.1.13: version "0.1.13" resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.13.tgz#56574afdd791f54a8e9b2785c0582a2d26210fa9" @@ -12898,6 +12923,18 @@ es-abstract@^1.19.0, es-abstract@^1.19.1: string.prototype.trimstart "^1.0.4" unbox-primitive "^1.0.1" +es-define-property@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/es-define-property/-/es-define-property-1.0.0.tgz#c7faefbdff8b2696cf5f46921edfb77cc4ba3845" + integrity sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ== + dependencies: + get-intrinsic "^1.2.4" + +es-errors@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/es-errors/-/es-errors-1.3.0.tgz#05f75a25dab98e4fb1dcd5e1472c0546d5057c8f" + integrity sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw== + es-module-lexer@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/es-module-lexer/-/es-module-lexer-1.2.1.tgz#ba303831f63e6a394983fde2f97ad77b22324527" @@ -13273,36 +13310,36 @@ expect@^29.0.0, expect@^29.5.0: jest-util "^29.5.0" express@^4.17.3: - version "4.19.2" - resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" - integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + version "4.20.0" + resolved "https://registry.yarnpkg.com/express/-/express-4.20.0.tgz#f1d08e591fcec770c07be4767af8eb9bcfd67c48" + integrity sha512-pLdae7I6QqShF5PnNTCVn4hI91Dx0Grkn2+IAsMTgMIKuQVte2dN9PeGSSAME2FR8anOhVA62QDIUaWVfEXVLw== dependencies: accepts "~1.3.8" array-flatten "1.1.1" - body-parser "1.20.2" + body-parser "1.20.3" content-disposition "0.5.4" content-type "~1.0.4" cookie "0.6.0" cookie-signature "1.0.6" debug "2.6.9" depd "2.0.0" - encodeurl "~1.0.2" + encodeurl "~2.0.0" escape-html "~1.0.3" etag "~1.8.1" finalhandler "1.2.0" fresh "0.5.2" http-errors "2.0.0" - merge-descriptors "1.0.1" + merge-descriptors "1.0.3" methods "~1.1.2" on-finished "2.4.1" parseurl "~1.3.3" - path-to-regexp "0.1.7" + path-to-regexp "0.1.10" proxy-addr "~2.0.7" qs "6.11.0" range-parser "~1.2.1" safe-buffer "5.2.1" - send "0.18.0" - serve-static "1.15.0" + send "0.19.0" + serve-static "1.16.0" setprototypeof "1.2.0" statuses "2.0.1" type-is "~1.6.18" @@ -13935,6 +13972,17 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.0, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.3" +get-intrinsic@^1.1.3, get-intrinsic@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.4.tgz#e385f5a4b5227d449c3eabbad05494ef0abbeadd" + integrity sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ== + dependencies: + es-errors "^1.3.0" + function-bind "^1.1.2" + has-proto "^1.0.1" + has-symbols "^1.0.3" + hasown "^2.0.0" + get-nonce@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/get-nonce/-/get-nonce-1.0.1.tgz#fdf3f0278073820d2ce9426c18f07481b1e0cdf3" @@ -14274,6 +14322,13 @@ googleapis@^118.0.0: google-auth-library "^8.0.2" googleapis-common "^6.0.0" +gopd@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c" + integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA== + dependencies: + get-intrinsic "^1.1.3" + graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.11, graceful-fs@^4.2.4, graceful-fs@^4.2.6, graceful-fs@^4.2.9: version "4.2.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3" @@ -14464,6 +14519,18 @@ has-flag@^4.0.0: resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== +has-property-descriptors@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz#963ed7d071dc7bf5f084c5bfbe0d1b6222586854" + integrity sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg== + dependencies: + es-define-property "^1.0.0" + +has-proto@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.3.tgz#b31ddfe9b0e6e9914536a6ab286426d0214f77fd" + integrity sha512-SJ1amZAJUiZS+PhsVLf5tGydlaVB8EdFpaSO4gmiUKUOxk8qzn5AIy4ZeJUmh22znIdk/uMAUT2pl3FxzVUH+Q== + has-symbols@^1.0.1, has-symbols@^1.0.2, has-symbols@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8" @@ -17143,10 +17210,10 @@ meow@^8.0.0: type-fest "^0.18.0" yargs-parser "^20.2.3" -merge-descriptors@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" - integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== +merge-descriptors@1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.3.tgz#d80319a65f3c7935351e5cfdac8f9318504dbed5" + integrity sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ== merge-stream@^2.0.0: version "2.0.0" @@ -18138,6 +18205,11 @@ object-inspect@^1.11.0, object-inspect@^1.12.0, object-inspect@^1.9.0: resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.2.tgz#c0641f26394532f28ab8d796ab954e43c009a8ea" integrity sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ== +object-inspect@^1.13.1: + version "1.13.2" + resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.13.2.tgz#dea0088467fb991e67af4058147a24824a3043ff" + integrity sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g== + object-keys@^1.0.12, object-keys@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" @@ -18717,7 +18789,12 @@ path-scurry@^1.7.0: lru-cache "^9.0.0" minipass "^5.0.0" -path-to-regexp@0.1.7, path-to-regexp@^0.1.2: +path-to-regexp@0.1.10: + version "0.1.10" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" + integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== + +path-to-regexp@^0.1.2: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= @@ -19586,6 +19663,13 @@ qs@6.11.0: dependencies: side-channel "^1.0.4" +qs@6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + qs@^6.10.3, qs@^6.10.5, qs@^6.7.0: version "6.11.2" resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.2.tgz#64bea51f12c1f5da1bc01496f48ffcff7c69d7d9" @@ -20787,6 +20871,25 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" +send@0.19.0: + version "0.19.0" + resolved "https://registry.yarnpkg.com/send/-/send-0.19.0.tgz#bbc5a388c8ea6c048967049dbeac0e4a3f09d7f8" + integrity sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw== + dependencies: + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + fresh "0.5.2" + http-errors "2.0.0" + mime "1.6.0" + ms "2.1.3" + on-finished "2.4.1" + range-parser "~1.2.1" + statuses "2.0.1" + sentence-case@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/sentence-case/-/sentence-case-3.0.4.tgz#3645a7b8c117c787fde8702056225bb62a45131f" @@ -20830,10 +20933,10 @@ serve-index@^1.9.1: mime-types "~2.1.17" parseurl "~1.3.2" -serve-static@1.15.0: - version "1.15.0" - resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" - integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== +serve-static@1.16.0: + version "1.16.0" + resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.16.0.tgz#2bf4ed49f8af311b519c46f272bf6ac3baf38a92" + integrity sha512-pDLK8zwl2eKaYrs8mrPZBJua4hMplRWJ1tIFksVC3FtBEBnl8dxgeHtsaMS8DhS9i4fLObaon6ABoc4/hQGdPA== dependencies: encodeurl "~1.0.2" escape-html "~1.0.3" @@ -20845,6 +20948,18 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw== +set-function-length@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/set-function-length/-/set-function-length-1.2.2.tgz#aac72314198eaed975cf77b2c3b6b880695e5449" + integrity sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg== + dependencies: + define-data-property "^1.1.4" + es-errors "^1.3.0" + function-bind "^1.1.2" + get-intrinsic "^1.2.4" + gopd "^1.0.1" + has-property-descriptors "^1.0.2" + set-value@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/set-value/-/set-value-4.1.0.tgz#aa433662d87081b75ad88a4743bd450f044e7d09" @@ -20983,6 +21098,16 @@ side-channel@^1.0.4: get-intrinsic "^1.0.2" object-inspect "^1.9.0" +side-channel@^1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" + integrity sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA== + dependencies: + call-bind "^1.0.7" + es-errors "^1.3.0" + get-intrinsic "^1.2.4" + object-inspect "^1.13.1" + signal-exit@^3.0.0, signal-exit@^3.0.2, signal-exit@^3.0.3, signal-exit@^3.0.7: version "3.0.7" resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" @@ -21458,7 +21583,7 @@ string-similarity@^3.0.0: resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-3.0.0.tgz#07b0bc69fae200ad88ceef4983878d03793847c7" integrity sha512-7kS7LyTp56OqOI2BDWQNVnLX/rCxIQn+/5M0op1WV6P8Xx6TZNdajpuqQdiJ7Xx+p1C5CsWMvdiBp9ApMhxzEQ== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -21476,6 +21601,15 @@ string-width@^1.0.1: is-fullwidth-code-point "^1.0.0" strip-ansi "^3.0.0" +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^5.0.0: version "5.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.0.tgz#5ab00980cfb29f43e736b113a120a73a0fb569d3" @@ -21547,7 +21681,7 @@ stringify-object@^3.3.0: is-obj "^1.0.1" is-regexp "^1.0.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -21561,6 +21695,13 @@ strip-ansi@^3.0.0, strip-ansi@^3.0.1: dependencies: ansi-regex "^2.0.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.0.1.tgz#61740a08ce36b61e50e65653f07060d000975fb2" @@ -23403,7 +23544,7 @@ workbox-window@6.5.4: "@types/trusted-types" "^2.0.2" workbox-core "6.5.4" -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -23421,6 +23562,15 @@ wrap-ansi@^6.0.1, wrap-ansi@^6.2.0: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 66097b8e6fc0d5f41c79f1b1b2a3ea3e5676526b Mon Sep 17 00:00:00 2001 From: Georg Bremer Date: Tue, 1 Oct 2024 18:02:10 +0200 Subject: [PATCH 481/529] fix: Fix crash in end checkin without pinned agenda items (#10282) --- packages/server/graphql/mutations/endCheckIn.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/graphql/mutations/endCheckIn.ts b/packages/server/graphql/mutations/endCheckIn.ts index dd11494d64d..77f91b62e33 100644 --- a/packages/server/graphql/mutations/endCheckIn.ts +++ b/packages/server/graphql/mutations/endCheckIn.ts @@ -82,6 +82,7 @@ const clonePinnedAgendaItems = async ( pinnedAgendaItems: AgendaItem[], dataLoader: DataLoaderInstance ) => { + if (!pinnedAgendaItems.length) return let curSortOrder = '' const clonedPins = pinnedAgendaItems.map((agendaItem) => { const agendaItemId = `${agendaItem.teamId}::${generateUID()}` From b0a317c335346f3bef8b65bc6d01676b5c9c60dd Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 19:04:06 +0200 Subject: [PATCH 482/529] chore(release): release v7.48.2 (#10280) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 208d8d55836..427b683a21b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.48.1" + ".": "7.48.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index c9d71c846bf..7e7db340e96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.48.2](https://github.com/ParabolInc/parabol/compare/v7.48.1...v7.48.2) (2024-10-01) + + +### Fixed + +* Fix crash in end checkin without pinned agenda items ([#10282](https://github.com/ParabolInc/parabol/issues/10282)) ([66097b8](https://github.com/ParabolInc/parabol/commit/66097b8e6fc0d5f41c79f1b1b2a3ea3e5676526b)) + + +### Changed + +* **deps:** bump express from 4.19.2 to 4.20.0 ([#10212](https://github.com/ParabolInc/parabol/issues/10212)) ([b8e9925](https://github.com/ParabolInc/parabol/commit/b8e99253757735212b0e6979d5a7a47ab8417a99)) + ## [7.48.1](https://github.com/ParabolInc/parabol/compare/v7.48.0...v7.48.1) (2024-09-27) diff --git a/package.json b/package.json index d01cbb8008f..2a0b9c8829f 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.48.1", + "version": "7.48.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 9161d0b3e7b..a4d4dd0719a 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.48.1", + "version": "7.48.2", "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.48.1" + "parabol-server": "7.48.2" } } diff --git a/packages/client/package.json b/packages/client/package.json index 1f1002e9280..99171fcae51 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.48.1", + "version": "7.48.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index de7e56e253b..1f0b2fd9465 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.48.1", + "version": "7.48.2", "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 2fe0e45de08..9a2ad5cd44a 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.48.1", + "version": "7.48.2", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.48.1", - "parabol-server": "7.48.1", + "parabol-client": "7.48.2", + "parabol-server": "7.48.2", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index bb59e09999f..0f9bac6e120 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.48.1", + "version": "7.48.2", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index fdf9c945831..9debf4cd837 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.48.1", + "version": "7.48.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.48.1", + "parabol-client": "7.48.2", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 1a86d3cddcb299880308211e8c8ffd7bd270f7be Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 1 Oct 2024 11:02:02 -0700 Subject: [PATCH 483/529] chore(rethinkdb): NewMeeting: Phase 2 (#10266) Signed-off-by: Matt Krick --- .../mutations/checkRethinkPgEquality.ts | 62 +++++-- .../1726602922665_NewMeeting-phase2.ts | 170 ++++++++++++++++++ packages/server/postgres/utils/checkEqBase.ts | 2 +- .../postgres/utils/rethinkEqualityFns.ts | 28 ++- 4 files changed, 242 insertions(+), 20 deletions(-) create mode 100644 packages/server/postgres/migrations/1726602922665_NewMeeting-phase2.ts diff --git a/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts b/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts index 927cf3685fe..03ff9c53399 100644 --- a/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts +++ b/packages/server/graphql/private/mutations/checkRethinkPgEquality.ts @@ -1,12 +1,14 @@ import getRethink from '../../../database/rethinkDriver' import getFileStoreManager from '../../../fileStorage/getFileStoreManager' -import getKysely from '../../../postgres/getKysely' +import {selectNewMeetings} from '../../../postgres/select' import {checkRowCount, checkTableEq} from '../../../postgres/utils/checkEqBase' import { compareDateAlmostEqual, + compareRValStringAsNumber, compareRValUndefinedAsFalse, compareRValUndefinedAsNull, compareRValUndefinedAsNullAndTruncateRVal, + compareRValUndefinedAsZero, defaultEqFn } from '../../../postgres/utils/rethinkEqualityFns' import {MutationResolvers} from '../resolverTypes' @@ -33,12 +35,12 @@ const checkRethinkPgEquality: MutationResolvers['checkRethinkPgEquality'] = asyn ) => { const r = await getRethink() - if (tableName === 'TeamMember') { + if (tableName === 'NewMeeting') { const rowCountResult = await checkRowCount(tableName) - const rethinkQuery = (joinedAt: Date, id: string | number) => { + const rethinkQuery = (updatedAt: Date, id: string | number) => { return r - .table('TeamMember' as any) - .between([joinedAt, id], [r.maxval, r.maxval], { + .table('NewMeeting' as any) + .between([updatedAt, id], [r.maxval, r.maxval], { index: 'updatedAtId', leftBound: 'open', rightBound: 'closed' @@ -46,24 +48,52 @@ const checkRethinkPgEquality: MutationResolvers['checkRethinkPgEquality'] = asyn .orderBy({index: 'updatedAtId'}) as any } const pgQuery = async (ids: string[]) => { - return getKysely().selectFrom('TeamMember').selectAll().where('id', 'in', ids).execute() + return selectNewMeetings().where('id', 'in', ids).execute() } const errors = await checkTableEq( rethinkQuery, pgQuery, { id: defaultEqFn, - isNotRemoved: compareRValUndefinedAsFalse, - isLead: compareRValUndefinedAsFalse, - isSpectatingPoker: compareRValUndefinedAsFalse, - email: defaultEqFn, - openDrawer: compareRValUndefinedAsNull, - picture: defaultEqFn, - preferredName: compareRValUndefinedAsNullAndTruncateRVal(100), - teamId: defaultEqFn, - userId: defaultEqFn, + isLegacy: compareRValUndefinedAsFalse, createdAt: compareDateAlmostEqual, - updatedAt: compareDateAlmostEqual + updatedAt: compareDateAlmostEqual, + createdBy: defaultEqFn, + endedAt: compareRValUndefinedAsNull, + facilitatorStageId: defaultEqFn, + facilitatorUserId: defaultEqFn, + meetingCount: compareRValUndefinedAsZero, + meetingNumber: compareRValUndefinedAsZero, + name: compareRValUndefinedAsNullAndTruncateRVal(100), + summarySentAt: compareRValUndefinedAsNull, + teamId: defaultEqFn, + meetingType: defaultEqFn, + phases: defaultEqFn, + showConversionModal: compareRValUndefinedAsFalse, + meetingSeriesId: compareRValUndefinedAsNull, + scheduledEndTime: compareRValUndefinedAsNull, + summary: compareRValUndefinedAsNullAndTruncateRVal(10000), + sentimentScore: compareRValUndefinedAsNull, + usedReactjis: compareRValUndefinedAsNull, + slackTs: compareRValStringAsNumber, + engagement: compareRValUndefinedAsNull, + totalVotes: compareRValUndefinedAsNull, + maxVotesPerGroup: compareRValUndefinedAsNull, + disableAnonymity: compareRValUndefinedAsNull, + commentCount: compareRValUndefinedAsNull, + taskCount: compareRValUndefinedAsNull, + agendaItemCount: compareRValUndefinedAsNull, + storyCount: compareRValUndefinedAsNull, + templateId: compareRValUndefinedAsNull, + topicCount: compareRValUndefinedAsNull, + reflectionCount: compareRValUndefinedAsNull, + transcription: compareRValUndefinedAsNull, + recallBotId: compareRValUndefinedAsNull, + videoMeetingURL: compareRValUndefinedAsNull, + autogroupReflectionGroups: compareRValUndefinedAsNull, + resetReflectionGroups: compareRValUndefinedAsNull, + templateRefId: compareRValUndefinedAsNull, + meetingPrompt: compareRValUndefinedAsNull }, maxErrors ) diff --git a/packages/server/postgres/migrations/1726602922665_NewMeeting-phase2.ts b/packages/server/postgres/migrations/1726602922665_NewMeeting-phase2.ts new file mode 100644 index 00000000000..cdb45ed0b34 --- /dev/null +++ b/packages/server/postgres/migrations/1726602922665_NewMeeting-phase2.ts @@ -0,0 +1,170 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + try { + console.log('Adding index') + await r + .table('NewMeeting') + .indexCreate('updatedAtId', (row: any) => [row('updatedAt'), row('id')]) + .run() + await r.table('NewMeeting').indexWait().run() + } catch { + // index already exists + } + + console.log('Adding index complete') + + await sql`ALTER TABLE "NewMeeting" DISABLE TRIGGER "check_meeting_overlap"`.execute(pg) + const MAX_PG_PARAMS = 65545 + const PG_COLS = [ + 'id', + 'isLegacy', + 'createdAt', + 'updatedAt', + 'createdBy', + 'endedAt', + 'facilitatorStageId', + 'facilitatorUserId', + 'meetingCount', + 'meetingNumber', + 'name', + 'summarySentAt', + 'teamId', + 'meetingType', + 'phases', + 'showConversionModal', + 'meetingSeriesId', + 'scheduledEndTime', + 'summary', + 'sentimentScore', + 'usedReactjis', + 'slackTs', + 'engagement', + 'totalVotes', + 'maxVotesPerGroup', + 'disableAnonymity', + 'commentCount', + 'taskCount', + 'agendaItemCount', + 'storyCount', + 'templateId', + 'topicCount', + 'reflectionCount', + 'transcription', + 'recallBotId', + 'videoMeetingURL', + 'autogroupReflectionGroups', + 'resetReflectionGroups', + 'templateRefId', + 'meetingPrompt' + ] as const + type NewMeeting = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + + let curUpdatedAt = r.minval + let curId = r.minval + + const insertRow = async (row) => { + if (!row.facilitatorStageId) { + console.log('Meeting has no facilitatorId, skipping insert', row.id, row.teamId) + return + } + try { + await pg + .insertInto('NewMeeting') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_createdBy') { + return insertRow({...row, createdBy: null}) + } + if (e.constraint === 'fk_facilitatorUserId') { + return insertRow({...row, facilitatorUserId: null}) + } + if (e.constraint === 'fk_teamId') { + console.log('Meeting has no team, skipping insert', row.id) + return + } + if (e.constraint === 'fk_meetingSeriesId') { + return insertRow({...row, meetingSeriesId: null}) + } + if (e.constraint === 'fk_templateId') { + console.log('Meeting has no template, skipping insert', row.id) + return + } + throw e + } + } + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, String(curUpdatedAt), String(curId)) + const rawRowsToInsert = (await r + .table('NewMeeting') + .between([curUpdatedAt, curId], [r.maxval, r.maxval], { + index: 'updatedAtId', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'updatedAtId'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as NewMeeting[] + + const rowsToInsert = rawRowsToInsert.map((row) => { + const { + phases, + name, + summary, + usedReactjis, + slackTs, + transcription, + autogroupReflectionGroups, + resetReflectionGroups, + meetingPrompt, + meetingCount, + ...rest + } = row as any + return { + ...rest, + phases: JSON.stringify(phases), + name: name.slice(0, 100), + summary: summary ? summary.slice(0, 10000) : null, + usedReactjis: JSON.stringify(usedReactjis), + slackTs: isNaN(Number(slackTs)) ? null : Number(slackTs), + transcription: JSON.stringify(transcription), + autogroupReflectionGroups: JSON.stringify(autogroupReflectionGroups), + resetReflectionGroups: JSON.stringify(resetReflectionGroups), + meetingPrompt: meetingPrompt ? meetingPrompt.slice(0, 255) : null, + meetingCount: meetingCount || 0 + } + }) + + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curUpdatedAt = lastRow.updatedAt + curId = lastRow.id + await Promise.all(rowsToInsert.map(async (row) => insertRow(row))) + } + await sql`ALTER TABLE "NewMeeting" ENABLE TRIGGER "check_meeting_overlap"`.execute(pg) +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql`TRUNCATE TABLE "NewMeeting" CASCADE`.execute(pg) +} diff --git a/packages/server/postgres/utils/checkEqBase.ts b/packages/server/postgres/utils/checkEqBase.ts index 175d20a57a5..8d483673c6e 100644 --- a/packages/server/postgres/utils/checkEqBase.ts +++ b/packages/server/postgres/utils/checkEqBase.ts @@ -65,7 +65,7 @@ export async function checkTableEq( const pgRow = pgRowsById[id] if (!pgRow) { - errors.push({id, prop: id, rVal: id, pgVal: null}) + errors.push({id, prop: '', rVal: null, pgVal: null}) if (errors.length >= maxErrors) return errors continue } diff --git a/packages/server/postgres/utils/rethinkEqualityFns.ts b/packages/server/postgres/utils/rethinkEqualityFns.ts index 201afa28e9c..0c003efbd57 100644 --- a/packages/server/postgres/utils/rethinkEqualityFns.ts +++ b/packages/server/postgres/utils/rethinkEqualityFns.ts @@ -1,10 +1,32 @@ import isValidDate from 'parabol-client/utils/isValidDate' import stringSimilarity from 'string-similarity' +function sortObjectKeys(obj: any): any { + if (Array.isArray(obj)) { + // If it's an array, recurse into each element + return obj.map(sortObjectKeys) + } else if (obj !== null && typeof obj === 'object') { + if (obj instanceof Date) return obj + // If it's an object, sort the keys and recurse on each value + const sortedObj: {[key: string]: any} = {} + Object.keys(obj) + .sort() + .forEach((key) => { + sortedObj[key] = sortObjectKeys(obj[key]) + }) + return sortedObj + } else { + // If it's a primitive value, just return it + return obj + } +} + export const defaultEqFn = (a: unknown, b: unknown) => { if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime() - if (Array.isArray(a) && Array.isArray(b)) return JSON.stringify(a) === JSON.stringify(b) - if (typeof a === 'object' && typeof b === 'object') return JSON.stringify(a) === JSON.stringify(b) + if (Array.isArray(a) && Array.isArray(b)) + return JSON.stringify(sortObjectKeys(a)) === JSON.stringify(sortObjectKeys(b)) + if (typeof a === 'object' && typeof b === 'object') + return JSON.stringify(sortObjectKeys(a)) === JSON.stringify(sortObjectKeys(b)) return a === b } export const compareDateAlmostEqual = (rVal: unknown, pgVal: unknown) => { @@ -47,7 +69,7 @@ export const compareRValUndefinedAsEmptyArray = (rVal: unknown, pgVal: unknown) } export const compareRValStringAsNumber = (rVal: unknown, pgVal: unknown) => { - const normalizedRVal = Number(rVal) + const normalizedRVal = rVal ? Number(rVal) : null return defaultEqFn(normalizedRVal, pgVal) } From 84a60cdcaced56e187f343d3a5beb90c0fe08787 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 1 Oct 2024 17:54:02 -0700 Subject: [PATCH 484/529] chore(release): release v7.48.3 (#10286) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 427b683a21b..ae2817c821a 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.48.2" + ".": "7.48.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 7e7db340e96..6b6e4cd864c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.48.3](https://github.com/ParabolInc/parabol/compare/v7.48.2...v7.48.3) (2024-10-01) + + +### Changed + +* **rethinkdb:** NewMeeting: Phase 2 ([#10266](https://github.com/ParabolInc/parabol/issues/10266)) ([1a86d3c](https://github.com/ParabolInc/parabol/commit/1a86d3cddcb299880308211e8c8ffd7bd270f7be)) + ## [7.48.2](https://github.com/ParabolInc/parabol/compare/v7.48.1...v7.48.2) (2024-10-01) diff --git a/package.json b/package.json index 2a0b9c8829f..895051c4773 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.48.2", + "version": "7.48.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index a4d4dd0719a..3f5dc02953c 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.48.2", + "version": "7.48.3", "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.48.2" + "parabol-server": "7.48.3" } } diff --git a/packages/client/package.json b/packages/client/package.json index 99171fcae51..1d39378523d 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.48.2", + "version": "7.48.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 1f0b2fd9465..b23ce3d3520 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.48.2", + "version": "7.48.3", "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 9a2ad5cd44a..d9433199b34 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.48.2", + "version": "7.48.3", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.48.2", - "parabol-server": "7.48.2", + "parabol-client": "7.48.3", + "parabol-server": "7.48.3", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 0f9bac6e120..5e3b3d4b0a6 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.48.2", + "version": "7.48.3", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 9debf4cd837..62e5f3b1dcf 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.48.2", + "version": "7.48.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.48.2", + "parabol-client": "7.48.3", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 1c87753102fcb2268ae3863d33cf99b72e663990 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Wed, 2 Oct 2024 10:52:23 +0800 Subject: [PATCH 485/529] feat(misc): add timer control to more meeting phases (#10279) --- .../components/ActionMeetingAgendaItems.tsx | 4 ++++ .../client/components/ActionMeetingUpdates.tsx | 4 ++++ packages/client/components/MeetingControlBar.tsx | 6 +++--- packages/client/components/PokerEstimatePhase.tsx | 4 ++++ packages/client/utils/showTimerInPhase.ts | 15 +++++++++++++++ 5 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 packages/client/utils/showTimerInPhase.ts diff --git a/packages/client/components/ActionMeetingAgendaItems.tsx b/packages/client/components/ActionMeetingAgendaItems.tsx index 0650fd1c732..4e1f05ca4e3 100644 --- a/packages/client/components/ActionMeetingAgendaItems.tsx +++ b/packages/client/components/ActionMeetingAgendaItems.tsx @@ -19,6 +19,7 @@ import MeetingHeaderAndPhase from './MeetingHeaderAndPhase' import MeetingTopBar from './MeetingTopBar' import PhaseHeaderTitle from './PhaseHeaderTitle' import PhaseWrapper from './PhaseWrapper' +import StageTimerDisplay from './StageTimerDisplay' interface Props extends ActionMeetingPhaseProps { meeting: ActionMeetingAgendaItems_meeting$key @@ -57,6 +58,8 @@ const ActionMeetingAgendaItems = (props: Props) => { const meeting = useFragment( graphql` fragment ActionMeetingAgendaItems_meeting on ActionMeeting { + ...StageTimerDisplay_meeting + ...StageTimerControl_meeting showSidebar endedAt facilitatorUserId @@ -97,6 +100,7 @@ const ActionMeetingAgendaItems = (props: Props) => { {content} {`${preferredName}, what do you need?`} + { const meeting = useFragment( graphql` fragment ActionMeetingUpdates_meeting on ActionMeeting { + ...StageTimerDisplay_meeting + ...StageTimerControl_meeting ...ActionMeetingUpdatesPrompt_meeting id endedAt @@ -106,6 +109,7 @@ const ActionMeetingUpdates = (props: Props) => { + { const {phaseType} = localPhase const {id: localStageId, isComplete} = localStage const isCheckIn = phaseType === 'checkin' - const isRetro = meetingType === 'retrospective' const isPoker = meetingType === 'poker' const getPossibleButtons = () => { const buttons = ['tips'] if (!isFacilitating && !isCheckIn && !isComplete && !isPoker) buttons.push('ready') if (!isFacilitating && localStageId !== facilitatorStageId) buttons.push('rejoin') - if (isFacilitating && isRetro && !isCheckIn && !isComplete) buttons.push('timer') + if (isFacilitating && !isComplete && showTimerInPhase(phaseType)) buttons.push('timer') if ((isFacilitating || isPoker) && findStageAfterId(phases, localStageId)) buttons.push('next') if (isFacilitating) buttons.push('end') return buttons.map((key) => ({key})) @@ -204,7 +204,7 @@ const MeetingControlBar = (props: Props) => { ) diff --git a/packages/client/components/PokerEstimatePhase.tsx b/packages/client/components/PokerEstimatePhase.tsx index a4008ca422c..4c4e6036f6d 100644 --- a/packages/client/components/PokerEstimatePhase.tsx +++ b/packages/client/components/PokerEstimatePhase.tsx @@ -19,6 +19,7 @@ import PhaseHeaderTitle from './PhaseHeaderTitle' import PokerEstimateHeaderCard from './PokerEstimateHeaderCard' import {PokerMeetingPhaseProps} from './PokerMeeting' import ResponsiveDashSidebar from './ResponsiveDashSidebar' +import StageTimerDisplay from './StageTimerDisplay' const StyledMeetingHeaderAndPhase = styled(MeetingHeaderAndPhase)<{isOpen: boolean}>( ({isOpen}) => ({ @@ -53,6 +54,8 @@ const PokerEstimatePhase = (props: Props) => { const meeting = useFragment( graphql` fragment PokerEstimatePhase_meeting on PokerMeeting { + ...StageTimerDisplay_meeting + ...StageTimerControl_meeting ...EstimatePhaseArea_meeting id endedAt @@ -103,6 +106,7 @@ const PokerEstimatePhase = (props: Props) => { + diff --git a/packages/client/utils/showTimerInPhase.ts b/packages/client/utils/showTimerInPhase.ts new file mode 100644 index 00000000000..ff6b5fd4bef --- /dev/null +++ b/packages/client/utils/showTimerInPhase.ts @@ -0,0 +1,15 @@ +import {NewMeetingPhaseTypeEnum} from '~/__generated__/ActionMeeting_meeting.graphql' + +const noTimerPhases: NewMeetingPhaseTypeEnum[] = [ + 'lobby', + 'checkin', + 'firstcall', + 'lastcall', + 'SUMMARY' +] + +const showTimerInPhase = (phaseType: NewMeetingPhaseTypeEnum): boolean => { + return !noTimerPhases.includes(phaseType) +} + +export default showTimerInPhase From ae72c0d88e7329e05597eae40ba730f927a21537 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 2 Oct 2024 22:17:41 -0700 Subject: [PATCH 486/529] fix: deadlock on teamprompt (#10290) Signed-off-by: Matt Krick --- packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts index 33364edf2fb..07b8e402ea5 100644 --- a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts +++ b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts @@ -76,6 +76,7 @@ const safeEndTeamPrompt = async ({ usedReactjis: JSON.stringify(insights.usedReactjis), engagement: insights.engagement }) + .where('id', '=', meetingId) .execute() const completedTeamPrompt = await r .table('NewMeeting') From 55a79783fbf73858178a99358556acaf735e4dcc Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 2 Oct 2024 22:26:15 -0700 Subject: [PATCH 487/529] chore(release): release v7.49.0 (#10288) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index ae2817c821a..819c17d84df 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.48.3" + ".": "7.49.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b6e4cd864c..ed72ec39e60 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.49.0](https://github.com/ParabolInc/parabol/compare/v7.48.3...v7.49.0) (2024-10-03) + + +### Added + +* **misc:** add timer control to more meeting phases ([#10279](https://github.com/ParabolInc/parabol/issues/10279)) ([1c87753](https://github.com/ParabolInc/parabol/commit/1c87753102fcb2268ae3863d33cf99b72e663990)) + + +### Fixed + +* deadlock on teamprompt ([#10290](https://github.com/ParabolInc/parabol/issues/10290)) ([ae72c0d](https://github.com/ParabolInc/parabol/commit/ae72c0d88e7329e05597eae40ba730f927a21537)) + ## [7.48.3](https://github.com/ParabolInc/parabol/compare/v7.48.2...v7.48.3) (2024-10-01) diff --git a/package.json b/package.json index 895051c4773..15c52b4e124 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.48.3", + "version": "7.49.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 3f5dc02953c..e535a8a9bb9 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.48.3", + "version": "7.49.0", "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.48.3" + "parabol-server": "7.49.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 1d39378523d..7dac964b7cb 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.48.3", + "version": "7.49.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index b23ce3d3520..777871fa381 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.48.3", + "version": "7.49.0", "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 d9433199b34..7a7199e0f83 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.48.3", + "version": "7.49.0", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.48.3", - "parabol-server": "7.48.3", + "parabol-client": "7.49.0", + "parabol-server": "7.49.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 5e3b3d4b0a6..f888af462d6 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.48.3", + "version": "7.49.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 62e5f3b1dcf..236d16de09d 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.48.3", + "version": "7.49.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.48.3", + "parabol-client": "7.49.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From d18a7a4bf2703ded03a807f6778c9e85e8d92b82 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 3 Oct 2024 17:19:53 -0700 Subject: [PATCH 488/529] fix: endTeamPrompt bugs (#10295) Signed-off-by: Matt Krick --- .../server/graphql/mutations/helpers/safeEndTeamPrompt.ts | 4 +++- packages/server/graphql/public/types/MeetingSeries.ts | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts index 07b8e402ea5..48cb75e8d32 100644 --- a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts +++ b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts @@ -24,7 +24,9 @@ const summarizeTeamPrompt = async (meeting: TeamPromptMeeting, context: Internal const r = await getRethink() const summary = await generateStandupMeetingSummary(meeting, dataLoader) - await pg.updateTable('NewMeeting').set({summary}).where('id', '=', meeting.id).execute() + if (summary) { + await pg.updateTable('NewMeeting').set({summary}).where('id', '=', meeting.id).execute() + } await r .table('NewMeeting') .get(meeting.id) diff --git a/packages/server/graphql/public/types/MeetingSeries.ts b/packages/server/graphql/public/types/MeetingSeries.ts index 5812ed6e566..eaf99efdb83 100644 --- a/packages/server/graphql/public/types/MeetingSeries.ts +++ b/packages/server/graphql/public/types/MeetingSeries.ts @@ -7,7 +7,8 @@ const MeetingSeries: MeetingSeriesResolvers = { return MeetingSeriesId.join(id) }, activeMeetings: async (meetingSeries, _args, {dataLoader}) => { - return dataLoader.get('activeMeetingsByMeetingSeriesId').load(meetingSeries.id) + const res = await dataLoader.get('activeMeetingsByMeetingSeriesId').load(meetingSeries.id) + return res || [] }, mostRecentMeeting: async ({id: meetingSeriesId}, _args, _context) => { const r = await getRethink() From 78d85f6a6b4b570c56feeb354fed54aa3a0d9e5a Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 10:23:46 +0200 Subject: [PATCH 489/529] chore(release): release v7.49.1 (#10296) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 819c17d84df..e2abe57e39d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.49.0" + ".": "7.49.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index ed72ec39e60..1bcb297a730 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.49.1](https://github.com/ParabolInc/parabol/compare/v7.49.0...v7.49.1) (2024-10-04) + + +### Fixed + +* endTeamPrompt bugs ([#10295](https://github.com/ParabolInc/parabol/issues/10295)) ([d18a7a4](https://github.com/ParabolInc/parabol/commit/d18a7a4bf2703ded03a807f6778c9e85e8d92b82)) + ## [7.49.0](https://github.com/ParabolInc/parabol/compare/v7.48.3...v7.49.0) (2024-10-03) diff --git a/package.json b/package.json index 15c52b4e124..d88658e297d 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.49.0", + "version": "7.49.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index e535a8a9bb9..503679cc1c5 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.49.0", + "version": "7.49.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.49.0" + "parabol-server": "7.49.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index 7dac964b7cb..78f82cf1257 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.49.0", + "version": "7.49.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 777871fa381..75a75961921 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.49.0", + "version": "7.49.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 7a7199e0f83..88a3275ed7e 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.49.0", + "version": "7.49.1", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.49.0", - "parabol-server": "7.49.0", + "parabol-client": "7.49.1", + "parabol-server": "7.49.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index f888af462d6..d243af0b5ca 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.49.0", + "version": "7.49.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 236d16de09d..cb93f0f7f7e 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.49.0", + "version": "7.49.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.49.0", + "parabol-client": "7.49.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From ff6c25e38dbab78075bc7398f32939fec5b44f45 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Mon, 7 Oct 2024 15:08:50 +0100 Subject: [PATCH 490/529] feat: add feature flag tables (#10184) --- codegen.json | 12 +-- .../ActivityLibrary/ActivityLibrary.tsx | 6 +- .../DashNavList/DashNavListTeams.tsx | 9 +- packages/client/components/Dashboard.tsx | 9 +- .../RetroDiscussionThreadHeader.tsx | 6 +- .../client/components/RetroGroupPhase.tsx | 7 +- .../client/components/StandardHubUserMenu.tsx | 9 +- .../client/components/TeamPromptMeeting.tsx | 11 +-- packages/client/modules/demo/DemoUser.ts | 3 - packages/client/modules/demo/initDB.ts | 7 -- .../MeetingSummaryEmail/RetroTopic.tsx | 10 +-- .../WholeMeetingSummary.tsx | 12 ++- .../NewCheckInQuestion.tsx | 9 +- .../components/OrgTeams/OrgTeams.tsx | 7 +- .../mutations/EndRetrospectiveMutation.ts | 8 +- .../mutations/LoginWithGoogleMutation.ts | 6 +- .../mutations/SignUpWithPasswordMutation.ts | 6 +- .../mutations/UpdateFeatureFlagMutation.ts | 41 --------- .../client/mutations/VerifyEmailMutation.ts | 6 +- .../mutations/fragments/PublicTeamsFrag.ts | 4 +- .../subscriptions/NotificationSubscription.ts | 8 -- .../__tests__/updateFeatureFlag.test.ts | 82 ----------------- .../server/billing/helpers/teamLimitsCheck.ts | 26 ++++-- .../server/database/types/Organization.ts | 2 - packages/server/database/types/User.ts | 4 - .../__tests__/isOrgVerified.test.ts | 9 +- .../server/dataloader/customLoaderMakers.ts | 60 +++++++++++++ .../dataloader/primaryKeyLoaderMakers.ts | 4 + .../mutations/helpers/bootstrapNewUser.ts | 20 +---- .../mutations/helpers/canAccessAISummary.ts | 21 +++-- .../helpers/generateDiscussionPrompt.ts | 6 +- .../helpers/generateDiscussionSummary.ts | 6 +- .../mutations/helpers/generateGroups.ts | 6 +- .../helpers/generateStandupMeetingSummary.ts | 6 +- .../generateWholeMeetingSentimentScore.ts | 5 +- .../helpers/generateWholeMeetingSummary.ts | 6 +- .../mutations/helpers/updateTeamInsights.ts | 6 +- .../private/mutations/addFeatureFlag.ts | 30 +++++++ .../private/mutations/applyFeatureFlag.ts | 88 +++++++++++++++++++ .../private/mutations/connectSocket.ts | 1 - .../private/mutations/deleteFeatureFlag.ts | 25 ++++++ .../private/mutations/updateFeatureFlag.ts | 29 ++++++ .../private/mutations/updateOrgFeatureFlag.ts | 36 -------- .../private/queries/getAllFeatureFlags.ts | 10 +++ .../typeDefs/AddFeatureFlagPayload.graphql | 4 + .../typeDefs/AddFeatureFlagSuccess.graphql | 6 ++ .../typeDefs/ApplyFeatureFlagPayload.graphql | 4 + .../typeDefs/ApplyFeatureFlagSuccess.graphql | 21 +++++ .../typeDefs/DeleteFeatureFlagPayload.graphql | 4 + .../typeDefs/DeleteFeatureFlagSuccess.graphql | 6 ++ .../private/typeDefs/FeatureFlagScope.graphql | 8 ++ .../graphql/private/typeDefs/Mutation.graphql | 88 ++++++++++++++----- .../OrganizationFeatureFlagsEnum.graphql | 17 ---- .../graphql/private/typeDefs/Query.graphql | 5 ++ .../private/typeDefs/SubjectsInput.graphql | 22 +++++ .../typeDefs/UpdateFeatureFlagPayload.graphql | 4 + .../typeDefs/UpdateFeatureFlagSuccess.graphql | 6 ++ .../UpdateOrgFeatureFlagPayload.graphql | 1 - .../UpdateOrgFeatureFlagSuccess.graphql | 6 -- .../private/types/AddFeatureFlagSuccess.ts | 13 +++ .../private/types/ApplyFeatureFlagSuccess.ts | 29 ++++++ .../private/types/DeleteFeatureFlagSuccess.ts | 13 +++ .../private/types/UpdateFeatureFlagSuccess.ts | 13 +++ .../types/UpdateOrgFeatureFlagSuccess.ts | 17 ---- .../public/mutations/setMeetingSettings.ts | 6 +- .../public/mutations/updateFeatureFlag.ts | 59 ------------- .../public/typeDefs/FeatureFlag.graphql | 16 ++++ .../public/typeDefs/FeatureFlagOwner.graphql | 21 +++++ .../graphql/public/typeDefs/Mutation.graphql | 26 ------ .../NotificationSubscriptionPayload.graphql | 1 - .../public/typeDefs/Organization.graphql | 4 +- .../typeDefs/OrganizationFeatureFlags.graphql | 17 ---- .../graphql/public/typeDefs/Team.graphql | 5 ++ .../typeDefs/UpdateFeatureFlagPayload.graphql | 13 --- .../graphql/public/typeDefs/User.graphql | 4 +- .../public/typeDefs/UserFeatureFlags.graphql | 11 --- .../public/typeDefs/UserFlagEnum.graphql | 11 --- .../graphql/public/types/Organization.ts | 14 +-- .../public/types/OrganizationFeatureFlags.ts | 18 ---- packages/server/graphql/public/types/Team.ts | 10 ++- .../public/types/UpdateFeatureFlagPayload.ts | 20 ----- packages/server/graphql/public/types/User.ts | 4 +- .../graphql/public/types/UserFeatureFlags.ts | 10 --- .../graphql/types/helpers/isMeetingLocked.ts | 12 ++- .../1727249259984_addFeatureFlagTables.ts | 62 +++++++++++++ .../1727885996466_migrateFeatureFlags.ts | 64 ++++++++++++++ .../postgres/queries/src/backupUserQuery.sql | 3 - packages/server/postgres/select.ts | 3 +- .../isRequestToJoinDomainAllowed.test.ts | 5 +- packages/server/utils/analytics/analytics.ts | 1 - 90 files changed, 777 insertions(+), 614 deletions(-) delete mode 100644 packages/client/mutations/UpdateFeatureFlagMutation.ts delete mode 100644 packages/server/__tests__/updateFeatureFlag.test.ts create mode 100644 packages/server/graphql/private/mutations/addFeatureFlag.ts create mode 100644 packages/server/graphql/private/mutations/applyFeatureFlag.ts create mode 100644 packages/server/graphql/private/mutations/deleteFeatureFlag.ts create mode 100644 packages/server/graphql/private/mutations/updateFeatureFlag.ts delete mode 100644 packages/server/graphql/private/mutations/updateOrgFeatureFlag.ts create mode 100644 packages/server/graphql/private/queries/getAllFeatureFlags.ts create mode 100644 packages/server/graphql/private/typeDefs/AddFeatureFlagPayload.graphql create mode 100644 packages/server/graphql/private/typeDefs/AddFeatureFlagSuccess.graphql create mode 100644 packages/server/graphql/private/typeDefs/ApplyFeatureFlagPayload.graphql create mode 100644 packages/server/graphql/private/typeDefs/ApplyFeatureFlagSuccess.graphql create mode 100644 packages/server/graphql/private/typeDefs/DeleteFeatureFlagPayload.graphql create mode 100644 packages/server/graphql/private/typeDefs/DeleteFeatureFlagSuccess.graphql create mode 100644 packages/server/graphql/private/typeDefs/FeatureFlagScope.graphql delete mode 100644 packages/server/graphql/private/typeDefs/OrganizationFeatureFlagsEnum.graphql create mode 100644 packages/server/graphql/private/typeDefs/SubjectsInput.graphql create mode 100644 packages/server/graphql/private/typeDefs/UpdateFeatureFlagPayload.graphql create mode 100644 packages/server/graphql/private/typeDefs/UpdateFeatureFlagSuccess.graphql delete mode 100644 packages/server/graphql/private/typeDefs/UpdateOrgFeatureFlagPayload.graphql delete mode 100644 packages/server/graphql/private/typeDefs/UpdateOrgFeatureFlagSuccess.graphql create mode 100644 packages/server/graphql/private/types/AddFeatureFlagSuccess.ts create mode 100644 packages/server/graphql/private/types/ApplyFeatureFlagSuccess.ts create mode 100644 packages/server/graphql/private/types/DeleteFeatureFlagSuccess.ts create mode 100644 packages/server/graphql/private/types/UpdateFeatureFlagSuccess.ts delete mode 100644 packages/server/graphql/private/types/UpdateOrgFeatureFlagSuccess.ts delete mode 100644 packages/server/graphql/public/mutations/updateFeatureFlag.ts create mode 100644 packages/server/graphql/public/typeDefs/FeatureFlag.graphql create mode 100644 packages/server/graphql/public/typeDefs/FeatureFlagOwner.graphql delete mode 100644 packages/server/graphql/public/typeDefs/OrganizationFeatureFlags.graphql delete mode 100644 packages/server/graphql/public/typeDefs/UpdateFeatureFlagPayload.graphql delete mode 100644 packages/server/graphql/public/typeDefs/UserFeatureFlags.graphql delete mode 100644 packages/server/graphql/public/typeDefs/UserFlagEnum.graphql delete mode 100644 packages/server/graphql/public/types/OrganizationFeatureFlags.ts delete mode 100644 packages/server/graphql/public/types/UpdateFeatureFlagPayload.ts delete mode 100644 packages/server/graphql/public/types/UserFeatureFlags.ts create mode 100644 packages/server/postgres/migrations/1727249259984_addFeatureFlagTables.ts create mode 100644 packages/server/postgres/migrations/1727885996466_migrateFeatureFlags.ts diff --git a/codegen.json b/codegen.json index 14b74d7dc35..bb1eec60477 100644 --- a/codegen.json +++ b/codegen.json @@ -11,6 +11,10 @@ "config": { "contextType": "../graphql#InternalContext", "mappers": { + "AddFeatureFlagSuccess": "./types/AddFeatureFlagSuccess#AddFeatureFlagSuccessSource", + "ApplyFeatureFlagSuccess": "./types/ApplyFeatureFlagSuccess#ApplyFeatureFlagSuccessSource", + "DeleteFeatureFlagSuccess": "./types/DeleteFeatureFlagSuccess#DeleteFeatureFlagSuccessSource", + "UpdateFeatureFlagSuccess": "./types/UpdateFeatureFlagSuccess#UpdateFeatureFlagSuccessSource", "ChangeEmailDomainSuccess": "./types/ChangeEmailDomainSuccess#ChangeEmailDomainSuccessSource", "Company": "./queries/company#CompanySource", "DraftEnterpriseInvoicePayload": "./types/DraftEnterpriseInvoicePayload#DraftEnterpriseInvoicePayloadSource", @@ -33,7 +37,6 @@ "StartTrialSuccess": "./types/StartTrialSuccess#StartTrialSuccessSource", "StripeFailPaymentPayload": "./mutations/stripeFailPayment#StripeFailPaymentPayloadSource", "Team": "../../postgres/types/index#Team as TeamDB", - "UpdateOrgFeatureFlagSuccess": "./types/UpdateOrgFeatureFlagSuccess#UpdateOrgFeatureFlagSuccessSource", "UpgradeToTeamTierSuccess": "./mutations/upgradeToTeamTier#UpgradeToTeamTierSuccessSource", "User": "../../postgres/types/IUser#default as IUser", "VerifyDomainSuccess": "./types/VerifyDomainSuccess#VerifyDomainSuccessSource" @@ -63,7 +66,6 @@ "AddAgendaItemPayload": "./types/AddAgendaItemPayload#AddAgendaItemPayloadSource", "UpdateAgendaItemPayload": "./types/UpdateAgendaItemPayload#UpdateAgendaItemPayloadSource", "TeamMeetingSettings": "../../postgres/types/index#MeetingSettings as TeamMeetingSettingsDB", - "TeamPromptMeetingSettings": "../../postgres/types/index#MeetingSettings as TeamMeetingSettingsDB", "PokerMeetingSettings": "../../postgres/types/index#PokerMeetingSettings as PokerMeetingSettingsDB", "RetrospectiveMeetingSettings": "../../postgres/types/index#RetrospectiveMeetingSettings as RetrospectiveMeetingSettingsDB", "RemovePokerTemplatePayload": "./types/RemovePokerTemplatePayload#RemovePokerTemplatePayloadSource", @@ -121,8 +123,8 @@ "MeetingSeries": "../../postgres/types/MeetingSeries#MeetingSeries", "MeetingTemplate": "../../database/types/MeetingTemplate#default", "NewMeeting": "../../postgres/types/Meeting#AnyMeeting", - "NewMeetingStage": "./types/NewMeetingStage#NewMeetingStageSource", "NewMeetingPhase": "./types/NewMeetingPhase#NewMeetingPhaseSource", + "NewMeetingStage": "./types/NewMeetingStage#NewMeetingStageSource", "NotificationMeetingStageTimeLimitEnd": "../../database/types/NotificationMeetingStageTimeLimitEnd#default as NotificationMeetingStageTimeLimitEndDB", "NotificationTeamInvitation": "../../database/types/NotificationTeamInvitation#default as NotificationTeamInvitationDB", "NotifyDiscussionMentioned": "../../database/types/NotificationDiscussionMentioned#default as NotificationDiscussionMentionedDB", @@ -178,12 +180,13 @@ "TeamHealthStage": "./types/TeamHealthStage#TeamHealthStageSource", "TeamInvitation": "../../database/types/TeamInvitation#default", "TeamMember": "../../postgres/types/index#TeamMember as TeamMember", - "TeamMemberIntegrationAuthWebhook": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", "TeamMemberIntegrationAuthOAuth1": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", "TeamMemberIntegrationAuthOAuth2": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", + "TeamMemberIntegrationAuthWebhook": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", "TeamMemberIntegrations": "./types/TeamMemberIntegrations#TeamMemberIntegrationsSource", "TeamPromptMeeting": "../../postgres/types/Meeting#TeamPromptMeeting", "TeamPromptMeetingMember": "../../database/types/TeamPromptMeetingMember#default as TeamPromptMeetingMemberDB", + "TeamPromptMeetingSettings": "../../postgres/types/index#MeetingSettings as TeamMeetingSettingsDB", "TeamPromptResponse": "../../postgres/types/index#TeamPromptResponse as TeamPromptResponseDB", "TemplateDimension": "../../postgres/types/index#TemplateDimension as TemplateDimensionDB", "TimelineEventTeamPromptComplete": "./types/TimelineEventTeamPromptComplete#TimelineEventTeamPromptCompleteSource", @@ -193,7 +196,6 @@ "UpdateAutoJoinSuccess": "./types/UpdateAutoJoinSuccess#UpdateAutoJoinSuccessSource", "UpdateCreditCardSuccess": "./types/UpdateCreditCardSuccess#UpdateCreditCardSuccessSource", "UpdateDimensionFieldSuccess": "./types/UpdateDimensionFieldSuccess#UpdateDimensionFieldSuccessSource", - "UpdateFeatureFlagPayload": "./types/UpdateFeatureFlagPayload#UpdateFeatureFlagPayloadSource", "UpdateGitLabDimensionFieldSuccess": "./types/UpdateGitLabDimensionFieldSuccess#UpdateGitLabDimensionFieldSuccessSource", "UpdateMeetingPromptSuccess": "./types/UpdateMeetingPromptSuccess#UpdateMeetingPromptSuccessSource", "UpdateMeetingTemplateSuccess": "./types/UpdateMeetingTemplateSuccess#UpdateMeetingTemplateSuccessSource", diff --git a/packages/client/components/ActivityLibrary/ActivityLibrary.tsx b/packages/client/components/ActivityLibrary/ActivityLibrary.tsx index ec937970738..219cdc8139e 100644 --- a/packages/client/components/ActivityLibrary/ActivityLibrary.tsx +++ b/packages/client/components/ActivityLibrary/ActivityLibrary.tsx @@ -118,9 +118,7 @@ const query = graphql` } } organizations { - featureFlags { - aiTemplate - } + hasAITemplateFlag: featureFlag(featureName: "aiTemplate") } } } @@ -229,7 +227,7 @@ export const ActivityLibrary = (props: Props) => { const data = usePreloadedQuery(query, queryRef) const {viewer} = data const {availableTemplates, organizations} = viewer - const hasAITemplateFeatureFlag = !!organizations.find((org) => org.featureFlags.aiTemplate) + const hasAITemplateFeatureFlag = organizations.some((org) => org.hasAITemplateFlag) const [isSearching, setIsSearching] = React.useState(true) const [templateSearch, refetchTemplateSearch] = useRefetchableFragment< diff --git a/packages/client/components/DashNavList/DashNavListTeams.tsx b/packages/client/components/DashNavList/DashNavListTeams.tsx index 4a76af84fa7..39dfba9799b 100644 --- a/packages/client/components/DashNavList/DashNavListTeams.tsx +++ b/packages/client/components/DashNavList/DashNavListTeams.tsx @@ -29,9 +29,7 @@ const DashNavListTeams = (props: Props) => { id name tier - featureFlags { - publicTeams - } + hasPublicTeamsFlag: featureFlag(featureName: "publicTeams") viewerTeams { ...DashNavListTeam @relay(mask: false) } @@ -43,9 +41,8 @@ const DashNavListTeams = (props: Props) => { organizationRef ) const [showModal, setShowModal] = useState(false) - const {publicTeams, viewerTeams, featureFlags} = organization - const publicTeamsEnabled = featureFlags?.publicTeams - const publicTeamsCount = publicTeamsEnabled ? publicTeams.length : 0 + const {publicTeams, viewerTeams, hasPublicTeamsFlag} = organization + const publicTeamsCount = hasPublicTeamsFlag ? publicTeams.length : 0 const handleClose = () => { setShowModal(false) diff --git a/packages/client/components/Dashboard.tsx b/packages/client/components/Dashboard.tsx index e636a27285f..5697a7549d0 100644 --- a/packages/client/components/Dashboard.tsx +++ b/packages/client/components/Dashboard.tsx @@ -105,9 +105,7 @@ const Dashboard = (props: Props) => { ...DashSidebar_viewer ...useNewFeatureSnackbar_viewer overLimitCopy - featureFlags { - insights - } + hasInsightsFlag: featureFlag(featureName: "insights") teams { activeMeetings { ...useSnacksForNewMeetings_meetings @@ -119,15 +117,14 @@ const Dashboard = (props: Props) => { queryRef ) const {viewer} = data - const {teams, featureFlags} = viewer - const {insights} = featureFlags + const {teams, hasInsightsFlag} = viewer const activeMeetings = teams.flatMap((team) => team.activeMeetings).filter(Boolean) const {isOpen, toggle, handleMenuClick} = useSidebar() const isDesktop = useBreakpoint(Breakpoint.SIDEBAR_LEFT) const overLimitCopy = viewer?.overLimitCopy const meetingsDashRef = useRef(null) useSnackNag(overLimitCopy) - useUsageSnackNag(insights) + useUsageSnackNag(hasInsightsFlag) useSnacksForNewMeetings(activeMeetings) useNewFeatureSnackbar(viewer) diff --git a/packages/client/components/RetroDiscussionThreadHeader.tsx b/packages/client/components/RetroDiscussionThreadHeader.tsx index 15a0a29786b..41cd525dfaa 100644 --- a/packages/client/components/RetroDiscussionThreadHeader.tsx +++ b/packages/client/components/RetroDiscussionThreadHeader.tsx @@ -51,14 +51,12 @@ const RetroDiscussionThreadHeader = (props: Props) => { const organization = useFragment( graphql` fragment RetroDiscussionThreadHeader_organization on Organization { - featureFlags { - zoomTranscription - } + hasZoomFlag: featureFlag(featureName: "zoomTranscription") } `, organizationRef ?? null ) - const hasZoomFlag = organization?.featureFlags.zoomTranscription ?? false + const hasZoomFlag = organization?.hasZoomFlag ?? false const handleHeaderClick = (header: 'discussion' | 'transcription') => { if (showTranscription && header === 'transcription') return diff --git a/packages/client/components/RetroGroupPhase.tsx b/packages/client/components/RetroGroupPhase.tsx index 52dafdf05a8..1451abcd966 100644 --- a/packages/client/components/RetroGroupPhase.tsx +++ b/packages/client/components/RetroGroupPhase.tsx @@ -64,9 +64,7 @@ const RetroGroupPhase = (props: Props) => { } organization { tier - featureFlags { - suggestGroups - } + hasSuggestGroupsFlag: featureFlag(featureName: "suggestGroups") } } `, @@ -83,8 +81,7 @@ const RetroGroupPhase = (props: Props) => { autogroupReflectionGroups, resetReflectionGroups } = meeting - const {featureFlags, tier} = organization - const {suggestGroups: hasSuggestGroupsFlag} = featureFlags + const {hasSuggestGroupsFlag, tier} = organization const {openTooltip, closeTooltip, tooltipPortal, originRef} = useTooltip( MenuPosition.UPPER_CENTER ) diff --git a/packages/client/components/StandardHubUserMenu.tsx b/packages/client/components/StandardHubUserMenu.tsx index 00c0c2f9e35..94a9e33864b 100644 --- a/packages/client/components/StandardHubUserMenu.tsx +++ b/packages/client/components/StandardHubUserMenu.tsx @@ -43,9 +43,7 @@ const StandardHubUserMenu = (props: Props) => { graphql` fragment StandardHubUserMenu_viewer on User { email - featureFlags { - insights - } + hasInsightsFlag: featureFlag(featureName: "insights") organizations { id billingTier @@ -54,8 +52,7 @@ const StandardHubUserMenu = (props: Props) => { `, viewerRef ) - const {email, featureFlags, organizations} = viewer - const {insights} = featureFlags + const {email, hasInsightsFlag, organizations} = viewer const ownedFreeOrgs = organizations.filter((org) => org.billingTier === 'starter') const showUpgradeCTA = ownedFreeOrgs.length > 0 const routeSuffix = ownedFreeOrgs.length === 1 ? `/${ownedFreeOrgs[0]!.id}` : '' @@ -83,7 +80,7 @@ const StandardHubUserMenu = (props: Props) => { } /> - {insights && ( + {hasInsightsFlag && ( diff --git a/packages/client/components/TeamPromptMeeting.tsx b/packages/client/components/TeamPromptMeeting.tsx index 203a1b25fb9..9875249e078 100644 --- a/packages/client/components/TeamPromptMeeting.tsx +++ b/packages/client/components/TeamPromptMeeting.tsx @@ -89,17 +89,14 @@ const TeamPromptMeeting = (props: Props) => { } } organization { - featureFlags { - singleColumnStandups - } + hasSingleColumnStandupsFlag: featureFlag(featureName: "singleColumnStandups") } } `, meetingRef ) const {phases, organization} = meeting - const {featureFlags} = organization - const {singleColumnStandups} = featureFlags + const {hasSingleColumnStandupsFlag} = organization const atmosphere = useAtmosphere() const {viewerId} = atmosphere @@ -183,7 +180,7 @@ const TeamPromptMeeting = (props: Props) => { - + {transitioningStages.map((transitioningStage) => { const {child: stage, onTransitionEnd, status} = transitioningStage const {key, displayIdx} = stage @@ -195,7 +192,7 @@ const TeamPromptMeeting = (props: Props) => { onTransitionEnd={onTransitionEnd} displayIdx={displayIdx} stageRef={stage} - isSingleColumn={singleColumnStandups} + isSingleColumn={hasSingleColumnStandupsFlag} /> ) })} diff --git a/packages/client/modules/demo/DemoUser.ts b/packages/client/modules/demo/DemoUser.ts index d875eb2bbcb..83338386299 100644 --- a/packages/client/modules/demo/DemoUser.ts +++ b/packages/client/modules/demo/DemoUser.ts @@ -7,9 +7,6 @@ export default class DemoUser { viewerId: string createdAt = new Date().toJSON() email: string - featureFlags = { - azureDevOps: false - } facilitatorUserId: string facilitatorName: string inactive = false diff --git a/packages/client/modules/demo/initDB.ts b/packages/client/modules/demo/initDB.ts index 23e400d75f8..4b7d95391ee 100644 --- a/packages/client/modules/demo/initDB.ts +++ b/packages/client/modules/demo/initDB.ts @@ -297,13 +297,6 @@ const initDemoOrg = () => { activeUserCount: 5, inactiveUserCount: 0 }, - featureFlags: { - zoomTranscription: false, - suggestGroups: false, - teamsLimit: false, - noPromptToJoinOrg: false, - publicTeams: false - }, showConversionModal: false } as const } diff --git a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx index 7e2da89d3b4..ff6b1d204da 100644 --- a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx +++ b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/RetroTopic.tsx @@ -105,9 +105,7 @@ const RetroTopic = (props: Props) => { fragment RetroTopic_meeting on RetrospectiveMeeting { id organization { - featureFlags { - shareSummary - } + hasShareSummaryFlag: featureFlag(featureName: "shareSummary") } } `, @@ -115,9 +113,7 @@ const RetroTopic = (props: Props) => { ) const {id: meetingId} = meeting - - const hasShareSummaryFeatureFlag = meeting.organization.featureFlags.shareSummary - + const hasShareSummaryFlag = meeting.organization.hasShareSummaryFlag const {reflectionGroup, discussion, id: stageId} = stage const {commentCount, discussionSummary} = discussion const {reflections, title, voteCount} = reflectionGroup @@ -174,7 +170,7 @@ const RetroTopic = (props: Props) => { {commentLinkLabel} - {hasShareSummaryFeatureFlag && ( + {hasShareSummaryFlag && ( { id summary organization { - featureFlags { - standupAISummary - noAISummary - } + hasStandupAISummaryFlag: featureFlag(featureName: "standupAISummary") + hasNoAISummaryFlag: featureFlag(featureName: "noAISummary") } ... on RetrospectiveMeeting { reflectionGroups(sortBy: voteCount) { @@ -49,14 +47,14 @@ const WholeMeetingSummary = (props: Props) => { const {summary: wholeMeetingSummary, reflectionGroups, organization} = meeting const reflections = reflectionGroups?.flatMap((group) => group.reflections) // reflectionCount hasn't been calculated yet so check reflections length const hasMoreThanOneReflection = reflections?.length && reflections.length > 1 - if (!hasMoreThanOneReflection || organization.featureFlags.noAISummary || !hasAI) return null + if (!hasMoreThanOneReflection || organization.hasNoAISummaryFlag || !hasAI) return null if (!wholeMeetingSummary) return return } else if (meeting.__typename === 'TeamPromptMeeting') { const {summary: wholeMeetingSummary, responses, organization} = meeting if ( - !organization.featureFlags.standupAISummary || - organization.featureFlags.noAISummary || + !organization.hasStandupAISummaryFlag || + organization.hasNoAISummaryFlag || !hasAI || !responses || responses.length === 0 diff --git a/packages/client/modules/meeting/components/MeetingCheckInPrompt/NewCheckInQuestion.tsx b/packages/client/modules/meeting/components/MeetingCheckInPrompt/NewCheckInQuestion.tsx index 76c9d130f83..a7b1294e39c 100644 --- a/packages/client/modules/meeting/components/MeetingCheckInPrompt/NewCheckInQuestion.tsx +++ b/packages/client/modules/meeting/components/MeetingCheckInPrompt/NewCheckInQuestion.tsx @@ -87,9 +87,7 @@ const NewCheckInQuestion = (props: Props) => { } team { organization { - featureFlags { - noAISummary - } + hasNoAISummaryFlag: featureFlag(featureName: "noAISummary") } } } @@ -103,7 +101,7 @@ const NewCheckInQuestion = (props: Props) => { localPhase, facilitatorUserId, team: { - organization: {featureFlags} + organization: {hasNoAISummaryFlag} } } = meeting const {checkInQuestion} = localPhase @@ -228,8 +226,7 @@ const NewCheckInQuestion = (props: Props) => { } }) } - const showAiIcebreaker = - !featureFlags.noAISummary && isFacilitating && window.__ACTION__.hasOpenAI + const showAiIcebreaker = !hasNoAISummaryFlag && isFacilitating && window.__ACTION__.hasOpenAI return ( <> diff --git a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx index 1afe0340e6c..062277dee6c 100644 --- a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx +++ b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx @@ -19,9 +19,7 @@ const OrgTeams = (props: Props) => { fragment OrgTeams_organization on Organization { id isOrgAdmin - featureFlags { - publicTeams - } + hasPublicTeamsFlag: featureFlag(featureName: "publicTeams") allTeams { id ...OrgTeamsRow_team @@ -37,8 +35,7 @@ const OrgTeams = (props: Props) => { isOpen: isAddTeamDialogOpened } = useDialogState() - const {allTeams, isOrgAdmin, featureFlags} = organization - const hasPublicTeamsFlag = featureFlags.publicTeams + const {allTeams, isOrgAdmin, hasPublicTeamsFlag} = organization const showAllTeams = isOrgAdmin || hasPublicTeamsFlag return (
diff --git a/packages/client/mutations/EndRetrospectiveMutation.ts b/packages/client/mutations/EndRetrospectiveMutation.ts index c44f0b85d88..c7f3d2c1df2 100644 --- a/packages/client/mutations/EndRetrospectiveMutation.ts +++ b/packages/client/mutations/EndRetrospectiveMutation.ts @@ -32,9 +32,7 @@ graphql` groupTitle } organization { - featureFlags { - noAISummary - } + hasNoAISummaryFlag: featureFlag(featureName: "noAISummary") } reflectionGroups(sortBy: voteCount) { reflections { @@ -129,9 +127,7 @@ export const endRetrospectiveTeamOnNext: OnNextHandler< const reflections = reflectionGroups.flatMap((group) => group.reflections) // reflectionCount hasn't been calculated yet so check reflections length const hasMoreThanOneReflection = reflections.length > 1 const hasOpenAISummary = - hasMoreThanOneReflection && - !organization.featureFlags.noAISummary && - window.__ACTION__.hasOpenAI + hasMoreThanOneReflection && !organization.hasNoAISummaryFlag && window.__ACTION__.hasOpenAI const hasTeamHealth = phases.some((phase) => phase.phaseType === 'TEAM_HEALTH') const pathname = `/new-summary/${meetingId}` const search = new URLSearchParams() diff --git a/packages/client/mutations/LoginWithGoogleMutation.ts b/packages/client/mutations/LoginWithGoogleMutation.ts index 2adfa6fc24a..9c57bca95e3 100644 --- a/packages/client/mutations/LoginWithGoogleMutation.ts +++ b/packages/client/mutations/LoginWithGoogleMutation.ts @@ -25,9 +25,7 @@ const mutation = graphql` } isNewUser user { - featureFlags { - signUpDestinationTeam - } + hasSignUpDestinationTeamFlag: featureFlag(featureName: "signUpDestinationTeam") teams { id } @@ -60,7 +58,7 @@ const LoginWithGoogleMutation: StandardMutation = ( - atmosphere, - variables, - {onError, onCompleted} -) => { - return commitMutation(atmosphere, { - mutation, - variables, - onCompleted, - onError - }) -} - -export default UpdateFeatureFlagMutation diff --git a/packages/client/mutations/VerifyEmailMutation.ts b/packages/client/mutations/VerifyEmailMutation.ts index 4c8e45c56b2..65034fb61fb 100644 --- a/packages/client/mutations/VerifyEmailMutation.ts +++ b/packages/client/mutations/VerifyEmailMutation.ts @@ -16,9 +16,7 @@ const mutation = graphql` message } user { - featureFlags { - signUpDestinationTeam - } + hasSignUpDestinationTeam: featureFlag(featureName: "signUpDestinationTeam") teams { id } @@ -48,7 +46,7 @@ const VerifyEmailMutation: StandardMutation { - const {email, userId} = await signUp() - const authToken = encodeAuthToken(new ServerAuthToken()) - - const update = await sendPublic({ - query: UPDATE_FEATURE_FLAG, - variables: { - emails: [email], - flag: 'noAISummary', - addFlag: true - }, - authToken - }) - - expect(update).toEqual({ - data: { - updateFeatureFlag: { - error: null, - users: [ - { - id: userId, - featureFlags: { - noAISummary: true - } - } - ] - } - } - }) -}) - -test('Remove feature flag by email', async () => { - const {email, userId} = await signUp() - const authToken = encodeAuthToken(new ServerAuthToken()) - - const update = await sendPublic({ - query: UPDATE_FEATURE_FLAG, - variables: { - emails: [email], - flag: 'noAISummary', - addFlag: false - }, - authToken - }) - - expect(update).toEqual({ - data: { - updateFeatureFlag: { - error: null, - users: [ - { - id: userId, - featureFlags: { - noAISummary: false - } - } - ] - } - } - }) -}) diff --git a/packages/server/billing/helpers/teamLimitsCheck.ts b/packages/server/billing/helpers/teamLimitsCheck.ts index c9c59983cf1..3f3f1750e3a 100644 --- a/packages/server/billing/helpers/teamLimitsCheck.ts +++ b/packages/server/billing/helpers/teamLimitsCheck.ts @@ -2,7 +2,6 @@ import ms from 'ms' import {Threshold} from 'parabol-client/types/constEnums' // Uncomment for easier testing // import { ThresholdTest as Threshold } from "~/types/constEnums"; -import {sql} from 'kysely' import {r} from 'rethinkdb-ts' import NotificationTeamsLimitExceeded from '../../database/types/NotificationTeamsLimitExceeded' import scheduleTeamLimitsJobs from '../../database/types/scheduleTeamLimitsJobs' @@ -28,11 +27,19 @@ const enableUsageStats = async (userIds: string[], orgId: string) => { .where('userId', 'in', userIds) .where('removedAt', 'is', null) .execute() - await pg - .updateTable('User') - .set({featureFlags: sql`arr_append_uniq("featureFlags", 'insights')`}) - .where('id', 'in', userIds) - .execute() + const featureFlag = await pg + .selectFrom('FeatureFlag') + .select(['id']) + .where('featureName', '=', 'insights') + .executeTakeFirst() + if (featureFlag) { + const values = [...userIds.map((userId) => ({userId, featureFlagId: featureFlag.id}))] + await pg + .insertInto('FeatureFlagOwner') + .values(values) + .onConflict((oc) => oc.doNothing()) + .execute() + } } const sendWebsiteNotifications = async ( @@ -104,9 +111,12 @@ export const maybeRemoveRestrictions = async (orgId: string, dataLoader: DataLoa // Warning: the function might be expensive export const checkTeamsLimit = async (orgId: string, dataLoader: DataLoaderWorker) => { const organization = await dataLoader.get('organizations').loadNonNull(orgId) - const {tierLimitExceededAt, tier, trialStartDate, featureFlags, name: orgName} = organization + const {tierLimitExceededAt, tier, trialStartDate, name: orgName} = organization - if (!featureFlags?.includes('teamsLimit')) return + const hasTeamsLimitFlag = await dataLoader + .get('featureFlagByOwnerId') + .load({ownerId: orgId, featureName: 'teamsLimit'}) + if (!hasTeamsLimitFlag) return if (tierLimitExceededAt || getFeatureTier({tier, trialStartDate}) !== 'starter') return diff --git a/packages/server/database/types/Organization.ts b/packages/server/database/types/Organization.ts index 423ecf139e5..8734b1c140f 100644 --- a/packages/server/database/types/Organization.ts +++ b/packages/server/database/types/Organization.ts @@ -15,7 +15,6 @@ interface Input { updatedAt?: Date showConversionModal?: boolean payLaterClickCount?: number - featureFlags?: string[] } export default class Organization { @@ -39,7 +38,6 @@ export default class Organization { scheduledLockAt?: Date | null lockedAt?: Date | null updatedAt: Date - featureFlags?: string[] constructor(input: Input) { const { id, diff --git a/packages/server/database/types/User.ts b/packages/server/database/types/User.ts index e04364e7747..3c278e3816a 100644 --- a/packages/server/database/types/User.ts +++ b/packages/server/database/types/User.ts @@ -10,7 +10,6 @@ interface Input { preferredName: string email: string favoriteTemplateIds?: string[] - featureFlags?: string[] lastSeenAt?: Date lastSeenAtURLs?: string[] updatedAt?: Date @@ -30,7 +29,6 @@ export default class User { preferredName: string email: string favoriteTemplateIds: string[] - featureFlags: string[] lastSeenAt: Date lastSeenAtURLs: string[] | null updatedAt: Date @@ -58,7 +56,6 @@ export default class User { picture, updatedAt, favoriteTemplateIds, - featureFlags, lastSeenAt, lastSeenAtURLs, identities, @@ -77,7 +74,6 @@ export default class User { this.picture = picture this.updatedAt = updatedAt || now this.favoriteTemplateIds = favoriteTemplateIds || [] - this.featureFlags = featureFlags || [] this.identities = identities || [] this.inactive = inactive || false this.isWatched = isWatched || false diff --git a/packages/server/dataloader/__tests__/isOrgVerified.test.ts b/packages/server/dataloader/__tests__/isOrgVerified.test.ts index 2c6957b83f4..59a304c92ee 100644 --- a/packages/server/dataloader/__tests__/isOrgVerified.test.ts +++ b/packages/server/dataloader/__tests__/isOrgVerified.test.ts @@ -18,17 +18,12 @@ type TestOrganizationUser = Partial< Pick > -const addOrg = async ( - activeDomain: string | null, - members: TestOrganizationUser[], - featureFlags?: string[] -) => { +const addOrg = async (activeDomain: string | null, members: TestOrganizationUser[]) => { const orgId = activeDomain! const org = { id: orgId, activeDomain, - name: 'baddadan', - featureFlags: featureFlags ?? [] + name: 'baddadan' } const orgUsers = members.map((member) => ({ diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index a809dae9d0a..a172ce2a0f9 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -919,6 +919,66 @@ export const meetingCount = (parent: RootDataLoader, dependsOn: RegisterDependsO ) } +export const featureFlagByOwnerId = (parent: RootDataLoader) => { + return new DataLoader<{ownerId: string; featureName: string}, boolean, string>( + async (keys) => { + const pg = getKysely() + + const featureNames = [...new Set(keys.map(({featureName}) => featureName))] + const ownerIds = [...new Set(keys.map(({ownerId}) => ownerId))] + + if (!__PRODUCTION__) { + const existingFeatureNames = await pg + .selectFrom('FeatureFlag') + .select('featureName') + .where('featureName', 'in', featureNames) + .execute() + + const existingFeatureNameSet = new Set(existingFeatureNames.map((row) => row.featureName)) + + const missingFeatureNames = featureNames.filter((name) => !existingFeatureNameSet.has(name)) + if (missingFeatureNames.length > 0) { + throw new Error(`Feature flag name(s) not found: ${missingFeatureNames.join(', ')}`) + } + } + + const results = await pg + .selectFrom('FeatureFlag') + .innerJoin('FeatureFlagOwner', 'FeatureFlag.id', 'FeatureFlagOwner.featureFlagId') + .where((eb) => + eb.and([ + eb.or([ + eb('FeatureFlagOwner.userId', 'in', ownerIds), + eb('FeatureFlagOwner.teamId', 'in', ownerIds), + eb('FeatureFlagOwner.orgId', 'in', ownerIds) + ]), + eb('FeatureFlag.featureName', 'in', featureNames), + eb('FeatureFlag.expiresAt', '>', new Date()) + ]) + ) + .select([ + 'FeatureFlagOwner.userId', + 'FeatureFlagOwner.teamId', + 'FeatureFlagOwner.orgId', + 'FeatureFlag.featureName' + ]) + .execute() + + const featureFlagMap = new Map() + results.forEach(({userId, teamId, orgId, featureName}) => { + const ownerId = userId || teamId || orgId + featureFlagMap.set(`${ownerId}:${featureName}`, true) + }) + + return keys.map(({ownerId, featureName}) => featureFlagMap.has(`${ownerId}:${featureName}`)) + }, + { + ...parent.dataLoaderOptions, + cacheKeyFn: (key) => `${key.ownerId}:${key.featureName}` + } + ) +} + export const _pgmeetingCount = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { dependsOn('newMeetings') return new DataLoader<{teamId: string; meetingType: MeetingTypeEnum}, number, string>( diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 842397eb089..a9d31fbf068 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -109,6 +109,10 @@ export const slackNotifications = primaryKeyLoaderMaker((ids: readonly string[]) return selectSlackNotifications().where('id', 'in', ids).execute() }) +export const featureFlags = primaryKeyLoaderMaker((ids: readonly string[]) => { + return getKysely().selectFrom('FeatureFlag').selectAll().where('id', 'in', ids).execute() +}) + export const comments = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectComments().where('id', 'in', ids).execute() }) diff --git a/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts b/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts index b6225769604..16bdec6f6b2 100644 --- a/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts +++ b/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts @@ -20,16 +20,7 @@ const bootstrapNewUser = async ( isOrganic: boolean, dataLoader: DataLoaderWorker ) => { - const { - id: userId, - createdAt, - preferredName, - email, - featureFlags, - tier, - pseudoId, - identities - } = newUser + const {id: userId, createdAt, preferredName, email, tier, pseudoId, identities} = newUser // email is checked by the caller const domain = email.split('@')[1]! const [isCompanyDomain, organizations] = await Promise.all([ @@ -41,13 +32,6 @@ const bootstrapNewUser = async ( const joinEvent = new TimelineEventJoinedParabol({userId}) - const experimentalFlags = [...featureFlags] - - // Add signUpDestinationTeam feature flag to 50% of new accounts - if (Math.random() < 0.5) { - experimentalFlags.push('signUpDestinationTeam') - } - const isVerified = identities.some((identity) => identity.isEmailVerified) const hasSAMLURL = !!(await getSAMLURLFromEmail(email, dataLoader, false)) const isQualifiedForAutoJoin = (isVerified || hasSAMLURL) && isCompanyDomain @@ -60,7 +44,6 @@ const bootstrapNewUser = async ( qc.insertInto('User').values({ ...newUser, isPatient0, - featureFlags: experimentalFlags, identities: newUser.identities.map((identity) => JSON.stringify(identity)) }) ) @@ -76,7 +59,6 @@ const bootstrapNewUser = async ( email, name: preferredName, isActive: true, - featureFlags: experimentalFlags, highestTier: tier, isPatient0, anonymousId: pseudoId diff --git a/packages/server/graphql/mutations/helpers/canAccessAISummary.ts b/packages/server/graphql/mutations/helpers/canAccessAISummary.ts index e5a36e0cc1f..3675b02804f 100644 --- a/packages/server/graphql/mutations/helpers/canAccessAISummary.ts +++ b/packages/server/graphql/mutations/helpers/canAccessAISummary.ts @@ -5,17 +5,22 @@ import {getFeatureTier} from '../../types/helpers/getFeatureTier' const canAccessAISummary = async ( team: Team, - featureFlags: string[], - dataLoader: DataLoaderWorker, - meetingType: 'standup' | 'retrospective' + userId: string, + meetingType: 'standup' | 'retrospective', + dataLoader: DataLoaderWorker ) => { - if (featureFlags.includes('noAISummary') || !team) return false const {qualAIMeetingsCount, orgId} = team - const organization = await dataLoader.get('organizations').loadNonNull(orgId) - if (organization.featureFlags?.includes('noAISummary')) return false + const [noAIOrgSummary, noAIUserSummary] = await Promise.all([ + dataLoader.get('featureFlagByOwnerId').load({ownerId: orgId, featureName: 'noAISummary'}), + dataLoader.get('featureFlagByOwnerId').load({ownerId: userId, featureName: 'noAISummary'}) + ]) + + if (noAIOrgSummary || noAIUserSummary) return false if (meetingType === 'standup') { - if (!organization.featureFlags?.includes('standupAISummary')) return false - return true + const hasStandupFlag = await dataLoader + .get('featureFlagByOwnerId') + .load({ownerId: orgId, featureName: 'standupAISummary'}) + return hasStandupFlag } if (getFeatureTier(team) !== 'starter') return true diff --git a/packages/server/graphql/mutations/helpers/generateDiscussionPrompt.ts b/packages/server/graphql/mutations/helpers/generateDiscussionPrompt.ts index 2c8e3a094f6..586bd7bf350 100644 --- a/packages/server/graphql/mutations/helpers/generateDiscussionPrompt.ts +++ b/packages/server/graphql/mutations/helpers/generateDiscussionPrompt.ts @@ -16,9 +16,9 @@ const generateDiscussionPrompt = async ( ]) const isAISummaryAccessible = await canAccessAISummary( team, - facilitator.featureFlags, - dataLoader, - 'retrospective' + facilitator.id, + 'retrospective', + dataLoader ) if (!isAISummaryAccessible) return const [reflections, reflectionGroups] = await Promise.all([ diff --git a/packages/server/graphql/mutations/helpers/generateDiscussionSummary.ts b/packages/server/graphql/mutations/helpers/generateDiscussionSummary.ts index 0c4943e6a7e..a5871fb124b 100644 --- a/packages/server/graphql/mutations/helpers/generateDiscussionSummary.ts +++ b/packages/server/graphql/mutations/helpers/generateDiscussionSummary.ts @@ -19,9 +19,9 @@ const generateDiscussionSummary = async ( ]) const isAISummaryAccessible = await canAccessAISummary( team, - facilitator.featureFlags, - dataLoader, - 'retrospective' + facilitator.id, + 'retrospective', + dataLoader ) if (!isAISummaryAccessible) return const [comments, tasks] = await Promise.all([ diff --git a/packages/server/graphql/mutations/helpers/generateGroups.ts b/packages/server/graphql/mutations/helpers/generateGroups.ts index df1d1ea0427..071cd20bad9 100644 --- a/packages/server/graphql/mutations/helpers/generateGroups.ts +++ b/packages/server/graphql/mutations/helpers/generateGroups.ts @@ -16,9 +16,9 @@ const generateGroups = async ( if (reflections.length === 0) return const {meetingId} = reflections[0]! const team = await dataLoader.get('teams').loadNonNull(teamId) - const organization = await dataLoader.get('organizations').loadNonNull(team.orgId) - const {featureFlags} = organization - const hasSuggestGroupsFlag = featureFlags?.includes('suggestGroups') + const hasSuggestGroupsFlag = await dataLoader + .get('featureFlagByOwnerId') + .load({ownerId: team.orgId, featureName: 'suggestGroups'}) if (!hasSuggestGroupsFlag) return const groupReflectionsInput = reflections.map((reflection) => reflection.plaintextContent) const manager = new OpenAIServerManager() diff --git a/packages/server/graphql/mutations/helpers/generateStandupMeetingSummary.ts b/packages/server/graphql/mutations/helpers/generateStandupMeetingSummary.ts index 7b84ec54f00..997996f7b2c 100644 --- a/packages/server/graphql/mutations/helpers/generateStandupMeetingSummary.ts +++ b/packages/server/graphql/mutations/helpers/generateStandupMeetingSummary.ts @@ -14,9 +14,9 @@ const generateStandupMeetingSummary = async ( ]) const isAISummaryAccessible = await canAccessAISummary( team, - facilitator.featureFlags, - dataLoader, - 'standup' + facilitator.id, + 'standup', + dataLoader ) if (!isAISummaryAccessible) return diff --git a/packages/server/graphql/mutations/helpers/generateWholeMeetingSentimentScore.ts b/packages/server/graphql/mutations/helpers/generateWholeMeetingSentimentScore.ts index 6a594ba02a0..4400e3998b4 100644 --- a/packages/server/graphql/mutations/helpers/generateWholeMeetingSentimentScore.ts +++ b/packages/server/graphql/mutations/helpers/generateWholeMeetingSentimentScore.ts @@ -9,7 +9,10 @@ const generateWholeMeetingSentimentScore = async ( dataLoader.get('users').loadNonNull(facilitatorUserId), dataLoader.get('retroReflectionsByMeetingId').load(meetingId) ]) - if (facilitator.featureFlags.includes('noAISummary') || reflections.length === 0) return undefined + const hasNoAISummary = await dataLoader + .get('featureFlagByOwnerId') + .load({ownerId: facilitator.id, featureName: 'noAISummary'}) + if (hasNoAISummary || reflections.length === 0) return undefined const reflectionsWithSentimentScores = reflections.filter( ({sentimentScore}) => sentimentScore !== undefined ) diff --git a/packages/server/graphql/mutations/helpers/generateWholeMeetingSummary.ts b/packages/server/graphql/mutations/helpers/generateWholeMeetingSummary.ts index 037cc4135de..b9d8ed4932e 100644 --- a/packages/server/graphql/mutations/helpers/generateWholeMeetingSummary.ts +++ b/packages/server/graphql/mutations/helpers/generateWholeMeetingSummary.ts @@ -17,9 +17,9 @@ const generateWholeMeetingSummary = async ( ]) const isAISummaryAccessible = await canAccessAISummary( team, - facilitator.featureFlags, - dataLoader, - 'retrospective' + facilitator.id, + 'retrospective', + dataLoader ) if (!isAISummaryAccessible) return const [commentsByDiscussions, tasksByDiscussions, reflections] = await Promise.all([ diff --git a/packages/server/graphql/mutations/helpers/updateTeamInsights.ts b/packages/server/graphql/mutations/helpers/updateTeamInsights.ts index ba9bc32cc5f..d54f321a173 100644 --- a/packages/server/graphql/mutations/helpers/updateTeamInsights.ts +++ b/packages/server/graphql/mutations/helpers/updateTeamInsights.ts @@ -17,8 +17,10 @@ const updateTeamInsights = async (teamId: string, dataLoader: DataLoaderWorker) // team is loaded anyways by the callers, so no harm in loading it here again for a more concise argument list const team = await dataLoader.get('teams').loadNonNull(teamId) const {orgId} = team - const organization = await dataLoader.get('organizations').load(orgId) - if (organization?.featureFlags?.includes('noTeamInsights')) return + const hasNoTeamInsightsFlag = await dataLoader + .get('featureFlagByOwnerId') + .load({ownerId: orgId, featureName: 'noTeamInsights'}) + if (hasNoTeamInsightsFlag) return // actual update const r = await getRethink() diff --git a/packages/server/graphql/private/mutations/addFeatureFlag.ts b/packages/server/graphql/private/mutations/addFeatureFlag.ts new file mode 100644 index 00000000000..f61cb879017 --- /dev/null +++ b/packages/server/graphql/private/mutations/addFeatureFlag.ts @@ -0,0 +1,30 @@ +import getKysely from '../../../postgres/getKysely' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const addFeatureFlag: MutationResolvers['addFeatureFlag'] = async ( + _source, + {featureName, description, expiresAt, scope} +) => { + const pg = getKysely() + + const newFeatureFlag = await pg + .insertInto('FeatureFlag') + .values({ + featureName, + description, + expiresAt, + scope + }) + .returning('id') + .executeTakeFirst() + + if (!newFeatureFlag) { + return standardError(new Error('Failed to insert new feature flag')) + } + return { + featureFlagId: newFeatureFlag.id + } +} + +export default addFeatureFlag diff --git a/packages/server/graphql/private/mutations/applyFeatureFlag.ts b/packages/server/graphql/private/mutations/applyFeatureFlag.ts new file mode 100644 index 00000000000..8db3a7fdee1 --- /dev/null +++ b/packages/server/graphql/private/mutations/applyFeatureFlag.ts @@ -0,0 +1,88 @@ +import getKysely from '../../../postgres/getKysely' +import getUsersByDomain from '../../../postgres/queries/getUsersByDomain' +import {getUsersByEmails} from '../../../postgres/queries/getUsersByEmails' +import {getUserId} from '../../../utils/authorization' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const applyFeatureFlag: MutationResolvers['applyFeatureFlag'] = async ( + _source, + {flagName, subjects}, + {authToken} +) => { + const pg = getKysely() + + const viewerId = getUserId(authToken) + + const subjectKeys = Object.keys(subjects) + + if (subjectKeys.length === 0) { + return standardError(new Error('At least one subject type must be provided'), { + userId: viewerId + }) + } + + const featureFlag = await pg + .selectFrom('FeatureFlag') + .select(['id', 'scope']) + .where('featureName', '=', flagName) + .executeTakeFirst() + + if (!featureFlag) { + return standardError(new Error('Feature flag not found'), {userId: viewerId}) + } + + const {id: featureFlagId, scope} = featureFlag + + const userIds: string[] = [] + const teamIds: string[] = [] + const orgIds: string[] = [] + + if (scope === 'User') { + if (subjects.emails) { + const users = await getUsersByEmails(subjects.emails) + userIds.push(...users.map((user) => user.id)) + } + + if (subjects.domains) { + for (const domain of subjects.domains) { + const domainUsers = await getUsersByDomain(domain) + userIds.push(...domainUsers.map((user) => user.id)) + } + } + + if (subjects.userIds) { + userIds.push(...subjects.userIds) + } + } else if (scope === 'Team') { + if (subjects.teamIds) { + teamIds.push(...subjects.teamIds) + } + } else if (scope === 'Organization') { + if (subjects.orgIds) { + orgIds.push(...subjects.orgIds) + } + } + + const values = + scope === 'User' + ? userIds.map((userId) => ({userId, featureFlagId})) + : scope === 'Team' + ? teamIds.map((teamId) => ({teamId, featureFlagId})) + : orgIds.map((orgId) => ({orgId, featureFlagId})) + + await pg + .insertInto('FeatureFlagOwner') + .values(values) + .onConflict((oc) => oc.doNothing()) + .execute() + + return { + featureFlagId, + userIds: scope === 'User' ? userIds : [], + teamIds: scope === 'Team' ? teamIds : [], + orgIds: scope === 'Organization' ? orgIds : [] + } +} + +export default applyFeatureFlag diff --git a/packages/server/graphql/private/mutations/connectSocket.ts b/packages/server/graphql/private/mutations/connectSocket.ts index 6b69ebcb515..e15bc9e7d77 100644 --- a/packages/server/graphql/private/mutations/connectSocket.ts +++ b/packages/server/graphql/private/mutations/connectSocket.ts @@ -82,7 +82,6 @@ const connectSocket: MutationResolvers['connectSocket'] = async ( userId, email: user.email, isActive: true, - featureFlags: user.featureFlags, highestTier: user.tier, isPatient0: user.isPatient0 }) diff --git a/packages/server/graphql/private/mutations/deleteFeatureFlag.ts b/packages/server/graphql/private/mutations/deleteFeatureFlag.ts new file mode 100644 index 00000000000..1227b396e0e --- /dev/null +++ b/packages/server/graphql/private/mutations/deleteFeatureFlag.ts @@ -0,0 +1,25 @@ +import getKysely from '../../../postgres/getKysely' +import {MutationResolvers} from '../resolverTypes' + +const deleteFeatureFlag: MutationResolvers['deleteFeatureFlag'] = async ( + _source, + {featureName} +) => { + const pg = getKysely() + + const deletedFeatureFlag = await pg + .deleteFrom('FeatureFlag') + .where('featureName', '=', featureName) + .returning('id') + .executeTakeFirst() + + if (!deletedFeatureFlag) { + throw new Error('Feature flag not found') + } + + return { + featureFlagId: deletedFeatureFlag.id + } +} + +export default deleteFeatureFlag diff --git a/packages/server/graphql/private/mutations/updateFeatureFlag.ts b/packages/server/graphql/private/mutations/updateFeatureFlag.ts new file mode 100644 index 00000000000..7e5225138cd --- /dev/null +++ b/packages/server/graphql/private/mutations/updateFeatureFlag.ts @@ -0,0 +1,29 @@ +import getKysely from '../../../postgres/getKysely' +import {MutationResolvers} from '../resolverTypes' + +const updateFeatureFlag: MutationResolvers['updateFeatureFlag'] = async ( + _source, + {featureName, description, expiresAt} +) => { + const pg = getKysely() + + const updatedFeatureFlag = await pg + .updateTable('FeatureFlag') + .set({ + description, + expiresAt: expiresAt || undefined + }) + .where('featureName', '=', featureName) + .returning('id') + .executeTakeFirst() + + if (!updatedFeatureFlag) { + throw new Error('Feature flag not found') + } + + return { + featureFlagId: updatedFeatureFlag.id + } +} + +export default updateFeatureFlag diff --git a/packages/server/graphql/private/mutations/updateOrgFeatureFlag.ts b/packages/server/graphql/private/mutations/updateOrgFeatureFlag.ts deleted file mode 100644 index dbd12ba0c41..00000000000 --- a/packages/server/graphql/private/mutations/updateOrgFeatureFlag.ts +++ /dev/null @@ -1,36 +0,0 @@ -import {sql} from 'kysely' -import getKysely from '../../../postgres/getKysely' -import isValid from '../../isValid' -import {MutationResolvers} from '../resolverTypes' - -const updateOrgFeatureFlag: MutationResolvers['updateOrgFeatureFlag'] = async ( - _source, - {orgIds, flag, addFlag}, - {dataLoader} -) => { - const existingOrgs = (await dataLoader.get('organizations').loadMany(orgIds)).filter(isValid) - const existingIds = existingOrgs.map(({id}) => id) - - const nonExistingIds = orgIds.filter((x) => !existingIds.includes(x)) - - if (nonExistingIds.length) { - return {error: {message: `Organizations does not exists: ${nonExistingIds.join(', ')}`}} - } - - // RESOLUTION - const updatedOrgIds = 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() - - return {updatedOrgIds: updatedOrgIds.map(({id}) => id)} -} - -export default updateOrgFeatureFlag diff --git a/packages/server/graphql/private/queries/getAllFeatureFlags.ts b/packages/server/graphql/private/queries/getAllFeatureFlags.ts new file mode 100644 index 00000000000..28acfda878c --- /dev/null +++ b/packages/server/graphql/private/queries/getAllFeatureFlags.ts @@ -0,0 +1,10 @@ +import getKysely from '../../../postgres/getKysely' +import {QueryResolvers} from '../resolverTypes' + +const getAllFeatureFlags: QueryResolvers['getAllFeatureFlags'] = async () => { + const pg = getKysely() + + return await pg.selectFrom('FeatureFlag').selectAll().orderBy('featureName').execute() +} + +export default getAllFeatureFlags diff --git a/packages/server/graphql/private/typeDefs/AddFeatureFlagPayload.graphql b/packages/server/graphql/private/typeDefs/AddFeatureFlagPayload.graphql new file mode 100644 index 00000000000..6d665e6a039 --- /dev/null +++ b/packages/server/graphql/private/typeDefs/AddFeatureFlagPayload.graphql @@ -0,0 +1,4 @@ +""" +Return value for addFeatureFlag, which could be an error +""" +union AddFeatureFlagPayload = ErrorPayload | AddFeatureFlagSuccess diff --git a/packages/server/graphql/private/typeDefs/AddFeatureFlagSuccess.graphql b/packages/server/graphql/private/typeDefs/AddFeatureFlagSuccess.graphql new file mode 100644 index 00000000000..abaf639179e --- /dev/null +++ b/packages/server/graphql/private/typeDefs/AddFeatureFlagSuccess.graphql @@ -0,0 +1,6 @@ +type AddFeatureFlagSuccess { + """ + The newly created feature flag + """ + featureFlag: FeatureFlag! +} diff --git a/packages/server/graphql/private/typeDefs/ApplyFeatureFlagPayload.graphql b/packages/server/graphql/private/typeDefs/ApplyFeatureFlagPayload.graphql new file mode 100644 index 00000000000..1c2ddafc2b5 --- /dev/null +++ b/packages/server/graphql/private/typeDefs/ApplyFeatureFlagPayload.graphql @@ -0,0 +1,4 @@ +""" +Return value for applyFeatureFlag, which could be an error or success +""" +union ApplyFeatureFlagPayload = ErrorPayload | ApplyFeatureFlagSuccess diff --git a/packages/server/graphql/private/typeDefs/ApplyFeatureFlagSuccess.graphql b/packages/server/graphql/private/typeDefs/ApplyFeatureFlagSuccess.graphql new file mode 100644 index 00000000000..bfee6a54a1e --- /dev/null +++ b/packages/server/graphql/private/typeDefs/ApplyFeatureFlagSuccess.graphql @@ -0,0 +1,21 @@ +""" +Successful result of applying a feature flag +""" +type ApplyFeatureFlagSuccess { + """ + The feature flag that was applied + """ + featureFlag: FeatureFlag! + """ + The Users to which the feature flag was applied + """ + users: [User!] + """ + The Teams to which the feature flag was applied + """ + teams: [Team!] + """ + The Organizations to which the feature flag was applied + """ + organizations: [Organization!] +} diff --git a/packages/server/graphql/private/typeDefs/DeleteFeatureFlagPayload.graphql b/packages/server/graphql/private/typeDefs/DeleteFeatureFlagPayload.graphql new file mode 100644 index 00000000000..ec4b201b272 --- /dev/null +++ b/packages/server/graphql/private/typeDefs/DeleteFeatureFlagPayload.graphql @@ -0,0 +1,4 @@ +""" +Return value for deleteFeatureFlag, which could be an error +""" +union DeleteFeatureFlagPayload = ErrorPayload | DeleteFeatureFlagSuccess diff --git a/packages/server/graphql/private/typeDefs/DeleteFeatureFlagSuccess.graphql b/packages/server/graphql/private/typeDefs/DeleteFeatureFlagSuccess.graphql new file mode 100644 index 00000000000..42e25d2dcef --- /dev/null +++ b/packages/server/graphql/private/typeDefs/DeleteFeatureFlagSuccess.graphql @@ -0,0 +1,6 @@ +type DeleteFeatureFlagSuccess { + """ + The deleted feature flag + """ + featureFlag: FeatureFlag! +} diff --git a/packages/server/graphql/private/typeDefs/FeatureFlagScope.graphql b/packages/server/graphql/private/typeDefs/FeatureFlagScope.graphql new file mode 100644 index 00000000000..f3cc09c7c0b --- /dev/null +++ b/packages/server/graphql/private/typeDefs/FeatureFlagScope.graphql @@ -0,0 +1,8 @@ +""" +The list of scopes available for feature flags +""" +enum FeatureFlagScope { + User + Team + Organization +} diff --git a/packages/server/graphql/private/typeDefs/Mutation.graphql b/packages/server/graphql/private/typeDefs/Mutation.graphql index 8aad93abd57..f27c8f308a8 100644 --- a/packages/server/graphql/private/typeDefs/Mutation.graphql +++ b/packages/server/graphql/private/typeDefs/Mutation.graphql @@ -1,4 +1,72 @@ type Mutation { + """ + Add a new feature flag + """ + addFeatureFlag( + """ + The name of the feature flag + """ + featureName: String! + + """ + A description of the feature flag + """ + description: String! + + """ + The expiration date of the feature flag + """ + expiresAt: DateTime! + + """ + The scope of the feature flag + """ + scope: FeatureFlagScope! + ): AddFeatureFlagPayload! + + """ + Apply a feature flag to specified subjects (users, teams, or organizations) + """ + applyFeatureFlag( + """ + The name of the feature flag to apply + """ + flagName: String! + """ + The subjects to which the feature flag will be applied + """ + subjects: SubjectsInput! + ): ApplyFeatureFlagPayload! + + """ + Delete an existing feature flag + """ + deleteFeatureFlag( + """ + The name of the feature flag to delete + """ + featureName: String! + ): DeleteFeatureFlagPayload! + + """ + Update an existing feature flag + """ + updateFeatureFlag( + """ + The name of the feature flag + """ + featureName: String! + + """ + The new description of the feature flag (optional) + """ + description: String + + """ + The new expiration date of the feature flag (optional) + """ + expiresAt: DateTime + ): UpdateFeatureFlagPayload! """ Updates the user email """ @@ -463,26 +531,6 @@ type Mutation { emails: [String!]! ): ToggleAllowInsightsPayload! - """ - Give some organizations advanced features in a flag - """ - updateOrgFeatureFlag( - """ - a list of organization ids - """ - orgIds: [String!]! - - """ - the flag that you want to give to the organization - """ - flag: OrganizationFeatureFlagsEnum! - - """ - whether or not you want to give the organization the flag - """ - addFlag: Boolean! - ): UpdateOrgFeatureFlagPayload! - """ Describe the mutation here """ diff --git a/packages/server/graphql/private/typeDefs/OrganizationFeatureFlagsEnum.graphql b/packages/server/graphql/private/typeDefs/OrganizationFeatureFlagsEnum.graphql deleted file mode 100644 index c464347283c..00000000000 --- a/packages/server/graphql/private/typeDefs/OrganizationFeatureFlagsEnum.graphql +++ /dev/null @@ -1,17 +0,0 @@ -""" -A flag to give an individual organization super powers -""" -enum OrganizationFeatureFlagsEnum { - noAISummary - standupAISummary - noPromptToJoinOrg - suggestGroups - zoomTranscription - shareSummary - teamsLimit - noTeamInsights - singleColumnStandups - publicTeams - aiTemplate - relatedDiscussions -} diff --git a/packages/server/graphql/private/typeDefs/Query.graphql b/packages/server/graphql/private/typeDefs/Query.graphql index e03d9be4f6e..fda75c5b187 100644 --- a/packages/server/graphql/private/typeDefs/Query.graphql +++ b/packages/server/graphql/private/typeDefs/Query.graphql @@ -40,6 +40,11 @@ type Query { userId: ID ): Company + """ + Get all feature flags to see all of the unique feature flag names + """ + getAllFeatureFlags: [FeatureFlag!] + """ Post signup and login metrics to slack """ diff --git a/packages/server/graphql/private/typeDefs/SubjectsInput.graphql b/packages/server/graphql/private/typeDefs/SubjectsInput.graphql new file mode 100644 index 00000000000..2db0629a82e --- /dev/null +++ b/packages/server/graphql/private/typeDefs/SubjectsInput.graphql @@ -0,0 +1,22 @@ +input SubjectsInput { + """ + List of email addresses to apply the feature flag to + """ + emails: [String!] + """ + List of domains to apply the feature flag to all users of + """ + domains: [String!] + """ + List of User IDs to apply the feature flag to + """ + userIds: [ID!] + """ + List of Team IDs to apply the feature flag to + """ + teamIds: [ID!] + """ + List of Organization IDs to apply the feature flag to + """ + orgIds: [ID!] +} diff --git a/packages/server/graphql/private/typeDefs/UpdateFeatureFlagPayload.graphql b/packages/server/graphql/private/typeDefs/UpdateFeatureFlagPayload.graphql new file mode 100644 index 00000000000..59e8da6862e --- /dev/null +++ b/packages/server/graphql/private/typeDefs/UpdateFeatureFlagPayload.graphql @@ -0,0 +1,4 @@ +""" +Return value for updateFeatureFlag, which could be an error +""" +union UpdateFeatureFlagPayload = ErrorPayload | UpdateFeatureFlagSuccess diff --git a/packages/server/graphql/private/typeDefs/UpdateFeatureFlagSuccess.graphql b/packages/server/graphql/private/typeDefs/UpdateFeatureFlagSuccess.graphql new file mode 100644 index 00000000000..e6f9ddbdb4c --- /dev/null +++ b/packages/server/graphql/private/typeDefs/UpdateFeatureFlagSuccess.graphql @@ -0,0 +1,6 @@ +type UpdateFeatureFlagSuccess { + """ + The newly updated feature flag + """ + featureFlag: FeatureFlag! +} diff --git a/packages/server/graphql/private/typeDefs/UpdateOrgFeatureFlagPayload.graphql b/packages/server/graphql/private/typeDefs/UpdateOrgFeatureFlagPayload.graphql deleted file mode 100644 index cc79b4a21d3..00000000000 --- a/packages/server/graphql/private/typeDefs/UpdateOrgFeatureFlagPayload.graphql +++ /dev/null @@ -1 +0,0 @@ -union UpdateOrgFeatureFlagPayload = ErrorPayload | UpdateOrgFeatureFlagSuccess diff --git a/packages/server/graphql/private/typeDefs/UpdateOrgFeatureFlagSuccess.graphql b/packages/server/graphql/private/typeDefs/UpdateOrgFeatureFlagSuccess.graphql deleted file mode 100644 index e3bf3b86821..00000000000 --- a/packages/server/graphql/private/typeDefs/UpdateOrgFeatureFlagSuccess.graphql +++ /dev/null @@ -1,6 +0,0 @@ -type UpdateOrgFeatureFlagSuccess { - """ - the organizations given the super power - """ - updatedOrganizations: [Organization] -} diff --git a/packages/server/graphql/private/types/AddFeatureFlagSuccess.ts b/packages/server/graphql/private/types/AddFeatureFlagSuccess.ts new file mode 100644 index 00000000000..d54de22edcd --- /dev/null +++ b/packages/server/graphql/private/types/AddFeatureFlagSuccess.ts @@ -0,0 +1,13 @@ +import {AddFeatureFlagSuccessResolvers} from '../resolverTypes' + +export type AddFeatureFlagSuccessSource = { + featureFlagId: string +} + +const AddFeatureFlagSuccess: AddFeatureFlagSuccessResolvers = { + featureFlag: async ({featureFlagId}, _, {dataLoader}) => { + return dataLoader.get('featureFlags').loadNonNull(featureFlagId) + } +} + +export default AddFeatureFlagSuccess diff --git a/packages/server/graphql/private/types/ApplyFeatureFlagSuccess.ts b/packages/server/graphql/private/types/ApplyFeatureFlagSuccess.ts new file mode 100644 index 00000000000..3a20b2b05f3 --- /dev/null +++ b/packages/server/graphql/private/types/ApplyFeatureFlagSuccess.ts @@ -0,0 +1,29 @@ +import isValid from '../../isValid' +import {ApplyFeatureFlagSuccessResolvers} from '../resolverTypes' + +export type ApplyFeatureFlagSuccessSource = { + featureFlagId: string + userIds: string[] | null + teamIds: string[] | null + orgIds: string[] | null +} + +const ApplyFeatureFlagSuccess: ApplyFeatureFlagSuccessResolvers = { + featureFlag: async ({featureFlagId}, _args, {dataLoader}) => { + return dataLoader.get('featureFlags').loadNonNull(featureFlagId) + }, + users: async ({userIds}, _args, {dataLoader}) => { + if (!userIds) return null + return (await dataLoader.get('users').loadMany(userIds)).filter(isValid) + }, + teams: async ({teamIds}, _args, {dataLoader}) => { + if (!teamIds) return null + return (await dataLoader.get('teams').loadMany(teamIds)).filter(isValid) + }, + organizations: async ({orgIds}, _args, {dataLoader}) => { + if (!orgIds) return null + return (await dataLoader.get('organizations').loadMany(orgIds)).filter(isValid) + } +} + +export default ApplyFeatureFlagSuccess diff --git a/packages/server/graphql/private/types/DeleteFeatureFlagSuccess.ts b/packages/server/graphql/private/types/DeleteFeatureFlagSuccess.ts new file mode 100644 index 00000000000..9c053b8191a --- /dev/null +++ b/packages/server/graphql/private/types/DeleteFeatureFlagSuccess.ts @@ -0,0 +1,13 @@ +import {DeleteFeatureFlagSuccessResolvers} from '../resolverTypes' + +export type DeleteFeatureFlagSuccessSource = { + featureFlagId: string +} + +const DeleteFeatureFlagSuccess: DeleteFeatureFlagSuccessResolvers = { + featureFlag: async ({featureFlagId}, _args, {dataLoader}) => { + return dataLoader.get('featureFlags').loadNonNull(featureFlagId) + } +} + +export default DeleteFeatureFlagSuccess diff --git a/packages/server/graphql/private/types/UpdateFeatureFlagSuccess.ts b/packages/server/graphql/private/types/UpdateFeatureFlagSuccess.ts new file mode 100644 index 00000000000..87c1d1228ab --- /dev/null +++ b/packages/server/graphql/private/types/UpdateFeatureFlagSuccess.ts @@ -0,0 +1,13 @@ +import {UpdateFeatureFlagSuccessResolvers} from '../resolverTypes' + +export type UpdateFeatureFlagSuccessSource = { + featureFlagId: string +} + +const UpdateFeatureFlagSuccess: UpdateFeatureFlagSuccessResolvers = { + featureFlag: async ({featureFlagId}, _args, {dataLoader}) => { + return dataLoader.get('featureFlags').loadNonNull(featureFlagId) + } +} + +export default UpdateFeatureFlagSuccess diff --git a/packages/server/graphql/private/types/UpdateOrgFeatureFlagSuccess.ts b/packages/server/graphql/private/types/UpdateOrgFeatureFlagSuccess.ts deleted file mode 100644 index 3816d9b5733..00000000000 --- a/packages/server/graphql/private/types/UpdateOrgFeatureFlagSuccess.ts +++ /dev/null @@ -1,17 +0,0 @@ -import isValid from '../../isValid' -import {UpdateOrgFeatureFlagSuccessResolvers} from '../resolverTypes' - -export type UpdateOrgFeatureFlagSuccessSource = - | {updatedOrgIds: string[]} - | {error: {message: string}} - -const UpdateOrgFeatureFlagSuccess: UpdateOrgFeatureFlagSuccessResolvers = { - updatedOrganizations: async (source, _args, {dataLoader}) => { - if ('error' in source) return [] - const {updatedOrgIds} = source - const organizations = await dataLoader.get('organizations').loadMany(updatedOrgIds) - return organizations.filter(isValid) - } -} - -export default UpdateOrgFeatureFlagSuccess diff --git a/packages/server/graphql/public/mutations/setMeetingSettings.ts b/packages/server/graphql/public/mutations/setMeetingSettings.ts index 7dda9b23d33..76b2f3d30f0 100644 --- a/packages/server/graphql/public/mutations/setMeetingSettings.ts +++ b/packages/server/graphql/public/mutations/setMeetingSettings.ts @@ -29,9 +29,9 @@ const setMeetingSettings: MutationResolvers['setMeetingSettings'] = async ( dataLoader.get('teams').loadNonNull(teamId), dataLoader.get('users').loadNonNull(viewerId) ]) - const organization = await dataLoader.get('organizations').loadNonNull(team.orgId) - const {featureFlags} = organization - const hasTranscriptFlag = featureFlags?.includes('zoomTranscription') + const hasTranscriptFlag = await dataLoader + .get('featureFlagByOwnerId') + .load({ownerId: team.orgId, featureName: 'zoomTranscription'}) const firstPhases: NewMeetingPhaseTypeEnum[] = [] if (checkinEnabled || (checkinEnabled !== false && phaseTypes.includes('checkin'))) { diff --git a/packages/server/graphql/public/mutations/updateFeatureFlag.ts b/packages/server/graphql/public/mutations/updateFeatureFlag.ts deleted file mode 100644 index bf7a19cef69..00000000000 --- a/packages/server/graphql/public/mutations/updateFeatureFlag.ts +++ /dev/null @@ -1,59 +0,0 @@ -import {ValueExpression, sql} from 'kysely' -import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getKysely from '../../../postgres/getKysely' -import {DB} from '../../../postgres/pg' -import getUsersByDomain from '../../../postgres/queries/getUsersByDomain' -import {getUsersByEmails} from '../../../postgres/queries/getUsersByEmails' -import IUser from '../../../postgres/types/IUser' -import {getUserId, isSuperUser} from '../../../utils/authorization' -import publish from '../../../utils/publish' -import standardError from '../../../utils/standardError' -import {MutationResolvers} from '../resolverTypes' - -const updateFeatureFlag: MutationResolvers['updateFeatureFlag'] = async ( - _source, - {emails, domain, flag, addFlag}, - {authToken, dataLoader} -) => { - const operationId = dataLoader.share() - const subOptions = {operationId} - const pg = getKysely() - - // AUTH - const viewerId = getUserId(authToken) - const isUpdatingViewerFlag = !emails?.length && !domain - if (!isUpdatingViewerFlag && !isSuperUser(authToken)) { - return standardError(new Error('Not authorised to update feature flag'), { - userId: viewerId - }) - } - - // RESOLUTION - const users = [] as IUser[] - if (emails) { - const usersByEmail = await getUsersByEmails(emails) - users.push(...usersByEmail) - } - if (domain) { - const usersByDomain = await getUsersByDomain(domain) - users.push(...usersByDomain) - } - - if (users.length === 0) { - return {error: {message: 'No users found matching the email or domain'}} - } - - const userIds = isUpdatingViewerFlag ? [viewerId] : users.map(({id}) => id) - const featureFlags: ValueExpression = addFlag - ? sql`arr_append_uniq("featureFlags", ${flag})` - : sql`array_remove("featureFlags", ${flag})` - await pg.updateTable('User').set({featureFlags}).where('id', 'in', userIds).execute() - userIds.forEach((userId) => { - const data = {userId} - publish(SubscriptionChannel.NOTIFICATION, userId, 'UpdateFeatureFlagPayload', data, subOptions) - }) - - return {userIds} -} - -export default updateFeatureFlag diff --git a/packages/server/graphql/public/typeDefs/FeatureFlag.graphql b/packages/server/graphql/public/typeDefs/FeatureFlag.graphql new file mode 100644 index 00000000000..36b230d9498 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/FeatureFlag.graphql @@ -0,0 +1,16 @@ +type FeatureFlag { + """ + The name of the feature flag + """ + featureName: String! + + """ + Description of the feature flag + """ + description: String + + """ + Expiration date of the feature flag + """ + expiresAt: DateTime! +} diff --git a/packages/server/graphql/public/typeDefs/FeatureFlagOwner.graphql b/packages/server/graphql/public/typeDefs/FeatureFlagOwner.graphql new file mode 100644 index 00000000000..e6a81edcbdc --- /dev/null +++ b/packages/server/graphql/public/typeDefs/FeatureFlagOwner.graphql @@ -0,0 +1,21 @@ +type FeatureFlagOwner { + """ + The feature flag id + """ + featureFlagId: ID! + + """ + The user ID if the owner is a user + """ + userId: ID + + """ + The team ID if the owner is a team + """ + teamId: ID + + """ + The organization ID if the owner is an organization + """ + orgId: ID +} diff --git a/packages/server/graphql/public/typeDefs/Mutation.graphql b/packages/server/graphql/public/typeDefs/Mutation.graphql index 7b59ea7a5a5..8e63179b3a9 100644 --- a/packages/server/graphql/public/typeDefs/Mutation.graphql +++ b/packages/server/graphql/public/typeDefs/Mutation.graphql @@ -1754,32 +1754,6 @@ type Mutation { paymentMethodId: ID! ): UpdateCreditCardPayload! - """ - Give someone advanced features in a flag - """ - updateFeatureFlag( - """ - a list of the complete or partial email of the person to whom you are giving advanced features. - Matches via a regex to support entire domains - """ - emails: [String!] - - """ - grant access to an entire domain. the part of the email after the @ - """ - domain: String - - """ - the flag that you want to give to the user - """ - flag: UserFlagEnum! - - """ - whether to add or remove the flag - """ - addFlag: Boolean! - ): UpdateFeatureFlagPayload! - """ Update how a parabol dimension maps to a GitLab label """ diff --git a/packages/server/graphql/public/typeDefs/NotificationSubscriptionPayload.graphql b/packages/server/graphql/public/typeDefs/NotificationSubscriptionPayload.graphql index 0a719c7ac0a..b119801745f 100644 --- a/packages/server/graphql/public/typeDefs/NotificationSubscriptionPayload.graphql +++ b/packages/server/graphql/public/typeDefs/NotificationSubscriptionPayload.graphql @@ -22,7 +22,6 @@ type NotificationSubscriptionPayload { PersistGitHubSearchQuerySuccess: PersistGitHubSearchQuerySuccess JiraServerIssue: JiraServerIssue AddedNotification: AddedNotification - UpdateFeatureFlagPayload: UpdateFeatureFlagPayload JiraIssue: JiraIssue UpdatedNotification: UpdatedNotification RemoveIntegrationSearchQuerySuccess: RemoveIntegrationSearchQuerySuccess diff --git a/packages/server/graphql/public/typeDefs/Organization.graphql b/packages/server/graphql/public/typeDefs/Organization.graphql index 4a08189c7f2..2c178205380 100644 --- a/packages/server/graphql/public/typeDefs/Organization.graphql +++ b/packages/server/graphql/public/typeDefs/Organization.graphql @@ -171,9 +171,9 @@ type Organization { teamStats: [TeamStat!]! """ - Any super power given to the organization via a super user + Whether the org has a feature flag enabled or not """ - featureFlags: OrganizationFeatureFlags! + featureFlag(featureName: String!): Boolean! """ The SAML record attached to the Organization, if any diff --git a/packages/server/graphql/public/typeDefs/OrganizationFeatureFlags.graphql b/packages/server/graphql/public/typeDefs/OrganizationFeatureFlags.graphql deleted file mode 100644 index d0c765b0d10..00000000000 --- a/packages/server/graphql/public/typeDefs/OrganizationFeatureFlags.graphql +++ /dev/null @@ -1,17 +0,0 @@ -""" -The types of flags that give an individual organization super powers -""" -type OrganizationFeatureFlags { - noAISummary: Boolean! - standupAISummary: Boolean! - noPromptToJoinOrg: Boolean! - suggestGroups: Boolean! - zoomTranscription: Boolean! - shareSummary: Boolean! - teamsLimit: Boolean! - noTeamInsights: Boolean! - singleColumnStandups: Boolean! - publicTeams: Boolean! - aiTemplate: Boolean! - relatedDiscussions: Boolean! -} diff --git a/packages/server/graphql/public/typeDefs/Team.graphql b/packages/server/graphql/public/typeDefs/Team.graphql index 3e8ee51d148..5bed3eae97e 100644 --- a/packages/server/graphql/public/typeDefs/Team.graphql +++ b/packages/server/graphql/public/typeDefs/Team.graphql @@ -107,6 +107,11 @@ type Team { """ activeMeetings: [NewMeeting!]! + """ + Whether the team has a feature flag enabled or not + """ + featureFlag(featureName: String!): Boolean! + """ The number of qualifying meetings that have an AI generated summary. Qualifying meetings are meetings with three or more meeting members and five or more reflections """ diff --git a/packages/server/graphql/public/typeDefs/UpdateFeatureFlagPayload.graphql b/packages/server/graphql/public/typeDefs/UpdateFeatureFlagPayload.graphql deleted file mode 100644 index d1b49e06e88..00000000000 --- a/packages/server/graphql/public/typeDefs/UpdateFeatureFlagPayload.graphql +++ /dev/null @@ -1,13 +0,0 @@ -type UpdateFeatureFlagPayload { - error: StandardMutationError - - """ - the user that was given the super power. Use users instead in GraphiQL since it may affect multiple users - """ - user: User - - """ - the users given the super power - """ - users: [User] -} diff --git a/packages/server/graphql/public/typeDefs/User.graphql b/packages/server/graphql/public/typeDefs/User.graphql index 65469d0d6c8..59aeef1eb1c 100644 --- a/packages/server/graphql/public/typeDefs/User.graphql +++ b/packages/server/graphql/public/typeDefs/User.graphql @@ -394,9 +394,9 @@ type User { favoriteTemplates: [MeetingTemplate!]! """ - Any super power given to the user via a super user + Whether the user has a feature flag enabled or not """ - featureFlags: UserFeatureFlags! + featureFlag(featureName: String!): Boolean! """ url of user’s profile picture diff --git a/packages/server/graphql/public/typeDefs/UserFeatureFlags.graphql b/packages/server/graphql/public/typeDefs/UserFeatureFlags.graphql deleted file mode 100644 index a5b79f2b454..00000000000 --- a/packages/server/graphql/public/typeDefs/UserFeatureFlags.graphql +++ /dev/null @@ -1,11 +0,0 @@ -""" -The types of flags that give an individual user super powers -""" -type UserFeatureFlags { - standups: Boolean! - insights: Boolean! - recurrence: Boolean! - noAISummary: Boolean! - noMeetingHistoryLimit: Boolean! - signUpDestinationTeam: Boolean! -} diff --git a/packages/server/graphql/public/typeDefs/UserFlagEnum.graphql b/packages/server/graphql/public/typeDefs/UserFlagEnum.graphql deleted file mode 100644 index af80f22f44d..00000000000 --- a/packages/server/graphql/public/typeDefs/UserFlagEnum.graphql +++ /dev/null @@ -1,11 +0,0 @@ -""" -A flag to give an individual user super powers -""" -enum UserFlagEnum { - standups - insights - recurrence - noAISummary - noMeetingHistoryLimit - signUpDestinationTeam -} diff --git a/packages/server/graphql/public/types/Organization.ts b/packages/server/graphql/public/types/Organization.ts index 9383e193f7d..a6f8bb007da 100644 --- a/packages/server/graphql/public/types/Organization.ts +++ b/packages/server/graphql/public/types/Organization.ts @@ -23,9 +23,8 @@ const Organization: OrganizationResolvers = { if (!activeDomain || !isSuperUser(authToken)) return null return {id: activeDomain} }, - featureFlags: ({featureFlags}) => { - if (!featureFlags) return {} - return Object.fromEntries(featureFlags.map((flag) => [flag as any, true])) + featureFlag: async ({id: orgId}, {featureName}, {dataLoader}) => { + return await dataLoader.get('featureFlagByOwnerId').load({ownerId: orgId, featureName}) }, picture: async ({picture}, _args, {dataLoader}) => { if (!picture) return null @@ -62,7 +61,9 @@ const Organization: OrganizationResolvers = { isUserOrgAdmin(viewerId, orgId, dataLoader) ]) const sortedTeamsOnOrg = allTeamsOnOrg.sort((a, b) => a.name.localeCompare(b.name)) - const hasPublicTeamsFlag = !!organization.featureFlags?.includes('publicTeams') + const hasPublicTeamsFlag = await dataLoader + .get('featureFlagByOwnerId') + .load({ownerId: organization.id, featureName: 'publicTeams'}) if (isOrgAdmin || isSuperUser(authToken) || hasPublicTeamsFlag) { const viewerTeams = sortedTeamsOnOrg.filter((team) => authToken.tms.includes(team.id)) const otherTeams = sortedTeamsOnOrg.filter((team) => !authToken.tms.includes(team.id)) @@ -80,11 +81,10 @@ const Organization: OrganizationResolvers = { }, publicTeams: async ({id: orgId}, _args, {dataLoader, authToken}) => { - const [allTeamsOnOrg, organization] = await Promise.all([ + const [allTeamsOnOrg, hasPublicTeamsFlag] = await Promise.all([ dataLoader.get('teamsByOrgIds').load(orgId), - dataLoader.get('organizations').loadNonNull(orgId) + dataLoader.get('featureFlagByOwnerId').load({ownerId: orgId, featureName: 'publicTeams'}) ]) - const hasPublicTeamsFlag = !!organization.featureFlags?.includes('publicTeams') if (!isSuperUser(authToken) || !hasPublicTeamsFlag) return [] const publicTeams = allTeamsOnOrg.filter((team) => !isTeamMember(authToken, team.id)) return publicTeams diff --git a/packages/server/graphql/public/types/OrganizationFeatureFlags.ts b/packages/server/graphql/public/types/OrganizationFeatureFlags.ts deleted file mode 100644 index cf3ba1898a5..00000000000 --- a/packages/server/graphql/public/types/OrganizationFeatureFlags.ts +++ /dev/null @@ -1,18 +0,0 @@ -import {OrganizationFeatureFlagsResolvers} from '../resolverTypes' - -const OrganizationFeatureFlags: OrganizationFeatureFlagsResolvers = { - noAISummary: ({noAISummary}) => !!noAISummary, - standupAISummary: ({standupAISummary}) => !!standupAISummary, - noPromptToJoinOrg: ({noPromptToJoinOrg}) => !!noPromptToJoinOrg, - zoomTranscription: ({zoomTranscription}) => !!zoomTranscription, - shareSummary: ({shareSummary}) => !!shareSummary, - suggestGroups: ({suggestGroups}) => !!suggestGroups, - teamsLimit: ({teamsLimit}) => !!teamsLimit, - noTeamInsights: ({noTeamInsights}) => !!noTeamInsights, - publicTeams: ({publicTeams}) => !!publicTeams, - singleColumnStandups: ({singleColumnStandups}) => !!singleColumnStandups, - aiTemplate: ({aiTemplate}) => !!aiTemplate, - relatedDiscussions: ({relatedDiscussions}) => !!relatedDiscussions -} - -export default OrganizationFeatureFlags diff --git a/packages/server/graphql/public/types/Team.ts b/packages/server/graphql/public/types/Team.ts index c55f46565dd..8cd5ccbbae9 100644 --- a/packages/server/graphql/public/types/Team.ts +++ b/packages/server/graphql/public/types/Team.ts @@ -10,8 +10,11 @@ const Team: TeamResolvers = { _args, {dataLoader} ) => { - const org = await dataLoader.get('organizations').load(orgId) - if (org?.featureFlags?.includes('noTeamInsights')) return null + const noTeamInsights = await dataLoader + .get('featureFlagByOwnerId') + .load({ownerId: orgId, featureName: 'noTeamInsights'}) + + if (noTeamInsights) return null if (!mostUsedEmojis && !meetingEngagement && !topRetroTemplates) return null const mappedTopRetroTemplates = Array.isArray(topRetroTemplates) @@ -51,6 +54,9 @@ const Team: TeamResolvers = { teamLead: async ({id: teamId}, _args, {dataLoader}) => { const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) return teamMembers.find((teamMember) => teamMember.isLead)! + }, + featureFlag: async ({id: teamId}, {featureName}, {dataLoader}) => { + return await dataLoader.get('featureFlagByOwnerId').load({ownerId: teamId, featureName}) } } diff --git a/packages/server/graphql/public/types/UpdateFeatureFlagPayload.ts b/packages/server/graphql/public/types/UpdateFeatureFlagPayload.ts deleted file mode 100644 index 26f0b5a6d06..00000000000 --- a/packages/server/graphql/public/types/UpdateFeatureFlagPayload.ts +++ /dev/null @@ -1,20 +0,0 @@ -import {getUserId} from '../../../utils/authorization' -import isValid from '../../isValid' -import {UpdateFeatureFlagPayloadResolvers} from '../resolverTypes' - -export type UpdateFeatureFlagPayloadSource = {userIds: string[]} | {error: {message: string}} - -const UpdateFeatureFlagPayload: UpdateFeatureFlagPayloadResolvers = { - user: (_source, _args, {authToken, dataLoader}) => { - const viewerId = getUserId(authToken) - return dataLoader.get('users').loadNonNull(viewerId) - }, - users: async (source, _args, {dataLoader}) => { - if ('error' in source) return [] - const {userIds} = source - const users = await dataLoader.get('users').loadMany(userIds) - return users.filter(isValid) - } -} - -export default UpdateFeatureFlagPayload diff --git a/packages/server/graphql/public/types/User.ts b/packages/server/graphql/public/types/User.ts index d1f63613754..736e6b9b102 100644 --- a/packages/server/graphql/public/types/User.ts +++ b/packages/server/graphql/public/types/User.ts @@ -680,8 +680,8 @@ const User: ReqResolvers<'User'> = { favoriteTemplates: async ({favoriteTemplateIds}, _args, {dataLoader}) => { return (await dataLoader.get('meetingTemplates').loadMany(favoriteTemplateIds)).filter(isValid) }, - featureFlags: ({featureFlags}) => { - return Object.fromEntries(featureFlags.map((flag) => [flag as any, true])) + featureFlag: async ({id: userId}, {featureName}, {dataLoader}) => { + return await dataLoader.get('featureFlagByOwnerId').load({ownerId: userId, featureName}) }, availableTemplates: async ({id: userId}, {first, after, type}, {authToken, dataLoader}) => { const viewerId = getUserId(authToken) diff --git a/packages/server/graphql/public/types/UserFeatureFlags.ts b/packages/server/graphql/public/types/UserFeatureFlags.ts deleted file mode 100644 index eb7947a851d..00000000000 --- a/packages/server/graphql/public/types/UserFeatureFlags.ts +++ /dev/null @@ -1,10 +0,0 @@ -import {UserFeatureFlagsResolvers} from '../resolverTypes' - -const UserFeatureFlags: UserFeatureFlagsResolvers = { - insights: ({insights}) => !!insights, - noAISummary: ({noAISummary}) => !!noAISummary, - noMeetingHistoryLimit: ({noMeetingHistoryLimit}) => !!noMeetingHistoryLimit, - signUpDestinationTeam: ({signUpDestinationTeam}) => !!signUpDestinationTeam -} - -export default UserFeatureFlags diff --git a/packages/server/graphql/types/helpers/isMeetingLocked.ts b/packages/server/graphql/types/helpers/isMeetingLocked.ts index e455a26b02e..a607f764b06 100644 --- a/packages/server/graphql/types/helpers/isMeetingLocked.ts +++ b/packages/server/graphql/types/helpers/isMeetingLocked.ts @@ -12,18 +12,16 @@ const isMeetingLocked = async ( if (!endedAt || endedAt > freeLimit) { return false } - const [team, viewer] = await Promise.all([ + const [team, hasNoMeetingHistoryLimit] = await Promise.all([ dataLoader.get('teams').loadNonNull(teamId), - dataLoader.get('users').loadNonNull(viewerId) + dataLoader + .get('featureFlagByOwnerId') + .load({ownerId: viewerId, featureName: 'noMeetingHistoryLimit'}) ]) - const {featureFlags} = viewer + if (hasNoMeetingHistoryLimit) return false const {tier, trialStartDate, isPaid, orgId, isArchived} = team - if (featureFlags.includes('noMeetingHistoryLimit')) { - return false - } - if ((tier !== 'starter' && isPaid) || trialStartDate) { return false } diff --git a/packages/server/postgres/migrations/1727249259984_addFeatureFlagTables.ts b/packages/server/postgres/migrations/1727249259984_addFeatureFlagTables.ts new file mode 100644 index 00000000000..7b84d353ab9 --- /dev/null +++ b/packages/server/postgres/migrations/1727249259984_addFeatureFlagTables.ts @@ -0,0 +1,62 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import getPg from '../getPg' + +export async function up() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + await pg.schema.createType('FeatureFlagScope').asEnum(['User', 'Team', 'Organization']).execute() + + await pg.schema + .createTable('FeatureFlag') + .ifNotExists() + .addColumn('id', 'uuid', (col) => col.primaryKey().defaultTo(sql`gen_random_uuid()`)) + .addColumn('featureName', 'varchar(255)', (col) => col.notNull()) + .addColumn('scope', sql`"FeatureFlagScope"`, (col) => col.notNull()) + .addColumn('description', 'text') + .addColumn('expiresAt', 'timestamptz', (col) => col.notNull()) + .addColumn('createdAt', 'timestamptz', (col) => col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)) + .addColumn('updatedAt', 'timestamptz', (col) => col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)) + .addUniqueConstraint('unique_featureName_scope', ['featureName', 'scope']) + .execute() + + await pg.schema + .createTable('FeatureFlagOwner') + .ifNotExists() + .addColumn('featureFlagId', 'uuid', (col) => + col.notNull().references('FeatureFlag.id').onDelete('cascade') + ) + .addColumn('userId', 'varchar(255)', (col) => col.references('User.id').onDelete('cascade')) + .addColumn('teamId', 'varchar(255)', (col) => col.references('Team.id').onDelete('cascade')) + .addColumn('orgId', 'varchar(255)', (col) => + col.references('Organization.id').onDelete('cascade') + ) + .addColumn('createdAt', 'timestamptz', (col) => col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)) + .addCheckConstraint( + 'check_feature_flag_owner_exclusivity', + sql` + (("userId" IS NOT NULL AND "teamId" IS NULL AND "orgId" IS NULL) OR + ("userId" IS NULL AND "teamId" IS NOT NULL AND "orgId" IS NULL) OR + ("userId" IS NULL AND "teamId" IS NULL AND "orgId" IS NOT NULL)) + ` + ) + .addUniqueConstraint('unique_feature_flag_owner_user', ['userId', 'featureFlagId']) + .addUniqueConstraint('unique_feature_flag_owner_team', ['teamId', 'featureFlagId']) + .addUniqueConstraint('unique_feature_flag_owner_org', ['orgId', 'featureFlagId']) + .execute() +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + await pg.schema.dropTable('FeatureFlagOwner').execute() + await pg.schema.dropTable('FeatureFlag').execute() + await pg.schema.dropType('FeatureFlagScope').execute() // Drop the enum type +} diff --git a/packages/server/postgres/migrations/1727885996466_migrateFeatureFlags.ts b/packages/server/postgres/migrations/1727885996466_migrateFeatureFlags.ts new file mode 100644 index 00000000000..b5eac845af5 --- /dev/null +++ b/packages/server/postgres/migrations/1727885996466_migrateFeatureFlags.ts @@ -0,0 +1,64 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import getPg from '../getPg' + +export async function up() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + const FIFTY_YEARS_FROM_NOW = new Date(Date.now() + 50 * 365 * 24 * 60 * 60 * 1000) + + await pg.transaction().execute(async (trx) => { + const insertResult = await trx + .insertInto('FeatureFlag') + .values({ + featureName: 'noAISummary', + scope: 'Organization', + description: 'Disables AI summary feature', + expiresAt: FIFTY_YEARS_FROM_NOW + }) + .returning('id') + .executeTakeFirstOrThrow() + + const featureFlagId = insertResult.id + + await sql` + INSERT INTO "FeatureFlagOwner" ("featureFlagId", "orgId") + SELECT ${featureFlagId} AS "featureFlagId", "id" AS "orgId" + FROM "Organization" + WHERE "featureFlags" @> ARRAY['noAISummary']::text[]; + `.execute(trx) + + await sql` + INSERT INTO "FeatureFlagOwner" ("featureFlagId", "orgId") + SELECT DISTINCT ON ("OrganizationUser"."orgId") + ${featureFlagId} AS "featureFlagId", + "OrganizationUser"."orgId" AS "orgId" + FROM "User" + INNER JOIN "OrganizationUser" ON "OrganizationUser"."userId" = "User"."id" + WHERE "User"."featureFlags" @> ARRAY['noAISummary']::varchar(50)[] + ON CONFLICT ("featureFlagId", "orgId") DO NOTHING; + `.execute(trx) + }) +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + await pg.transaction().execute(async (trx) => { + await trx + .deleteFrom('FeatureFlagOwner') + .using('FeatureFlag') + .where('FeatureFlagOwner.featureFlagId', '=', sql.ref('FeatureFlag.id')) + .where('FeatureFlag.featureName', '=', 'noAISummary') + .execute() + + await trx.deleteFrom('FeatureFlag').where('featureName', '=', 'noAISummary').execute() + }) +} diff --git a/packages/server/postgres/queries/src/backupUserQuery.sql b/packages/server/postgres/queries/src/backupUserQuery.sql index f25d98092bc..3749771682c 100644 --- a/packages/server/postgres/queries/src/backupUserQuery.sql +++ b/packages/server/postgres/queries/src/backupUserQuery.sql @@ -11,7 +11,6 @@ tier, picture, tms, - featureFlags, lastSeenAtURLs, identities, pseudoId, @@ -34,7 +33,6 @@ "tier", "picture", "tms", - "featureFlags", "lastSeenAtURLs", "identities", "pseudoId", @@ -55,7 +53,6 @@ "tier" = EXCLUDED."tier", "picture" = EXCLUDED."picture", "tms" = EXCLUDED."tms", - "featureFlags" = EXCLUDED."featureFlags", "lastSeenAtURLs" = EXCLUDED."lastSeenAtURLs", "identities" = EXCLUDED."identities", "pseudoId" = EXCLUDED."pseudoId", diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index d0eacbffb2e..d8baac2ce3d 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -147,8 +147,7 @@ export const selectOrganizations = () => 'trialStartDate', 'scheduledLockAt', 'lockedAt', - 'updatedAt', - 'featureFlags' + 'updatedAt' ]) .select(({fn}) => [fn('to_json', ['creditCard']).as('creditCard')]) diff --git a/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts b/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts index 543515656c3..d518a0ad301 100644 --- a/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts +++ b/packages/server/utils/__tests__/isRequestToJoinDomainAllowed.test.ts @@ -23,14 +23,13 @@ const addUsers = async (users: TestUser[]) => { const addOrg = async ( activeDomain: string | null, members: TestOrganizationUser[], - rest?: {featureFlags?: string[]; tier?: TierEnum} + rest?: {tier?: TierEnum} ) => { - const {featureFlags, tier} = rest ?? {} + const {tier} = rest ?? {} const orgId = generateUID() const org = { id: orgId, activeDomain, - featureFlags, name: 'foog', tier: tier ?? 'starter' } diff --git a/packages/server/utils/analytics/analytics.ts b/packages/server/utils/analytics/analytics.ts index 568cda427e8..c5767be4733 100644 --- a/packages/server/utils/analytics/analytics.ts +++ b/packages/server/utils/analytics/analytics.ts @@ -30,7 +30,6 @@ export type IdentifyOptions = { anonymousId?: string name?: string isActive?: boolean - featureFlags?: string[] highestTier?: string isPatient0?: boolean createdAt?: Date From 209921f12d3248a557b5aaf34cdb7f6cd40137b4 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 15:15:37 +0100 Subject: [PATCH 491/529] chore(release): release v7.50.0 (#10302) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index e2abe57e39d..8ce6906ffd8 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.49.1" + ".": "7.50.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bcb297a730..8c998b70366 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.50.0](https://github.com/ParabolInc/parabol/compare/v7.49.1...v7.50.0) (2024-10-07) + + +### Added + +* add feature flag tables ([#10184](https://github.com/ParabolInc/parabol/issues/10184)) ([ff6c25e](https://github.com/ParabolInc/parabol/commit/ff6c25e38dbab78075bc7398f32939fec5b44f45)) + ## [7.49.1](https://github.com/ParabolInc/parabol/compare/v7.49.0...v7.49.1) (2024-10-04) diff --git a/package.json b/package.json index d88658e297d..7751ff437f3 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.49.1", + "version": "7.50.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 503679cc1c5..8866040008f 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.49.1", + "version": "7.50.0", "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.49.1" + "parabol-server": "7.50.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index 78f82cf1257..b3ff5a0fa3c 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.49.1", + "version": "7.50.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 75a75961921..73ff431270a 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.49.1", + "version": "7.50.0", "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 88a3275ed7e..6cbb3807e3c 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.49.1", + "version": "7.50.0", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.49.1", - "parabol-server": "7.49.1", + "parabol-client": "7.50.0", + "parabol-server": "7.50.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index d243af0b5ca..d04e17a132b 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.49.1", + "version": "7.50.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index cb93f0f7f7e..bbf0d454b9c 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.49.1", + "version": "7.50.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.49.1", + "parabol-client": "7.50.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 6c058acf51b1031edb53feee0102602788d3bfca Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Mon, 7 Oct 2024 16:12:17 +0100 Subject: [PATCH 492/529] chore: improve feature flag error feedback (#10304) --- packages/server/dataloader/customLoaderMakers.ts | 4 +++- .../server/graphql/private/mutations/applyFeatureFlag.ts | 8 ++++++++ .../server/graphql/public/typeDefs/FeatureFlag.graphql | 5 +++++ .../{private => public}/typeDefs/FeatureFlagScope.graphql | 0 4 files changed, 16 insertions(+), 1 deletion(-) rename packages/server/graphql/{private => public}/typeDefs/FeatureFlagScope.graphql (100%) diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index a172ce2a0f9..1c0edc837ad 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -938,7 +938,9 @@ export const featureFlagByOwnerId = (parent: RootDataLoader) => { const missingFeatureNames = featureNames.filter((name) => !existingFeatureNameSet.has(name)) if (missingFeatureNames.length > 0) { - throw new Error(`Feature flag name(s) not found: ${missingFeatureNames.join(', ')}`) + console.error( + `Feature flag name(s) not found: ${missingFeatureNames.join(', ')}. Add the feature flag name with the addFeatureFlag mutation.` + ) } } diff --git a/packages/server/graphql/private/mutations/applyFeatureFlag.ts b/packages/server/graphql/private/mutations/applyFeatureFlag.ts index 8db3a7fdee1..ad99e3a796a 100644 --- a/packages/server/graphql/private/mutations/applyFeatureFlag.ts +++ b/packages/server/graphql/private/mutations/applyFeatureFlag.ts @@ -71,6 +71,14 @@ const applyFeatureFlag: MutationResolvers['applyFeatureFlag'] = async ( ? teamIds.map((teamId) => ({teamId, featureFlagId})) : orgIds.map((orgId) => ({orgId, featureFlagId})) + if (values.length === 0) { + return standardError( + new Error( + 'No valid subjects found to apply the feature flag. Check the scope of the feature flag.' + ) + ) + } + await pg .insertInto('FeatureFlagOwner') .values(values) diff --git a/packages/server/graphql/public/typeDefs/FeatureFlag.graphql b/packages/server/graphql/public/typeDefs/FeatureFlag.graphql index 36b230d9498..4582658e167 100644 --- a/packages/server/graphql/public/typeDefs/FeatureFlag.graphql +++ b/packages/server/graphql/public/typeDefs/FeatureFlag.graphql @@ -13,4 +13,9 @@ type FeatureFlag { Expiration date of the feature flag """ expiresAt: DateTime! + + """ + The scope of the feature flag + """ + scope: FeatureFlagScope! } diff --git a/packages/server/graphql/private/typeDefs/FeatureFlagScope.graphql b/packages/server/graphql/public/typeDefs/FeatureFlagScope.graphql similarity index 100% rename from packages/server/graphql/private/typeDefs/FeatureFlagScope.graphql rename to packages/server/graphql/public/typeDefs/FeatureFlagScope.graphql From 533f55560bbfa43154f5a90be6f36e81410b507c Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:12:54 +0100 Subject: [PATCH 493/529] chore(release): release v7.50.1 (#10305) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 8ce6906ffd8..7f6c84caa2d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.50.0" + ".": "7.50.1" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 8c998b70366..12d0358e162 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.50.1](https://github.com/ParabolInc/parabol/compare/v7.50.0...v7.50.1) (2024-10-07) + + +### Changed + +* improve feature flag error feedback ([#10304](https://github.com/ParabolInc/parabol/issues/10304)) ([6c058ac](https://github.com/ParabolInc/parabol/commit/6c058acf51b1031edb53feee0102602788d3bfca)) + ## [7.50.0](https://github.com/ParabolInc/parabol/compare/v7.49.1...v7.50.0) (2024-10-07) diff --git a/package.json b/package.json index 7751ff437f3..7926abd8184 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.50.0", + "version": "7.50.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 8866040008f..2f7df668169 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.50.0", + "version": "7.50.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.50.0" + "parabol-server": "7.50.1" } } diff --git a/packages/client/package.json b/packages/client/package.json index b3ff5a0fa3c..81b98a0d3d5 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.50.0", + "version": "7.50.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 73ff431270a..1e30af2d778 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.50.0", + "version": "7.50.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 6cbb3807e3c..3927c3edbcc 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.50.0", + "version": "7.50.1", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.50.0", - "parabol-server": "7.50.0", + "parabol-client": "7.50.1", + "parabol-server": "7.50.1", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index d04e17a132b..35bbaac5962 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.50.0", + "version": "7.50.1", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index bbf0d454b9c..b34ff1e608b 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.50.0", + "version": "7.50.1", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.50.0", + "parabol-client": "7.50.1", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From ef997187ffa92fce0e8866f93943abfbf59ea660 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Mon, 7 Oct 2024 22:40:02 +0700 Subject: [PATCH 494/529] chore(metrics): update org activities GraphQL query (#10278) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .../orgActivities.ts} | 65 ++++++++++++------- ...tyReport.graphql => orgActivities.graphql} | 30 ++++++--- 2 files changed, 61 insertions(+), 34 deletions(-) rename packages/server/graphql/private/{mutations/runOrgActivityReport.ts => queries/orgActivities.ts} (62%) rename packages/server/graphql/private/typeDefs/{runOrgActivityReport.graphql => orgActivities.graphql} (52%) diff --git a/packages/server/graphql/private/mutations/runOrgActivityReport.ts b/packages/server/graphql/private/queries/orgActivities.ts similarity index 62% rename from packages/server/graphql/private/mutations/runOrgActivityReport.ts rename to packages/server/graphql/private/queries/orgActivities.ts index 17c27f8d8c0..fcdc37ea6cd 100644 --- a/packages/server/graphql/private/mutations/runOrgActivityReport.ts +++ b/packages/server/graphql/private/queries/orgActivities.ts @@ -2,12 +2,9 @@ import {sql} from 'kysely' import getRethink from '../../../database/rethinkDriver' import {RDatum, RValue} from '../../../database/stricterR' import getKysely from '../../../postgres/getKysely' -import {MutationResolvers} from '../resolverTypes' +import {OrgActivityRow, QueryResolvers} from '../resolverTypes' -const runOrgActivityReport: MutationResolvers['runOrgActivityReport'] = async ( - _source, - {startDate, endDate} -) => { +const orgActivities: QueryResolvers['orgActivities'] = async (_source, {startDate, endDate}) => { const pg = getKysely() const now = new Date() const queryEndDate = endDate || now @@ -25,13 +22,17 @@ const runOrgActivityReport: MutationResolvers['runOrgActivityReport'] = async ( const userSignups = pg .selectFrom('User') + .innerJoin('OrganizationUser', 'User.id', 'OrganizationUser.userId') + .innerJoin('Organization', 'OrganizationUser.orgId', 'Organization.id') .select([ - sql`date_trunc('month', "createdAt")`.as('month'), - sql`COUNT(DISTINCT "id")`.as('signup_count') + sql`date_trunc('month', "User"."createdAt")`.as('month'), + 'Organization.name as orgName', + sql`COUNT(DISTINCT "User"."id")`.as('signup_count') ]) - .where('createdAt', '>=', queryStartDate) - .where('createdAt', '<', queryEndDate) - .groupBy(sql`date_trunc('month', "createdAt")`) + .where('User.createdAt', '>=', queryStartDate) + .where('User.createdAt', '<', queryEndDate) + .groupBy(sql`date_trunc('month', "User"."createdAt")`) + .groupBy('Organization.name') const query = pg .selectFrom(months.as('m')) @@ -40,6 +41,7 @@ const runOrgActivityReport: MutationResolvers['runOrgActivityReport'] = async ( ) .select([ sql`m."monthStart"`.as('monthStart'), + sql`COALESCE(us."orgName", 'All Organizations')`.as('orgName'), sql`COALESCE(us.signup_count, 0)`.as('signupCount') ]) .orderBy('monthStart') @@ -82,31 +84,44 @@ const runOrgActivityReport: MutationResolvers['runOrgActivityReport'] = async ( ]) // Combine PostgreSQL and RethinkDB results - const combinedResults = pgResults.map((pgRow: any) => { + const combinedResults: OrgActivityRow[] = pgResults.reduce((acc: any, pgRow: any) => { const monthStart = new Date(pgRow.monthStart) - const rethinkParticipants = rethinkResults.find( - (r: any) => - r.yearMonth.month === monthStart.getUTCMonth() + 1 && - r.yearMonth.year === monthStart.getUTCFullYear() - ) - const rethinkMeetings = rethinkResults.find( + const monthKey = monthStart.toISOString() + + if (!acc[monthKey]) { + acc[monthKey] = { + monthStart: pgRow.monthStart, + signups: [], + participantCount: 0, + meetingCount: 0 + } + } + + acc[monthKey].signups.push({ + orgName: pgRow.orgName, + count: pgRow.signupCount + }) + + const rethinkData = rethinkResults.find( (r: any) => r.yearMonth.month === monthStart.getUTCMonth() + 1 && r.yearMonth.year === monthStart.getUTCFullYear() ) - return { - monthStart: pgRow.monthStart, - signupCount: pgRow.signupCount ? pgRow.signupCount : 0, - participantCount: rethinkParticipants ? rethinkParticipants.participantCount : 0, - meetingCount: rethinkMeetings ? rethinkMeetings.meetingCount : 0 + if (rethinkData) { + acc[monthKey].participantCount = rethinkData.participantCount + acc[monthKey].meetingCount = rethinkData.meetingCount } - }) - return {rows: combinedResults} + + return acc + }, {}) + + const rows = Object.values(combinedResults) + return {rows} } catch (error) { console.error('Error executing Org Activity Report:', error) return {error: {message: 'Error executing Org Activity Report'}} } } -export default runOrgActivityReport +export default orgActivities diff --git a/packages/server/graphql/private/typeDefs/runOrgActivityReport.graphql b/packages/server/graphql/private/typeDefs/orgActivities.graphql similarity index 52% rename from packages/server/graphql/private/typeDefs/runOrgActivityReport.graphql rename to packages/server/graphql/private/typeDefs/orgActivities.graphql index f4a90a913fe..1743e24f452 100644 --- a/packages/server/graphql/private/typeDefs/runOrgActivityReport.graphql +++ b/packages/server/graphql/private/typeDefs/orgActivities.graphql @@ -1,8 +1,8 @@ -extend type Mutation { +extend type Query { """ - Get org activity by month + Get org activities by month """ - runOrgActivityReport( + orgActivities( """ the start date for the query """ @@ -12,15 +12,15 @@ extend type Mutation { the end date for the query """ endDate: DateTime - ): RunOrgActivityReportPayload! + ): OrgActivitiesPayload! } """ -Return value for runOrgActivityReport, which could be an error +Return value for orgActivities, which could be an error """ -union RunOrgActivityReportPayload = ErrorPayload | RunOrgActivityReportSuccess +union OrgActivitiesPayload = ErrorPayload | OrgActivitiesSuccess -type RunOrgActivityReportSuccess { +type OrgActivitiesSuccess { """ The rows of the report """ @@ -34,9 +34,9 @@ type OrgActivityRow { monthStart: DateTime! """ - The number of signups in the month + The signups grouped by organization name """ - signupCount: Int! + signups: [OrgSignup!]! """ The number of unique meeting participants in the month @@ -48,3 +48,15 @@ type OrgActivityRow { """ meetingCount: Int! } + +type OrgSignup { + """ + The name of the organization + """ + orgName: String! + + """ + The number of signups for this organization + """ + count: Int! +} From 939324df257202205f0952b9e87fbac650b10bef Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Mon, 7 Oct 2024 16:46:07 +0100 Subject: [PATCH 495/529] chore(release): release v7.50.2 (#10307) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7f6c84caa2d..021ff70bd94 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.50.1" + ".": "7.50.2" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 12d0358e162..a4f4da458e0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.50.2](https://github.com/ParabolInc/parabol/compare/v7.50.1...v7.50.2) (2024-10-07) + + +### Changed + +* **metrics:** update org activities GraphQL query ([#10278](https://github.com/ParabolInc/parabol/issues/10278)) ([ef99718](https://github.com/ParabolInc/parabol/commit/ef997187ffa92fce0e8866f93943abfbf59ea660)) + ## [7.50.1](https://github.com/ParabolInc/parabol/compare/v7.50.0...v7.50.1) (2024-10-07) diff --git a/package.json b/package.json index 7926abd8184..a448f4d9418 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.50.1", + "version": "7.50.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 2f7df668169..31081b2540c 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.50.1", + "version": "7.50.2", "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.50.1" + "parabol-server": "7.50.2" } } diff --git a/packages/client/package.json b/packages/client/package.json index 81b98a0d3d5..e71fba695ff 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.50.1", + "version": "7.50.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 1e30af2d778..02de87fa8ea 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.50.1", + "version": "7.50.2", "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 3927c3edbcc..26f0bf114e8 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.50.1", + "version": "7.50.2", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.50.1", - "parabol-server": "7.50.1", + "parabol-client": "7.50.2", + "parabol-server": "7.50.2", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 35bbaac5962..3bf110af28f 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.50.1", + "version": "7.50.2", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index b34ff1e608b..8ed83890863 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.50.1", + "version": "7.50.2", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.50.1", + "parabol-client": "7.50.2", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From de317d2dabcf6834b0564f7aab1af5ad02443d66 Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Tue, 8 Oct 2024 17:45:32 +0100 Subject: [PATCH 496/529] fix(webserver): exits with code 0 when SIGTERM is handled (#10301) --- packages/server/server.ts | 3 ++- packages/server/socketHandlers/handleDisconnect.ts | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/server/server.ts b/packages/server/server.ts index b5c9e316782..c17b262568b 100644 --- a/packages/server/server.ts +++ b/packages/server/server.ts @@ -47,10 +47,11 @@ process.on('SIGTERM', async (signal) => { Object.values(activeClients.store).map(async (connectionContext) => { const disconnectIn = Math.floor(Math.random() * RECONNECT_WINDOW) await sleep(disconnectIn) - handleDisconnect(connectionContext) + await handleDisconnect(connectionContext) }) ) console.log(`Server ID: ${process.env.SERVER_ID}. Graceful shutdown complete, exiting.`) + process.exit() }) const PORT = Number(__PRODUCTION__ ? process.env.PORT : process.env.SOCKET_PORT) diff --git a/packages/server/socketHandlers/handleDisconnect.ts b/packages/server/socketHandlers/handleDisconnect.ts index 6f95edc12bb..99f2bb2fcda 100644 --- a/packages/server/socketHandlers/handleDisconnect.ts +++ b/packages/server/socketHandlers/handleDisconnect.ts @@ -18,7 +18,7 @@ mutation DisconnectSocket($userId: ID!) { } }` -const handleDisconnect = (connectionContext: ConnectionContext, options: Options = {}) => { +const handleDisconnect = async (connectionContext: ConnectionContext, options: Options = {}) => { const {exitCode = 1000, reason} = options const {authToken, ip, cancelKeepAlive, id: socketId, socket, isDisconnecting} = connectionContext if (isDisconnecting) return @@ -28,7 +28,7 @@ const handleDisconnect = (connectionContext: ConnectionContext, options: Options relayUnsubscribeAll(connectionContext) if (authToken.rol !== 'impersonate') { const userId = getUserId(authToken) - publishInternalGQL({authToken, ip, query: disconnectQuery, socketId, variables: {userId}}) + await publishInternalGQL({authToken, ip, query: disconnectQuery, socketId, variables: {userId}}) } activeClients.delete(connectionContext.id) closeTransport(socket, exitCode, reason) From 1667810a192e82bd8d2cd49b2ed0ba138559458f Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 8 Oct 2024 10:41:42 -0700 Subject: [PATCH 497/529] chore(rethinkdb): NewMeeting: Phase 3 (#10273) Signed-off-by: Matt Krick --- ...MetadataForRetrospectiveDiscussionTopic.ts | 17 +- .../indexing/retrospectiveDiscussionTopic.ts | 2 +- .../workflows/relatedDiscussionsStart.ts | 2 +- packages/server/__tests__/globalSetup.ts | 6 +- .../__tests__/processRecurrence.test.ts | 380 +++++++++++------- packages/server/database/rMapIf.ts | 12 - packages/server/database/rethinkDriver.ts | 11 +- packages/server/database/updateStage.ts | 33 -- .../server/dataloader/customLoaderMakers.ts | 126 +----- .../server/dataloader/customRedisQueries.ts | 29 +- .../dataloader/foreignKeyLoaderMakers.ts | 11 +- .../dataloader/primaryKeyLoaderMakers.ts | 5 +- .../rethinkForeignKeyLoaderMakers.ts | 27 -- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../email/newMeetingSummaryEmailCreator.tsx | 2 +- .../graphql/mutations/createReflection.ts | 9 - .../graphql/mutations/dragDiscussionTopic.ts | 9 - .../graphql/mutations/dragEstimatingTask.ts | 9 - .../server/graphql/mutations/endCheckIn.ts | 35 +- .../graphql/mutations/endSprintPoker.ts | 25 +- .../graphql/mutations/flagReadyToAdvance.ts | 4 - .../addAgendaItemToActiveActionMeeting.ts | 51 +-- .../graphql/mutations/helpers/addRecallBot.ts | 3 - .../helpers/createNewMeetingPhases.ts | 52 ++- .../endMeeting/sendNewMeetingSummary.ts | 4 - .../mutations/helpers/generateGroups.ts | 13 +- .../mutations/helpers/handleCompletedStage.ts | 8 - .../mutations/helpers/hideConversionModal.ts | 21 +- .../helpers/notifications/SlackNotifier.ts | 18 +- .../helpers/removeStagesFromMeetings.ts | 11 - .../mutations/helpers/removeTeamMember.ts | 71 ++-- .../helpers/removeUserFromMeetingStages.ts | 11 - .../mutations/helpers/safeEndRetrospective.ts | 37 +- .../mutations/helpers/safeEndTeamPrompt.ts | 39 +- .../helpers/updateQualAIMeetingsCount.ts | 2 +- .../mutations/helpers/updateTeamInsights.ts | 46 +-- .../server/graphql/mutations/joinMeeting.ts | 27 -- .../graphql/mutations/navigateMeeting.ts | 24 +- packages/server/graphql/mutations/payLater.ts | 37 +- .../graphql/mutations/pokerResetDimension.ts | 9 +- .../graphql/mutations/pokerRevealVotes.ts | 9 - .../mutations/promoteNewMeetingFacilitator.ts | 11 - .../graphql/mutations/removeReflection.ts | 13 +- .../server/graphql/mutations/renameMeeting.ts | 9 - .../resetRetroMeetingToGroupStage.ts | 5 +- .../server/graphql/mutations/setPhaseFocus.ts | 3 - .../server/graphql/mutations/setStageTimer.ts | 12 +- .../graphql/mutations/startSprintPoker.ts | 16 +- .../mutations/updateNewCheckInQuestion.ts | 11 - .../graphql/mutations/updatePokerScope.ts | 11 - .../mutations/updateReflectionContent.ts | 2 +- .../mutations/updateReflectionGroupTitle.ts | 2 +- .../graphql/mutations/updateRetroMaxVotes.ts | 37 +- .../graphql/mutations/updateTemplateScope.ts | 24 +- .../graphql/mutations/voteForPokerStory.ts | 32 -- .../mutations/voteForReflectionGroup.ts | 2 +- .../mutations/generateMeetingSummary.ts | 43 +- .../private/mutations/hardDeleteUser.ts | 37 +- .../private/mutations/processRecurrence.ts | 20 +- .../private/mutations/runScheduledJobs.ts | 2 +- .../graphql/private/queries/orgActivities.ts | 123 +++--- .../graphql/public/mutations/addComment.ts | 2 +- .../public/mutations/addReactjiToReactable.ts | 2 +- .../graphql/public/mutations/autogroup.ts | 3 - .../graphql/public/mutations/endTeamPrompt.ts | 5 +- .../public/mutations/generateInsight.ts | 2 +- .../public/mutations/helpers/getSummaries.ts | 43 +- .../public/mutations/helpers/getTopics.ts | 41 +- .../public/mutations/resetReflectionGroups.ts | 7 - .../public/mutations/revealTeamHealthVotes.ts | 2 - .../public/mutations/setTeamHealthVote.ts | 26 +- .../graphql/public/mutations/shareTopic.ts | 2 +- .../graphql/public/mutations/startCheckIn.ts | 20 +- .../public/mutations/startRetrospective.ts | 16 +- .../public/mutations/startTeamPrompt.ts | 9 +- .../public/mutations/updateAgendaItem.ts | 9 - .../public/mutations/updateMeetingPrompt.ts | 9 - .../public/mutations/updateMeetingTemplate.ts | 3 - .../mutations/updateRecurrenceSettings.ts | 47 +-- .../graphql/public/types/ActionMeeting.ts | 2 +- .../public/types/ActionMeetingMember.ts | 2 +- .../public/types/AddAgendaItemPayload.ts | 2 +- .../types/AddTranscriptionBotSuccess.ts | 2 +- .../graphql/public/types/AutogroupSuccess.ts | 2 +- .../server/graphql/public/types/Company.ts | 115 +++--- .../server/graphql/public/types/Discussion.ts | 2 +- .../public/types/EndTeamPromptSuccess.ts | 2 +- .../graphql/public/types/EstimateStage.ts | 6 +- .../public/types/GenerateGroupsSuccess.ts | 2 +- .../graphql/public/types/MeetingSeries.ts | 17 +- .../graphql/public/types/NewMeetingStage.ts | 7 +- .../NotificationMeetingStageTimeLimitEnd.ts | 2 +- .../public/types/NotifyDiscussionMentioned.ts | 2 +- .../public/types/NotifyResponseMentioned.ts | 2 +- .../public/types/NotifyResponseReplied.ts | 2 +- .../graphql/public/types/ReflectPhase.ts | 4 +- .../public/types/RemoveAgendaItemPayload.ts | 2 +- .../types/ResetReflectionGroupsSuccess.ts | 2 +- .../graphql/public/types/RetroDiscussStage.ts | 4 +- .../graphql/public/types/RetroReflection.ts | 8 +- .../public/types/RetroReflectionGroup.ts | 4 +- .../public/types/RetrospectiveMeeting.ts | 4 +- .../types/RetrospectiveMeetingMember.ts | 2 +- .../graphql/public/types/ShareTopicSuccess.ts | 2 +- .../public/types/StartCheckInSuccess.ts | 2 +- .../public/types/StartRetrospectiveSuccess.ts | 2 +- .../public/types/StartTeamPromptSuccess.ts | 2 +- .../graphql/public/types/TeamPromptMeeting.ts | 45 +-- .../types/TimelineEventTeamPromptComplete.ts | 2 +- .../public/types/UpdateAgendaItemPayload.ts | 2 +- .../types/UpdateDimensionFieldSuccess.ts | 2 +- .../UpdateGitLabDimensionFieldSuccess.ts | 2 +- .../types/UpdateMeetingPromptSuccess.ts | 2 +- .../types/UpdateMeetingTemplateSuccess.ts | 2 +- .../types/UpdateRecurrenceSettingsSuccess.ts | 2 +- .../types/UpsertTeamPromptResponseSuccess.ts | 2 +- .../helpers/getActiveTeamCountByTeamIds.ts | 88 ++-- .../helpers/getPublicScoredTemplates.ts | 29 -- packages/server/graphql/resolvers.ts | 2 +- .../server/graphql/resolvers/resolveStage.ts | 2 +- .../types/DragEstimatingTaskPayload.ts | 2 +- .../types/FlagReadyToAdvancePayload.ts | 2 +- .../graphql/types/NavigateMeetingPayload.ts | 4 +- .../PromoteNewMeetingFacilitatorPayload.ts | 2 +- .../graphql/types/SetStageTimerPayload.ts | 2 +- .../graphql/types/UpdatePokerScopePayload.ts | 4 +- .../incrementUserPayLaterClickCountQuery.sql | 7 - 127 files changed, 807 insertions(+), 1516 deletions(-) delete mode 100644 packages/server/database/rMapIf.ts delete mode 100644 packages/server/database/updateStage.ts delete mode 100644 packages/server/graphql/queries/helpers/getPublicScoredTemplates.ts delete mode 100644 packages/server/postgres/queries/src/incrementUserPayLaterClickCountQuery.sql diff --git a/packages/embedder/addEmbeddingsMetadataForRetrospectiveDiscussionTopic.ts b/packages/embedder/addEmbeddingsMetadataForRetrospectiveDiscussionTopic.ts index 72bd3519779..8c4f5b6cd10 100644 --- a/packages/embedder/addEmbeddingsMetadataForRetrospectiveDiscussionTopic.ts +++ b/packages/embedder/addEmbeddingsMetadataForRetrospectiveDiscussionTopic.ts @@ -1,6 +1,4 @@ import {ExpressionOrFactory, SqlBool, sql} from 'kysely' -import getRethink from 'parabol-server/database/rethinkDriver' -import {RDatum} from 'parabol-server/database/stricterR' import {DB} from 'parabol-server/postgres/pg' import {Logger} from 'parabol-server/utils/Logger' import getKysely from '../server/postgres/getKysely' @@ -14,16 +12,17 @@ export interface DiscussionMeta { } const validateDiscussions = async (discussions: (DiscussionMeta & {meetingId: string})[]) => { - const r = await getRethink() + const pg = getKysely() if (discussions.length === 0) return discussions // Exclude discussions that belong to an unfinished meeting const meetingIds = [...new Set(discussions.map(({meetingId}) => meetingId))] - const endedMeetingIds = await r - .table('NewMeeting') - .getAll(r.args(meetingIds), {index: 'id'}) - .filter((row: RDatum) => row('endedAt').default(null).ne(null))('id') - .distinct() - .run() + const endedMeetings = await pg + .selectFrom('NewMeeting') + .select('id') + .where('id', 'in', meetingIds) + .where('endedAt', 'is', null) + .execute() + const endedMeetingIds = endedMeetings.map(({id}) => id) const endedMeetingIdsSet = new Set(endedMeetingIds) return discussions.filter(({meetingId}) => endedMeetingIdsSet.has(meetingId)) } diff --git a/packages/embedder/indexing/retrospectiveDiscussionTopic.ts b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts index c962d3bcbdf..b695d77c049 100644 --- a/packages/embedder/indexing/retrospectiveDiscussionTopic.ts +++ b/packages/embedder/indexing/retrospectiveDiscussionTopic.ts @@ -68,7 +68,7 @@ export const createTextFromRetrospectiveDiscussionTopic = async ( if (!discussion) throw new Error(`Discussion not found: ${discussionId}`) const {discussionTopicId: reflectionGroupId, meetingId, summary: discussionSummary} = discussion const [newMeeting, reflectionGroup, reflections] = await Promise.all([ - dataLoader.get('newMeetings').load(meetingId), + dataLoader.get('newMeetings').loadNonNull(meetingId), dataLoader.get('retroReflectionGroups').load(reflectionGroupId), dataLoader.get('retroReflectionsByGroupId').load(reflectionGroupId) ]) diff --git a/packages/embedder/workflows/relatedDiscussionsStart.ts b/packages/embedder/workflows/relatedDiscussionsStart.ts index a9881f2999c..aa280a01326 100644 --- a/packages/embedder/workflows/relatedDiscussionsStart.ts +++ b/packages/embedder/workflows/relatedDiscussionsStart.ts @@ -15,7 +15,7 @@ export const relatedDiscussionsStart: JobQueueStepRun< const {meetingId} = data const [discussions, meeting] = await Promise.all([ dataLoader.get('discussionsByMeetingId').load(meetingId), - dataLoader.get('newMeetings').load(meetingId) + dataLoader.get('newMeetings').loadNonNull(meetingId) ]) const {phases} = meeting const discussPhase = getPhase(phases, 'discuss') diff --git a/packages/server/__tests__/globalSetup.ts b/packages/server/__tests__/globalSetup.ts index 555249a35c2..46ee00b3191 100644 --- a/packages/server/__tests__/globalSetup.ts +++ b/packages/server/__tests__/globalSetup.ts @@ -5,7 +5,11 @@ import getKysely from '../postgres/getKysely' async function setup() { // The IP address is always localhost // so the safety checks will eventually fail if run too much - await sql`TRUNCATE TABLE "PasswordResetRequest"`.execute(getKysely()) + const pg = getKysely() + await sql` + TRUNCATE TABLE "PasswordResetRequest"; + ALTER TABLE "NewMeeting" DISABLE TRIGGER "check_meeting_overlap"; + `.execute(pg) } export default setup diff --git a/packages/server/__tests__/processRecurrence.test.ts b/packages/server/__tests__/processRecurrence.test.ts index 86edc1b87fb..50b6476a534 100644 --- a/packages/server/__tests__/processRecurrence.test.ts +++ b/packages/server/__tests__/processRecurrence.test.ts @@ -2,15 +2,12 @@ import dayjs from 'dayjs' import ms from 'ms' import TeamMemberId from 'parabol-client/shared/gqlIds/TeamMemberId' import {toDateTime} from '../../client/shared/rruleUtil' -import getRethink from '../database/rethinkDriver' import DiscussPhase from '../database/types/DiscussPhase' -import MeetingRetrospective from '../database/types/MeetingRetrospective' -import MeetingTeamPrompt from '../database/types/MeetingTeamPrompt' import ReflectPhase from '../database/types/ReflectPhase' import TeamPromptResponsesPhase from '../database/types/TeamPromptResponsesPhase' import generateUID from '../generateUID' +import getKysely from '../postgres/getKysely' import {insertMeetingSeries as insertMeetingSeriesQuery} from '../postgres/queries/insertMeetingSeries' -import {RetroMeetingPhase} from '../postgres/types/NewMeetingPhase' import {getUserTeams, sendIntranet, signUp} from './common' const PROCESS_RECURRENCE = ` @@ -77,19 +74,25 @@ beforeAll(async () => { }) test('Should not end meetings that are not scheduled to end', async () => { - const r = await getRethink() + const pg = getKysely() const meetingId = generateUID() - const meeting = new MeetingTeamPrompt({ - id: meetingId, - teamId, - meetingCount: 0, - phases: [new TeamPromptResponsesPhase([teamMemberId])], - facilitatorUserId: userId, - meetingPrompt: 'What are you working on today? Stuck on anything?' - }) - - await r.table('NewMeeting').insert(meeting).run() + const phase = new TeamPromptResponsesPhase([teamMemberId]) + await pg + .insertInto('NewMeeting') + .values({ + id: meetingId, + teamId, + meetingCount: 0, + meetingNumber: 1, + phases: JSON.stringify([phase]), + facilitatorUserId: userId, + meetingPrompt: 'What are you working on today? Stuck on anything?', + name: `Team Prompt #1`, + meetingType: 'teamPrompt', + facilitatorStageId: phase.stages[0]?.id + }) + .execute() const update = await sendIntranet({ query: PROCESS_RECURRENCE, @@ -107,25 +110,35 @@ test('Should not end meetings that are not scheduled to end', async () => { await assertIdempotency() - const actualMeeting = await r.table('NewMeeting').get(meetingId).run() + const actualMeeting = await pg + .selectFrom('NewMeeting') + .selectAll() + .where('id', '=', meetingId) + .executeTakeFirstOrThrow() expect(actualMeeting.endedAt).toBeFalsy() }) test('Should not end meetings that are scheduled to end in the future', async () => { - const r = await getRethink() + const pg = getKysely() const meetingId = generateUID() - const meeting = new MeetingTeamPrompt({ - id: meetingId, - teamId, - meetingCount: 0, - phases: [new TeamPromptResponsesPhase([teamMemberId])], - facilitatorUserId: userId, - meetingPrompt: 'What are you working on today? Stuck on anything?', - scheduledEndTime: new Date(Date.now() + ms('5m')) - }) - - await r.table('NewMeeting').insert(meeting).run() + const phase = new TeamPromptResponsesPhase([teamMemberId]) + await pg + .insertInto('NewMeeting') + .values({ + id: meetingId, + teamId, + meetingCount: 0, + meetingNumber: 1, + phases: JSON.stringify([phase]), + facilitatorUserId: userId, + meetingPrompt: 'What are you working on today? Stuck on anything?', + name: `Team Prompt #1`, + meetingType: 'teamPrompt', + scheduledEndTime: new Date(Date.now() + ms('5m')), + facilitatorStageId: phase.stages[0]?.id + }) + .execute() const update = await sendIntranet({ query: PROCESS_RECURRENCE, @@ -143,27 +156,36 @@ test('Should not end meetings that are scheduled to end in the future', async () await assertIdempotency() - const actualMeeting = await r.table('NewMeeting').get(meetingId).run() + const actualMeeting = await pg + .selectFrom('NewMeeting') + .selectAll() + .where('id', '=', meetingId) + .executeTakeFirstOrThrow() expect(actualMeeting.endedAt).toBeFalsy() - - await r.table('NewMeeting').get(meetingId).delete().run() + await pg.deleteFrom('NewMeeting').where('id', '=', meetingId).execute() }) test('Should end meetings that are scheduled to end in the past', async () => { - const r = await getRethink() + const pg = getKysely() const meetingId = generateUID() - const meeting = new MeetingTeamPrompt({ - id: meetingId, - teamId, - meetingCount: 0, - phases: [new TeamPromptResponsesPhase([teamMemberId])], - facilitatorUserId: userId, - meetingPrompt: 'What are you working on today? Stuck on anything?', - scheduledEndTime: new Date(Date.now() - ms('5m')) - }) - - await r.table('NewMeeting').insert(meeting).run() + const phase = new TeamPromptResponsesPhase([teamMemberId]) + await pg + .insertInto('NewMeeting') + .values({ + id: meetingId, + teamId, + meetingCount: 0, + meetingNumber: 1, + phases: JSON.stringify([phase]), + facilitatorUserId: userId, + meetingPrompt: 'What are you working on today? Stuck on anything?', + name: `Team Prompt #1`, + meetingType: 'teamPrompt', + scheduledEndTime: new Date(Date.now() - ms('5m')), + facilitatorStageId: phase.stages[0]?.id + }) + .execute() const update = await sendIntranet({ query: PROCESS_RECURRENCE, @@ -181,12 +203,16 @@ test('Should end meetings that are scheduled to end in the past', async () => { await assertIdempotency() - const actualMeeting = await r.table('NewMeeting').get(meetingId).run() + const actualMeeting = await pg + .selectFrom('NewMeeting') + .selectAll() + .where('id', '=', meetingId) + .executeTakeFirstOrThrow() expect(actualMeeting.endedAt).toBeTruthy() }, 10000) test('Should end the current team prompt meeting and start a new meeting', async () => { - const r = await getRethink() + const pg = getKysely() const startDate = dayjs().utc().subtract(2, 'day').set('hour', 9) const dateTime = toDateTime(startDate, 'UTC') const recurrenceRule = `DTSTART:${dateTime} @@ -202,22 +228,27 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` }) const meetingId = generateUID() - const meeting = new MeetingTeamPrompt({ - id: meetingId, - teamId, - meetingCount: 0, - phases: [new TeamPromptResponsesPhase([teamMemberId])], - facilitatorUserId: userId, - meetingPrompt: 'What are you working on today? Stuck on anything?', - scheduledEndTime: new Date(Date.now() - ms('5m')), - meetingSeriesId - }) - - // The last meeting in the series was created just over 24h ago, so the next one should start - // soon. - meeting.createdAt = new Date(meeting.createdAt.getTime() - ms('25h')) - - await r.table('NewMeeting').insert(meeting).run() + const phase = new TeamPromptResponsesPhase([teamMemberId]) + await pg + .insertInto('NewMeeting') + .values({ + id: meetingId, + teamId, + meetingCount: 0, + meetingNumber: 1, + phases: JSON.stringify([phase]), + facilitatorUserId: userId, + meetingPrompt: 'What are you working on today? Stuck on anything?', + name: `Team Prompt #1`, + meetingType: 'teamPrompt', + scheduledEndTime: new Date(Date.now() - ms('5m')), + facilitatorStageId: phase.stages[0]?.id, + meetingSeriesId, + // The last meeting in the series was created just over 24h ago, so the next one should start + // soon. + createdAt: new Date(Date.now() - ms('25h')) + }) + .execute() const update = await sendIntranet({ query: PROCESS_RECURRENCE, @@ -235,15 +266,20 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` await assertIdempotency() - const actualMeeting = await r.table('NewMeeting').get(meetingId).run() + const actualMeeting = await pg + .selectFrom('NewMeeting') + .selectAll() + .where('id', '=', meetingId) + .executeTakeFirstOrThrow() expect(actualMeeting.endedAt).toBeTruthy() - const lastMeeting = await r - .table('NewMeeting') - .filter({meetingType: 'teamPrompt', meetingSeriesId}) - .orderBy(r.desc('createdAt')) - .nth(0) - .run() + const lastMeeting = await pg + .selectFrom('NewMeeting') + .selectAll() + .where('meetingType', '=', 'teamPrompt') + .orderBy('createdAt desc') + .limit(1) + .executeTakeFirst() expect(lastMeeting).toMatchObject({ name: expect.stringMatching(/Daily Test Standup.*/), @@ -252,7 +288,7 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` }) test('Should end the current retro meeting and start a new meeting', async () => { - const r = await getRethink() + const pg = getKysely() // Create a meeting series that's been going on for a few days, and happens daily at 9a UTC. const startDate = dayjs().utc().subtract(2, 'day').set('hour', 9) @@ -270,29 +306,31 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` }) const meetingId = generateUID() - const meeting = new MeetingRetrospective({ - id: meetingId, - teamId, - meetingCount: 0, - phases: [ - new ReflectPhase(teamId, []) as RetroMeetingPhase, - new DiscussPhase(undefined) as RetroMeetingPhase - ], - facilitatorUserId: userId, - scheduledEndTime: new Date(Date.now() - ms('5m')), - meetingSeriesId, - templateId: 'startStopContinueTemplate', - disableAnonymity: false, - totalVotes: 5, - name: '', - maxVotesPerGroup: 5 - }) - - // The last meeting in the series was created just over 24h ago, so the next one should start - // soon. - meeting.createdAt = new Date(meeting.createdAt.getTime() - ms('25h')) - - await r.table('NewMeeting').insert(meeting).run() + const phases = [new ReflectPhase(teamId, []), new DiscussPhase(undefined)] + await pg + .insertInto('NewMeeting') + .values({ + id: meetingId, + teamId, + meetingCount: 0, + meetingNumber: 1, + phases: JSON.stringify(phases), + facilitatorUserId: userId, + meetingType: 'retrospective', + scheduledEndTime: new Date(Date.now() - ms('5m')), + facilitatorStageId: phases[0]!.stages[0]!.id, + meetingSeriesId, + templateId: 'startStopContinueTemplate', + disableAnonymity: false, + totalVotes: 5, + name: '', + maxVotesPerGroup: 5, + meetingPrompt: 'What are you working on today? Stuck on anything?', + // The last meeting in the series was created just over 24h ago, so the next one should start + // soon. + createdAt: new Date(Date.now() - ms('25h')) + }) + .execute() const update = await sendIntranet({ query: PROCESS_RECURRENCE, @@ -310,15 +348,20 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` await assertIdempotency() - const actualMeeting = await r.table('NewMeeting').get(meetingId).run() + const actualMeeting = await pg + .selectFrom('NewMeeting') + .selectAll() + .where('id', '=', meetingId) + .executeTakeFirstOrThrow() expect(actualMeeting.endedAt).toBeTruthy() - const lastMeeting = await r - .table('NewMeeting') - .filter({meetingType: 'retrospective', meetingSeriesId}) - .orderBy(r.desc('createdAt')) - .nth(0) - .run() + const lastMeeting = await pg + .selectFrom('NewMeeting') + .selectAll() + .where('meetingType', '=', 'retrospective') + .orderBy('createdAt desc') + .limit(1) + .executeTakeFirst() expect(lastMeeting).toMatchObject({ meetingSeriesId @@ -326,7 +369,7 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` }) test('Should only start a new meeting if it would still be active', async () => { - const r = await getRethink() + const pg = getKysely() const startDate = dayjs().utc().subtract(2, 'day').set('hour', 9) const dateTime = toDateTime(startDate, 'UTC') @@ -343,23 +386,28 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` }) const meetingId = generateUID() - const meeting = new MeetingTeamPrompt({ - id: meetingId, - teamId, - meetingCount: 0, - phases: [new TeamPromptResponsesPhase([teamMemberId])], - facilitatorUserId: userId, - meetingPrompt: 'What are you working on today? Stuck on anything?', - scheduledEndTime: new Date(Date.now() - ms('73h')), - meetingSeriesId: newMeetingSeriesId - }) - - // The last meeting in the series was created just over 72h ago, so 3 meetings should have started - // since then, but only 1 meeting should start as a result of the mutation. - meeting.createdAt = new Date(meeting.createdAt.getTime() - ms('73h')) - meeting.endedAt = new Date(Date.now() - ms('49h')) - - await r.table('NewMeeting').insert(meeting).run() + const phase = new TeamPromptResponsesPhase([teamMemberId]) + await pg + .insertInto('NewMeeting') + .values({ + id: meetingId, + teamId, + meetingCount: 0, + meetingNumber: 1, + phases: JSON.stringify([phase]), + facilitatorUserId: userId, + meetingPrompt: 'What are you working on today? Stuck on anything?', + name: `Team Prompt #1`, + meetingType: 'teamPrompt', + facilitatorStageId: phase.stages[0]?.id, + scheduledEndTime: new Date(Date.now() - ms('73h')), + meetingSeriesId: newMeetingSeriesId, + // The last meeting in the series was created just over 72h ago, so 3 meetings should have started + // since then, but only 1 meeting should start as a result of the mutation. + createdAt: new Date(Date.now() - ms('73h')), + endedAt: new Date(Date.now() - ms('49h')) + }) + .execute() const update = await sendIntranet({ query: PROCESS_RECURRENCE, @@ -377,12 +425,16 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` await assertIdempotency() - const actualMeeting = await r.table('NewMeeting').get(meetingId).run() + const actualMeeting = await pg + .selectFrom('NewMeeting') + .selectAll() + .where('id', '=', meetingId) + .executeTakeFirstOrThrow() expect(actualMeeting.endedAt).toBeTruthy() }, 10000) test('Should not start a new meeting if the rrule has not started', async () => { - const r = await getRethink() + const pg = getKysely() const startDate = dayjs().utc().add(1, 'day').set('hour', 9) const dateTime = toDateTime(startDate, 'UTC') @@ -399,23 +451,28 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` }) const meetingId = generateUID() - const meeting = new MeetingTeamPrompt({ - id: meetingId, - teamId, - meetingCount: 0, - phases: [new TeamPromptResponsesPhase([teamMemberId])], - facilitatorUserId: userId, - meetingPrompt: 'What are you working on today? Stuck on anything?', - scheduledEndTime: new Date(Date.now() - ms('1h')), - meetingSeriesId: newMeetingSeriesId - }) - - // The last meeting in the series was created just over 24h ago, but the active rrule doesn't - // start until tomorrow. - meeting.createdAt = new Date(meeting.createdAt.getTime() - ms('25h')) - meeting.endedAt = new Date(Date.now() - ms('1h')) - - await r.table('NewMeeting').insert(meeting).run() + const phase = new TeamPromptResponsesPhase([teamMemberId]) + await pg + .insertInto('NewMeeting') + .values({ + id: meetingId, + teamId, + meetingCount: 0, + meetingNumber: 1, + phases: JSON.stringify([phase]), + facilitatorUserId: userId, + meetingPrompt: 'What are you working on today? Stuck on anything?', + name: `Team Prompt #1`, + meetingType: 'teamPrompt', + facilitatorStageId: phase.stages[0]?.id, + scheduledEndTime: new Date(Date.now() - ms('1h')), + meetingSeriesId: newMeetingSeriesId, + // The last meeting in the series was created just over 24h ago, but the active rrule doesn't + // start until tomorrow. + createdAt: new Date(Date.now() - ms('25h')), + endedAt: new Date(Date.now() - ms('1h')) + }) + .execute() const update = await sendIntranet({ query: PROCESS_RECURRENCE, @@ -433,12 +490,16 @@ RRULE:FREQ=WEEKLY;INTERVAL=1;BYDAY=MO,TU,WE,TH,FR,SA,SU` await assertIdempotency() - const actualMeeting = await r.table('NewMeeting').get(meetingId).run() + const actualMeeting = await pg + .selectFrom('NewMeeting') + .selectAll() + .where('id', '=', meetingId) + .executeTakeFirstOrThrow() expect(actualMeeting.endedAt).toBeTruthy() }) test('Should not hang if the rrule interval is invalid', async () => { - const r = await getRethink() + const pg = getKysely() const startDate = dayjs().utc().subtract(2, 'day').set('hour', 9) const dateTime = toDateTime(startDate, 'UTC') @@ -455,22 +516,27 @@ RRULE:FREQ=WEEKLY;INTERVAL=NaN;BYDAY=MO,TU,WE,TH,FR,SA,SU` }) const meetingId = generateUID() - const meeting = new MeetingTeamPrompt({ - id: meetingId, - teamId, - meetingCount: 0, - phases: [new TeamPromptResponsesPhase([teamMemberId])], - facilitatorUserId: userId, - meetingPrompt: 'What are you working on today? Stuck on anything?', - scheduledEndTime: new Date(Date.now() - ms('5m')), - meetingSeriesId: newMeetingSeriesId - }) - - // The last meeting in the series was created just over 24h ago, so the next one should start soon - // but the rrule is invalid, so it won't happen - meeting.createdAt = new Date(meeting.createdAt.getTime() - ms('25h')) - - await r.table('NewMeeting').insert(meeting).run() + const phase = new TeamPromptResponsesPhase([teamMemberId]) + await pg + .insertInto('NewMeeting') + .values({ + id: meetingId, + teamId, + meetingCount: 0, + meetingNumber: 1, + phases: JSON.stringify([phase]), + facilitatorUserId: userId, + meetingPrompt: 'What are you working on today? Stuck on anything?', + name: `Team Prompt #1`, + meetingType: 'teamPrompt', + facilitatorStageId: phase.stages[0]?.id, + scheduledEndTime: new Date(Date.now() - ms('5m')), + meetingSeriesId: newMeetingSeriesId, + // The last meeting in the series was created just over 24h ago, so the next one should start soon + // but the rrule is invalid, so it won't happen + createdAt: new Date(Date.now() - ms('25h')) + }) + .execute() const update = await sendIntranet({ query: PROCESS_RECURRENCE, @@ -488,6 +554,10 @@ RRULE:FREQ=WEEKLY;INTERVAL=NaN;BYDAY=MO,TU,WE,TH,FR,SA,SU` await assertIdempotency() - const actualMeeting = await r.table('NewMeeting').get(meetingId).run() + const actualMeeting = await pg + .selectFrom('NewMeeting') + .selectAll() + .where('id', '=', meetingId) + .executeTakeFirstOrThrow() expect(actualMeeting.endedAt).toBeTruthy() }) diff --git a/packages/server/database/rMapIf.ts b/packages/server/database/rMapIf.ts deleted file mode 100644 index a976d40a89b..00000000000 --- a/packages/server/database/rMapIf.ts +++ /dev/null @@ -1,12 +0,0 @@ -// mapIf is a repeatable pattern for updating 1 or more values inside an arbitrarily deep nested array on a RethinkDB document -// rArr is the array -// test should return true if you want to update that specific value inside the array -// f is the updater - -import {ParabolR} from './rethinkDriver' - -const rMapIf = (r: ParabolR) => (rArr: unknown[], test: any, f: (x: unknown) => void) => { - return rArr.map((x) => r.branch(test(x), f(x), x)) -} - -export default rMapIf diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index 0776e9efdad..e3db1bfb74e 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -1,6 +1,6 @@ import {MasterPool, r} from 'rethinkdb-ts' import TeamInvitation from '../database/types/TeamInvitation' -import {AnyMeeting, AnyMeetingTeamMember} from '../postgres/types/Meeting' +import {AnyMeetingTeamMember} from '../postgres/types/Meeting' import getRethinkConfig from './getRethinkConfig' import {R} from './stricterR' import MassInvitation from './types/MassInvitation' @@ -25,15 +25,6 @@ export type RethinkSchema = { type: AnyMeetingTeamMember index: 'meetingId' | 'teamId' | 'userId' } - NewMeeting: { - type: AnyMeeting - index: - | 'facilitatorUserId' - | 'teamId' - | 'templateId' - | 'meetingSeriesId' - | 'hasEndedScheduledEndTime' - } NewFeature: { type: any index: '' diff --git a/packages/server/database/updateStage.ts b/packages/server/database/updateStage.ts deleted file mode 100644 index 51d8505d5ac..00000000000 --- a/packages/server/database/updateStage.ts +++ /dev/null @@ -1,33 +0,0 @@ -import getRethink from './rethinkDriver' -import rMapIf from './rMapIf' -import {NewMeetingPhaseTypeEnum} from './types/GenericMeetingPhase' - -// this is a uesful function for updating a stage inside a meeting object -// it is superior to mutating the `phases` object in JS and then pushing the whole object -// because it eliminates the chance of a race - -const updateStage = async ( - meetingId: string, - stageId: string, - phaseType: NewMeetingPhaseTypeEnum, - updater: (stage: any) => any -) => { - const r = await getRethink() - const mapIf = rMapIf(r) - return r - .table('NewMeeting') - .get(meetingId) - .update((meeting: any) => ({ - phases: mapIf( - meeting('phases'), - (phase: any) => phase('phaseType').eq(phaseType), - (phase: any) => - phase.merge({ - stages: mapIf(phase('stages'), (stage: any) => stage('id').eq(stageId), updater) - }) - ) - })) - .run() -} - -export default updateStage diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 1c0edc837ad..bb03e4d9300 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -1,5 +1,4 @@ import DataLoader from 'dataloader' -import tracer from 'dd-trace' import {Selectable, SqlBool, sql} from 'kysely' import {PARABOL_AI_USER_ID} from '../../client/utils/constants' import getRethink from '../database/rethinkDriver' @@ -34,7 +33,6 @@ import isUserVerified from '../utils/isUserVerified' import NullableDataLoader from './NullableDataLoader' import RootDataLoader, {RegisterDependsOn} from './RootDataLoader' import normalizeArrayResults from './normalizeArrayResults' -import normalizeResults from './normalizeResults' export interface MeetingSettingsKey { teamId: string @@ -478,39 +476,8 @@ type MeetingStat = { meetingType: MeetingTypeEnum createdAt: Date } -export const meetingStatsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { - dependsOn('newMeetings') - return new DataLoader( - async (orgIds) => { - const r = await getRethink() - const meetingStatsByOrgId = await Promise.all( - orgIds.map(async (orgId) => { - // note: does not include archived teams! - const teams = await parent.get('teamsByOrgIds').load(orgId) - const teamIds = teams.map(({id}) => id) - const stats = (await r - .table('NewMeeting') - .getAll(r.args(teamIds), {index: 'teamId'}) - .pluck('createdAt', 'meetingType') - // DO NOT CALL orderBy, it makes the query 10x more expensive! - // .orderBy('createdAt') - .run()) as {createdAt: Date; meetingType: MeetingTypeEnum}[] - return stats.map((stat) => ({ - createdAt: stat.createdAt, - meetingType: stat.meetingType, - id: `ms${stat.createdAt.getTime()}` - })) - }) - ) - return meetingStatsByOrgId - }, - { - ...parent.dataLoaderOptions - } - ) -} -export const _pgmeetingStatsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { +export const meetingStatsByOrgId = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { dependsOn('newMeetings') return new DataLoader( async (orgIds) => { @@ -606,28 +573,6 @@ export const meetingHighlightedTaskId = (parent: RootDataLoader) => { export const activeMeetingsByMeetingSeriesId = ( parent: RootDataLoader, dependsOn: RegisterDependsOn -) => { - dependsOn('newMeetings') - return new DataLoader( - async (keys) => { - const r = await getRethink() - const res = await r - .table('NewMeeting') - .getAll(r.args(keys), {index: 'meetingSeriesId'}) - .filter({endedAt: null}, {default: true}) - .orderBy(r.asc('createdAt')) - .run() - return normalizeArrayResults(keys, res, 'meetingSeriesId') - }, - { - ...parent.dataLoaderOptions - } - ) -} - -export const _pgactiveMeetingsByMeetingSeriesId = ( - parent: RootDataLoader, - dependsOn: RegisterDependsOn ) => { dependsOn('newMeetings') return new DataLoader( @@ -649,34 +594,6 @@ export const _pgactiveMeetingsByMeetingSeriesId = ( export const lastMeetingByMeetingSeriesId = ( parent: RootDataLoader, dependsOn: RegisterDependsOn -) => { - dependsOn('newMeetings') - return new DataLoader( - async (keys) => - tracer.trace('lastMeetingByMeetingSeriesId', async () => { - const r = await getRethink() - const res = await ( - r - .table('NewMeeting') - .getAll(r.args(keys), {index: 'meetingSeriesId'}) - .group('meetingSeriesId') as RDatum - ) - .orderBy(r.desc('createdAt')) - .nth(0) - .default(null) - .ungroup()('reduction') - .run() - return normalizeResults(keys, res as AnyMeeting[], 'meetingSeriesId') - }), - { - ...parent.dataLoaderOptions - } - ) -} - -export const _pglastMeetingByMeetingSeriesId = ( - parent: RootDataLoader, - dependsOn: RegisterDependsOn ) => { dependsOn('newMeetings') return new DataLoader( @@ -898,19 +815,17 @@ export const meetingCount = (parent: RootDataLoader, dependsOn: RegisterDependsO dependsOn('newMeetings') return new DataLoader<{teamId: string; meetingType: MeetingTypeEnum}, number, string>( async (keys) => { - const r = await getRethink() - const res = await Promise.all( + return await Promise.all( keys.map(async ({teamId, meetingType}) => { - return r - .table('NewMeeting') - .getAll(teamId, {index: 'teamId'}) - .filter({meetingType: meetingType as any}) - .count() - .default(0) - .run() + const row = await getKysely() + .selectFrom('NewMeeting') + .select(({fn}) => fn.count('id').as('count')) + .where('teamId', '=', teamId) + .where('meetingType', '=', meetingType) + .executeTakeFirstOrThrow() + return Number(row.count) }) ) - return res }, { ...parent.dataLoaderOptions, @@ -980,26 +895,3 @@ export const featureFlagByOwnerId = (parent: RootDataLoader) => { } ) } - -export const _pgmeetingCount = (parent: RootDataLoader, dependsOn: RegisterDependsOn) => { - dependsOn('newMeetings') - return new DataLoader<{teamId: string; meetingType: MeetingTypeEnum}, number, string>( - async (keys) => { - return await Promise.all( - keys.map(async ({teamId, meetingType}) => { - const row = await getKysely() - .selectFrom('NewMeeting') - .select(({fn}) => fn.count('id').as('count')) - .where('teamId', '=', teamId) - .where('meetingType', '=', meetingType) - .executeTakeFirstOrThrow() - return Number(row.count) - }) - ) - }, - { - ...parent.dataLoaderOptions, - cacheKeyFn: (key) => `${key.teamId}:${key.meetingType}` - } - ) -} diff --git a/packages/server/dataloader/customRedisQueries.ts b/packages/server/dataloader/customRedisQueries.ts index 8ce3cd85ac7..ba6123abfef 100644 --- a/packages/server/dataloader/customRedisQueries.ts +++ b/packages/server/dataloader/customRedisQueries.ts @@ -3,28 +3,27 @@ import {sql, SqlBool} from 'kysely' import ms from 'ms' -import getRethink from '../database/rethinkDriver' -import {RDatum} from '../database/stricterR' import getKysely from '../postgres/getKysely' // All results must be mapped to their ids! const customRedisQueries = { endTimesByTemplateId: async (templateIds: string[]) => { - const r = await getRethink() + if (!templateIds.length) return [] + const pg = getKysely() const aQuarterAgo = new Date(Date.now() - ms('90d')) - const meetings = (await ( - r - .table('NewMeeting') - .getAll(r.args(templateIds), {index: 'templateId'}) - .pluck('templateId', 'endedAt') - .filter((row: RDatum) => row('endedAt').ge(aQuarterAgo)) - .group('templateId' as any) as any - ) - .limit(1000)('endedAt') - .run()) as {group: string; reduction: Date[]}[] + const meetings = await pg + .selectFrom('NewMeeting') + .select('templateId') + .select(({fn}) => [fn.agg('array_agg', ['endedAt']).as('endedAts')]) + .where('templateId', 'in', templateIds) + .where('endedAt', '>=', aQuarterAgo) + .groupBy('templateId') + .limit(1000) + .execute() + return templateIds.map((id) => { - const group = meetings.find((meeting) => meeting.group === id) - return group ? group.reduction.map((date) => date.getTime()) : [] + const group = meetings.find((meeting) => meeting.templateId === id) + return group ? group.endedAts.map((date) => date.getTime()) : [] }) }, publicTemplates: async (meetingTypes: string[]) => { diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 5b0e9d2eb2e..030f62bf990 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -15,6 +15,7 @@ import { selectTemplateScale, selectTimelineEvent } from '../postgres/select' +import {AnyMeeting} from '../postgres/types/Meeting' import {foreignKeyLoaderMaker} from './foreignKeyLoaderMaker' export const teamsByOrgIds = foreignKeyLoaderMaker('teams', 'orgId', (orgIds) => @@ -229,25 +230,27 @@ export const reflectPromptsByTemplateId = foreignKeyLoaderMaker( } ) -export const _pgactiveMeetingsByTeamId = foreignKeyLoaderMaker( - '_pgnewMeetings', +export const activeMeetingsByTeamId = foreignKeyLoaderMaker( + 'newMeetings', 'teamId', async (teamIds) => { return selectNewMeetings() .where('teamId', 'in', teamIds) .where('endedAt', 'is', null) .orderBy('createdAt desc') + .$narrowType() .execute() } ) -export const _pgcompletedMeetingsByTeamId = foreignKeyLoaderMaker( - '_pgnewMeetings', +export const completedMeetingsByTeamId = foreignKeyLoaderMaker( + 'newMeetings', 'teamId', async (teamIds) => { return selectNewMeetings() .where('teamId', 'in', teamIds) .where('endedAt', 'is not', null) .orderBy('endedAt desc') + .$narrowType() .execute() } ) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index a9d31fbf068..9df90bcc56b 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -23,6 +23,7 @@ import { selectTemplateScaleRef, selectTimelineEvent } from '../postgres/select' +import {AnyMeeting} from '../postgres/types/Meeting' import {primaryKeyLoaderMaker} from './primaryKeyLoaderMaker' export const users = primaryKeyLoaderMaker(getUsersByIds) @@ -121,6 +122,6 @@ export const reflectPrompts = primaryKeyLoaderMaker((ids: readonly string[]) => return selectReflectPrompts().where('id', 'in', ids).execute() }) -export const _pgnewMeetings = primaryKeyLoaderMaker((ids: readonly string[]) => { - return selectNewMeetings().where('id', 'in', ids).execute() +export const newMeetings = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectNewMeetings().where('id', 'in', ids).$narrowType().execute() }) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index 15317dc9d3c..06741f45f69 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -2,33 +2,6 @@ import getRethink from '../database/rethinkDriver' import {RDatum} from '../database/stricterR' import RethinkForeignKeyLoaderMaker from './RethinkForeignKeyLoaderMaker' -export const activeMeetingsByTeamId = new RethinkForeignKeyLoaderMaker( - 'newMeetings', - 'teamId', - async (teamIds) => { - const r = await getRethink() - return r - .table('NewMeeting') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter({endedAt: null}, {default: true}) - .orderBy(r.desc('createdAt')) - .run() - } -) -export const completedMeetingsByTeamId = new RethinkForeignKeyLoaderMaker( - 'newMeetings', - 'teamId', - async (teamIds) => { - const r = await getRethink() - return r - .table('NewMeeting') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((row: RDatum) => row('endedAt').default(null).ne(null)) - .orderBy(r.desc('endedAt')) - .run() - } -) - export const massInvitationsByTeamMemberId = new RethinkForeignKeyLoaderMaker( 'massInvitations', 'teamMemberId', diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index 2a7baaebc59..0bef3795af3 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -5,7 +5,6 @@ import RethinkPrimaryKeyLoaderMaker from './RethinkPrimaryKeyLoaderMaker' */ export const massInvitations = new RethinkPrimaryKeyLoaderMaker('MassInvitation') export const meetingMembers = new RethinkPrimaryKeyLoaderMaker('MeetingMember') -export const newMeetings = new RethinkPrimaryKeyLoaderMaker('NewMeeting') export const newFeatures = new RethinkPrimaryKeyLoaderMaker('NewFeature') export const notifications = new RethinkPrimaryKeyLoaderMaker('Notification') export const tasks = new RethinkPrimaryKeyLoaderMaker('Task') diff --git a/packages/server/email/newMeetingSummaryEmailCreator.tsx b/packages/server/email/newMeetingSummaryEmailCreator.tsx index f0e065a2f33..811e20e6687 100644 --- a/packages/server/email/newMeetingSummaryEmailCreator.tsx +++ b/packages/server/email/newMeetingSummaryEmailCreator.tsx @@ -18,7 +18,7 @@ const newMeetingSummaryEmailCreator = async (props: Props) => { const {dataLoader} = context const dataLoaderId = dataLoader.share() - const newMeeting = await dataLoader.get('newMeetings').load(meetingId) + const newMeeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const facilitator = await dataLoader.get('users').loadNonNull(newMeeting.facilitatorUserId!) const {tms} = facilitator const authToken = new AuthToken({sub: newMeeting.facilitatorUserId!, tms, rol: 'impersonate'}) diff --git a/packages/server/graphql/mutations/createReflection.ts b/packages/server/graphql/mutations/createReflection.ts index 7a20208c72d..ca41613f6bb 100644 --- a/packages/server/graphql/mutations/createReflection.ts +++ b/packages/server/graphql/mutations/createReflection.ts @@ -5,7 +5,6 @@ import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' import getGroupSmartTitle from 'parabol-client/utils/smartGroup/getGroupSmartTitle' import unlockAllStagesForPhase from 'parabol-client/utils/unlockAllStagesForPhase' import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' -import getRethink from '../../database/rethinkDriver' import ReflectionGroup from '../../database/types/ReflectionGroup' import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' @@ -34,7 +33,6 @@ export default { {input}: {input: CreateReflectionInputType}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { - const r = await getRethink() const pg = getKysely() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -112,13 +110,6 @@ export default { let unlockedStageIds if (!groupStage?.isNavigableByFacilitator) { unlockedStageIds = unlockAllStagesForPhase(phases, 'group', true) - await r - .table('NewMeeting') - .get(meetingId) - .update({ - phases - }) - .run() await pg .updateTable('NewMeeting') .set({phases: JSON.stringify(phases)}) diff --git a/packages/server/graphql/mutations/dragDiscussionTopic.ts b/packages/server/graphql/mutations/dragDiscussionTopic.ts index 472c314197c..304944856d9 100644 --- a/packages/server/graphql/mutations/dragDiscussionTopic.ts +++ b/packages/server/graphql/mutations/dragDiscussionTopic.ts @@ -1,6 +1,5 @@ import {GraphQLFloat, GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' @@ -29,7 +28,6 @@ export default { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const pg = getKysely() - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const viewerId = getUserId(authToken) @@ -58,13 +56,6 @@ export default { stages.sort((a, b) => { return a.sortOrder > b.sortOrder ? 1 : -1 }) - await r - .table('NewMeeting') - .get(meetingId) - .update({ - phases - }) - .run() await pg .updateTable('NewMeeting') .set({phases: JSON.stringify(phases)}) diff --git a/packages/server/graphql/mutations/dragEstimatingTask.ts b/packages/server/graphql/mutations/dragEstimatingTask.ts index 5539ab1667b..7b639e3c48e 100644 --- a/packages/server/graphql/mutations/dragEstimatingTask.ts +++ b/packages/server/graphql/mutations/dragEstimatingTask.ts @@ -1,7 +1,6 @@ import {GraphQLID, GraphQLInt, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {ESTIMATE_TASK_SORT_ORDER} from '../../../client/utils/constants' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' @@ -36,7 +35,6 @@ export default { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const pg = getKysely() - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const viewerId = getUserId(authToken) @@ -87,13 +85,6 @@ export default { stages.sort((a, b) => { return a.sortOrder > b.sortOrder ? 1 : -1 }) - await r - .table('NewMeeting') - .get(meetingId) - .update({ - phases - }) - .run() await pg .updateTable('NewMeeting') .set({phases: JSON.stringify(phases)}) diff --git a/packages/server/graphql/mutations/endCheckIn.ts b/packages/server/graphql/mutations/endCheckIn.ts index 77f91b62e33..b5c65d89ec5 100644 --- a/packages/server/graphql/mutations/endCheckIn.ts +++ b/packages/server/graphql/mutations/endCheckIn.ts @@ -152,19 +152,7 @@ const summarizeCheckInMeeting = async (meeting: CheckInMeeting, dataLoader: Data taskCount: tasks.length }) .where('id', '=', meetingId) - .execute(), - r - .table('NewMeeting') - .get(meetingId) - .update( - { - agendaItemCount: activeAgendaItems.length, - commentCount, - taskCount: tasks.length - }, - {nonAtomic: true} - ) - .run() + .execute() ]) dataLoader.clearAll('newMeetings') return {updatedTaskIds: [...tasks, ...doneTasks].map(({id}) => id)} @@ -182,7 +170,6 @@ export default { async resolve(_source: unknown, {meetingId}: {meetingId: string}, context: GQLContext) { const {authToken, socketId: mutatorId, dataLoader} = context const pg = getKysely() - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const now = new Date() @@ -212,19 +199,6 @@ export default { const phase = getMeetingPhase(phases) const insights = await gatherInsights(meeting, dataLoader) - const completedCheckIn = await r - .table('NewMeeting') - .get(meetingId) - .update( - { - endedAt: now, - phases, - ...insights - }, - {returnChanges: true} - )('changes')(0)('new_val') - .default(null) - .run() await pg .updateTable('NewMeeting') .set({ @@ -236,12 +210,7 @@ export default { .where('id', '=', meetingId) .execute() dataLoader.clearAll('newMeetings') - if (!completedCheckIn) { - return standardError(new Error('Completed check-in meeting does not exist'), { - userId: viewerId - }) - } - + const completedCheckIn = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (completedCheckIn.meetingType !== 'action') { return standardError(new Error('Completed check-in meeting is not an action'), { userId: viewerId diff --git a/packages/server/graphql/mutations/endSprintPoker.ts b/packages/server/graphql/mutations/endSprintPoker.ts index f057e37a57c..4f827428b08 100644 --- a/packages/server/graphql/mutations/endSprintPoker.ts +++ b/packages/server/graphql/mutations/endSprintPoker.ts @@ -4,7 +4,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getMeetingPhase from 'parabol-client/utils/getMeetingPhase' import findStageById from 'parabol-client/utils/meetings/findStageById' import {checkTeamsLimit} from '../../billing/helpers/teamLimitsCheck' -import getRethink from '../../database/rethinkDriver' import TimelineEventPokerComplete from '../../database/types/TimelineEventPokerComplete' import getKysely from '../../postgres/getKysely' import {Logger} from '../../utils/Logger' @@ -34,7 +33,6 @@ export default { }, async resolve(_source: unknown, {meetingId}: {meetingId: string}, context: GQLContext) { const {authToken, socketId: mutatorId, dataLoader} = context - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const now = new Date() @@ -80,21 +78,6 @@ export default { await dataLoader.get('commentCountByDiscussionId').loadMany(discussionIds) ).filter(isValid) const commentCount = commentCounts.reduce((cumSum, count) => cumSum + count, 0) - const completedMeeting = await r - .table('NewMeeting') - .get(meetingId) - .update( - { - endedAt: now, - phases, - commentCount, - storyCount, - ...insights - }, - {returnChanges: true, nonAtomic: true} - )('changes')(0)('new_val') - .default(null) - .run() await getKysely() .updateTable('NewMeeting') .set({ @@ -106,13 +89,9 @@ export default { engagement: insights.engagement }) .where('id', '=', meetingId) - .executeTakeFirst() + .execute() dataLoader.clearAll('newMeetings') - if (!completedMeeting) { - return standardError(new Error('Completed poker meeting does not exist'), { - userId: viewerId - }) - } + const completedMeeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (completedMeeting.meetingType !== 'poker') { return standardError(new Error('Meeting is not a poker meeting'), {userId: viewerId}) } diff --git a/packages/server/graphql/mutations/flagReadyToAdvance.ts b/packages/server/graphql/mutations/flagReadyToAdvance.ts index 24a07512bf5..289b2ddaa74 100644 --- a/packages/server/graphql/mutations/flagReadyToAdvance.ts +++ b/packages/server/graphql/mutations/flagReadyToAdvance.ts @@ -2,7 +2,6 @@ import {GraphQLBoolean, GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import findStageById from 'parabol-client/utils/meetings/findStageById' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {getUserId} from '../../utils/authorization' import publish from '../../utils/publish' @@ -31,9 +30,7 @@ const flagReadyToAdvance = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { const pg = getKysely() - const r = await getRethink() const viewerId = getUserId(authToken) - const now = new Date() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -82,7 +79,6 @@ const flagReadyToAdvance = { // RESOLUTION // TODO there's enough evidence showing that we should probably worry about atomicity - await r.table('NewMeeting').get(meetingId).update({phases, updatedAt: now}).run() await pg .updateTable('NewMeeting') .set({phases: JSON.stringify(phases)}) diff --git a/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts b/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts index b7a34bc7b6f..6370fb1fbe3 100644 --- a/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts +++ b/packages/server/graphql/mutations/helpers/addAgendaItemToActiveActionMeeting.ts @@ -1,4 +1,3 @@ -import getRethink from '../../../database/rethinkDriver' import AgendaItemsStage from '../../../database/types/AgendaItemsStage' import getKysely from '../../../postgres/getKysely' import getPhase from '../../../utils/getPhase' @@ -12,8 +11,6 @@ const addAgendaItemToActiveActionMeeting = async ( teamId: string, dataLoader: DataLoaderWorker ) => { - const now = new Date() - const r = await getRethink() const activeMeetings = await dataLoader.get('activeMeetingsByTeamId').load(teamId) const actionMeeting = activeMeetings.find( (activeMeeting) => activeMeeting.meetingType === 'action' @@ -36,36 +33,26 @@ const addAgendaItemToActiveActionMeeting = async ( const {discussionId} = newStage stages.push(newStage) - await Promise.all([ - r - .table('NewMeeting') - .get(meetingId) - .update({ - phases, - updatedAt: now + await getKysely() + .with('UpdatePhases', (qb) => + qb + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + ) + .with('InsertDiscussion', (qb) => + qb.insertInto('Discussion').values({ + id: discussionId, + teamId, + meetingId, + discussionTopicType: 'agendaItem', + discussionTopicId: agendaItemId }) - .run(), - getKysely() - .with('UpdatePhases', (qb) => - qb - .updateTable('NewMeeting') - .set({phases: JSON.stringify(phases)}) - .where('id', '=', meetingId) - ) - .with('InsertDiscussion', (qb) => - qb.insertInto('Discussion').values({ - id: discussionId, - teamId, - meetingId, - discussionTopicType: 'agendaItem', - discussionTopicId: agendaItemId - }) - ) - .updateTable('AgendaItem') - .set({meetingId}) - .where('id', '=', agendaItemId) - .execute() - ]) + ) + .updateTable('AgendaItem') + .set({meetingId}) + .where('id', '=', agendaItemId) + .execute() return meetingId } diff --git a/packages/server/graphql/mutations/helpers/addRecallBot.ts b/packages/server/graphql/mutations/helpers/addRecallBot.ts index 3a16b97c0d2..5a466eee7df 100644 --- a/packages/server/graphql/mutations/helpers/addRecallBot.ts +++ b/packages/server/graphql/mutations/helpers/addRecallBot.ts @@ -1,4 +1,3 @@ -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import RecallAIServerManager from '../../../utils/RecallAIServerManager' @@ -9,9 +8,7 @@ const getBotId = async (videoMeetingURL: string) => { } const addRecallBot = async (meetingId: string, videoMeetingURL: string) => { - const r = await getRethink() const recallBotId = (await getBotId(videoMeetingURL)) ?? undefined - await r.table('NewMeeting').get(meetingId).update({recallBotId, videoMeetingURL}).run() await getKysely() .updateTable('NewMeeting') .set({recallBotId, videoMeetingURL}) diff --git a/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts b/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts index 44846ed791f..1f8b8afd09c 100644 --- a/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts +++ b/packages/server/graphql/mutations/helpers/createNewMeetingPhases.ts @@ -10,8 +10,6 @@ import { VOTE } from 'parabol-client/utils/constants' import toTeamMemberId from '../../../../client/utils/relay/toTeamMemberId' -import getRethink from '../../../database/rethinkDriver' -import {RValue} from '../../../database/stricterR' import AgendaItemsPhase from '../../../database/types/AgendaItemsPhase' import CheckInPhase from '../../../database/types/CheckInPhase' import CheckInStage from '../../../database/types/CheckInStage' @@ -23,9 +21,10 @@ import TeamHealthPhase from '../../../database/types/TeamHealthPhase' import TeamHealthStage from '../../../database/types/TeamHealthStage' import UpdatesPhase from '../../../database/types/UpdatesPhase' import UpdatesStage from '../../../database/types/UpdatesStage' +import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' import getKysely from '../../../postgres/getKysely' import {MeetingTypeEnum} from '../../../postgres/types/Meeting' -import {NewMeetingPhase} from '../../../postgres/types/NewMeetingPhase' +import {NewMeetingPhase, NewMeetingStages} from '../../../postgres/types/NewMeetingPhase' import isPhaseAvailable from '../../../utils/isPhaseAvailable' import {DataLoaderWorker} from '../../graphql' import {getFeatureTier} from '../../types/helpers/getFeatureTier' @@ -44,29 +43,28 @@ export const primePhases = (phases: GenericMeetingPhase[], startIndex = 0) => { } } -const getPastStageDurations = async (teamId: string) => { - const r = await getRethink() - return ( - r - .table('NewMeeting') - .getAll(teamId, {index: 'teamId'}) - .filter({isLegacy: false}, {default: true}) - // .orderBy(r.desc('endedAt')) - .concatMap((row: RValue) => row('phases')) - .concatMap((row: RValue) => row('stages')) - .filter((row: RValue) => row.hasFields('startAt', 'endAt')) - // convert seconds to ms - .merge((row: RValue) => ({ - duration: r.sub(row('endAt'), row('startAt')).mul(1000).floor() - })) - // remove stages that took under 1 minute - .filter((row: RValue) => row('duration').ge(60000)) - .orderBy(r.desc('startAt')) - .group('phaseType') - .ungroup() - .map((row) => [row('group'), row('reduction')('duration')]) - .coerceTo('object') - .run() as unknown as {[key: string]: number[]} +const getPastStageDurations = async (teamId: string, dataLoader: DataLoaderInstance) => { + const completedMeetings = await dataLoader.get('completedMeetingsByTeamId').load(teamId) + const phases = completedMeetings.flatMap((meeting) => meeting.phases as NewMeetingPhase[]) + const stages = phases + .flatMap((phase) => phase.stages as NewMeetingStages[]) + .map((stage) => ({ + phaseType: stage.phaseType, + duration: + stage.startAt && stage.endAt + ? new Date(stage.endAt).getTime() - new Date(stage.startAt).getTime() + : 0 + })) + .filter((stage) => stage.duration >= 60_000) + return stages.reduce( + (acc, stage) => { + if (!acc[stage.phaseType]) { + acc[stage.phaseType] = [] + } + acc[stage.phaseType]!.push(stage.duration) + return acc + }, + {} as Record ) } @@ -81,7 +79,7 @@ const createNewMeetingPhases = async id) - await pg - .updateTable('NewMeeting') - .set({showConversionModal: false}) - .where('id', 'in', meetingIds) - .execute() - await r - .table('NewMeeting') - .getAll(r.args(meetingIds)) - .update({ - showConversionModal: false - }) - .run() + if (meetingIds.length > 0) { + await pg + .updateTable('NewMeeting') + .set({showConversionModal: false}) + .where('id', 'in', meetingIds) + .execute() + } return activeMeetings } } diff --git a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts index d38ed8dffc4..183e4ab19d1 100644 --- a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts @@ -6,7 +6,7 @@ import {phaseLabelLookup} from 'parabol-client/utils/meetings/lookups' import TeamPromptResponseId from '../../../../../client/shared/gqlIds/TeamPromptResponseId' import {ErrorResponse, PostMessageResponse} from '../../../../../client/utils/SlackManager' import appOrigin from '../../../../appOrigin' -import getRethink, {RethinkSchema} from '../../../../database/rethinkDriver' +import {RethinkSchema} from '../../../../database/rethinkDriver' import SlackAuth from '../../../../database/types/SlackAuth' import {SlackNotificationAuth} from '../../../../dataloader/integrationAuthLoaders' import getKysely from '../../../../postgres/getKysely' @@ -359,17 +359,11 @@ export const SlackSingleChannelNotifier: NotificationIntegrationHelper { const pg = getKysely() const now = new Date() - const r = await getRethink() const [activeMeetings, completedMeetings] = await Promise.all([ dataLoader.get('activeMeetingsByTeamId').load(teamId), dataLoader.get('completedMeetingsByTeamId').load(teamId) @@ -52,15 +50,6 @@ const removeStagesFromMeetings = async ( .set({facilitatorStageId: meeting.facilitatorStageId, phases: JSON.stringify(phases)}) .where('id', '=', meetingId) .execute() - return r - .table('NewMeeting') - .get(meetingId) - .update({ - facilitatorStageId: meeting.facilitatorStageId, - phases, - updatedAt: now - }) - .run() }) ) return meetings.map((meeting) => meeting.id) diff --git a/packages/server/graphql/mutations/helpers/removeTeamMember.ts b/packages/server/graphql/mutations/helpers/removeTeamMember.ts index 9a9452ba27e..73a9f934dfd 100644 --- a/packages/server/graphql/mutations/helpers/removeTeamMember.ts +++ b/packages/server/graphql/mutations/helpers/removeTeamMember.ts @@ -27,7 +27,6 @@ const removeTeamMember = async ( const {evictorUserId} = options const r = await getRethink() const pg = getKysely() - const now = new Date() const {userId, teamId} = fromTeamMemberId(teamMemberId) // see if they were a leader, make a new guy leader so later we can reassign tasks const activeTeamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) @@ -141,47 +140,39 @@ const removeTeamMember = async ( .run() // Reassign facilitator for meetings this user is facilitating. - const facilitatingMeetings = await r - .table('NewMeeting') - .getAll(r.args(meetingIds), {index: 'id'}) - .filter({ - facilitatorUserId: userId - }) - .run() - - const newMeetingFacilitators = ( - await dataLoader - .get('meetingMembersByMeetingId') - .loadMany(facilitatingMeetings.map((meeting) => meeting.id)) - ) - .filter(errorFilter) - .map((members) => members[0]) - .filter((member) => !!member) + if (meetingIds.length > 0) { + const facilitatingMeetings = await pg + .selectFrom('NewMeeting') + .select('id') + .where('id', 'in', meetingIds) + .where('facilitatorUserId', '=', userId) + .execute() - Promise.allSettled( - newMeetingFacilitators.map(async (newFacilitator) => { - if (!newFacilitator) { - // This user is the only meeting member, so do nothing. - // :TODO: (jmtaber129): Consider closing meetings where this user is the only meeting - // member. - return - } - await pg - .updateTable('NewMeeting') - .set({facilitatorUserId: newFacilitator.userId}) - .where('id', '=', newFacilitator.meetingId) - .execute() - await r - .table('NewMeeting') - .get(newFacilitator.meetingId) - .update({ - facilitatorUserId: newFacilitator.userId, - updatedAt: now - }) - .run() - }) - ) + const newMeetingFacilitators = ( + await dataLoader + .get('meetingMembersByMeetingId') + .loadMany(facilitatingMeetings.map((meeting) => meeting.id)) + ) + .filter(errorFilter) + .map((members) => members[0]) + .filter((member) => !!member) + await Promise.allSettled( + newMeetingFacilitators.map(async (newFacilitator) => { + if (!newFacilitator) { + // This user is the only meeting member, so do nothing. + // :TODO: (jmtaber129): Consider closing meetings where this user is the only meeting + // member. + return + } + await pg + .updateTable('NewMeeting') + .set({facilitatorUserId: newFacilitator.userId}) + .where('id', '=', newFacilitator.meetingId) + .execute() + }) + ) + } return { user, notificationId, diff --git a/packages/server/graphql/mutations/helpers/removeUserFromMeetingStages.ts b/packages/server/graphql/mutations/helpers/removeUserFromMeetingStages.ts index dff4349cf30..470a20c7943 100644 --- a/packages/server/graphql/mutations/helpers/removeUserFromMeetingStages.ts +++ b/packages/server/graphql/mutations/helpers/removeUserFromMeetingStages.ts @@ -1,4 +1,3 @@ -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {DataLoaderWorker} from '../../graphql' import {isEstimateStage} from '../../meetingTypePredicates' @@ -14,8 +13,6 @@ const removeUserFromMeetingStages = async ( dataLoader: DataLoaderWorker ) => { const pg = getKysely() - const now = new Date() - const r = await getRethink() const [activeMeetings, completedMeetings] = await Promise.all([ dataLoader.get('activeMeetingsByTeamId').load(teamId), dataLoader.get('completedMeetingsByTeamId').load(teamId) @@ -52,14 +49,6 @@ const removeUserFromMeetingStages = async ( .set({phases: JSON.stringify(phases)}) .where('id', '=', meetingId) .execute() - return r - .table('NewMeeting') - .get(meetingId) - .update({ - phases, - updatedAt: now - }) - .run() }) ) return meetings.map((meeting) => meeting.id) diff --git a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts index 6a0b1e21689..c486815af94 100644 --- a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts @@ -76,23 +76,6 @@ const summarizeRetroMeeting = async (meeting: RetrospectiveMeeting, context: Int }) .where('id', '=', meetingId) .execute() - await r - .table('NewMeeting') - .get(meetingId) - .update( - { - commentCount, - taskCount, - topicCount: reflectionGroupIds.length, - reflectionCount: reflections.length, - sentimentScore, - summary, - transcription - }, - {nonAtomic: true} - ) - .run() - dataLoader.clearAll('newMeetings') // wait for whole meeting summary to be generated before sending summary email and updating qualAIMeetingCount sendNewMeetingSummary(meeting, context).catch(Logger.log) @@ -116,7 +99,6 @@ const safeEndRetrospective = async ({ }) => { const {authToken, socketId: mutatorId, dataLoader} = context const {id: meetingId, phases, facilitatorStageId, teamId} = meeting - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const viewerId = getUserId(authToken) @@ -132,19 +114,6 @@ const safeEndRetrospective = async ({ const phase = getMeetingPhase(phases) const insights = await gatherInsights(meeting, dataLoader) - const completedRetrospective = await r - .table('NewMeeting') - .get(meetingId) - .update( - { - endedAt: now, - phases, - ...insights - }, - {returnChanges: true} - )('changes')(0)('new_val') - .default(null) - .run() await getKysely() .updateTable('NewMeeting') .set({ @@ -156,11 +125,7 @@ const safeEndRetrospective = async ({ .where('id', '=', meetingId) .executeTakeFirst() dataLoader.clearAll('newMeetings') - if (!completedRetrospective) { - return standardError(new Error('Completed retrospective meeting does not exist'), { - userId: viewerId - }) - } + const completedRetrospective = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (completedRetrospective.meetingType !== 'retrospective') { return standardError(new Error('Meeting type is not retrospective'), { userId: viewerId diff --git a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts index 48cb75e8d32..8d58eff0645 100644 --- a/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts +++ b/packages/server/graphql/mutations/helpers/safeEndTeamPrompt.ts @@ -1,7 +1,6 @@ import {sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {checkTeamsLimit} from '../../../billing/helpers/teamLimitsCheck' -import getRethink, {ParabolR} from '../../../database/rethinkDriver' import TimelineEventTeamPromptComplete from '../../../database/types/TimelineEventTeamPromptComplete' import getKysely from '../../../postgres/getKysely' import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTeamPromptResponsesByMeetingIds' @@ -21,19 +20,11 @@ import updateTeamInsights from './updateTeamInsights' const summarizeTeamPrompt = async (meeting: TeamPromptMeeting, context: InternalContext) => { const {dataLoader} = context const pg = getKysely() - const r = await getRethink() const summary = await generateStandupMeetingSummary(meeting, dataLoader) if (summary) { await pg.updateTable('NewMeeting').set({summary}).where('id', '=', meeting.id).execute() } - await r - .table('NewMeeting') - .get(meeting.id) - .update({ - summary - }) - .run() dataLoader.clearAll('newMeetings') // wait for whole meeting summary to be generated before sending summary email and updating qualAIMeetingCount @@ -49,16 +40,12 @@ const summarizeTeamPrompt = async (meeting: TeamPromptMeeting, context: Internal const safeEndTeamPrompt = async ({ meeting, - now, viewerId, - r, context, subOptions }: { meeting: TeamPromptMeeting - now: Date viewerId?: string - r: ParabolR context: InternalContext subOptions: SubOptions }) => { @@ -80,30 +67,10 @@ const safeEndTeamPrompt = async ({ }) .where('id', '=', meetingId) .execute() - const completedTeamPrompt = await r - .table('NewMeeting') - .get(meetingId) - .update( - { - endedAt: now, - ...insights - }, - {returnChanges: true} - )('changes')(0)('new_val') - .default(null) - .run() - - if (!completedTeamPrompt) { - return standardError(new Error('Completed team prompt meeting does not exist'), { - userId: viewerId - }) - } - - if (completedTeamPrompt.meetingType !== 'teamPrompt') { - return standardError(new Error('Meeting is not a team prompt'), {userId: viewerId}) - } + dataLoader.clearAll('newMeetings') - const [meetingMembers, team, teamMembers, responses] = await Promise.all([ + const [completedTeamPrompt, meetingMembers, team, teamMembers, responses] = await Promise.all([ + dataLoader.get('newMeetings').loadNonNull(meetingId), dataLoader.get('meetingMembersByMeetingId').load(meetingId), dataLoader.get('teams').loadNonNull(teamId), dataLoader.get('teamMembersByTeamId').load(teamId), diff --git a/packages/server/graphql/mutations/helpers/updateQualAIMeetingsCount.ts b/packages/server/graphql/mutations/helpers/updateQualAIMeetingsCount.ts index b6d11940cef..1c2cbb7d7de 100644 --- a/packages/server/graphql/mutations/helpers/updateQualAIMeetingsCount.ts +++ b/packages/server/graphql/mutations/helpers/updateQualAIMeetingsCount.ts @@ -10,7 +10,7 @@ const updateQualAIMeetingsCount = async ( dataLoader.get('meetingMembersByMeetingId').load(meetingId), dataLoader.get('teams').load(teamId), dataLoader.get('retroReflectionsByMeetingId').load(meetingId), - dataLoader.get('newMeetings').load(meetingId) + dataLoader.get('newMeetings').loadNonNull(meetingId) ]) if (meetingMembers.length < 3 || !team || !meeting.summary || reflections.length < 5) return const {qualAIMeetingsCount} = team diff --git a/packages/server/graphql/mutations/helpers/updateTeamInsights.ts b/packages/server/graphql/mutations/helpers/updateTeamInsights.ts index d54f321a173..732d194be5f 100644 --- a/packages/server/graphql/mutations/helpers/updateTeamInsights.ts +++ b/packages/server/graphql/mutations/helpers/updateTeamInsights.ts @@ -1,6 +1,5 @@ +import {NotNull} from 'kysely' import ms from 'ms' -import {RValue} from 'rethinkdb-ts' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {DataLoaderWorker} from '../../graphql' @@ -23,30 +22,27 @@ const updateTeamInsights = async (teamId: string, dataLoader: DataLoaderWorker) if (hasNoTeamInsightsFlag) return // actual update - const r = await getRethink() const pg = getKysely() const now = new Date() const insightsPeriod = new Date(now.getTime() - TEAM_INSIGHTS_PERIOD) const topRetroTemplatesPeriod = new Date(now.getTime() - TOP_RETRO_TEMPLATES_PERIOD) const [meetingInsights, retroTemplates] = await Promise.all([ - r - .table('NewMeeting') - .getAll(teamId, {index: 'teamId'}) - .filter((row: RValue) => row('createdAt').gt(insightsPeriod)) - .pluck('endedAt', 'usedReactjis', 'meetingType', 'templateId', 'engagement') - .run(), - ( - r - .table('NewMeeting') - .getAll(teamId, {index: 'teamId'}) - .filter((row: RValue) => - row('meetingType').eq('retrospective').and(row('createdAt').gt(topRetroTemplatesPeriod)) - ) as any - ) - .group('templateId') - .count() - .run() as Promise<{group: string; reduction: number}[]> + pg + .selectFrom('NewMeeting') + .select(['endedAt', 'usedReactjis', 'meetingType', 'templateId', 'engagement']) + .where('teamId', '=', teamId) + .where('createdAt', '>', insightsPeriod) + .execute(), + pg + .selectFrom('NewMeeting') + .select(({fn}) => ['templateId as group', fn.count('id').as('reduction')]) + .where('teamId', '=', teamId) + .where('meetingType', '=', 'retrospective') + .where('createdAt', '>', topRetroTemplatesPeriod) + .groupBy('templateId') + .$narrowType<{group: NotNull}>() + .execute() ]) // emojis @@ -90,12 +86,10 @@ const updateTeamInsights = async (teamId: string, dataLoader: DataLoaderWorker) ) // top retro template - const topRetroTemplates = retroTemplates.map( - ({group, reduction}: {group: string; reduction: number}) => ({ - reflectTemplateId: group, - count: reduction - }) - ) + const topRetroTemplates = retroTemplates.map(({group, reduction}) => ({ + reflectTemplateId: group, + count: Number(reduction) + })) topRetroTemplates.sort((a, b) => b.count - a.count) const update = { diff --git a/packages/server/graphql/mutations/joinMeeting.ts b/packages/server/graphql/mutations/joinMeeting.ts index e830e298608..7a209aafbae 100644 --- a/packages/server/graphql/mutations/joinMeeting.ts +++ b/packages/server/graphql/mutations/joinMeeting.ts @@ -1,7 +1,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import toTeamMemberId from '../../../client/utils/relay/toTeamMemberId' -import rMapIf from '../../database/rMapIf' import getRethink from '../../database/rethinkDriver' import ActionMeetingMember from '../../database/types/ActionMeetingMember' import CheckInStage from '../../database/types/CheckInStage' @@ -89,8 +88,6 @@ const joinMeeting = { return {error: {message: 'Already joined meeting'}} } - const mapIf = rMapIf(r) - const addStageToPhase = async ( stage: CheckInStage | UpdatesStage | TeamPromptResponseStage, phaseType: NewMeetingPhase['phaseType'] @@ -122,30 +119,6 @@ const joinMeeting = { .where('id', '=', meetingId) .execute() }) - return r - .table('NewMeeting') - .get(meetingId) - .update((meeting: any) => ({ - phases: mapIf( - meeting('phases'), - (phase: any) => phase('phaseType').eq(phaseType), - (phase: any) => - phase.merge({ - stages: phase('stages').append({ - ...stage, - // this is a departure from before. Let folks move ahead while the check-in is going on! - isNavigable: true, - isNavigableByFacilitator: true, - // the stage is complete if all other stages are complete & there's at least 1 - isComplete: r.and( - phase('stages')('isComplete').contains(false).not(), - phase('stages').count().ge(1) - ) - }) - }) - ) - })) - .run() } const appendToCheckin = async () => { diff --git a/packages/server/graphql/mutations/navigateMeeting.ts b/packages/server/graphql/mutations/navigateMeeting.ts index 8563af871b1..20172d7cec9 100644 --- a/packages/server/graphql/mutations/navigateMeeting.ts +++ b/packages/server/graphql/mutations/navigateMeeting.ts @@ -3,7 +3,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import findStageById from 'parabol-client/utils/meetings/findStageById' import startStage_ from 'parabol-client/utils/startStage_' import unlockNextStages from 'parabol-client/utils/unlockNextStages' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {Logger} from '../../utils/Logger' import {getUserId} from '../../utils/authorization' @@ -40,7 +39,6 @@ export default { }: {completedStageId: string | null; facilitatorStageId: string | null; meetingId: string}, {authToken, socketId: mutatorId, dataLoader}: GQLContext ) { - const r = await getRethink() const now = new Date() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -100,7 +98,9 @@ export default { if (!facilitatorStage.isNavigableByFacilitator) { return standardError(new Error('Stage has not started'), {userId: viewerId}) } - + if (meeting.facilitatorStageId === facilitatorStageId) { + return standardError(new Error('Already at this stage'), {userId: viewerId}) + } // mutative // NOTE: it is possible to start a stage then move backwards & complete another phase, which would make it seem like this phase took a long time // the cleanest way to fix this is to store start/stop on each stage visit, since i could visit B, then visit A, then move B before A, then visit B @@ -111,19 +111,6 @@ export default { } // RESOLUTION - const oldFacilitatorStageId = await r - .table('NewMeeting') - .get(meetingId) - .update( - { - facilitatorStageId: facilitatorStageId ?? undefined, - phases, - updatedAt: now - }, - {returnChanges: true} - )('changes')(0)('old_val')('facilitatorStageId') - .default(null) - .run() await getKysely() .updateTable('NewMeeting') .set({ @@ -133,13 +120,10 @@ export default { .where('id', '=', meetingId) .execute() dataLoader.clearAll('newMeetings') - if (!oldFacilitatorStageId) { - return {error: {message: 'Stage already advanced'}} - } const data = { meetingId, - oldFacilitatorStageId, + oldFacilitatorStageId: meeting.facilitatorStageId, facilitatorStageId, unlockedStageIds, ...phaseCompleteData diff --git a/packages/server/graphql/mutations/payLater.ts b/packages/server/graphql/mutations/payLater.ts index fa4c0189a89..af614ca0fc0 100644 --- a/packages/server/graphql/mutations/payLater.ts +++ b/packages/server/graphql/mutations/payLater.ts @@ -1,9 +1,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' -import getPg from '../../postgres/getPg' -import {incrementUserPayLaterClickCountQuery} from '../../postgres/queries/generated/incrementUserPayLaterClickCountQuery' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -25,7 +22,6 @@ export default { {meetingId}: {meetingId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -50,26 +46,27 @@ export default { 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('NewMeeting') - .get(meetingId) - .update({ - showConversionModal: false - }) - .run() - await getKysely() + .with('UpdateOrg', (qc) => + qc + .updateTable('Organization') + .set((eb) => ({ + payLaterClickCount: eb('payLaterClickCount', '+', 1) + })) + .where('id', '=', orgId) + ) + .with('UpdateUser', (qc) => + qc + .updateTable('User') + .set((eb) => ({ + payLaterClickCount: eb('payLaterClickCount', '+', 1) + })) + .where('id', '=', viewerId) + ) .updateTable('NewMeeting') .set({showConversionModal: false}) .where('id', '=', meetingId) .execute() - dataLoader.clearAll('newMeetings') - await incrementUserPayLaterClickCountQuery.run({id: viewerId}, getPg()) + dataLoader.clearAll(['newMeetings', 'organizations', 'users']) analytics.conversionModalPayLaterClicked(viewer) const data = {orgId, meetingId} diff --git a/packages/server/graphql/mutations/pokerResetDimension.ts b/packages/server/graphql/mutations/pokerResetDimension.ts index 5dafb36786b..af168b87ea5 100644 --- a/packages/server/graphql/mutations/pokerResetDimension.ts +++ b/packages/server/graphql/mutations/pokerResetDimension.ts @@ -2,8 +2,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' -import {RValue} from '../../database/stricterR' -import updateStage from '../../database/updateStage' import getKysely from '../../postgres/getKysely' import removeMeetingTaskEstimates from '../../postgres/queries/removeMeetingTaskEstimates' import {getUserId, isTeamMember} from '../../utils/authorization' @@ -42,16 +40,13 @@ const pokerResetDimension = { if (meeting.meetingType !== 'poker') { return {error: {message: 'Not a poker meeting'}} } - const {endedAt, phases, meetingType, teamId, createdBy, facilitatorUserId} = meeting + const {endedAt, phases, teamId, createdBy, facilitatorUserId} = meeting if (!isTeamMember(authToken, teamId)) { return {error: {message: 'Not on the team'}} } if (endedAt) { return {error: {message: 'Meeting has ended'}} } - if (meetingType !== 'poker') { - return {error: {message: 'Not a poker meeting'}} - } if (isPhaseComplete('ESTIMATE', phases)) { return {error: {message: 'Estimate phase is already complete'}} } @@ -84,11 +79,9 @@ const pokerResetDimension = { // mutate the cached meeting Object.assign(stage, updates) - const updater = (estimateStage: RValue) => estimateStage.merge(updates) const [meetingMembers, teamMembers] = await Promise.all([ dataLoader.get('meetingMembersByMeetingId').load(meetingId), dataLoader.get('teamMembersByTeamId').load(teamId), - updateStage(meetingId, stageId, 'ESTIMATE', updater), pg .updateTable('NewMeeting') .set({ diff --git a/packages/server/graphql/mutations/pokerRevealVotes.ts b/packages/server/graphql/mutations/pokerRevealVotes.ts index 1c0f22fc51d..63d23dccfbb 100644 --- a/packages/server/graphql/mutations/pokerRevealVotes.ts +++ b/packages/server/graphql/mutations/pokerRevealVotes.ts @@ -1,10 +1,8 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {sql} from 'kysely' import {PokerCards, SubscriptionChannel} from 'parabol-client/types/constEnums' -import {RValue} from '../../database/stricterR' import EstimateUserScore from '../../database/types/EstimateUserScore' import PokerMeetingMember from '../../database/types/PokerMeetingMember' -import updateStage from '../../database/updateStage' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' @@ -92,12 +90,6 @@ const pokerRevealVotes = { }) stage.isVoting = false - const updater = (estimateStage: RValue) => - estimateStage.merge({ - isVoting: false, - // note that a race condition exists here. it's possible that i cast my vote after the meeting is fetched but before this update & that'll be overwritten - scores - }) await pg .updateTable('NewMeeting') .set({ @@ -108,7 +100,6 @@ const pokerRevealVotes = { }) .where('id', '=', meetingId) .execute() - await updateStage(meetingId, stageId, 'ESTIMATE', updater) dataLoader.clearAll('newMeetings') const data = {meetingId, stageId} publish(SubscriptionChannel.MEETING, meetingId, 'PokerRevealVotesSuccess', data, subOptions) diff --git a/packages/server/graphql/mutations/promoteNewMeetingFacilitator.ts b/packages/server/graphql/mutations/promoteNewMeetingFacilitator.ts index ce3a30827f9..c1de141c948 100644 --- a/packages/server/graphql/mutations/promoteNewMeetingFacilitator.ts +++ b/packages/server/graphql/mutations/promoteNewMeetingFacilitator.ts @@ -1,6 +1,5 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -25,10 +24,8 @@ export default { {facilitatorUserId, meetingId}: {facilitatorUserId: string; meetingId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} - const now = new Date() const viewerId = getUserId(authToken) // AUTH @@ -52,14 +49,6 @@ export default { } // RESOLUTION - await r - .table('NewMeeting') - .get(meetingId) - .update({ - facilitatorUserId, - updatedAt: now - }) - .run() await getKysely() .updateTable('NewMeeting') .set({facilitatorUserId}) diff --git a/packages/server/graphql/mutations/removeReflection.ts b/packages/server/graphql/mutations/removeReflection.ts index b864cfff51a..01f1712a789 100644 --- a/packages/server/graphql/mutations/removeReflection.ts +++ b/packages/server/graphql/mutations/removeReflection.ts @@ -2,7 +2,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' import unlockAllStagesForPhase from 'parabol-client/utils/unlockAllStagesForPhase' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -24,7 +23,6 @@ export default { {reflectionId}: {reflectionId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { - const r = await getRethink() const pg = getKysely() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -40,7 +38,7 @@ export default { if (creatorId !== viewerId) { return standardError(new Error('Reflection'), {userId: viewerId}) } - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {endedAt, phases, teamId} = meeting if (!isTeamMember(authToken, teamId)) { return standardError(new Error('Team not found'), {userId: viewerId}) @@ -61,14 +59,7 @@ export default { let unlockedStageIds if (reflections.length === 0) { unlockedStageIds = unlockAllStagesForPhase(phases, 'group', true, false) - await r - .table('NewMeeting') - .get(meetingId) - .update({ - phases - }) - .run() - await getKysely() + await pg .updateTable('NewMeeting') .set({phases: JSON.stringify(phases)}) .where('id', '=', meetingId) diff --git a/packages/server/graphql/mutations/renameMeeting.ts b/packages/server/graphql/mutations/renameMeeting.ts index a4d2263145b..99f6c0a53d3 100644 --- a/packages/server/graphql/mutations/renameMeeting.ts +++ b/packages/server/graphql/mutations/renameMeeting.ts @@ -1,7 +1,6 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import linkify from 'parabol-client/utils/linkify' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {getUserId} from '../../utils/authorization' import publish from '../../utils/publish' @@ -28,7 +27,6 @@ const renameMeeting = { {name, meetingId}: {name: string; meetingId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -55,13 +53,6 @@ const renameMeeting = { // RESOLUTION meeting.name = name - await r - .table('NewMeeting') - .get(meetingId) - .update({ - name - }) - .run() await getKysely().updateTable('NewMeeting').set({name}).where('id', '=', meetingId).execute() const data = {meetingId} IntegrationNotifier.updateMeeting?.(dataLoader, meetingId, teamId) diff --git a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts index 6985db0c274..ec97b6837c7 100644 --- a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts +++ b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts @@ -119,8 +119,9 @@ const resetRetroMeetingToGroupStage = { .where('id', '=', meetingId) .execute(), r.table('Task').getAll(r.args(discussionIdsToDelete), {index: 'discussionId'}).delete().run(), - r.table('NewMeeting').get(meetingId).update({phases: newPhases}).run(), - (r.table('MeetingMember').getAll(meetingId, {index: 'meetingId'}) as any) + r + .table('MeetingMember') + .getAll(meetingId, {index: 'meetingId'}) .update({votesRemaining: meeting.totalVotes}) .run() ]) diff --git a/packages/server/graphql/mutations/setPhaseFocus.ts b/packages/server/graphql/mutations/setPhaseFocus.ts index 301502c2aa6..e48f2550ad2 100644 --- a/packages/server/graphql/mutations/setPhaseFocus.ts +++ b/packages/server/graphql/mutations/setPhaseFocus.ts @@ -2,7 +2,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {GROUP} from 'parabol-client/utils/constants' import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {getUserId} from '../../utils/authorization' import getPhase from '../../utils/getPhase' @@ -28,7 +27,6 @@ const setPhaseFocus = { {meetingId, focusedPromptId}: {meetingId: string; focusedPromptId?: string | null}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -52,7 +50,6 @@ const setPhaseFocus = { // RESOLUTION // mutative reflectPhase.focusedPromptId = focusedPromptId ?? undefined - await r.table('NewMeeting').get(meetingId).update(meeting).run() await getKysely() .updateTable('NewMeeting') .set({phases: JSON.stringify(phases)}) diff --git a/packages/server/graphql/mutations/setStageTimer.ts b/packages/server/graphql/mutations/setStageTimer.ts index 6e93cdd39ff..b580df1ca1a 100644 --- a/packages/server/graphql/mutations/setStageTimer.ts +++ b/packages/server/graphql/mutations/setStageTimer.ts @@ -1,7 +1,6 @@ import {GraphQLFloat, GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import findStageById from 'parabol-client/utils/meetings/findStageById' -import getRethink from '../../database/rethinkDriver' import ScheduledJobMeetingStageTimeLimit from '../../database/types/ScheduledJobMetingStageTimeLimit' import getKysely from '../../postgres/getKysely' import {analytics} from '../../utils/analytics/analytics' @@ -46,7 +45,6 @@ export default { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const pg = getKysely() - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const viewerId = getUserId(authToken) @@ -54,7 +52,7 @@ export default { // AUTH const [meeting, viewer] = await Promise.all([ - dataLoader.get('newMeetings').load(meetingId), + dataLoader.get('newMeetings').loadNonNull(meetingId), dataLoader.get('users').loadNonNull(viewerId) ]) const {endedAt, facilitatorStageId, facilitatorUserId, phases, teamId} = meeting @@ -108,14 +106,6 @@ export default { } // RESOLUTION - await r - .table('NewMeeting') - .get(meetingId) - .update({ - phases, - updatedAt: now - }) - .run() await pg .updateTable('NewMeeting') .set({phases: JSON.stringify(phases)}) diff --git a/packages/server/graphql/mutations/startSprintPoker.ts b/packages/server/graphql/mutations/startSprintPoker.ts index abb727757e7..e1836d9b83f 100644 --- a/packages/server/graphql/mutations/startSprintPoker.ts +++ b/packages/server/graphql/mutations/startSprintPoker.ts @@ -102,7 +102,6 @@ export default { const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const viewerId = getUserId(authToken) - const DUPLICATE_THRESHOLD = 3000 // AUTH if (!isTeamMember(authToken, teamId)) { return standardError(new Error('Not on team'), {userId: viewerId}) @@ -148,26 +147,17 @@ export default { }) as PokerMeeting const template = await dataLoader.get('meetingTemplates').load(selectedTemplateId) - await Promise.allSettled([ + const [newMeetingRes] = await Promise.allSettled([ pg .insertInto('NewMeeting') .values({...meeting, phases: JSON.stringify(phases)}) .execute(), - r.table('NewMeeting').insert(meeting).run(), updateMeetingTemplateLastUsedAt(selectedTemplateId, teamId) ]) - dataLoader.clearAll('newMeetings') - // Disallow accidental starts (2 meetings within 2 seconds) - const newActiveMeetings = await dataLoader.get('activeMeetingsByTeamId').load(teamId) - const otherActiveMeeting = newActiveMeetings.find((activeMeeting) => { - const {createdAt, id} = activeMeeting - if (id === meetingId || activeMeeting.meetingType !== 'poker') return false - return createdAt.getTime() > Date.now() - DUPLICATE_THRESHOLD - }) - if (otherActiveMeeting) { - await r.table('NewMeeting').get(meetingId).delete().run() + if (newMeetingRes.status === 'rejected') { return {error: {message: 'Meeting already started'}} } + dataLoader.clearAll('newMeetings') const teamMemberId = toTeamMemberId(teamId, viewerId) const teamMember = await dataLoader.get('teamMembers').loadNonNull(teamMemberId) diff --git a/packages/server/graphql/mutations/updateNewCheckInQuestion.ts b/packages/server/graphql/mutations/updateNewCheckInQuestion.ts index 0446229d527..e00258dcb32 100644 --- a/packages/server/graphql/mutations/updateNewCheckInQuestion.ts +++ b/packages/server/graphql/mutations/updateNewCheckInQuestion.ts @@ -3,7 +3,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import convertToTaskContent from 'parabol-client/utils/draftjs/convertToTaskContent' import {makeCheckinQuestion} from 'parabol-client/utils/makeCheckinGreeting' import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' @@ -31,10 +30,8 @@ export default { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const pg = getKysely() - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} - const now = new Date() const viewerId = getUserId(authToken) // AUTH @@ -58,14 +55,6 @@ export default { // mutative checkInPhase.checkInQuestion = normalizedCheckInQuestion - await r - .table('NewMeeting') - .get(meetingId) - .update({ - phases, - updatedAt: now - }) - .run() await pg .updateTable('NewMeeting') .set({phases: JSON.stringify(phases)}) diff --git a/packages/server/graphql/mutations/updatePokerScope.ts b/packages/server/graphql/mutations/updatePokerScope.ts index f104dc07282..f6d7af19549 100644 --- a/packages/server/graphql/mutations/updatePokerScope.ts +++ b/packages/server/graphql/mutations/updatePokerScope.ts @@ -2,7 +2,6 @@ import {GraphQLID, GraphQLList, GraphQLNonNull} from 'graphql' import {Insertable} from 'kysely' import {SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' import {ESTIMATE_TASK_SORT_ORDER} from '../../../client/utils/constants' -import getRethink from '../../database/rethinkDriver' import EstimateStage from '../../database/types/EstimateStage' import {TaskServiceEnum} from '../../database/types/Task' import getKysely from '../../postgres/getKysely' @@ -43,7 +42,6 @@ const updatePokerScope = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { const pg = getKysely() - const r = await getRethink() const redis = getRedis() const viewerId = getUserId(authToken) const operationId = dataLoader.share() @@ -166,15 +164,6 @@ const updatePokerScope = { }) .where('id', '=', meetingId) .execute() - await r - .table('NewMeeting') - .get(meetingId) - .update({ - facilitatorStageId: meeting.facilitatorStageId, - phases, - updatedAt: now - }) - .run() if (newDiscussions.length > 0) { await getKysely().insertInto('Discussion').values(newDiscussions).execute() } diff --git a/packages/server/graphql/mutations/updateReflectionContent.ts b/packages/server/graphql/mutations/updateReflectionContent.ts index 36b79bfb958..180a101d0a0 100644 --- a/packages/server/graphql/mutations/updateReflectionContent.ts +++ b/packages/server/graphql/mutations/updateReflectionContent.ts @@ -51,7 +51,7 @@ export default { return standardError(new Error('Category not found'), {userId: viewerId}) } const {question} = reflectPrompt - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {endedAt, phases, teamId} = meeting if (!isTeamMember(authToken, teamId)) { return standardError(new Error('Team not found'), {userId: viewerId}) diff --git a/packages/server/graphql/mutations/updateReflectionGroupTitle.ts b/packages/server/graphql/mutations/updateReflectionGroupTitle.ts index 41a3ca4978c..7470bddbbf1 100644 --- a/packages/server/graphql/mutations/updateReflectionGroupTitle.ts +++ b/packages/server/graphql/mutations/updateReflectionGroupTitle.ts @@ -47,7 +47,7 @@ export default { return {error: {message: 'Group already renamed'}} } const [meeting, viewer] = await Promise.all([ - dataLoader.get('newMeetings').load(meetingId), + dataLoader.get('newMeetings').loadNonNull(meetingId), dataLoader.get('users').loadNonNull(viewerId) ]) const {endedAt, phases, teamId} = meeting diff --git a/packages/server/graphql/mutations/updateRetroMaxVotes.ts b/packages/server/graphql/mutations/updateRetroMaxVotes.ts index 329ce6646fd..47fcf5c673a 100644 --- a/packages/server/graphql/mutations/updateRetroMaxVotes.ts +++ b/packages/server/graphql/mutations/updateRetroMaxVotes.ts @@ -138,31 +138,18 @@ const updateRetroMaxVotes = { } // RESOLUTION - await Promise.all([ - getKysely() - .with('MeetingUpdates', (qb) => - qb - .updateTable('NewMeeting') - .set({totalVotes, maxVotesPerGroup}) - .where('id', '=', meetingId) - ) - .updateTable('MeetingSettings') - .set({ - totalVotes, - maxVotesPerGroup - }) - .where('teamId', '=', teamId) - .where('meetingType', '=', 'retrospective') - .execute(), - r - .table('NewMeeting') - .get(meetingId) - .update({ - totalVotes, - maxVotesPerGroup - }) - .run() - ]) + await getKysely() + .with('MeetingUpdates', (qb) => + qb.updateTable('NewMeeting').set({totalVotes, maxVotesPerGroup}).where('id', '=', meetingId) + ) + .updateTable('MeetingSettings') + .set({ + totalVotes, + maxVotesPerGroup + }) + .where('teamId', '=', teamId) + .where('meetingType', '=', 'retrospective') + .execute() dataLoader.get('newMeetings').clear(meetingId) const data = {meetingId} publish(SubscriptionChannel.MEETING, meetingId, 'UpdateRetroMaxVotesSuccess', data, subOptions) diff --git a/packages/server/graphql/mutations/updateTemplateScope.ts b/packages/server/graphql/mutations/updateTemplateScope.ts index 41a0c48b590..41acfed50ab 100644 --- a/packages/server/graphql/mutations/updateTemplateScope.ts +++ b/packages/server/graphql/mutations/updateTemplateScope.ts @@ -1,7 +1,5 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import {RDatum} from '../../database/stricterR' import {SharingScopeEnum as ESharingScope} from '../../database/types/MeetingTemplate' import PokerTemplate from '../../database/types/PokerTemplate' import ReflectTemplate from '../../database/types/ReflectTemplate' @@ -32,7 +30,7 @@ const updateTemplateScope = { {templateId, scope: newScope}: {templateId: string; scope: SharingScopeEnumType}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { - const r = await getRethink() + const pg = getKysely() const now = new Date() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -59,16 +57,16 @@ const updateTemplateScope = { template.scope = newScope // mutate the cached record const SCOPES: ESharingScope[] = ['TEAM', 'ORGANIZATION', 'PUBLIC'] const isDownscope = SCOPES.indexOf(newScope) < SCOPES.indexOf(scope) - const shouldClone = isDownscope - ? await r - .table('NewMeeting') - .getAll(templateId, {index: 'templateId'}) - .filter((meeting: RDatum) => meeting('teamId').ne(teamId)) - .nth(0) - .default(null) - .ne(null) - .run() - : false + const usedMeeting = isDownscope + ? await pg + .selectFrom('NewMeeting') + .select('id') + .where('templateId', '=', templateId) + .where('teamId', '!=', teamId) + .limit(1) + .executeTakeFirst() + : null + const shouldClone = !!usedMeeting let clonedTemplateId: string | undefined const cloneReflectTemplate = async () => { diff --git a/packages/server/graphql/mutations/voteForPokerStory.ts b/packages/server/graphql/mutations/voteForPokerStory.ts index 79e4012c8a4..d0c26ffd2bc 100644 --- a/packages/server/graphql/mutations/voteForPokerStory.ts +++ b/packages/server/graphql/mutations/voteForPokerStory.ts @@ -1,9 +1,6 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' -import {RValue} from '../../database/stricterR' import EstimateUserScore from '../../database/types/EstimateUserScore' -import updateStage from '../../database/updateStage' import getKysely from '../../postgres/getKysely' import {NewMeetingPhase} from '../../postgres/types/NewMeetingPhase' import {getUserId, isTeamMember} from '../../utils/authorization' @@ -36,15 +33,6 @@ export const removeVoteForUserId = async (userId: string, stageId: string, meeti .where('id', '=', meetingId) .execute() }) - const updater = (estimateStage: RValue) => - estimateStage.merge({ - scores: estimateStage('scores').deleteAt( - estimateStage('scores') - .offsetsOf((score: RValue) => score('userId').eq(userId)) - .nth(0) - ) - }) - return updateStage(meetingId, stageId, 'ESTIMATE', updater) } const upsertVote = async (vote: EstimateUserScore, stageId: string, meetingId: string) => { @@ -71,26 +59,6 @@ const upsertVote = async (vote: EstimateUserScore, stageId: string, meetingId: s .where('id', '=', meetingId) .execute() }) - - const r = await getRethink() - const updater = (estimateStage: RValue) => - estimateStage.merge({ - scores: r.branch( - estimateStage('scores') - .offsetsOf((score: RValue) => score('userId').eq(vote.userId)) - .nth(0) - .default(-1) - .eq(-1), - estimateStage('scores').append(vote), - estimateStage('scores').changeAt( - estimateStage('scores') - .offsetsOf((score: RValue) => score('userId').eq(vote.userId)) - .nth(0), - vote - ) - ) - }) - return updateStage(meetingId, stageId, 'ESTIMATE', updater) } const voteForPokerStory = { diff --git a/packages/server/graphql/mutations/voteForReflectionGroup.ts b/packages/server/graphql/mutations/voteForReflectionGroup.ts index 6afe994dee6..304051a39e8 100644 --- a/packages/server/graphql/mutations/voteForReflectionGroup.ts +++ b/packages/server/graphql/mutations/voteForReflectionGroup.ts @@ -42,7 +42,7 @@ export default { }) } const {meetingId} = reflectionGroup - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'retrospective') { return {error: {message: 'Meeting type is not retrospective'}} } diff --git a/packages/server/graphql/private/mutations/generateMeetingSummary.ts b/packages/server/graphql/private/mutations/generateMeetingSummary.ts index 0d5d21ede25..04ca683f4c6 100644 --- a/packages/server/graphql/private/mutations/generateMeetingSummary.ts +++ b/packages/server/graphql/private/mutations/generateMeetingSummary.ts @@ -1,6 +1,7 @@ import yaml from 'js-yaml' -import getRethink from '../../../database/rethinkDriver' +import {sql} from 'kysely' import getKysely from '../../../postgres/getKysely' +import {selectNewMeetings} from '../../../postgres/select' import {RetrospectiveMeeting} from '../../../postgres/types/Meeting' import OpenAIServerManager from '../../../utils/OpenAIServerManager' import getPhase from '../../../utils/getPhase' @@ -11,28 +12,32 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn {teamIds, prompt}, {dataLoader} ) => { - const r = await getRethink() const pg = getKysely() - const MIN_MILLISECONDS = 60 * 1000 // 1 minute + const MIN_SECONDS = 60 const MIN_REFLECTION_COUNT = 3 const endDate = new Date() const twoYearsAgo = new Date() twoYearsAgo.setFullYear(endDate.getFullYear() - 2) - const rawMeetings = await r - .table('NewMeeting') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((row: any) => - row('meetingType') - .eq('retrospective') - .and(row('createdAt').ge(twoYearsAgo)) - .and(row('createdAt').le(endDate)) - .and(row('reflectionCount').gt(MIN_REFLECTION_COUNT)) - .and(r.table('MeetingMember').getAll(row('id'), {index: 'meetingId'}).count().gt(1)) - .and(row('endedAt').sub(row('createdAt')).gt(MIN_MILLISECONDS)) - ) - .run() + const rawMeetingsWithAnyMembers = await selectNewMeetings() + .where('teamId', 'in', teamIds) + .where('meetingType', '=', 'retrospective') + .where('createdAt', '>=', twoYearsAgo) + .where('createdAt', '<=', endDate) + .where('reflectionCount', '>', MIN_REFLECTION_COUNT) + .where(sql`EXTRACT(EPOCH FROM ("endedAt" - "createdAt")) > ${MIN_SECONDS}`) + .$narrowType() + .execute() + + const allMeetingMembers = await dataLoader + .get('meetingMembersByMeetingId') + .loadMany(rawMeetingsWithAnyMembers.map(({id}) => id)) + + const rawMeetings = rawMeetingsWithAnyMembers.filter((_, idx) => { + const meetingMembers = allMeetingMembers[idx] + return Array.isArray(meetingMembers) && meetingMembers.length > 1 + }) const getComments = async (reflectionGroupId: string) => { const IGNORE_COMMENT_USER_IDS = ['parabolAIUser'] @@ -168,17 +173,11 @@ const generateMeetingSummary: MutationResolvers['generateMeetingSummary'] = asyn const newSummary = await manager.generateSummary(yamlData, prompt) if (!newSummary) return null - const now = new Date() await getKysely() .updateTable('NewMeeting') .set({summary: newSummary}) .where('id', '=', meeting.id) .execute() - await r - .table('NewMeeting') - .get(meeting.id) - .update({summary: newSummary, updatedAt: now}) - .run() meeting.summary = newSummary return meeting.id }) diff --git a/packages/server/graphql/private/mutations/hardDeleteUser.ts b/packages/server/graphql/private/mutations/hardDeleteUser.ts index 27995849eea..b4c4903891e 100644 --- a/packages/server/graphql/private/mutations/hardDeleteUser.ts +++ b/packages/server/graphql/private/mutations/hardDeleteUser.ts @@ -16,12 +16,13 @@ const setFacilitatedUserIdOrDelete = async ( dataLoader: DataLoaderInstance ) => { const pg = getKysely() - const r = await getRethink() - const facilitatedMeetings = await r - .table('NewMeeting') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((row: RValue) => row('createdBy').eq(userIdToDelete)) - .run() + const facilitatedMeetings = await pg + .selectFrom('NewMeeting') + .select('id') + .where('teamId', 'in', teamIds) + .where('createdBy', '=', userIdToDelete) + .execute() + facilitatedMeetings.map(async (meeting) => { const {id: meetingId} = meeting const meetingMembers = await dataLoader.get('meetingMembersByMeetingId').load(meetingId) @@ -32,17 +33,9 @@ const setFacilitatedUserIdOrDelete = async ( .set({facilitatorUserId: otherMember.userId}) .where('id', '=', meetingId) .execute() - await r - .table('NewMeeting') - .get(meetingId) - .update({ - facilitatorUserId: otherMember.userId - }) - .run() } else { - await pg.deleteFrom('NewMeeting').where('id', '=', meetingId).execute() // single-person meeting must be deleted because facilitatorUserId must be non-null - await r.table('NewMeeting').get(meetingId).delete().run() + await pg.deleteFrom('NewMeeting').where('id', '=', meetingId).execute() } }) } @@ -76,11 +69,9 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( const teamIds = teamMembers.map(({teamId}) => teamId) const meetingIds = meetingMembers.map(({meetingId}) => meetingId) - const discussions = await pg - .selectFrom('Discussion') - .select('id') - .where('id', 'in', teamIds) - .execute() + const discussions = teamIds.length + ? await pg.selectFrom('Discussion').select('id').where('id', 'in', teamIds).execute() + : [] const teamDiscussionIds = discussions.map(({id}) => id) // soft delete first for side effects @@ -95,12 +86,6 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( .where('createdBy', '=', userIdToDelete) .execute() await r({ - nullifyCreatedBy: r - .table('NewMeeting') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((row: RValue) => row('createdBy').eq(userIdToDelete)) - .update({createdBy: null}) - .run(), meetingMember: r.table('MeetingMember').getAll(userIdToDelete, {index: 'userId'}).delete(), notification: r.table('Notification').getAll(userIdToDelete, {index: 'userId'}).delete(), createdTasks: r diff --git a/packages/server/graphql/private/mutations/processRecurrence.ts b/packages/server/graphql/private/mutations/processRecurrence.ts index 6d0b2099e3c..5d37cec09f0 100644 --- a/packages/server/graphql/private/mutations/processRecurrence.ts +++ b/packages/server/graphql/private/mutations/processRecurrence.ts @@ -1,14 +1,15 @@ import dayjs from 'dayjs' import tracer from 'dd-trace' +import {sql} from 'kysely' import ms from 'ms' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {DateTime, RRuleSet} from 'rrule-rust' import TeamMemberId from '../../../../client/shared/gqlIds/TeamMemberId' import {fromDateTime, toDateTime} from '../../../../client/shared/rruleUtil' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {getActiveMeetingSeries} from '../../../postgres/queries/getActiveMeetingSeries' -import {RetrospectiveMeeting, TeamPromptMeeting} from '../../../postgres/types/Meeting' +import {selectNewMeetings} from '../../../postgres/select' +import {AnyMeeting, RetrospectiveMeeting, TeamPromptMeeting} from '../../../postgres/types/Meeting' import {MeetingSeries} from '../../../postgres/types/MeetingSeries' import {analytics} from '../../../utils/analytics/analytics' import {getNextRRuleDate} from '../../../utils/getNextRRuleDate' @@ -32,7 +33,6 @@ const startRecurringMeeting = async ( subOptions: SubOptions ) => { const pg = getKysely() - const r = await getRethink() const {id: meetingSeriesId, teamId, facilitatorId, meetingType} = meetingSeries // AUTH @@ -63,7 +63,6 @@ const startRecurringMeeting = async ( .insertInto('NewMeeting') .values({...meeting, phases: JSON.stringify(meeting.phases)}) .execute() - await r.table('NewMeeting').insert(meeting).run() const data = {teamId, meetingId: meeting.id} publish(SubscriptionChannel.TEAM, teamId, 'StartTeamPromptSuccess', data, subOptions) return meeting @@ -88,7 +87,6 @@ const startRecurringMeeting = async ( }, dataLoader ) - await r.table('NewMeeting').insert(meeting).run() await pg .insertInto('NewMeeting') .values({...meeting, phases: JSON.stringify(meeting.phases)}) @@ -117,23 +115,23 @@ const processRecurrence: MutationResolvers['processRecurrence'] = async ( context ) => { const {dataLoader, socketId: mutatorId} = context - const r = await getRethink() const now = new Date() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} // RESOLUTION // Find any meetings with a scheduledEndTime before now, and close them - const meetingsToEnd = await r - .table('NewMeeting') - .between([false, r.minval], [false, now], {index: 'hasEndedScheduledEndTime'}) - .run() + const meetingsToEnd = await selectNewMeetings() + .where('scheduledEndTime', '<', sql`CURRENT_TIMESTAMP`) + .where('endedAt', 'is', null) + .$narrowType() + .execute() const res = await tracer.trace('processRecurrence.endMeetings', async () => Promise.all( meetingsToEnd.map((meeting) => { if (meeting.meetingType === 'teamPrompt') { - return safeEndTeamPrompt({meeting, now, context, r, subOptions}) + return safeEndTeamPrompt({meeting, context, subOptions}) } else if (meeting.meetingType === 'retrospective') { return safeEndRetrospective({meeting, now, context}) } else { diff --git a/packages/server/graphql/private/mutations/runScheduledJobs.ts b/packages/server/graphql/private/mutations/runScheduledJobs.ts index bc93f4f84f8..49651b3cef9 100644 --- a/packages/server/graphql/private/mutations/runScheduledJobs.ts +++ b/packages/server/graphql/private/mutations/runScheduledJobs.ts @@ -25,7 +25,7 @@ const processMeetingStageTimeLimits = async ( // if mattermost, send mattermost // if no integrated notification services, send an in-app notification const {meetingId} = job - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {teamId, facilitatorUserId} = meeting IntegrationNotifier.endTimeLimit(dataLoader, meetingId, teamId) diff --git a/packages/server/graphql/private/queries/orgActivities.ts b/packages/server/graphql/private/queries/orgActivities.ts index fcdc37ea6cd..9b61012cb60 100644 --- a/packages/server/graphql/private/queries/orgActivities.ts +++ b/packages/server/graphql/private/queries/orgActivities.ts @@ -1,6 +1,6 @@ import {sql} from 'kysely' import getRethink from '../../../database/rethinkDriver' -import {RDatum, RValue} from '../../../database/stricterR' +import {RValue} from '../../../database/stricterR' import getKysely from '../../../postgres/getKysely' import {OrgActivityRow, QueryResolvers} from '../resolverTypes' @@ -25,9 +25,9 @@ const orgActivities: QueryResolvers['orgActivities'] = async (_source, {startDat .innerJoin('OrganizationUser', 'User.id', 'OrganizationUser.userId') .innerJoin('Organization', 'OrganizationUser.orgId', 'Organization.id') .select([ - sql`date_trunc('month', "User"."createdAt")`.as('month'), + sql`date_trunc('month', "User"."createdAt")`.as('month'), 'Organization.name as orgName', - sql`COUNT(DISTINCT "User"."id")`.as('signup_count') + sql`COUNT(DISTINCT "User"."id")`.as('signup_count') ]) .where('User.createdAt', '>=', queryStartDate) .where('User.createdAt', '<', queryEndDate) @@ -40,81 +40,72 @@ const orgActivities: QueryResolvers['orgActivities'] = async (_source, {startDat join.onRef(sql`m."monthStart"`, '=', sql`us.month::timestamp`) ) .select([ - sql`m."monthStart"`.as('monthStart'), - sql`COALESCE(us."orgName", 'All Organizations')`.as('orgName'), - sql`COALESCE(us.signup_count, 0)`.as('signupCount') + sql`m."monthStart"`.as('monthStart'), + sql`COALESCE(us."orgName", 'All Organizations')`.as('orgName'), + sql`COALESCE(us.signup_count, 0)`.as('signupCount') ]) .orderBy('monthStart') const r = await getRethink() try { - const [pgResults, rethinkResults] = await Promise.all([ + const [signupsResult, rawMeetingResult] = await Promise.all([ query.execute(), - r - .table('NewMeeting') - .between( - r.epochTime(queryStartDate.getTime() / 1000), - r.epochTime(queryEndDate.getTime() / 1000), - {index: 'createdAt'} - ) - .merge((row: RValue) => ({ - yearMonth: { - year: row('createdAt').year(), - month: row('createdAt').month() - } - })) - .group((row) => row('yearMonth')) - .ungroup() - .map((group: RDatum) => ({ - yearMonth: group('group'), - meetingCount: group('reduction').count(), - participantIds: group('reduction') - .concatMap((row: RDatum) => - r.table('MeetingMember').getAll(row('id'), {index: 'meetingId'})('userId') - ) - .distinct() - })) - .map((row: RDatum) => - row.merge({ - participantCount: row('participantIds').count() - }) - ) - .without('participantIds') - .run() + pg + .selectFrom('NewMeeting') + .select(({fn, ref, val}) => [ + fn('date_trunc', [val('month'), ref('createdAt')]).as('monthStart'), + fn('array_agg', ['id']).as('meetingIds') + ]) + .where('createdAt', '>=', queryStartDate) + .where('createdAt', '<', queryEndDate) + .groupBy('monthStart') + .execute() ]) - + const meetingIds = rawMeetingResult.flatMap((row) => row.meetingIds) + const participantCounts = (await ( + r + .table('MeetingMember') + .getAll(r.args(meetingIds), {index: 'meetingId'}) + .group('meetingId') as any + ) + .count() + .ungroup() + .map((group: RValue) => ({ + meetingId: group('group'), + participantCount: group('reduction') + })) + .run()) as {meetingId: string; participantCount: number}[] // Combine PostgreSQL and RethinkDB results - const combinedResults: OrgActivityRow[] = pgResults.reduce((acc: any, pgRow: any) => { - const monthStart = new Date(pgRow.monthStart) - const monthKey = monthStart.toISOString() + const combinedResults = signupsResult.reduce( + (acc, signupRow) => { + const epochMonthStart = signupRow.monthStart.getTime() + const monthKey = signupRow.monthStart.toISOString() - if (!acc[monthKey]) { - acc[monthKey] = { - monthStart: pgRow.monthStart, - signups: [], - participantCount: 0, - meetingCount: 0 + if (!acc[monthKey]) { + acc[monthKey] = { + monthStart: signupRow.monthStart, + signups: [], + participantCount: 0, + meetingCount: 0 + } } - } - - acc[monthKey].signups.push({ - orgName: pgRow.orgName, - count: pgRow.signupCount - }) - const rethinkData = rethinkResults.find( - (r: any) => - r.yearMonth.month === monthStart.getUTCMonth() + 1 && - r.yearMonth.year === monthStart.getUTCFullYear() - ) + acc[monthKey].signups.push({ + orgName: signupRow.orgName, + count: Number(signupRow.signupCount) + }) - if (rethinkData) { - acc[monthKey].participantCount = rethinkData.participantCount - acc[monthKey].meetingCount = rethinkData.meetingCount - } - - return acc - }, {}) + const meetingData = rawMeetingResult.find((r) => r.monthStart.getTime() === epochMonthStart) + const participantCount = participantCounts + .filter((pc) => meetingData?.meetingIds.includes(pc.meetingId)) + .map((pc) => pc.participantCount) + .reduce((a, b) => a + b, 0) + acc[monthKey].participantCount = participantCount + acc[monthKey].meetingCount = meetingData?.meetingIds.length ?? 0 + return acc + }, + {} as Record + ) const rows = Object.values(combinedResults) return {rows} diff --git a/packages/server/graphql/public/mutations/addComment.ts b/packages/server/graphql/public/mutations/addComment.ts index e20405c6184..616b0118b2e 100644 --- a/packages/server/graphql/public/mutations/addComment.ts +++ b/packages/server/graphql/public/mutations/addComment.ts @@ -90,7 +90,7 @@ const addComment: MutationResolvers['addComment'] = async ( } const meetingMemberId = MeetingMemberId.join(meetingId, viewerId) const [meeting, viewerMeetingMember, viewer] = await Promise.all([ - dataLoader.get('newMeetings').load(meetingId), + dataLoader.get('newMeetings').loadNonNull(meetingId), dataLoader.get('meetingMembers').load(meetingMemberId), dataLoader.get('users').loadNonNull(viewerId) ]) diff --git a/packages/server/graphql/public/mutations/addReactjiToReactable.ts b/packages/server/graphql/public/mutations/addReactjiToReactable.ts index 2debd57a3ac..1cb2e78a67f 100644 --- a/packages/server/graphql/public/mutations/addReactjiToReactable.ts +++ b/packages/server/graphql/public/mutations/addReactjiToReactable.ts @@ -105,7 +105,7 @@ const addReactjiToReactable: MutationResolvers['addReactjiToReactable'] = async } const [meeting] = await Promise.all([ - dataLoader.get('newMeetings').load(meetingId), + dataLoader.get('newMeetings').loadNonNull(meetingId), updatePG(tableName) ]) dataLoader.clearAll(['comments', 'teamPromptResponses', 'retroReflections']) diff --git a/packages/server/graphql/public/mutations/autogroup.ts b/packages/server/graphql/public/mutations/autogroup.ts index 21db1e150a7..a711b33f6fc 100644 --- a/packages/server/graphql/public/mutations/autogroup.ts +++ b/packages/server/graphql/public/mutations/autogroup.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {analytics} from '../../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../../utils/authorization' @@ -15,7 +14,6 @@ const autogroup: MutationResolvers['autogroup'] = async ( context: GQLContext ) => { const pg = getKysely() - const r = await getRethink() const {authToken, dataLoader, socketId: mutatorId} = context const viewerId = getUserId(authToken) const operationId = dataLoader.share() @@ -72,7 +70,6 @@ const autogroup: MutationResolvers['autogroup'] = async ( ) ) }), - r.table('NewMeeting').get(meetingId).update({resetReflectionGroups}).run(), pg .updateTable('NewMeeting') .set({resetReflectionGroups: JSON.stringify(resetReflectionGroups)}) diff --git a/packages/server/graphql/public/mutations/endTeamPrompt.ts b/packages/server/graphql/public/mutations/endTeamPrompt.ts index b98a70f1261..b255bc4a4d6 100644 --- a/packages/server/graphql/public/mutations/endTeamPrompt.ts +++ b/packages/server/graphql/public/mutations/endTeamPrompt.ts @@ -1,4 +1,3 @@ -import getRethink from '../../../database/rethinkDriver' import {getUserId, isTeamMember} from '../../../utils/authorization' import standardError from '../../../utils/standardError' import safeEndTeamPrompt from '../../mutations/helpers/safeEndTeamPrompt' @@ -6,9 +5,7 @@ import {MutationResolvers} from '../resolverTypes' const endTeamPrompt: MutationResolvers['endTeamPrompt'] = async (_source, {meetingId}, context) => { const {authToken, dataLoader, socketId: mutatorId} = context - const r = await getRethink() const viewerId = getUserId(authToken) - const now = new Date() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -24,7 +21,7 @@ const endTeamPrompt: MutationResolvers['endTeamPrompt'] = async (_source, {meeti if (!isTeamMember(authToken, teamId) && authToken.rol !== 'su') { return standardError(new Error('Team not found'), {userId: viewerId}) } - return safeEndTeamPrompt({meeting, now, r, context, subOptions, viewerId}) + return safeEndTeamPrompt({meeting, context, subOptions, viewerId}) } export default endTeamPrompt diff --git a/packages/server/graphql/public/mutations/generateInsight.ts b/packages/server/graphql/public/mutations/generateInsight.ts index 903383135f0..c7ec6cbb459 100644 --- a/packages/server/graphql/public/mutations/generateInsight.ts +++ b/packages/server/graphql/public/mutations/generateInsight.ts @@ -20,7 +20,7 @@ const generateInsight: MutationResolvers['generateInsight'] = async ( } const response = useSummaries - ? await getSummaries(teamId, startDate, endDate, prompt) + ? await getSummaries(teamId, startDate, endDate, dataLoader, prompt) : await getTopics(teamId, startDate, endDate, dataLoader, prompt) if ('error' in response) { diff --git a/packages/server/graphql/public/mutations/helpers/getSummaries.ts b/packages/server/graphql/public/mutations/helpers/getSummaries.ts index 30ff97c9f9d..c27e9f7a905 100644 --- a/packages/server/graphql/public/mutations/helpers/getSummaries.ts +++ b/packages/server/graphql/public/mutations/helpers/getSummaries.ts @@ -1,5 +1,8 @@ import yaml from 'js-yaml' -import getRethink from '../../../../database/rethinkDriver' +import {sql} from 'kysely' +import {DataLoaderInstance} from '../../../../dataloader/RootDataLoader' +import getKysely from '../../../../postgres/getKysely' +import {RetrospectiveMeeting} from '../../../../postgres/types/Meeting' import OpenAIServerManager from '../../../../utils/OpenAIServerManager' import standardError from '../../../../utils/standardError' @@ -7,26 +10,32 @@ export const getSummaries = async ( teamId: string, startDate: Date, endDate: Date, + dataLoader: DataLoaderInstance, prompt?: string | null ) => { - const r = await getRethink() - const MIN_MILLISECONDS = 60 * 1000 // 1 minute + const pg = getKysely() + const MIN_SECONDS = 60 const MIN_REFLECTION_COUNT = 3 + const rawMeetingsWithAnyMembers = await pg + .selectFrom('NewMeeting') + .select(['id', 'name', 'createdAt', 'summary']) + .where('teamId', '=', teamId) + .where('summary', 'is not', null) + .where('meetingType', '=', 'retrospective') + .where('createdAt', '>=', startDate) + .where('createdAt', '<=', endDate) + .where('reflectionCount', '>=', MIN_REFLECTION_COUNT) + .where(sql`EXTRACT(EPOCH FROM ("endedAt" - "createdAt")) > ${MIN_SECONDS}`) + .$narrowType() + .execute() + const allMeetingMembers = await dataLoader + .get('meetingMembersByMeetingId') + .loadMany(rawMeetingsWithAnyMembers.map(({id}) => id)) - const rawMeetings = await r - .table('NewMeeting') - .getAll(teamId, {index: 'teamId'}) - .filter((row: any) => - row('meetingType') - .eq('retrospective') - .and(row('createdAt').ge(startDate)) - .and(row('createdAt').le(endDate)) - .and(row('reflectionCount').gt(MIN_REFLECTION_COUNT)) - .and(r.table('MeetingMember').getAll(row('id'), {index: 'meetingId'}).count().gt(1)) - .and(row('endedAt').sub(row('createdAt')).gt(MIN_MILLISECONDS)) - .and(row.hasFields('summary')) - ) - .run() + const rawMeetings = rawMeetingsWithAnyMembers.filter((_, idx) => { + const meetingMembers = allMeetingMembers[idx] + return Array.isArray(meetingMembers) && meetingMembers.length > 1 + }) if (!rawMeetings.length) { return standardError(new Error('No meetings found')) diff --git a/packages/server/graphql/public/mutations/helpers/getTopics.ts b/packages/server/graphql/public/mutations/helpers/getTopics.ts index 47782e177f9..06bd515c2b4 100644 --- a/packages/server/graphql/public/mutations/helpers/getTopics.ts +++ b/packages/server/graphql/public/mutations/helpers/getTopics.ts @@ -1,10 +1,11 @@ import yaml from 'js-yaml' -import getRethink from '../../../../database/rethinkDriver' +import {sql} from 'kysely' import getKysely from '../../../../postgres/getKysely' import OpenAIServerManager from '../../../../utils/OpenAIServerManager' import sendToSentry from '../../../../utils/sendToSentry' import standardError from '../../../../utils/standardError' import {DataLoaderWorker} from '../../../graphql' +import {RetrospectiveMeeting} from '../../resolverTypes' const getComments = async (reflectionGroupId: string, dataLoader: DataLoaderWorker) => { const pg = getKysely() @@ -109,23 +110,29 @@ export const getTopics = async ( dataLoader: DataLoaderWorker, prompt?: string | null ) => { - const r = await getRethink() + const pg = getKysely() const MIN_REFLECTION_COUNT = 3 - const MIN_MILLISECONDS = 60 * 1000 // 1 minute - const rawAnyMeetings = await r - .table('NewMeeting') - .getAll(teamId, {index: 'teamId'}) - .filter((row: any) => - row('meetingType') - .eq('retrospective') - .and(row('createdAt').ge(startDate)) - .and(row('createdAt').le(endDate)) - .and(row('reflectionCount').gt(MIN_REFLECTION_COUNT)) - .and(r.table('MeetingMember').getAll(row('id'), {index: 'meetingId'}).count().gt(1)) - .and(row('endedAt').sub(row('createdAt')).gt(MIN_MILLISECONDS)) - ) - .run() - const rawMeetings = rawAnyMeetings.filter((m) => m.meetingType === 'retrospective') + const MIN_SECONDS = 60 + const rawMeetingsWithAnyMembers = await pg + .selectFrom('NewMeeting') + .select(['id', 'name', 'createdAt', 'disableAnonymity']) + .where('teamId', '=', teamId) + .where('meetingType', '=', 'retrospective') + .where('createdAt', '>=', startDate) + .where('createdAt', '<=', endDate) + .where('reflectionCount', '>=', MIN_REFLECTION_COUNT) + .where(sql`EXTRACT(EPOCH FROM ("endedAt" - "createdAt")) > ${MIN_SECONDS}`) + .$narrowType() + .execute() + const allMeetingMembers = await dataLoader + .get('meetingMembersByMeetingId') + .loadMany(rawMeetingsWithAnyMembers.map(({id}) => id)) + + const rawMeetings = rawMeetingsWithAnyMembers.filter((_, idx) => { + const meetingMembers = allMeetingMembers[idx] + return Array.isArray(meetingMembers) && meetingMembers.length > 1 + }) + const meetings = await Promise.all( rawMeetings.map(async (meeting) => { const {id: meetingId, disableAnonymity, name: meetingName, createdAt: meetingDate} = meeting diff --git a/packages/server/graphql/public/mutations/resetReflectionGroups.ts b/packages/server/graphql/public/mutations/resetReflectionGroups.ts index 991eea4ddda..27d74bc6107 100644 --- a/packages/server/graphql/public/mutations/resetReflectionGroups.ts +++ b/packages/server/graphql/public/mutations/resetReflectionGroups.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {analytics} from '../../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../../utils/authorization' @@ -27,7 +26,6 @@ const resetReflectionGroups: MutationResolvers['resetReflectionGroups'] = async const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} const viewerId = getUserId(authToken) - const r = await getRethink() const [meeting, viewer] = await Promise.all([ dataLoader.get('newMeetings').load(meetingId), dataLoader.get('users').loadNonNull(viewerId) @@ -72,11 +70,6 @@ const resetReflectionGroups: MutationResolvers['resetReflectionGroups'] = async .flat() ) - await r - .table('NewMeeting') - .get(meetingId) - .replace(r.row.without('resetReflectionGroups') as any) - .run() await pg .updateTable('NewMeeting') .set({resetReflectionGroups: null}) diff --git a/packages/server/graphql/public/mutations/revealTeamHealthVotes.ts b/packages/server/graphql/public/mutations/revealTeamHealthVotes.ts index 86bbbb99744..d7f52b510cc 100644 --- a/packages/server/graphql/public/mutations/revealTeamHealthVotes.ts +++ b/packages/server/graphql/public/mutations/revealTeamHealthVotes.ts @@ -1,6 +1,5 @@ import {sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import updateStage from '../../../database/updateStage' import getKysely from '../../../postgres/getKysely' import {getUserId, isTeamMember} from '../../../utils/authorization' import getPhase from '../../../utils/getPhase' @@ -62,7 +61,6 @@ const revealTeamHealthVotes: MutationResolvers['revealTeamHealthVotes'] = async }) .where('id', '=', meetingId) .execute() - updateStage(meetingId, stageId, 'TEAM_HEALTH', (stage) => stage.merge({isRevealed: true})) stage.isRevealed = true const data = { diff --git a/packages/server/graphql/public/mutations/setTeamHealthVote.ts b/packages/server/graphql/public/mutations/setTeamHealthVote.ts index 68198ba59cf..689561177ac 100644 --- a/packages/server/graphql/public/mutations/setTeamHealthVote.ts +++ b/packages/server/graphql/public/mutations/setTeamHealthVote.ts @@ -1,8 +1,5 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' -import {RValue} from '../../../database/stricterR' import TeamHealthVote from '../../../database/types/TeamHealthVote' -import updateStage from '../../../database/updateStage' import getKysely from '../../../postgres/getKysely' import {NewMeetingPhase} from '../../../postgres/types/NewMeetingPhase.d' import {getUserId, isTeamMember} from '../../../utils/authorization' @@ -10,7 +7,7 @@ import getPhase from '../../../utils/getPhase' import publish from '../../../utils/publish' import {MutationResolvers} from '../resolverTypes' -const upsertVote = async (meetingId: string, stageId: string, newVote: TeamHealthVote) => { +const upsertVote = async (meetingId: string, newVote: TeamHealthVote) => { const pg = getKysely() await pg.transaction().execute(async (trx) => { const meeting = await trx @@ -38,25 +35,6 @@ const upsertVote = async (meetingId: string, stageId: string, newVote: TeamHealt .where('id', '=', meetingId) .execute() }) - const r = await getRethink() - const updater = (stage: RValue) => - stage.merge({ - votes: r.branch( - stage('votes') - .offsetsOf((oldVote: RValue) => oldVote('userId').eq(newVote.userId)) - .nth(0) - .default(-1) - .eq(-1), - stage('votes').append(newVote), - stage('votes').changeAt( - stage('votes') - .offsetsOf((oldVote: RValue) => oldVote('userId').eq(newVote.userId)) - .nth(0), - newVote - ) - ) - }) - return updateStage(meetingId, stageId, 'TEAM_HEALTH', updater) } const setTeamHealthVote: MutationResolvers['setTeamHealthVote'] = async ( @@ -96,7 +74,7 @@ const setTeamHealthVote: MutationResolvers['setTeamHealthVote'] = async ( if (vote === -1) { return {error: {message: 'Invalid label provided'}} } - await upsertVote(meetingId, stageId, {userId: viewerId, vote}) + await upsertVote(meetingId, {userId: viewerId, vote}) // update dataloader const existingVote = stage.votes.find((vote) => vote.userId === viewerId) if (existingVote) { diff --git a/packages/server/graphql/public/mutations/shareTopic.ts b/packages/server/graphql/public/mutations/shareTopic.ts index f8ab1e3f4c5..916dcf5df7a 100644 --- a/packages/server/graphql/public/mutations/shareTopic.ts +++ b/packages/server/graphql/public/mutations/shareTopic.ts @@ -11,7 +11,7 @@ const shareTopic: MutationResolvers['shareTopic'] = async ( {authToken, dataLoader} ) => { const viewerId = getUserId(authToken) - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {teamId} = meeting if (!isTeamMember(authToken, teamId)) { diff --git a/packages/server/graphql/public/mutations/startCheckIn.ts b/packages/server/graphql/public/mutations/startCheckIn.ts index 3714e6a2042..94c19a319fc 100644 --- a/packages/server/graphql/public/mutations/startCheckIn.ts +++ b/packages/server/graphql/public/mutations/startCheckIn.ts @@ -61,20 +61,12 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( phases, facilitatorUserId: viewerId }) as CheckInMeeting - await r.table('NewMeeting').insert(meeting).run() - await pg - .insertInto('NewMeeting') - .values({...meeting, phases: JSON.stringify(phases)}) - .execute() - // Disallow 2 active check-in meetings - const newActiveMeetings = await dataLoader.get('activeMeetingsByTeamId').load(teamId) - const otherActiveMeeting = newActiveMeetings.find((activeMeeting) => { - const {id} = activeMeeting - if (id === meetingId || activeMeeting.meetingType !== meetingType) return false - return true - }) - if (otherActiveMeeting) { - await r.table('NewMeeting').get(meetingId).delete().run() + try { + await pg + .insertInto('NewMeeting') + .values({...meeting, phases: JSON.stringify(phases)}) + .execute() + } catch (e) { return {error: {message: 'Meeting already started'}} } dataLoader.clearAll('newMeetings') diff --git a/packages/server/graphql/public/mutations/startRetrospective.ts b/packages/server/graphql/public/mutations/startRetrospective.ts index a27d4cb4b0d..6f82f89d7ef 100644 --- a/packages/server/graphql/public/mutations/startRetrospective.ts +++ b/packages/server/graphql/public/mutations/startRetrospective.ts @@ -26,7 +26,6 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( const pg = getKysely() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} - const DUPLICATE_THRESHOLD = 3000 // AUTH const viewerId = getUserId(authToken) if (!isTeamMember(authToken, teamId)) { @@ -74,25 +73,14 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( const meetingId = meeting.id const template = await dataLoader.get('meetingTemplates').load(selectedTemplateId) - await Promise.allSettled([ - r.table('NewMeeting').insert(meeting).run(), + const [newMeetingRes] = await Promise.allSettled([ pg .insertInto('NewMeeting') .values({...meeting, phases: JSON.stringify(meeting.phases)}) .execute(), updateMeetingTemplateLastUsedAt(selectedTemplateId, teamId) ]) - - // Disallow accidental starts (2 meetings within 2 seconds) - const newActiveMeetings = await dataLoader.get('activeMeetingsByTeamId').load(teamId) - const otherActiveMeeting = newActiveMeetings.find((activeMeeting) => { - const {createdAt, id} = activeMeeting - if (id === meetingId || activeMeeting.meetingType !== meetingType) return false - return createdAt.getTime() > Date.now() - DUPLICATE_THRESHOLD - }) - if (otherActiveMeeting) { - // trigger exists in PG to prevent this - await r.table('NewMeeting').get(meetingId).delete().run() + if (newMeetingRes.status === 'rejected') { return {error: {message: 'Meeting already started'}} } diff --git a/packages/server/graphql/public/mutations/startTeamPrompt.ts b/packages/server/graphql/public/mutations/startTeamPrompt.ts index 0f22ea40ab6..bf0b8b32612 100644 --- a/packages/server/graphql/public/mutations/startTeamPrompt.ts +++ b/packages/server/graphql/public/mutations/startTeamPrompt.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import RedisLockQueue from '../../../utils/RedisLockQueue' import {analytics} from '../../../utils/analytics/analytics' @@ -22,7 +21,6 @@ const startTeamPrompt: MutationResolvers['startTeamPrompt'] = async ( {authToken, dataLoader, socketId: mutatorId} ) => { const pg = getKysely() - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -51,8 +49,7 @@ const startTeamPrompt: MutationResolvers['startTeamPrompt'] = async ( const eventName = rrule ? name || 'Standup' : meetingName const meeting = await safeCreateTeamPrompt(meetingName, teamId, viewerId, dataLoader) - await Promise.all([ - r.table('NewMeeting').insert(meeting).run(), + const [newMeetingRes] = await Promise.allSettled([ pg .with('NewMeetingInsert', (qb) => qb.insertInto('NewMeeting').values({...meeting, phases: JSON.stringify(meeting.phases)}) @@ -62,7 +59,9 @@ const startTeamPrompt: MutationResolvers['startTeamPrompt'] = async ( .where('id', '=', teamId) .execute() ]) - + if (newMeetingRes.status === 'rejected') { + return {error: {message: 'Meeting already started'}} + } const {id: meetingId} = meeting const meetingSeries = rrule && (await startNewMeetingSeries(meeting, rrule, name)) if (meetingSeries) { diff --git a/packages/server/graphql/public/mutations/updateAgendaItem.ts b/packages/server/graphql/public/mutations/updateAgendaItem.ts index 6acc7e5efd2..bae71ce0eba 100644 --- a/packages/server/graphql/public/mutations/updateAgendaItem.ts +++ b/packages/server/graphql/public/mutations/updateAgendaItem.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' import AgendaItemsStage from '../../../database/types/AgendaItemsStage' import getKysely from '../../../postgres/getKysely' import {getUserId, isTeamMember} from '../../../utils/authorization' @@ -13,7 +12,6 @@ const updateAgendaItem: MutationResolvers['updateAgendaItem'] = async ( {updatedAgendaItem}, {authToken, dataLoader, socketId: mutatorId} ) => { - const r = await getRethink() const pg = getKysely() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -62,13 +60,6 @@ const updateAgendaItem: MutationResolvers['updateAgendaItem'] = async ( .set({phases: JSON.stringify(phases)}) .where('id', '=', meetingId) .execute() - await r - .table('NewMeeting') - .get(meetingId) - .update({ - phases - }) - .run() } const data = {agendaItemId, meetingId} publish(SubscriptionChannel.TEAM, teamId, 'UpdateAgendaItemPayload', data, subOptions) diff --git a/packages/server/graphql/public/mutations/updateMeetingPrompt.ts b/packages/server/graphql/public/mutations/updateMeetingPrompt.ts index edc59a97d9c..104c4b41024 100644 --- a/packages/server/graphql/public/mutations/updateMeetingPrompt.ts +++ b/packages/server/graphql/public/mutations/updateMeetingPrompt.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {getUserId} from '../../../utils/authorization' import publish from '../../../utils/publish' @@ -12,7 +11,6 @@ const updateMeetingPrompt: MutationResolvers['updateMeetingPrompt'] = async ( {authToken, dataLoader, socketId: mutatorId} ) => { const pg = getKysely() - const r = await getRethink() const viewerId = getUserId(authToken) const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -43,13 +41,6 @@ const updateMeetingPrompt: MutationResolvers['updateMeetingPrompt'] = async ( .set({meetingPrompt: newPrompt}) .where('id', '=', meetingId) .execute() - await r - .table('NewMeeting') - .get(meetingId) - .update({ - meetingPrompt: newPrompt - }) - .run() dataLoader.get('newMeetings').clear(meetingId) // RESOLUTION diff --git a/packages/server/graphql/public/mutations/updateMeetingTemplate.ts b/packages/server/graphql/public/mutations/updateMeetingTemplate.ts index de34566210a..ad0afa1e53f 100644 --- a/packages/server/graphql/public/mutations/updateMeetingTemplate.ts +++ b/packages/server/graphql/public/mutations/updateMeetingTemplate.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from '../../../../client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import {getUserId, isTeamMember} from '../../../utils/authorization' import getPhase from '../../../utils/getPhase' @@ -14,7 +13,6 @@ const updateMeetingTemplate: MutationResolvers['updateMeetingTemplate'] = async ) => { const pg = getKysely() const viewerId = getUserId(authToken) - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const meeting = await dataLoader.get('newMeetings').load(meetingId) @@ -40,7 +38,6 @@ const updateMeetingTemplate: MutationResolvers['updateMeetingTemplate'] = async ) } await pg.updateTable('NewMeeting').set({templateId}).where('id', '=', meetingId).execute() - await r.table('NewMeeting').get(meetingId).update({templateId}).run() meeting.templateId = templateId const data = {meetingId, templateId} diff --git a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts index 2692015636f..44c18dc709a 100644 --- a/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts +++ b/packages/server/graphql/public/mutations/updateRecurrenceSettings.ts @@ -3,7 +3,7 @@ import {sql} from 'kysely' import {toDateTime} from 'parabol-client/shared/rruleUtil' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {DateTime, RRuleSet} from 'rrule-rust' -import getRethink from '../../../database/rethinkDriver' +import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' import getKysely from '../../../postgres/getKysely' import {insertMeetingSeries as insertMeetingSeriesQuery} from '../../../postgres/queries/insertMeetingSeries' import restartMeetingSeries from '../../../postgres/queries/restartMeetingSeries' @@ -36,7 +36,6 @@ export const startNewMeetingSeries = async ( facilitatorUserId: facilitatorId } = meeting const pg = getKysely() - const r = await getRethink() if (!facilitatorId) { throw new Error('No facilitatorId') } @@ -52,14 +51,6 @@ export const startNewMeetingSeries = async ( const newMeetingSeriesId = await insertMeetingSeriesQuery(newMeetingSeriesParams) const nextMeetingStartDate = getNextRRuleDate(recurrenceRule) - await r - .table('NewMeeting') - .get(meetingId) - .update({ - meetingSeriesId: newMeetingSeriesId, - scheduledEndTime: nextMeetingStartDate - }) - .run() await pg .updateTable('NewMeeting') .set({meetingSeriesId: newMeetingSeriesId, scheduledEndTime: nextMeetingStartDate}) @@ -71,20 +62,21 @@ export const startNewMeetingSeries = async ( } } -const updateMeetingSeries = async (meetingSeries: MeetingSeries, newRecurrenceRule: RRuleSet) => { +const updateMeetingSeries = async ( + meetingSeries: MeetingSeries, + newRecurrenceRule: RRuleSet, + dataLoader: DataLoaderInstance +) => { const pg = getKysely() - const r = await getRethink() const {id: meetingSeriesId} = meetingSeries await restartMeetingSeries(meetingSeriesId, {recurrenceRule: newRecurrenceRule.toString()}) // lets close all active meetings at the time when // a new meeting will be created (tomorrow at 9 AM, same as date start of new recurrence rule) - const activeMeetings = await r - .table('NewMeeting') - .getAll(meetingSeriesId, {index: 'meetingSeriesId'}) - .filter({endedAt: null}, {default: true}) - .run() + const activeMeetings = await dataLoader + .get('activeMeetingsByMeetingSeriesId') + .load(meetingSeriesId) if (activeMeetings.length > 0) { const meetingIds = activeMeetings.map(({id}) => id) const scheduledEndTime = getNextRRuleDate(newRecurrenceRule) @@ -93,22 +85,11 @@ const updateMeetingSeries = async (meetingSeries: MeetingSeries, newRecurrenceRu .set({scheduledEndTime}) .where('id', 'in', meetingIds) .execute() - const updates = activeMeetings.map((meeting) => - r - .table('NewMeeting') - .get(meeting.id) - .update({ - scheduledEndTime - }) - .run() - ) - await Promise.all(updates) } } export const stopMeetingSeries = async (meetingSeries: MeetingSeries) => { const pg = getKysely() - const r = await getRethink() await pg .with('NewMeetingUpdateEnd', (qb) => qb @@ -121,14 +102,6 @@ export const stopMeetingSeries = async (meetingSeries: MeetingSeries) => { .set({cancelledAt: sql`CURRENT_TIMESTAMP`}) .where('id', '=', meetingSeries.id) .execute() - await r - .table('NewMeeting') - .getAll(meetingSeries.id, {index: 'meetingSeriesId'}) - .filter({endedAt: null}, {default: true}) - .update({ - scheduledEndTime: null - }) - .run() } const updateGCalRecurrenceRule = (oldRule: RRuleSet, newRule: RRuleSet | null | undefined) => { @@ -175,7 +148,7 @@ const updateRecurrenceSettings: MutationResolvers['updateRecurrenceSettings'] = await stopMeetingSeries(meetingSeries) analytics.recurrenceStopped(viewer, meetingSeries) } else { - await updateMeetingSeries(meetingSeries, rrule) + await updateMeetingSeries(meetingSeries, rrule, dataLoader) analytics.recurrenceStarted(viewer, meetingSeries) } if (gcalSeriesId) { diff --git a/packages/server/graphql/public/types/ActionMeeting.ts b/packages/server/graphql/public/types/ActionMeeting.ts index f6a7e04939f..4e2928042e7 100644 --- a/packages/server/graphql/public/types/ActionMeeting.ts +++ b/packages/server/graphql/public/types/ActionMeeting.ts @@ -29,7 +29,7 @@ const ActionMeeting: ActionMeetingResolvers = { }, tasks: async ({id: meetingId}, _args: unknown, {authToken, dataLoader}) => { const viewerId = getUserId(authToken) - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {teamId} = meeting const teamTasks = await dataLoader.get('tasksByTeamId').load(teamId) return filterTasksByMeeting(teamTasks, meetingId, viewerId) diff --git a/packages/server/graphql/public/types/ActionMeetingMember.ts b/packages/server/graphql/public/types/ActionMeetingMember.ts index 625df304179..036dbf7b2b9 100644 --- a/packages/server/graphql/public/types/ActionMeetingMember.ts +++ b/packages/server/graphql/public/types/ActionMeetingMember.ts @@ -16,7 +16,7 @@ const ActionMeetingMember: ActionMeetingMemberResolvers = { }, tasks: async ({meetingId, userId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {teamId} = meeting const teamTasks = await dataLoader.get('tasksByTeamId').load(teamId) return teamTasks.filter((task) => { diff --git a/packages/server/graphql/public/types/AddAgendaItemPayload.ts b/packages/server/graphql/public/types/AddAgendaItemPayload.ts index f82f7966890..335b81d13bc 100644 --- a/packages/server/graphql/public/types/AddAgendaItemPayload.ts +++ b/packages/server/graphql/public/types/AddAgendaItemPayload.ts @@ -16,7 +16,7 @@ const AddAgendaItemPayload: AddAgendaItemPayloadResolvers = { meeting: (source, _args, {dataLoader}) => { return 'meetingId' in source && source.meetingId - ? dataLoader.get('newMeetings').load(source.meetingId) + ? dataLoader.get('newMeetings').loadNonNull(source.meetingId) : null } } diff --git a/packages/server/graphql/public/types/AddTranscriptionBotSuccess.ts b/packages/server/graphql/public/types/AddTranscriptionBotSuccess.ts index 44839c75df1..5316798aae3 100644 --- a/packages/server/graphql/public/types/AddTranscriptionBotSuccess.ts +++ b/packages/server/graphql/public/types/AddTranscriptionBotSuccess.ts @@ -6,7 +6,7 @@ export type AddTranscriptionBotSuccessSource = { const AddTranscriptionBotSuccess: AddTranscriptionBotSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'retrospective') throw new Error('Meeting type is not retrospective') return meeting diff --git a/packages/server/graphql/public/types/AutogroupSuccess.ts b/packages/server/graphql/public/types/AutogroupSuccess.ts index 1c86eb2efce..194b64c0bf4 100644 --- a/packages/server/graphql/public/types/AutogroupSuccess.ts +++ b/packages/server/graphql/public/types/AutogroupSuccess.ts @@ -6,7 +6,7 @@ export type AutogroupSuccessSource = { const AutogroupSuccess: AutogroupSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') return meeting } diff --git a/packages/server/graphql/public/types/Company.ts b/packages/server/graphql/public/types/Company.ts index d04004afed0..2ef5d342342 100644 --- a/packages/server/graphql/public/types/Company.ts +++ b/packages/server/graphql/public/types/Company.ts @@ -1,6 +1,5 @@ -import getRethink from '../../../database/rethinkDriver' -import {RDatum, RValue} from '../../../database/stricterR' import AuthToken from '../../../database/types/AuthToken' +import getKysely from '../../../postgres/getKysely' import {TeamMember} from '../../../postgres/types' import {getUserId} from '../../../utils/authorization' import errorFilter from '../../errorFilter' @@ -120,89 +119,77 @@ const Company: CompanyResolvers = { return orgsIdsWithSufficientTeamMembers.length }, lastMetAt: async ({id: domain}, _args, {authToken, dataLoader}) => { - const r = await getRethink() + const pg = getKysely() const organizations = await getSuggestedTierOrganizations(domain, authToken, dataLoader) const orgIds = organizations.map(({id}) => id) const teams = (await dataLoader.get('teamsByOrgIds').loadMany(orgIds)).filter(isValid).flat() const teamIds = teams.map(({id}) => id) - if (teamIds.length === 0) return 0 - const lastMetAt = await r - .table('NewMeeting') - .getAll(r.args(teamIds), {index: 'teamId'}) - .max('createdAt' as any)('createdAt') - .default(null) - .run() - return lastMetAt + if (teamIds.length === 0) return null + const lastMetAt = await pg + .selectFrom('NewMeeting') + .select('createdAt') + .where('teamId', 'in', teamIds) + .orderBy('createdAt desc') + .limit(1) + .executeTakeFirst() + return lastMetAt?.createdAt ?? null }, meetingCount: async ({id: domain}, {after}, {authToken, dataLoader}) => { // number of meetings created by teams on organizations assigned to the domain - const r = await getRethink() + const pg = getKysely() const organizations = await getSuggestedTierOrganizations(domain, authToken, dataLoader) const orgIds = organizations.map(({id}) => id) const teams = (await dataLoader.get('teamsByOrgIds').loadMany(orgIds)).filter(isValid).flat() const teamIds = teams.map(({id}) => id) if (teamIds.length === 0) return 0 - const filterFn = after ? (meeting: any) => meeting('createdAt').ge(after) : () => true - return r - .table('NewMeeting') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter(filterFn) - .count() - .default(0) - .run() + const res = await pg + .selectFrom('NewMeeting') + .select(({fn}) => fn.count('id').as('count')) + .where('teamId', 'in', teamIds) + .$if(!!after, (qb) => qb.where('createdAt', '>=', after!)) + .executeTakeFirstOrThrow() + return res.count ? Number(res.count) : 0 }, monthlyTeamStreakMax: async ({id: domain}, _args, {authToken, dataLoader}) => { - const r = await getRethink() const organizations = await getSuggestedTierOrganizations(domain, authToken, dataLoader) const orgIds = organizations.map(({id}) => id) const teams = (await dataLoader.get('teamsByOrgIds').loadMany(orgIds)).filter(isValid).flat() const teamIds = teams.map(({id}) => id) if (teamIds.length === 0) return 0 - return ( - r - .table('NewMeeting') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((row: RDatum) => row('endedAt').default(null).ne(null)) - // number of months since unix epoch - .merge((row: RValue) => ({ - epochMonth: row('endedAt').month().add(row('endedAt').year().mul(12)) - })) - .group((row) => [row('teamId'), row('epochMonth')]) - .count() - .ungroup() - .map((row) => ({ - teamId: row('group')(0), - epochMonth: row('group')(1) - })) - .group('teamId')('epochMonth') - .ungroup() - .map((row) => ({ - teamId: row('group'), - epochMonth: row('reduction'), - // epochMonth shifted 1 index position - shift: row('reduction') - .deleteAt(0) - .map((z) => z.add(-1)) - })) - .merge((row: RValue) => ({ - // 1 if there are 2 consecutive epochMonths next to each other, else 0 - teamStreak: r - .map(row('shift'), row('epochMonth'), (shift, epochMonth) => - r.branch(shift.eq(epochMonth), '1', '0') - ) - .reduce((left, right) => left.add(right).default('')) - .default('') - // get an array of all the groupings of 1 - .split('0') - .map((val) => val.count()) - .max() - .add(1) - })) - .max('teamStreak')('teamStreak') - .run() - ) + const completedMeetingsRes = await dataLoader.get('completedMeetingsByTeamId').loadMany(teamIds) + const endTimes = completedMeetingsRes + .filter(isValid) + .flat() + .map(({endedAt}) => endedAt!) + .sort((a, b) => (a.getTime() < b.getTime() ? -1 : 1)) + + let longestStreak = 1 + let currentStreak = 1 + + // Step 2: Traverse the sorted array and count streaks of consecutive months + for (let i = 1; i < endTimes.length; i++) { + const prevDate = endTimes[i - 1]! + const currDate = endTimes[i]! + + // Calculate year and month differences + const yearDiff = currDate.getFullYear() - prevDate.getFullYear() + const monthDiff = currDate.getMonth() - prevDate.getMonth() + + // Step 3: Check if dates are consecutive months + if ((yearDiff === 0 && monthDiff === 1) || (yearDiff === 1 && monthDiff === -11)) { + currentStreak++ + } else { + // Reset streak if not consecutive + longestStreak = Math.max(longestStreak, currentStreak) + currentStreak = 1 + } + } + + // Step 4: Ensure the last streak is accounted for + longestStreak = Math.max(longestStreak, currentStreak) + return longestStreak }, organizations: async ({id: domain}, _args, {authToken, dataLoader}) => { diff --git a/packages/server/graphql/public/types/Discussion.ts b/packages/server/graphql/public/types/Discussion.ts index bf3f5908851..029cc285455 100644 --- a/packages/server/graphql/public/types/Discussion.ts +++ b/packages/server/graphql/public/types/Discussion.ts @@ -17,7 +17,7 @@ const Discussion: DiscussionResolvers = { }, stage: async ({discussionTopicId, discussionTopicType, meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {phases, teamId} = meeting switch (discussionTopicType) { case 'agendaItem': { diff --git a/packages/server/graphql/public/types/EndTeamPromptSuccess.ts b/packages/server/graphql/public/types/EndTeamPromptSuccess.ts index 2452403289d..afc3b6905f3 100644 --- a/packages/server/graphql/public/types/EndTeamPromptSuccess.ts +++ b/packages/server/graphql/public/types/EndTeamPromptSuccess.ts @@ -8,7 +8,7 @@ export type EndTeamPromptSuccessSource = { const EndTeamPromptSuccess: EndTeamPromptSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'teamPrompt') throw new Error('Meeting is not a team prompt') return meeting }, diff --git a/packages/server/graphql/public/types/EstimateStage.ts b/packages/server/graphql/public/types/EstimateStage.ts index 9e949ac0de7..dbb12ab652d 100644 --- a/packages/server/graphql/public/types/EstimateStage.ts +++ b/packages/server/graphql/public/types/EstimateStage.ts @@ -21,7 +21,7 @@ const EstimateStage: EstimateStageResolvers = { if (!integration) return NULL_FIELD const {service} = integration const getDimensionName = async (meetingId: string) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'poker') throw new Error('Meeting is not a poker meeting') const {templateRefId} = meeting const templateRef = await dataLoader.get('templateRefs').loadNonNull(templateRefId) @@ -175,7 +175,7 @@ const EstimateStage: EstimateStageResolvers = { }, dimensionRef: async ({meetingId, dimensionRefIdx}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'poker') return null const {templateRefId} = meeting const templateRef = await dataLoader.get('templateRefs').loadNonNull(templateRefId) @@ -191,7 +191,7 @@ const EstimateStage: EstimateStageResolvers = { finalScore: async ({taskId, meetingId, dimensionRefIdx}, _args, {dataLoader}) => { const [meeting, estimates] = await Promise.all([ - dataLoader.get('newMeetings').load(meetingId), + dataLoader.get('newMeetings').loadNonNull(meetingId), dataLoader.get('meetingTaskEstimates').load({taskId, meetingId}) ]) if (meeting.meetingType !== 'poker') return null diff --git a/packages/server/graphql/public/types/GenerateGroupsSuccess.ts b/packages/server/graphql/public/types/GenerateGroupsSuccess.ts index a15db891039..20e5d9ef97e 100644 --- a/packages/server/graphql/public/types/GenerateGroupsSuccess.ts +++ b/packages/server/graphql/public/types/GenerateGroupsSuccess.ts @@ -6,7 +6,7 @@ export type GenerateGroupsSuccessSource = { const GenerateGroupsSuccess: GenerateGroupsSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'retrospective') throw new Error('Meeting type is not retrospective') return meeting diff --git a/packages/server/graphql/public/types/MeetingSeries.ts b/packages/server/graphql/public/types/MeetingSeries.ts index eaf99efdb83..bf66c401b52 100644 --- a/packages/server/graphql/public/types/MeetingSeries.ts +++ b/packages/server/graphql/public/types/MeetingSeries.ts @@ -1,5 +1,6 @@ import MeetingSeriesId from 'parabol-client/shared/gqlIds/MeetingSeriesId' -import getRethink from '../../../database/rethinkDriver' +import {selectNewMeetings} from '../../../postgres/select' +import {AnyMeeting} from '../../../postgres/types/Meeting' import {MeetingSeriesResolvers} from '../resolverTypes' const MeetingSeries: MeetingSeriesResolvers = { @@ -11,15 +12,13 @@ const MeetingSeries: MeetingSeriesResolvers = { return res || [] }, mostRecentMeeting: async ({id: meetingSeriesId}, _args, _context) => { - const r = await getRethink() - const meetings = await r - .table('NewMeeting') - .getAll(meetingSeriesId, {index: 'meetingSeriesId'}) - // Sort order: active meetings first, then sorted by when created. - .orderBy((doc) => (doc('endedAt') ? 0 : 1), r.desc('createdAt')) + const meeting = await selectNewMeetings() + .where('meetingSeriesId', '=', meetingSeriesId) + .orderBy(['endedAt desc', 'createdAt desc']) .limit(1) - .run() - return meetings[0]! + .$narrowType() + .executeTakeFirstOrThrow() + return meeting } } diff --git a/packages/server/graphql/public/types/NewMeetingStage.ts b/packages/server/graphql/public/types/NewMeetingStage.ts index f94256304a7..9a32aba3862 100644 --- a/packages/server/graphql/public/types/NewMeetingStage.ts +++ b/packages/server/graphql/public/types/NewMeetingStage.ts @@ -10,10 +10,11 @@ export interface NewMeetingStageSource extends GenericMeetingStage { } const NewMeetingStage: NewMeetingStageResolvers = { - meeting: ({meetingId}, _args, {dataLoader}) => dataLoader.get('newMeetings').load(meetingId), + meeting: ({meetingId}, _args, {dataLoader}) => + dataLoader.get('newMeetings').loadNonNull(meetingId), phase: async ({meetingId, phaseType, teamId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {phases} = meeting const phase = phases.find((phase) => phase.phaseType === phaseType)! return {...phase, meetingId, teamId} @@ -27,7 +28,7 @@ const NewMeetingStage: NewMeetingStageResolvers = { readyCount: async ({meetingId, readyToAdvance}, _args, {dataLoader}, ref) => { if (!readyToAdvance) return 0 if (!meetingId) Logger.log('no meetingid', ref) - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {facilitatorUserId} = meeting return readyToAdvance.filter((userId: string) => userId !== facilitatorUserId).length }, diff --git a/packages/server/graphql/public/types/NotificationMeetingStageTimeLimitEnd.ts b/packages/server/graphql/public/types/NotificationMeetingStageTimeLimitEnd.ts index cb903179e48..4a5b5f46b07 100644 --- a/packages/server/graphql/public/types/NotificationMeetingStageTimeLimitEnd.ts +++ b/packages/server/graphql/public/types/NotificationMeetingStageTimeLimitEnd.ts @@ -3,7 +3,7 @@ import {NotificationMeetingStageTimeLimitEndResolvers} from '../resolverTypes' const NotificationMeetingStageTimeLimitEnd: NotificationMeetingStageTimeLimitEndResolvers = { __isTypeOf: ({type}) => type === 'MEETING_STAGE_TIME_LIMIT_END', meeting: async ({meetingId}, _args, {dataLoader}) => { - return dataLoader.get('newMeetings').load(meetingId) + return dataLoader.get('newMeetings').loadNonNull(meetingId) } } diff --git a/packages/server/graphql/public/types/NotifyDiscussionMentioned.ts b/packages/server/graphql/public/types/NotifyDiscussionMentioned.ts index d87b5bfefda..376f0a1482d 100644 --- a/packages/server/graphql/public/types/NotifyDiscussionMentioned.ts +++ b/packages/server/graphql/public/types/NotifyDiscussionMentioned.ts @@ -3,7 +3,7 @@ import {NotifyDiscussionMentionedResolvers} from '../resolverTypes' const NotifyDiscussionMentioned: NotifyDiscussionMentionedResolvers = { __isTypeOf: ({type}) => type === 'DISCUSSION_MENTIONED', meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) return meeting }, author: async ({authorId, commentId}, _args: unknown, {dataLoader}) => { diff --git a/packages/server/graphql/public/types/NotifyResponseMentioned.ts b/packages/server/graphql/public/types/NotifyResponseMentioned.ts index a9bc5d31501..06a3a6cd992 100644 --- a/packages/server/graphql/public/types/NotifyResponseMentioned.ts +++ b/packages/server/graphql/public/types/NotifyResponseMentioned.ts @@ -4,7 +4,7 @@ import {NotifyResponseMentionedResolvers} from '../resolverTypes' const NotifyResponseMentioned: NotifyResponseMentionedResolvers = { __isTypeOf: ({type}) => type === 'RESPONSE_MENTIONED', meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'teamPrompt') throw new Error('Meeting is not a team prompt') return meeting }, diff --git a/packages/server/graphql/public/types/NotifyResponseReplied.ts b/packages/server/graphql/public/types/NotifyResponseReplied.ts index 87ff6af7198..4a09395067f 100644 --- a/packages/server/graphql/public/types/NotifyResponseReplied.ts +++ b/packages/server/graphql/public/types/NotifyResponseReplied.ts @@ -4,7 +4,7 @@ import {NotifyResponseRepliedResolvers} from '../resolverTypes' const NotifyResponseReplied: NotifyResponseRepliedResolvers = { __isTypeOf: ({type}) => type === 'RESPONSE_REPLIED', meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'teamPrompt') throw new Error('Meeting is not a team prompt') return meeting }, diff --git a/packages/server/graphql/public/types/ReflectPhase.ts b/packages/server/graphql/public/types/ReflectPhase.ts index e11232d3cc6..7f0e93a7f96 100644 --- a/packages/server/graphql/public/types/ReflectPhase.ts +++ b/packages/server/graphql/public/types/ReflectPhase.ts @@ -8,8 +8,8 @@ const ReflectPhase: ReflectPhaseResolvers = { }, reflectPrompts: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) - if (!('templateId' in meeting)) return [] + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) + if (meeting.meetingType !== 'retrospective') return [] const prompts = await dataLoader.get('reflectPromptsByTemplateId').load(meeting.templateId) // only show prompts that were created before the meeting and // either have not been removed or they were removed after the meeting was created diff --git a/packages/server/graphql/public/types/RemoveAgendaItemPayload.ts b/packages/server/graphql/public/types/RemoveAgendaItemPayload.ts index 3527f938e25..6807092f4f3 100644 --- a/packages/server/graphql/public/types/RemoveAgendaItemPayload.ts +++ b/packages/server/graphql/public/types/RemoveAgendaItemPayload.ts @@ -16,7 +16,7 @@ const RemoveAgendaItemPayload: RemoveAgendaItemPayloadResolvers = { meeting: (source, _args, {dataLoader}) => { return 'meetingId' in source && source.meetingId - ? dataLoader.get('newMeetings').load(source.meetingId) + ? dataLoader.get('newMeetings').loadNonNull(source.meetingId) : null } } diff --git a/packages/server/graphql/public/types/ResetReflectionGroupsSuccess.ts b/packages/server/graphql/public/types/ResetReflectionGroupsSuccess.ts index be7d98e9b8c..8429501acf6 100644 --- a/packages/server/graphql/public/types/ResetReflectionGroupsSuccess.ts +++ b/packages/server/graphql/public/types/ResetReflectionGroupsSuccess.ts @@ -6,7 +6,7 @@ export type ResetReflectionGroupsSuccessSource = { const ResetReflectionGroupsSuccess: ResetReflectionGroupsSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') return meeting } diff --git a/packages/server/graphql/public/types/RetroDiscussStage.ts b/packages/server/graphql/public/types/RetroDiscussStage.ts index 3d4aa611c20..f0cbf747b4e 100644 --- a/packages/server/graphql/public/types/RetroDiscussStage.ts +++ b/packages/server/graphql/public/types/RetroDiscussStage.ts @@ -26,9 +26,9 @@ const RetroDiscussStage: RetroDiscussStageResolvers = { reflectionGroup: async ({reflectionGroupId, meetingId}, _args, {dataLoader}) => { if (!reflectionGroupId) { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (!('templateId' in meeting)) throw new Error('Meeting has no template') - const prompts = await dataLoader.get('reflectPromptsByTemplateId').load(meeting.templateId) + const prompts = await dataLoader.get('reflectPromptsByTemplateId').load(meeting.templateId!) return new ReflectionGroup({ id: `${meetingId}:dummyGroup`, meetingId, diff --git a/packages/server/graphql/public/types/RetroReflection.ts b/packages/server/graphql/public/types/RetroReflection.ts index 1443aba8d35..a1697b86259 100644 --- a/packages/server/graphql/public/types/RetroReflection.ts +++ b/packages/server/graphql/public/types/RetroReflection.ts @@ -4,7 +4,7 @@ import {RetroReflectionResolvers} from '../resolverTypes' const RetroReflection: RetroReflectionResolvers = { creatorId: async ({creatorId, meetingId}, _args, {authToken, dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {meetingType} = meeting if (!isSuperUser(authToken) && (meetingType !== 'retrospective' || !meeting.disableAnonymity)) { return null @@ -13,7 +13,7 @@ const RetroReflection: RetroReflectionResolvers = { }, creator: async ({creatorId, meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {meetingType} = meeting // let's not allow super users to grap this in case the UI does not check `disableAnonymity` in which case @@ -33,7 +33,7 @@ const RetroReflection: RetroReflectionResolvers = { }, meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') return meeting }, @@ -52,7 +52,7 @@ const RetroReflection: RetroReflectionResolvers = { }, team: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) return dataLoader.get('teams').loadNonNull(meeting.teamId) } } diff --git a/packages/server/graphql/public/types/RetroReflectionGroup.ts b/packages/server/graphql/public/types/RetroReflectionGroup.ts index c5435b3a00e..8a262b491d5 100644 --- a/packages/server/graphql/public/types/RetroReflectionGroup.ts +++ b/packages/server/graphql/public/types/RetroReflectionGroup.ts @@ -7,7 +7,7 @@ export interface RetroReflectionGroupSource extends Selectable { - const retroMeeting = await dataLoader.get('newMeetings').load(meetingId) + const retroMeeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (retroMeeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') return retroMeeting }, @@ -24,7 +24,7 @@ const RetroReflectionGroup: RetroReflectionGroupResolvers = { return filteredReflections }, team: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) return dataLoader.get('teams').loadNonNull(meeting.teamId) }, titleIsUserDefined: ({title, smartTitle}) => { diff --git a/packages/server/graphql/public/types/RetrospectiveMeeting.ts b/packages/server/graphql/public/types/RetrospectiveMeeting.ts index 1501e3805e7..6b1fd9e1fd5 100644 --- a/packages/server/graphql/public/types/RetrospectiveMeeting.ts +++ b/packages/server/graphql/public/types/RetrospectiveMeeting.ts @@ -30,7 +30,7 @@ const RetrospectiveMeeting: RetrospectiveMeetingResolvers = { reflectionGroups.sort((a, b) => (a.voterIds.length < b.voterIds.length ? 1 : -1)) return reflectionGroups } else if (sortBy === 'stageOrder') { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {phases} = meeting const discussPhase = getPhase(phases, 'discuss') if (!discussPhase) return reflectionGroups @@ -52,7 +52,7 @@ const RetrospectiveMeeting: RetrospectiveMeetingResolvers = { taskCount: ({taskCount}) => taskCount || 0, tasks: async ({id: meetingId}, _args: unknown, {authToken, dataLoader}) => { const viewerId = getUserId(authToken) - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {teamId} = meeting const teamTasks = await dataLoader.get('tasksByTeamId').load(teamId) return filterTasksByMeeting(teamTasks, meetingId, viewerId) diff --git a/packages/server/graphql/public/types/RetrospectiveMeetingMember.ts b/packages/server/graphql/public/types/RetrospectiveMeetingMember.ts index 06f8acb57c0..0d3c0b433df 100644 --- a/packages/server/graphql/public/types/RetrospectiveMeetingMember.ts +++ b/packages/server/graphql/public/types/RetrospectiveMeetingMember.ts @@ -4,7 +4,7 @@ import {RetrospectiveMeetingMemberResolvers} from '../resolverTypes' const RetrospectiveMeetingMember: RetrospectiveMeetingMemberResolvers = { __isTypeOf: ({meetingType}) => meetingType === 'retrospective', tasks: async ({meetingId, userId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {teamId} = meeting const teamTasks = await dataLoader.get('tasksByTeamId').load(teamId) return teamTasks.filter((task) => { diff --git a/packages/server/graphql/public/types/ShareTopicSuccess.ts b/packages/server/graphql/public/types/ShareTopicSuccess.ts index 65d59fb5d12..2824308938e 100644 --- a/packages/server/graphql/public/types/ShareTopicSuccess.ts +++ b/packages/server/graphql/public/types/ShareTopicSuccess.ts @@ -6,7 +6,7 @@ export type ShareTopicSuccessSource = { const ShareTopicSuccess: ShareTopicSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - return dataLoader.get('newMeetings').load(meetingId) + return dataLoader.get('newMeetings').loadNonNull(meetingId) } } diff --git a/packages/server/graphql/public/types/StartCheckInSuccess.ts b/packages/server/graphql/public/types/StartCheckInSuccess.ts index 7b44a4004ce..fe82e136fcc 100644 --- a/packages/server/graphql/public/types/StartCheckInSuccess.ts +++ b/packages/server/graphql/public/types/StartCheckInSuccess.ts @@ -7,7 +7,7 @@ export type StartCheckInSuccessSource = { const StartCheckInSuccess: StartCheckInSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'action') throw new Error('Not a check-in meeting') return meeting }, diff --git a/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts b/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts index 261edb42fec..bce6cc25d44 100644 --- a/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts +++ b/packages/server/graphql/public/types/StartRetrospectiveSuccess.ts @@ -8,7 +8,7 @@ export type StartRetrospectiveSuccessSource = { const StartRetrospectiveSuccess: StartRetrospectiveSuccessResolvers = { meeting: async ({meetingId}, _args: unknown, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'retrospective') throw new Error('Not a retrospective meeting') return meeting }, diff --git a/packages/server/graphql/public/types/StartTeamPromptSuccess.ts b/packages/server/graphql/public/types/StartTeamPromptSuccess.ts index 017c7b63180..dcc2b532758 100644 --- a/packages/server/graphql/public/types/StartTeamPromptSuccess.ts +++ b/packages/server/graphql/public/types/StartTeamPromptSuccess.ts @@ -7,7 +7,7 @@ export type StartTeamPromptSuccessSource = { const StartTeamPromptSuccess: StartTeamPromptSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'teamPrompt') throw new Error('Not a team prompt meeting') return meeting }, diff --git a/packages/server/graphql/public/types/TeamPromptMeeting.ts b/packages/server/graphql/public/types/TeamPromptMeeting.ts index 870dd09843b..d4c013869a8 100644 --- a/packages/server/graphql/public/types/TeamPromptMeeting.ts +++ b/packages/server/graphql/public/types/TeamPromptMeeting.ts @@ -1,6 +1,6 @@ import getRethink from '../../../database/rethinkDriver' -import {RValue} from '../../../database/stricterR' import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {selectNewMeetings} from '../../../postgres/select' import {TeamPromptMeeting as TeamPromptMeetingSource} from '../../../postgres/types/Meeting' import {getUserId} from '../../../utils/authorization' import filterTasksByMeeting from '../../../utils/filterTasksByMeeting' @@ -18,17 +18,15 @@ const TeamPromptMeeting: TeamPromptMeetingResolvers = { return null } - const r = await getRethink() - const meetings = await r - .table('NewMeeting') - .getAll(meetingSeriesId, {index: 'meetingSeriesId'}) - .filter({meetingType: 'teamPrompt'}) - .filter((row: RValue) => row('createdAt').lt(createdAt)) - .orderBy(r.desc('createdAt')) + const meeting = await selectNewMeetings() + .where('meetingSeriesId', '=', meetingSeriesId) + .where('meetingType', '=', 'teamPrompt') + .where('createdAt', '<', createdAt) + .orderBy('createdAt desc') .limit(1) - .run() - - return meetings[0] as TeamPromptMeetingSource + .$narrowType() + .executeTakeFirst() + return meeting || null }, nextMeeting: async ({meetingSeriesId, createdAt}, _args, {dataLoader}) => { if (!meetingSeriesId) return null @@ -37,22 +35,19 @@ const TeamPromptMeeting: TeamPromptMeetingResolvers = { if (!series || series.cancelledAt) { return null } - - const r = await getRethink() - const meetings = await r - .table('NewMeeting') - .getAll(meetingSeriesId, {index: 'meetingSeriesId'}) - .filter({meetingType: 'teamPrompt'}) - .filter((doc: RValue) => doc('createdAt').gt(createdAt)) - .orderBy(r.asc('createdAt')) + const meeting = await selectNewMeetings() + .where('meetingSeriesId', '=', meetingSeriesId) + .where('meetingType', '=', 'teamPrompt') + .where('createdAt', '>', createdAt) + .orderBy('createdAt asc') .limit(1) - .run() - - return meetings[0] as TeamPromptMeetingSource + .$narrowType() + .executeTakeFirst() + return meeting || null }, tasks: async ({id: meetingId}, _args: unknown, {authToken, dataLoader}) => { const viewerId = getUserId(authToken) - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {teamId} = meeting const teamTasks = await dataLoader.get('tasksByTeamId').load(teamId) return filterTasksByMeeting(teamTasks, meetingId, viewerId) @@ -73,7 +68,7 @@ const TeamPromptMeeting: TeamPromptMeetingResolvers = { }, taskCount: async ({id: meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'teamPrompt') { return 0 } @@ -91,7 +86,7 @@ const TeamPromptMeeting: TeamPromptMeetingResolvers = { }, commentCount: async ({id: meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'teamPrompt') { return 0 } diff --git a/packages/server/graphql/public/types/TimelineEventTeamPromptComplete.ts b/packages/server/graphql/public/types/TimelineEventTeamPromptComplete.ts index 75505524099..bea3273bc71 100644 --- a/packages/server/graphql/public/types/TimelineEventTeamPromptComplete.ts +++ b/packages/server/graphql/public/types/TimelineEventTeamPromptComplete.ts @@ -11,7 +11,7 @@ const TimelineEventTeamPromptComplete: TimelineEventTeamPromptCompleteResolvers ...timelineEventInterfaceResolvers(), __isTypeOf: ({type}) => type === 'TEAM_PROMPT_COMPLETE', meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'teamPrompt') throw new Error('Invalid meetingId') return meeting }, diff --git a/packages/server/graphql/public/types/UpdateAgendaItemPayload.ts b/packages/server/graphql/public/types/UpdateAgendaItemPayload.ts index f267c8be980..129eb1b4b28 100644 --- a/packages/server/graphql/public/types/UpdateAgendaItemPayload.ts +++ b/packages/server/graphql/public/types/UpdateAgendaItemPayload.ts @@ -16,7 +16,7 @@ const UpdateAgendaItemPayload: UpdateAgendaItemPayloadResolvers = { meeting: (source, _args, {dataLoader}) => { return 'meetingId' in source && source.meetingId - ? dataLoader.get('newMeetings').load(source.meetingId) + ? dataLoader.get('newMeetings').loadNonNull(source.meetingId) : null } } diff --git a/packages/server/graphql/public/types/UpdateDimensionFieldSuccess.ts b/packages/server/graphql/public/types/UpdateDimensionFieldSuccess.ts index a6e28d189a1..dc6ac18e1b9 100644 --- a/packages/server/graphql/public/types/UpdateDimensionFieldSuccess.ts +++ b/packages/server/graphql/public/types/UpdateDimensionFieldSuccess.ts @@ -8,7 +8,7 @@ export type UpdateDimensionFieldSuccessSource = { const UpdateDimensionFieldSuccess: UpdateDimensionFieldSuccessResolvers = { team: ({teamId}, _args, {dataLoader}) => dataLoader.get('teams').loadNonNull(teamId), meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'poker') throw new Error('Not a poker meeting') return meeting } diff --git a/packages/server/graphql/public/types/UpdateGitLabDimensionFieldSuccess.ts b/packages/server/graphql/public/types/UpdateGitLabDimensionFieldSuccess.ts index 38b2dd60b61..b2fd41603da 100644 --- a/packages/server/graphql/public/types/UpdateGitLabDimensionFieldSuccess.ts +++ b/packages/server/graphql/public/types/UpdateGitLabDimensionFieldSuccess.ts @@ -10,7 +10,7 @@ const UpdateGitLabDimensionFieldSuccess: UpdateGitLabDimensionFieldSuccessResolv return await dataLoader.get('teams').loadNonNull(teamId) }, meeting: async ({meetingId}, _args, {dataLoader}) => { - return dataLoader.get('newMeetings').load(meetingId) + return dataLoader.get('newMeetings').loadNonNull(meetingId) } } diff --git a/packages/server/graphql/public/types/UpdateMeetingPromptSuccess.ts b/packages/server/graphql/public/types/UpdateMeetingPromptSuccess.ts index 0873d892c7d..7fd58d5c1c7 100644 --- a/packages/server/graphql/public/types/UpdateMeetingPromptSuccess.ts +++ b/packages/server/graphql/public/types/UpdateMeetingPromptSuccess.ts @@ -7,7 +7,7 @@ export type UpdateMeetingPromptSuccessSource = { const UpdateMeetingPromptSuccess: UpdateMeetingPromptSuccessResolvers = { meeting: async (source, _args, {dataLoader}) => { const {meetingId} = source - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'teamPrompt') throw new Error('Not a team prompt meeting') return meeting } diff --git a/packages/server/graphql/public/types/UpdateMeetingTemplateSuccess.ts b/packages/server/graphql/public/types/UpdateMeetingTemplateSuccess.ts index 33a52658893..6105f1fc975 100644 --- a/packages/server/graphql/public/types/UpdateMeetingTemplateSuccess.ts +++ b/packages/server/graphql/public/types/UpdateMeetingTemplateSuccess.ts @@ -6,7 +6,7 @@ export type UpdateMeetingTemplateSuccessSource = { const UpdateMeetingTemplateSuccess: UpdateMeetingTemplateSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) return meeting } } diff --git a/packages/server/graphql/public/types/UpdateRecurrenceSettingsSuccess.ts b/packages/server/graphql/public/types/UpdateRecurrenceSettingsSuccess.ts index d9e4f566501..bc17c47df51 100644 --- a/packages/server/graphql/public/types/UpdateRecurrenceSettingsSuccess.ts +++ b/packages/server/graphql/public/types/UpdateRecurrenceSettingsSuccess.ts @@ -6,7 +6,7 @@ export type UpdateRecurrenceSettingsSuccessSource = { const UpdateRecurrenceSettingsSuccess: UpdateRecurrenceSettingsSuccessResolvers = { meeting: async ({meetingId}, _args, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'teamPrompt') throw new Error('Not a team prompt') return meeting } diff --git a/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts b/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts index d4a3042d4ee..7169e237801 100644 --- a/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts +++ b/packages/server/graphql/public/types/UpsertTeamPromptResponseSuccess.ts @@ -12,7 +12,7 @@ const UpsertTeamPromptResponseSuccess: UpsertTeamPromptResponseSuccessResolvers }, meeting: async (source, _args, {dataLoader}) => { const {meetingId} = source - return dataLoader.get('newMeetings').load(meetingId) + return dataLoader.get('newMeetings').loadNonNull(meetingId) } } diff --git a/packages/server/graphql/public/types/helpers/getActiveTeamCountByTeamIds.ts b/packages/server/graphql/public/types/helpers/getActiveTeamCountByTeamIds.ts index 0108e303221..72a04e06aa0 100644 --- a/packages/server/graphql/public/types/helpers/getActiveTeamCountByTeamIds.ts +++ b/packages/server/graphql/public/types/helpers/getActiveTeamCountByTeamIds.ts @@ -1,6 +1,6 @@ import {Threshold} from '~/types/constEnums' import getRethink from '../../../../database/rethinkDriver' -import {RDatum, RValue} from '../../../../database/stricterR' +import getKysely from '../../../../postgres/getKysely' // Uncomment for easier testing //import { ThresholdTest as Threshold } from "~/types/constEnums"; @@ -9,51 +9,51 @@ import {RDatum, RValue} from '../../../../database/stricterR' // Warning: the query is very expensive // TODO: store all calculations in the database, e.g. meeting.attendeeCount (see #7975) const getActiveTeamCountByTeamIds = async (teamIds: string[]) => { + if (!teamIds.length) return 0 const r = await getRethink() - - return r - .table('NewMeeting') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((row: RDatum) => row('endedAt').default(null).ne(null))('id') - .coerceTo('array') - .distinct() - .do((endedMeetingIds: RValue) => { - return ( - r - .table('MeetingMember') - .getAll(r.args(endedMeetingIds), {index: 'meetingId'}) - .group('teamId', 'meetingId') as RDatum - ) - .count() - .ungroup() - .map((row: RDatum) => ({ - teamId: row('group')(0), - meetingId: row('group')(1), - meetingMembers: row('reduction') - })) - .filter((row: RDatum) => - row('meetingMembers').ge(Threshold.MIN_STICKY_TEAM_MEETING_ATTENDEES) - ) - .group('teamId') - .ungroup() - .filter((row: RDatum) => row('reduction').count().ge(Threshold.MIN_STICKY_TEAM_MEETINGS)) - .filter((row: RValue) => { - const meetingIds = row('reduction')('meetingId') - return r - .table('NewMeeting') - .getAll(r.args(meetingIds)) - .filter((meeting: RValue) => { - return meeting('endedAt').during( - r.now().sub(Threshold.STICKY_TEAM_LAST_MEETING_TIMEFRAME), - r.now() - ) - }) - .count() - .gt(0) - }) - .count() - }) + const pg = getKysely() + const meetingIdsByTeamId = await pg + .selectFrom('NewMeeting') + .select(({fn}) => ['teamId', fn('array_agg', ['id']).as('meetingIds')]) + .where('teamId', 'in', teamIds) + .groupBy('teamId') + .execute() + const meetingIds = meetingIdsByTeamId.flatMap((row) => row.meetingIds) + const meetingMembers = await r + .table('MeetingMember') + .getAll(r.args(meetingIds), {index: 'meetingId'}) .run() + const teamsIdsWithMinMeetingsAndMembers = meetingIdsByTeamId + .map(({teamId, meetingIds}) => ({ + teamId, + meetingIds: meetingIds.filter((meetingId) => { + const memberCount = meetingMembers.filter( + (meetingMember) => meetingMember.meetingId === meetingId + ).length + return memberCount >= Threshold.MIN_STICKY_TEAM_MEETING_ATTENDEES + }) + })) + .filter((row) => row.meetingIds.length > Threshold.MIN_STICKY_TEAM_MEETINGS) + .map((row) => row.teamId) + if (teamsIdsWithMinMeetingsAndMembers.length === 0) return 0 + const recentMeetings = await pg + .selectFrom('NewMeeting') + .distinctOn('teamId') + .select(['teamId', 'createdAt']) + .where('teamId', 'in', teamsIdsWithMinMeetingsAndMembers) + .orderBy(['teamId', 'createdAt desc']) + .execute() + + return teamsIdsWithMinMeetingsAndMembers.filter((teamId) => { + const recentMeetingsForTeam = recentMeetings.find( + (recentMeeting) => recentMeeting.teamId === teamId + ) + if (!recentMeetingsForTeam) return false + return ( + recentMeetingsForTeam.createdAt.getTime() > + Date.now() - Threshold.STICKY_TEAM_LAST_MEETING_TIMEFRAME + ) + }).length } export default getActiveTeamCountByTeamIds diff --git a/packages/server/graphql/queries/helpers/getPublicScoredTemplates.ts b/packages/server/graphql/queries/helpers/getPublicScoredTemplates.ts deleted file mode 100644 index 384fb023a48..00000000000 --- a/packages/server/graphql/queries/helpers/getPublicScoredTemplates.ts +++ /dev/null @@ -1,29 +0,0 @@ -import db from '../../../db' -import getTemplateScore from '../../../utils/getTemplateScore' - -const getPublicScoredTemplates = async ( - templates: {createdAt: Date; id: string; isStarter?: boolean; isFree?: boolean}[] -) => { - const sharedTemplateIds = templates.map(({id}) => id) - const sharedTemplateEndTimes = await db.readMany('endTimesByTemplateId', sharedTemplateIds) - const scoreByTemplateId = {} as {[templateId: string]: number} - templates.forEach((template, idx) => { - const {id: templateId, createdAt, isStarter, isFree} = template - const endTimes = sharedTemplateEndTimes[idx]! - const isFreeBonus = isFree ? 1000 : 0 - const starterBonus = isStarter ? 100 : 0 - const bonuses = isFreeBonus + starterBonus - const minUsagePenalty = sharedTemplateEndTimes.length < 10 && !bonuses - scoreByTemplateId[templateId] = minUsagePenalty - ? -1 - : getTemplateScore(createdAt, endTimes, 0.2) + bonuses - }) - // mutative, but doesn't matter if we change the sort oder - return templates - .filter((template) => scoreByTemplateId[template.id]! > 0) - .sort((a, b) => { - return scoreByTemplateId[a.id]! > scoreByTemplateId[b.id]! ? -1 : 1 - }) -} - -export default getPublicScoredTemplates diff --git a/packages/server/graphql/resolvers.ts b/packages/server/graphql/resolvers.ts index 09c762ab07a..a038a5f7c95 100644 --- a/packages/server/graphql/resolvers.ts +++ b/packages/server/graphql/resolvers.ts @@ -158,7 +158,7 @@ export const resolveUnlockedStages = async ( {dataLoader}: GQLContext ) => { if (!unlockedStageIds || unlockedStageIds.length === 0 || !meetingId) return undefined - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) return unlockedStageIds.map((stageId) => resolveGQLStageFromId(stageId, meeting)) } diff --git a/packages/server/graphql/resolvers/resolveStage.ts b/packages/server/graphql/resolvers/resolveStage.ts index 51c6b39a2a2..87f75e05be5 100644 --- a/packages/server/graphql/resolvers/resolveStage.ts +++ b/packages/server/graphql/resolvers/resolveStage.ts @@ -11,7 +11,7 @@ const resolveStage = _args: unknown, {dataLoader}: GQLContext ) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {phases, teamId} = meeting const phase = phases.find((phase: GenericMeetingPhase) => phase.phaseType === phaseType)! const {stages} = phase diff --git a/packages/server/graphql/types/DragEstimatingTaskPayload.ts b/packages/server/graphql/types/DragEstimatingTaskPayload.ts index c09556af20a..4f021386647 100644 --- a/packages/server/graphql/types/DragEstimatingTaskPayload.ts +++ b/packages/server/graphql/types/DragEstimatingTaskPayload.ts @@ -25,7 +25,7 @@ export const DragEstimatingTaskSuccess = new GraphQLObjectType( stages: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(EstimateStage))), resolve: async ({meetingId, stageIds}, _args: unknown, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {phases, teamId} = meeting const phase = getPhase(phases, 'ESTIMATE') const {stages} = phase diff --git a/packages/server/graphql/types/FlagReadyToAdvancePayload.ts b/packages/server/graphql/types/FlagReadyToAdvancePayload.ts index fbb129308e6..91fb22256f6 100644 --- a/packages/server/graphql/types/FlagReadyToAdvancePayload.ts +++ b/packages/server/graphql/types/FlagReadyToAdvancePayload.ts @@ -19,7 +19,7 @@ export const FlagReadyToAdvanceSuccess = new GraphQLObjectType( type: new GraphQLNonNull(NewMeetingStage), description: 'the stage with the updated readyCount', resolve: async ({meetingId, stageId}, _args: unknown, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) return resolveGQLStageFromId(stageId, meeting) } } diff --git a/packages/server/graphql/types/NavigateMeetingPayload.ts b/packages/server/graphql/types/NavigateMeetingPayload.ts index 3c0f06f8c46..1ee2d44c6bb 100644 --- a/packages/server/graphql/types/NavigateMeetingPayload.ts +++ b/packages/server/graphql/types/NavigateMeetingPayload.ts @@ -22,7 +22,7 @@ const NavigateMeetingPayload = new GraphQLObjectType({ description: 'The stage that the facilitator is now on', resolve: async ({meetingId, facilitatorStageId}, _args: unknown, {dataLoader}) => { if (!meetingId) return null - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const stageRes = findStageById(meeting.phases, facilitatorStageId) return stageRes && stageRes.stage } @@ -32,7 +32,7 @@ const NavigateMeetingPayload = new GraphQLObjectType({ description: 'The stage that the facilitator left', resolve: async ({meetingId, oldFacilitatorStageId}, _args: unknown, {dataLoader}) => { if (!meetingId) return null - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const stageRes = findStageById(meeting.phases, oldFacilitatorStageId) return stageRes && stageRes.stage } diff --git a/packages/server/graphql/types/PromoteNewMeetingFacilitatorPayload.ts b/packages/server/graphql/types/PromoteNewMeetingFacilitatorPayload.ts index 2b14fa129d7..c416dc9de0a 100644 --- a/packages/server/graphql/types/PromoteNewMeetingFacilitatorPayload.ts +++ b/packages/server/graphql/types/PromoteNewMeetingFacilitatorPayload.ts @@ -20,7 +20,7 @@ const PromoteNewMeetingFacilitatorPayload = new GraphQLObjectType { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {facilitatorStageId} = meeting return resolveGQLStageFromId(facilitatorStageId, meeting) } diff --git a/packages/server/graphql/types/SetStageTimerPayload.ts b/packages/server/graphql/types/SetStageTimerPayload.ts index f5d2e386d5a..f144391ac21 100644 --- a/packages/server/graphql/types/SetStageTimerPayload.ts +++ b/packages/server/graphql/types/SetStageTimerPayload.ts @@ -15,7 +15,7 @@ const SetStageTimerPayload = new GraphQLObjectType({ description: 'The updated stage', resolve: async ({meetingId, stageId}, _args: unknown, {dataLoader}) => { if (!meetingId || !stageId) return null - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const stageRes = findStageById(meeting.phases, stageId) return stageRes!.stage } diff --git a/packages/server/graphql/types/UpdatePokerScopePayload.ts b/packages/server/graphql/types/UpdatePokerScopePayload.ts index 9c438469d94..03d74b841a5 100644 --- a/packages/server/graphql/types/UpdatePokerScopePayload.ts +++ b/packages/server/graphql/types/UpdatePokerScopePayload.ts @@ -13,14 +13,14 @@ export const UpdatePokerScopeSuccess = new GraphQLObjectType({ type: new GraphQLNonNull(PokerMeeting), description: 'The meeting with the updated estimate phases', resolve: ({meetingId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('newMeetings').load(meetingId) + return dataLoader.get('newMeetings').loadNonNull(meetingId) } }, newStages: { type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(EstimateStage))), description: 'The estimate stages added to the meeting', resolve: async ({meetingId, newStageIds}, _args: unknown, {dataLoader}) => { - const meeting = await dataLoader.get('newMeetings').load(meetingId) + const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {phases, teamId} = meeting const phase = getPhase(phases, 'ESTIMATE') const {stages} = phase diff --git a/packages/server/postgres/queries/src/incrementUserPayLaterClickCountQuery.sql b/packages/server/postgres/queries/src/incrementUserPayLaterClickCountQuery.sql deleted file mode 100644 index e4f89358676..00000000000 --- a/packages/server/postgres/queries/src/incrementUserPayLaterClickCountQuery.sql +++ /dev/null @@ -1,7 +0,0 @@ -/* - @name incrementUserPayLaterClickCountQuery -*/ - -UPDATE "User" SET - "payLaterClickCount" = "payLaterClickCount" + 1 -WHERE id = :id; From abd8281cc7e637311ea0920f21b8822b42396acd Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 8 Oct 2024 15:03:24 -0700 Subject: [PATCH 498/529] chore(rethinkdb): MeetingMember: Phase 1 (#10289) Signed-off-by: Matt Krick --- codegen.json | 8 +- packages/server/database/rethinkDriver.ts | 4 +- .../database/types/ActionMeetingMember.ts | 15 --- .../server/database/types/MeetingMember.ts | 1 + .../database/types/PokerMeetingMember.ts | 19 ---- .../database/types/RetroMeetingMember.ts | 19 ---- .../database/types/TeamPromptMeetingMember.ts | 17 ---- .../server/dataloader/NullableDataLoader.ts | 4 + .../server/dataloader/customLoaderMakers.ts | 2 - .../dataloader/foreignKeyLoaderMakers.ts | 20 +++- .../dataloader/primaryKeyLoaderMakers.ts | 8 +- .../mutations/helpers/removeTeamMember.ts | 18 ++-- .../mutations/helpers/safelyCastVote.ts | 67 +++++++++---- .../mutations/helpers/safelyWithdrawVote.ts | 5 + .../helpers/sendPokerMeetingRevoteEvent.ts | 5 +- .../server/graphql/mutations/joinMeeting.ts | 97 ++++++++----------- .../graphql/mutations/pokerRevealVotes.ts | 2 +- .../resetRetroMeetingToGroupStage.ts | 6 ++ .../graphql/mutations/setPokerSpectate.ts | 9 +- .../graphql/mutations/startSprintPoker.ts | 27 ++---- .../graphql/mutations/updateRetroMaxVotes.ts | 87 ++++++++++++++--- .../mutations/voteForReflectionGroup.ts | 13 +-- .../private/mutations/processRecurrence.ts | 3 +- .../graphql/public/mutations/startCheckIn.ts | 31 +++--- .../public/mutations/startRetrospective.ts | 24 ++--- .../graphql/public/types/ActionMeeting.ts | 9 +- .../graphql/public/types/MeetingSeries.ts | 2 - .../public/types/RetrospectiveMeeting.ts | 20 ++-- .../server/graphql/public/types/TeamMember.ts | 2 +- packages/server/graphql/public/types/User.ts | 12 +-- .../1727893031268_MeetingMember-phase1.ts | 49 ++++++++++ packages/server/postgres/select.ts | 22 ++--- packages/server/postgres/types/Meeting.d.ts | 57 ++++++----- packages/server/utils/analytics/analytics.ts | 18 ++-- packages/server/utils/analytics/helpers.ts | 5 +- 35 files changed, 396 insertions(+), 311 deletions(-) delete mode 100644 packages/server/database/types/ActionMeetingMember.ts delete mode 100644 packages/server/database/types/PokerMeetingMember.ts delete mode 100644 packages/server/database/types/RetroMeetingMember.ts delete mode 100644 packages/server/database/types/TeamPromptMeetingMember.ts create mode 100644 packages/server/postgres/migrations/1727893031268_MeetingMember-phase1.ts diff --git a/codegen.json b/codegen.json index bb1eec60477..08c29e714e5 100644 --- a/codegen.json +++ b/codegen.json @@ -76,7 +76,7 @@ "AcceptRequestToJoinDomainSuccess": "./types/AcceptRequestToJoinDomainSuccess#AcceptRequestToJoinDomainSuccessSource", "AcceptTeamInvitationPayload": "./types/AcceptTeamInvitationPayload#AcceptTeamInvitationPayloadSource", "ActionMeeting": "../../postgres/types/Meeting#CheckInMeeting", - "ActionMeetingMember": "../../database/types/ActionMeetingMember#default as ActionMeetingMemberDB", + "ActionMeetingMember": "../../postgres/types/Meeting.d#ActionMeetingMember as ActionMeetingMemberDB", "AddApprovedOrganizationDomainsSuccess": "./types/AddApprovedOrganizationDomainsSuccess#AddApprovedOrganizationDomainsSuccessSource", "AddPokerTemplateSuccess": "./types/AddPokerTemplateSuccess#AddPokerTemplateSuccessSource", "AddReactjiToReactableSuccess": "./types/AddReactjiToReactableSuccess#AddReactjiToReactableSuccessSource", @@ -146,7 +146,7 @@ "OrgIntegrationProviders": "./types/OrgIntegrationProviders#OrgIntegrationProvidersSource", "OrganizationUser": "../../postgres/types/index#OrganizationUser as OrganizationUserDB", "PokerMeeting": "../../postgres/types/Meeting#PokerMeeting", - "PokerMeetingMember": "../../database/types/MeetingPokerMeetingMember#default as PokerMeetingMemberDB", + "PokerMeetingMember": "../../postgres/types/Meeting.d#PokerMeetingMember as PokerMeetingMemberDB", "PokerTemplate": "../../database/types/PokerTemplate#default as PokerTemplateDB", "RRule": "rrule-rust#RRuleSet", "Reactable": "../../database/types/Reactable#Reactable", @@ -162,7 +162,7 @@ "RetroReflection": "../../postgres/types/index#RetroReflection as RetroReflectionDB", "RetroReflectionGroup": "./types/RetroReflectionGroup#RetroReflectionGroupSource", "RetrospectiveMeeting": "../../postgres/types/Meeting#RetrospectiveMeeting", - "RetrospectiveMeetingMember": "../../database/types/RetroMeetingMember#default", + "RetrospectiveMeetingMember": "../../postgres/types/Meeting.d#RetroMeetingMember as RetroMeetingMemberDB", "SAML": "./types/SAML#SAMLSource", "SetMeetingSettingsPayload": "../types/SetMeetingSettingsPayload#SetMeetingSettingsPayloadSource", "SetNotificationStatusPayload": "./types/SetNotificationStatusPayload#SetNotificationStatusPayloadSource", @@ -185,7 +185,7 @@ "TeamMemberIntegrationAuthWebhook": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", "TeamMemberIntegrations": "./types/TeamMemberIntegrations#TeamMemberIntegrationsSource", "TeamPromptMeeting": "../../postgres/types/Meeting#TeamPromptMeeting", - "TeamPromptMeetingMember": "../../database/types/TeamPromptMeetingMember#default as TeamPromptMeetingMemberDB", + "TeamPromptMeetingMember": "../../postgres/types/Meeting.d#TeamPromptMeetingMember as TeamPromptMeetingMemberDB", "TeamPromptMeetingSettings": "../../postgres/types/index#MeetingSettings as TeamMeetingSettingsDB", "TeamPromptResponse": "../../postgres/types/index#TeamPromptResponse as TeamPromptResponseDB", "TemplateDimension": "../../postgres/types/index#TemplateDimension as TemplateDimensionDB", diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index e3db1bfb74e..f1049f80727 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -1,6 +1,6 @@ import {MasterPool, r} from 'rethinkdb-ts' import TeamInvitation from '../database/types/TeamInvitation' -import {AnyMeetingTeamMember} from '../postgres/types/Meeting' +import {AnyMeetingMember} from '../postgres/types/Meeting' import getRethinkConfig from './getRethinkConfig' import {R} from './stricterR' import MassInvitation from './types/MassInvitation' @@ -22,7 +22,7 @@ export type RethinkSchema = { index: 'teamMemberId' } MeetingMember: { - type: AnyMeetingTeamMember + type: AnyMeetingMember index: 'meetingId' | 'teamId' | 'userId' } NewFeature: { diff --git a/packages/server/database/types/ActionMeetingMember.ts b/packages/server/database/types/ActionMeetingMember.ts deleted file mode 100644 index 5c6e80b1270..00000000000 --- a/packages/server/database/types/ActionMeetingMember.ts +++ /dev/null @@ -1,15 +0,0 @@ -import MeetingMember from './MeetingMember' - -interface Input { - id?: string - updatedAt?: Date - teamId: string - userId: string - meetingId: string -} - -export default class ActionMeetingMember extends MeetingMember { - constructor(input: Input) { - super({...input, meetingType: 'action'}) - } -} diff --git a/packages/server/database/types/MeetingMember.ts b/packages/server/database/types/MeetingMember.ts index c7cc6e8c4a7..82e8f856c89 100644 --- a/packages/server/database/types/MeetingMember.ts +++ b/packages/server/database/types/MeetingMember.ts @@ -1,3 +1,4 @@ +// Can remove this after getting rid of the old RethinkDB migrations import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import {MeetingTypeEnum} from '../../graphql/public/resolverTypes' diff --git a/packages/server/database/types/PokerMeetingMember.ts b/packages/server/database/types/PokerMeetingMember.ts deleted file mode 100644 index 02051a9131c..00000000000 --- a/packages/server/database/types/PokerMeetingMember.ts +++ /dev/null @@ -1,19 +0,0 @@ -import MeetingMember from './MeetingMember' - -interface Input { - id?: string - updatedAt?: Date - teamId: string - userId: string - meetingId: string - isSpectating: boolean -} - -export default class PokerMeetingMember extends MeetingMember { - isSpectating: boolean - constructor(input: Input) { - const {isSpectating} = input - super({...input, meetingType: 'poker'}) - this.isSpectating = isSpectating - } -} diff --git a/packages/server/database/types/RetroMeetingMember.ts b/packages/server/database/types/RetroMeetingMember.ts deleted file mode 100644 index f53cf488346..00000000000 --- a/packages/server/database/types/RetroMeetingMember.ts +++ /dev/null @@ -1,19 +0,0 @@ -import MeetingMember from './MeetingMember' - -interface Input { - id?: string - updatedAt?: Date - teamId: string - userId: string - meetingId: string - votesRemaining: number -} - -export default class RetroMeetingMember extends MeetingMember { - votesRemaining: number - constructor(input: Input) { - const {votesRemaining, ...superInput} = input - super({...superInput, meetingType: 'retrospective'}) - this.votesRemaining = votesRemaining - } -} diff --git a/packages/server/database/types/TeamPromptMeetingMember.ts b/packages/server/database/types/TeamPromptMeetingMember.ts deleted file mode 100644 index 13a7cb11273..00000000000 --- a/packages/server/database/types/TeamPromptMeetingMember.ts +++ /dev/null @@ -1,17 +0,0 @@ -import MeetingMember from './MeetingMember' - -interface Input { - id?: string - updatedAt?: Date - teamId: string - userId: string - meetingId: string -} - -export default class TeamPromptMeetingMember extends MeetingMember { - meetingType!: 'teamPrompt' - - constructor(input: Input) { - super({...input, meetingType: 'teamPrompt'}) - } -} diff --git a/packages/server/dataloader/NullableDataLoader.ts b/packages/server/dataloader/NullableDataLoader.ts index baba3c414f0..0cfb4393e1a 100644 --- a/packages/server/dataloader/NullableDataLoader.ts +++ b/packages/server/dataloader/NullableDataLoader.ts @@ -17,6 +17,10 @@ class NullableDataLoader extends UpdatableCacheDataL super(batchLoadFn, options) } + load(key: Key) { + return super.load(key) as Promise<(Value & NarrowType) | undefined> + } + async loadNonNull(key: Key): Promise { const value = await this.load(key) if (value === undefined) { diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index bb03e4d9300..dd75986ec34 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -581,7 +581,6 @@ export const activeMeetingsByMeetingSeriesId = ( .where('meetingSeriesId', 'in', keys) .where('endedAt', 'is', null) .orderBy('createdAt') - .$narrowType() .execute() return normalizeArrayResults(keys, res, 'meetingSeriesId') }, @@ -604,7 +603,6 @@ export const lastMeetingByMeetingSeriesId = ( .where('meetingSeriesId', '=', key) .orderBy('createdAt desc') .limit(1) - .$narrowType() .executeTakeFirst() return latestMeeting || null }) diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 030f62bf990..be87e4620ea 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -3,6 +3,7 @@ import {getTeamPromptResponsesByMeetingIds} from '../postgres/queries/getTeamPro import { selectAgendaItems, selectComments, + selectMeetingMembers, selectNewMeetings, selectOrganizations, selectReflectPrompts, @@ -15,7 +16,6 @@ import { selectTemplateScale, selectTimelineEvent } from '../postgres/select' -import {AnyMeeting} from '../postgres/types/Meeting' import {foreignKeyLoaderMaker} from './foreignKeyLoaderMaker' export const teamsByOrgIds = foreignKeyLoaderMaker('teams', 'orgId', (orgIds) => @@ -238,7 +238,6 @@ export const activeMeetingsByTeamId = foreignKeyLoaderMaker( .where('teamId', 'in', teamIds) .where('endedAt', 'is', null) .orderBy('createdAt desc') - .$narrowType() .execute() } ) @@ -250,7 +249,22 @@ export const completedMeetingsByTeamId = foreignKeyLoaderMaker( .where('teamId', 'in', teamIds) .where('endedAt', 'is not', null) .orderBy('endedAt desc') - .$narrowType() .execute() } ) + +export const _pgmeetingMembersByMeetingId = foreignKeyLoaderMaker( + '_pgmeetingMembers', + 'meetingId', + async (meetingIds) => { + return selectMeetingMembers().where('meetingId', 'in', meetingIds).execute() + } +) + +export const _pgmeetingMembersByUserId = foreignKeyLoaderMaker( + '_pgmeetingMembers', + 'userId', + async (userIds) => { + return selectMeetingMembers().where('userId', 'in', userIds).execute() + } +) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 9df90bcc56b..09053b04b43 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -8,6 +8,7 @@ import {getUsersByIds} from '../postgres/queries/getUsersByIds' import { selectAgendaItems, selectComments, + selectMeetingMembers, selectMeetingSettings, selectNewMeetings, selectOrganizations, @@ -23,7 +24,6 @@ import { selectTemplateScaleRef, selectTimelineEvent } from '../postgres/select' -import {AnyMeeting} from '../postgres/types/Meeting' import {primaryKeyLoaderMaker} from './primaryKeyLoaderMaker' export const users = primaryKeyLoaderMaker(getUsersByIds) @@ -123,5 +123,9 @@ export const reflectPrompts = primaryKeyLoaderMaker((ids: readonly string[]) => }) export const newMeetings = primaryKeyLoaderMaker((ids: readonly string[]) => { - return selectNewMeetings().where('id', 'in', ids).$narrowType().execute() + return selectNewMeetings().where('id', 'in', ids).execute() +}) + +export const _pgmeetingMembers = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectMeetingMembers().where('id', 'in', ids).execute() }) diff --git a/packages/server/graphql/mutations/helpers/removeTeamMember.ts b/packages/server/graphql/mutations/helpers/removeTeamMember.ts index 73a9f934dfd..49fd21ce16e 100644 --- a/packages/server/graphql/mutations/helpers/removeTeamMember.ts +++ b/packages/server/graphql/mutations/helpers/removeTeamMember.ts @@ -132,16 +132,22 @@ const removeTeamMember = async ( // TODO should probably just inactivate the meeting member const activeMeetings = await dataLoader.get('activeMeetingsByTeamId').load(teamId) const meetingIds = activeMeetings.map(({id}) => id) - await r - .table('MeetingMember') - .getAll(r.args(meetingIds), {index: 'meetingId'}) - .filter({userId}) - .delete() - .run() // Reassign facilitator for meetings this user is facilitating. if (meetingIds.length > 0) { + await r + .table('MeetingMember') + .getAll(r.args(meetingIds), {index: 'meetingId'}) + .filter({userId}) + .delete() + .run() const facilitatingMeetings = await pg + .with('DeleteMeetingMembers', (qb) => + qb + .deleteFrom('MeetingMember') + .where('userId', '=', userId) + .where('meetingId', 'in', meetingIds) + ) .selectFrom('NewMeeting') .select('id') .where('id', 'in', meetingIds) diff --git a/packages/server/graphql/mutations/helpers/safelyCastVote.ts b/packages/server/graphql/mutations/helpers/safelyCastVote.ts index d0e8e030ee9..edac81955fb 100644 --- a/packages/server/graphql/mutations/helpers/safelyCastVote.ts +++ b/packages/server/graphql/mutations/helpers/safelyCastVote.ts @@ -39,28 +39,53 @@ const safelyCastVote = async ( return standardError(new Error('No votes remaining'), {userId: viewerId}) } - const voteAddedResult = await pg - .updateTable('RetroReflectionGroup') - .set({voterIds: sql`ARRAY_APPEND("voterIds",${userId})`}) - .where('id', '=', reflectionGroupId) - .where( - sql`COALESCE(array_length(array_positions("voterIds", ${userId}),1),0)`, - '<', - maxVotesPerGroup - ) - .executeTakeFirst() + // in a transaction add to the reflection group and the meeting member + try { + await pg.transaction().execute(async (trx) => { + const res = await trx + .with('MeetingMemberUpdate', (qb) => + qb + .updateTable('MeetingMember') + .set((eb) => ({votesRemaining: eb('votesRemaining', '-', 1)})) + .where('id', '=', meetingMemberId) + .returning('id') + ) + .updateTable('RetroReflectionGroup') + .set({voterIds: sql`ARRAY_APPEND("voterIds",${userId})`}) + .where('id', '=', reflectionGroupId) + .where( + sql`COALESCE(array_length(array_positions("voterIds", ${userId}),1),0)`, + '<', + maxVotesPerGroup + ) + .returning((eb) => [ + 'id', + eb.selectFrom('MeetingMemberUpdate').select('id').as('meetingMemberUpdate') + ]) + .executeTakeFirst() - const isVoteAddedToGroup = voteAddedResult.numUpdatedRows === BigInt(1) - - if (!isVoteAddedToGroup) { - await r - .table('MeetingMember') - .get(meetingMemberId) - .update((member: RValue) => ({ - votesRemaining: member('votesRemaining').add(1) - })) - .run() - return standardError(new Error('Max votes per group exceeded'), {userId: viewerId}) + if (!res) { + await r + .table('MeetingMember') + .get(meetingMemberId) + .update((member: RValue) => ({ + votesRemaining: member('votesRemaining').add(1) + })) + .run() + throw new Error('Max votes per group exceeded') + } + if (!res.meetingMemberUpdate) { + // just for phase 2, make sure the row exists in the DB + const hasMember = await trx + .selectFrom('MeetingMember') + .select('id') + .where('id', '=', meetingMemberId) + .executeTakeFirst() + if (hasMember) throw new Error('No votes remaining') + } + }) + } catch (e) { + return {error: {message: (e as Error).message}} } return undefined } diff --git a/packages/server/graphql/mutations/helpers/safelyWithdrawVote.ts b/packages/server/graphql/mutations/helpers/safelyWithdrawVote.ts index b78559ee901..b91452779b3 100644 --- a/packages/server/graphql/mutations/helpers/safelyWithdrawVote.ts +++ b/packages/server/graphql/mutations/helpers/safelyWithdrawVote.ts @@ -41,6 +41,11 @@ const safelyWithdrawVote = async ( votesRemaining: member('votesRemaining').add(1) })) .run() + await pg + .updateTable('MeetingMember') + .set((eb) => ({votesRemaining: eb('votesRemaining', '+', 1)})) + .where('id', '=', meetingMemberId) + .execute() return undefined } diff --git a/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts b/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts index 88c74280788..579cc033036 100644 --- a/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts +++ b/packages/server/graphql/mutations/helpers/sendPokerMeetingRevoteEvent.ts @@ -1,13 +1,12 @@ -import MeetingMember from '../../../database/types/MeetingMember' import {TeamMember} from '../../../postgres/types' -import {AnyMeeting} from '../../../postgres/types/Meeting' +import {AnyMeeting, AnyMeetingMember} from '../../../postgres/types/Meeting' import {analytics} from '../../../utils/analytics/analytics' import {DataLoaderWorker} from '../../graphql' const sendPokerMeetingRevoteEvent = async ( meeting: AnyMeeting, teamMembers: TeamMember[], - meetingMembers: MeetingMember[], + meetingMembers: AnyMeetingMember[], dataLoader: DataLoaderWorker ) => { const {facilitatorUserId, meetingNumber, phases, teamId} = meeting diff --git a/packages/server/graphql/mutations/joinMeeting.ts b/packages/server/graphql/mutations/joinMeeting.ts index 7a209aafbae..2d7cbb7002d 100644 --- a/packages/server/graphql/mutations/joinMeeting.ts +++ b/packages/server/graphql/mutations/joinMeeting.ts @@ -1,15 +1,14 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' +import {Insertable} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import MeetingMemberId from '../../../client/shared/gqlIds/MeetingMemberId' import toTeamMemberId from '../../../client/utils/relay/toTeamMemberId' import getRethink from '../../database/rethinkDriver' -import ActionMeetingMember from '../../database/types/ActionMeetingMember' import CheckInStage from '../../database/types/CheckInStage' -import PokerMeetingMember from '../../database/types/PokerMeetingMember' -import RetroMeetingMember from '../../database/types/RetroMeetingMember' -import TeamPromptMeetingMember from '../../database/types/TeamPromptMeetingMember' import TeamPromptResponseStage from '../../database/types/TeamPromptResponseStage' import UpdatesStage from '../../database/types/UpdatesStage' import getKysely from '../../postgres/getKysely' +import {MeetingMember} from '../../postgres/pg' import {TeamMember} from '../../postgres/types' import {AnyMeeting} from '../../postgres/types/Meeting' import {NewMeetingPhase, NewMeetingStages} from '../../postgres/types/NewMeetingPhase' @@ -20,30 +19,21 @@ import publish from '../../utils/publish' import {GQLContext} from '../graphql' import JoinMeetingPayload from '../types/JoinMeetingPayload' -const createMeetingMember = (meeting: AnyMeeting, teamMember: TeamMember) => { +export const createMeetingMember = ( + meeting: AnyMeeting, + teamMember: Pick +): Insertable => { const {userId, teamId, isSpectatingPoker} = teamMember - switch (meeting.meetingType) { - case 'action': - return new ActionMeetingMember({teamId, userId, meetingId: meeting.id}) - case 'retrospective': - const {id: meetingId, totalVotes} = meeting - return new RetroMeetingMember({ - teamId, - userId, - meetingId, - votesRemaining: totalVotes - }) - case 'poker': - return new PokerMeetingMember({ - teamId, - userId, - meetingId: meeting.id, - isSpectating: isSpectatingPoker - }) - case 'teamPrompt': - return new TeamPromptMeetingMember({teamId, userId, meetingId: meeting.id}) - default: - throw new Error('Invalid meeting type') + const {id: meetingId, meetingType} = meeting + return { + id: MeetingMemberId.join(meetingId, userId), + updatedAt: new Date(), // can remove this in phase 3 + teamId, + userId, + meetingId, + meetingType, + isSpectating: meetingType === 'poker' ? isSpectatingPoker : null, + votesRemaining: meetingType === 'retrospective' ? meeting.totalVotes : null } } @@ -60,6 +50,7 @@ const joinMeeting = { {meetingId}: {meetingId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { + const pg = getKysely() const r = await getRethink() const viewerId = getUserId(authToken) const operationId = dataLoader.share() @@ -87,38 +78,36 @@ const joinMeeting = { if (errors > 0) { return {error: {message: 'Already joined meeting'}} } - + await pg.insertInto('MeetingMember').values(meetingMember).execute() const addStageToPhase = async ( stage: CheckInStage | UpdatesStage | TeamPromptResponseStage, phaseType: NewMeetingPhase['phaseType'] ) => { - await getKysely() - .transaction() - .execute(async (trx) => { - const meeting = await trx - .selectFrom('NewMeeting') - .select(({fn}) => fn('to_json', ['phases']).as('phases')) - .where('id', '=', meetingId) - .forUpdate() - // NewMeeting: add OrThrow in phase 3 - .executeTakeFirst() - if (!meeting) return - const {phases} = meeting - const phase = getPhase(phases, phaseType) - const stages = phase.stages as NewMeetingStages[] - stages.push({ - ...stage, - isNavigable: true, - isNavigableByFacilitator: true, - // the stage is complete if all other stages are complete & there's at least 1 - isComplete: stages.length >= 1 && stages.every((stage) => stage.isComplete) - }) - await trx - .updateTable('NewMeeting') - .set({phases: JSON.stringify(phases)}) - .where('id', '=', meetingId) - .execute() + await pg.transaction().execute(async (trx) => { + const meeting = await trx + .selectFrom('NewMeeting') + .select(({fn}) => fn('to_json', ['phases']).as('phases')) + .where('id', '=', meetingId) + .forUpdate() + // NewMeeting: add OrThrow in phase 3 + .executeTakeFirst() + if (!meeting) return + const {phases} = meeting + const phase = getPhase(phases, phaseType) + const stages = phase.stages as NewMeetingStages[] + stages.push({ + ...stage, + isNavigable: true, + isNavigableByFacilitator: true, + // the stage is complete if all other stages are complete & there's at least 1 + isComplete: stages.length >= 1 && stages.every((stage) => stage.isComplete) }) + await trx + .updateTable('NewMeeting') + .set({phases: JSON.stringify(phases)}) + .where('id', '=', meetingId) + .execute() + }) } const appendToCheckin = async () => { diff --git a/packages/server/graphql/mutations/pokerRevealVotes.ts b/packages/server/graphql/mutations/pokerRevealVotes.ts index 63d23dccfbb..56033b5085e 100644 --- a/packages/server/graphql/mutations/pokerRevealVotes.ts +++ b/packages/server/graphql/mutations/pokerRevealVotes.ts @@ -2,8 +2,8 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {sql} from 'kysely' import {PokerCards, SubscriptionChannel} from 'parabol-client/types/constEnums' import EstimateUserScore from '../../database/types/EstimateUserScore' -import PokerMeetingMember from '../../database/types/PokerMeetingMember' import getKysely from '../../postgres/getKysely' +import {PokerMeetingMember} from '../../postgres/types/Meeting' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' import publish from '../../utils/publish' diff --git a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts index ec97b6837c7..fd89e91f137 100644 --- a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts +++ b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts @@ -114,6 +114,12 @@ const resetRetroMeetingToGroupStage = { .set({voterIds: [], discussionPromptQuestion: null}) .where('id', 'in', reflectionGroupIds) ) + .with('ResetMeetingMember', (qb) => + qb + .updateTable('MeetingMember') + .set({votesRemaining: meeting.totalVotes}) + .where('meetingId', '=', meetingId) + ) .updateTable('NewMeeting') .set({phases: JSON.stringify(newPhases)}) .where('id', '=', meetingId) diff --git a/packages/server/graphql/mutations/setPokerSpectate.ts b/packages/server/graphql/mutations/setPokerSpectate.ts index 61b7fbf9868..173b63720a2 100644 --- a/packages/server/graphql/mutations/setPokerSpectate.ts +++ b/packages/server/graphql/mutations/setPokerSpectate.ts @@ -3,7 +3,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import toTeamMemberId from '../../../client/utils/relay/toTeamMemberId' import getRethink from '../../database/rethinkDriver' import EstimateStage from '../../database/types/EstimateStage' -import PokerMeetingMember from '../../database/types/PokerMeetingMember' import getKysely from '../../postgres/getKysely' import {getUserId} from '../../utils/authorization' import getPhase from '../../utils/getPhase' @@ -38,7 +37,7 @@ const setPokerSpectate = { //AUTH const meetingMemberId = toTeamMemberId(meetingId, viewerId) const [meetingMember, meeting] = await Promise.all([ - dataLoader.get('meetingMembers').load(meetingMemberId) as Promise, + dataLoader.get('meetingMembers').load(meetingMemberId), dataLoader.get('newMeetings').load(meetingId) ]) if (!meeting) { @@ -51,6 +50,9 @@ const setPokerSpectate = { if (meetingType !== 'poker') { return {error: {message: 'Not a poker meeting'}} } + if (meetingMember.meetingType !== 'poker') { + return {error: {message: 'Not a poker meeting'}} + } if (!meetingMember) { return {error: {message: 'Not in meeting'}} } @@ -62,6 +64,9 @@ const setPokerSpectate = { // RESOLUTION const teamMemberId = toTeamMemberId(teamId, viewerId) await pg + .with('MeetingMemberUpdate', (qb) => + qb.updateTable('MeetingMember').set({isSpectating}).where('id', '=', meetingMemberId) + ) .updateTable('TeamMember') .set({isSpectatingPoker: isSpectating}) .where('id', '=', teamMemberId) diff --git a/packages/server/graphql/mutations/startSprintPoker.ts b/packages/server/graphql/mutations/startSprintPoker.ts index e1836d9b83f..bef930b4c60 100644 --- a/packages/server/graphql/mutations/startSprintPoker.ts +++ b/packages/server/graphql/mutations/startSprintPoker.ts @@ -3,11 +3,9 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import toTeamMemberId from '../../../client/utils/relay/toTeamMemberId' import getRethink from '../../database/rethinkDriver' import MeetingPoker from '../../database/types/MeetingPoker' -import PokerMeetingMember from '../../database/types/PokerMeetingMember' import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' import updateMeetingTemplateLastUsedAt from '../../postgres/queries/updateMeetingTemplateLastUsedAt' -import updateTeamByTeamId from '../../postgres/queries/updateTeamByTeamId' import {MeetingTypeEnum, PokerMeeting} from '../../postgres/types/Meeting' import {PokerMeetingPhase} from '../../postgres/types/NewMeetingPhase' import {analytics} from '../../utils/analytics/analytics' @@ -23,6 +21,7 @@ import createGcalEvent from './helpers/createGcalEvent' import createNewMeetingPhases from './helpers/createNewMeetingPhases' import isStartMeetingLocked from './helpers/isStartMeetingLocked' import {IntegrationNotifier} from './helpers/notifications/IntegrationNotifier' +import {createMeetingMember} from './joinMeeting' const freezeTemplateAsRef = async (templateId: string, dataLoader: DataLoaderWorker) => { const pg = getKysely() @@ -161,23 +160,15 @@ export default { const teamMemberId = toTeamMemberId(teamId, viewerId) const teamMember = await dataLoader.get('teamMembers').loadNonNull(teamMemberId) - const {isSpectatingPoker} = teamMember - const updates = { - lastMeetingType: meetingType - } + const meetingMember = createMeetingMember(meeting, teamMember) await Promise.all([ - r - .table('MeetingMember') - .insert( - new PokerMeetingMember({ - meetingId, - userId: viewerId, - teamId, - isSpectating: isSpectatingPoker - }) - ) - .run(), - updateTeamByTeamId(updates, teamId) + pg + .with('MeetingMemberInsert', (qb) => qb.insertInto('MeetingMember').values(meetingMember)) + .updateTable('Team') + .set({lastMeetingType: meetingType}) + .where('id', '=', teamId) + .execute(), + r.table('MeetingMember').insert(meetingMember).run() ]) IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting, template) diff --git a/packages/server/graphql/mutations/updateRetroMaxVotes.ts b/packages/server/graphql/mutations/updateRetroMaxVotes.ts index 47fcf5c673a..847a480ba37 100644 --- a/packages/server/graphql/mutations/updateRetroMaxVotes.ts +++ b/packages/server/graphql/mutations/updateRetroMaxVotes.ts @@ -37,6 +37,7 @@ const updateRetroMaxVotes = { }: {totalVotes: number; maxVotesPerGroup: number; meetingId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { + const pg = getKysely() const r = await getRethink() const viewerId = getUserId(authToken) const operationId = dataLoader.share() @@ -49,9 +50,6 @@ const updateRetroMaxVotes = { return {error: {message: 'Meeting not found'}} } - if (meeting.meetingType !== 'retrospective') { - return {error: {message: `Meeting not retrospective`}} - } const { endedAt, meetingType, @@ -138,18 +136,79 @@ const updateRetroMaxVotes = { } // RESOLUTION - await getKysely() - .with('MeetingUpdates', (qb) => - qb.updateTable('NewMeeting').set({totalVotes, maxVotesPerGroup}).where('id', '=', meetingId) - ) - .updateTable('MeetingSettings') - .set({ - totalVotes, - maxVotesPerGroup + try { + await pg.transaction().execute(async (trx) => { + const canChangeMaxVotesPerGroup = + maxVotesPerGroup >= oldMaxVotesPerGroup + ? true + : await trx + .with('GroupVotes', (qb) => + qb + .selectFrom('RetroReflectionGroup') + .where('meetingId', '=', meetingId) + .where('isActive', '=', true) + .select(({fn}) => ['id', fn('unnest', ['voterIds']).as('userIds')]) + ) + .with('GroupVoteCount', (qb) => + qb + .selectFrom('GroupVotes') + .select(({fn}) => ['id', fn.count('userIds').as('mode')]) + .groupBy(['id', 'userIds']) + ) + .selectFrom('GroupVoteCount') + .select(({eb, fn}) => eb(fn.max('mode'), '<', maxVotesPerGroup).as('isValid')) + .executeTakeFirst() + if (!canChangeMaxVotesPerGroup) { + throw new Error('A topic already has too many votes') + } + const res = await trx + .with('MeetingMemberUpdates', (qb) => + qb + .updateTable('MeetingMember') + .set((eb) => ({votesRemaining: eb('votesRemaining', '+', delta)})) + .where('meetingId', '=', meetingId) + // TURN THIS ON IN PHASE 2 + // .$if(delta < 0, (qb) => + // qb.where(({selectFrom, eb}) => + // eb( + // selectFrom('MeetingMember') + // .select((eb) => eb.fn('min', ['votesRemaining']).as('min')) + // .where('meetingId', '=', meetingId), + // '>', + // -delta + // ) + // ) + // ) + .returning('id') + ) + .with( + 'NewMeetingUpdates', + (qb) => + qb + .updateTable('NewMeeting') + .set({totalVotes, maxVotesPerGroup}) + .where('id', '=', meetingId) + // TURN THIS ON IN PHASE 2 + // .where(({exists, selectFrom}) => exists(selectFrom('MeetingMemberUpdates').select('id'))) + ) + .updateTable('MeetingSettings') + .set({ + totalVotes, + maxVotesPerGroup + }) + .where('teamId', '=', teamId) + .where('meetingType', '=', 'retrospective') + // TURN THIS ON IN PHASE 2 + // .where(({exists, selectFrom}) => exists(selectFrom('MeetingMemberUpdates').select('id'))) + .executeTakeFirstOrThrow() + + if (res.numUpdatedRows === BigInt(0)) { + throw new Error('Your team has already spent their votes') + } }) - .where('teamId', '=', teamId) - .where('meetingType', '=', 'retrospective') - .execute() + } catch (e) { + return {error: {message: (e as Error).message}} + } dataLoader.get('newMeetings').clear(meetingId) const data = {meetingId} publish(SubscriptionChannel.MEETING, meetingId, 'UpdateRetroMaxVotesSuccess', data, subOptions) diff --git a/packages/server/graphql/mutations/voteForReflectionGroup.ts b/packages/server/graphql/mutations/voteForReflectionGroup.ts index 304051a39e8..d848cebed3b 100644 --- a/packages/server/graphql/mutations/voteForReflectionGroup.ts +++ b/packages/server/graphql/mutations/voteForReflectionGroup.ts @@ -2,7 +2,7 @@ import {GraphQLBoolean, GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {VOTE} from 'parabol-client/utils/constants' import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' -import getRethink from '../../database/rethinkDriver' +import MeetingMemberId from '../../../client/shared/gqlIds/MeetingMemberId' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -28,7 +28,6 @@ export default { {isUnvote = false, reflectionGroupId}: {isUnvote: boolean; reflectionGroupId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {operationId, mutatorId} @@ -56,13 +55,8 @@ export default { } // VALIDATION - const meetingMember = await r - .table('MeetingMember') - .getAll(meetingId, {index: 'meetingId'}) - .filter({userId: viewerId}) - .nth(0) - .default(null) - .run() + const meetingMemberId = MeetingMemberId.join(meetingId, viewerId) + const meetingMember = await dataLoader.get('meetingMembers').load(meetingMemberId) if (!meetingMember) { return standardError(new Error('Meeting member not found'), {userId: viewerId}) } @@ -87,6 +81,7 @@ export default { ) if (votingError) return votingError } + dataLoader.clearAll('meetingMembers') const data = { meetingId, diff --git a/packages/server/graphql/private/mutations/processRecurrence.ts b/packages/server/graphql/private/mutations/processRecurrence.ts index 5d37cec09f0..737ab494653 100644 --- a/packages/server/graphql/private/mutations/processRecurrence.ts +++ b/packages/server/graphql/private/mutations/processRecurrence.ts @@ -9,7 +9,7 @@ import {fromDateTime, toDateTime} from '../../../../client/shared/rruleUtil' import getKysely from '../../../postgres/getKysely' import {getActiveMeetingSeries} from '../../../postgres/queries/getActiveMeetingSeries' import {selectNewMeetings} from '../../../postgres/select' -import {AnyMeeting, RetrospectiveMeeting, TeamPromptMeeting} from '../../../postgres/types/Meeting' +import {RetrospectiveMeeting, TeamPromptMeeting} from '../../../postgres/types/Meeting' import {MeetingSeries} from '../../../postgres/types/MeetingSeries' import {analytics} from '../../../utils/analytics/analytics' import {getNextRRuleDate} from '../../../utils/getNextRRuleDate' @@ -124,7 +124,6 @@ const processRecurrence: MutationResolvers['processRecurrence'] = async ( const meetingsToEnd = await selectNewMeetings() .where('scheduledEndTime', '<', sql`CURRENT_TIMESTAMP`) .where('endedAt', 'is', null) - .$narrowType() .execute() const res = await tracer.trace('processRecurrence.endMeetings', async () => diff --git a/packages/server/graphql/public/mutations/startCheckIn.ts b/packages/server/graphql/public/mutations/startCheckIn.ts index 94c19a319fc..ffe6ab4c87d 100644 --- a/packages/server/graphql/public/mutations/startCheckIn.ts +++ b/packages/server/graphql/public/mutations/startCheckIn.ts @@ -1,10 +1,8 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../../database/rethinkDriver' -import ActionMeetingMember from '../../../database/types/ActionMeetingMember' import MeetingAction from '../../../database/types/MeetingAction' import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' -import updateTeamByTeamId from '../../../postgres/queries/updateTeamByTeamId' import {CheckInMeeting, MeetingTypeEnum} from '../../../postgres/types/Meeting' import {CheckInPhase} from '../../../postgres/types/NewMeetingPhase' import {analytics} from '../../../utils/analytics/analytics' @@ -15,6 +13,7 @@ import createGcalEvent from '../../mutations/helpers/createGcalEvent' import createNewMeetingPhases from '../../mutations/helpers/createNewMeetingPhases' import isStartMeetingLocked from '../../mutations/helpers/isStartMeetingLocked' import {IntegrationNotifier} from '../../mutations/helpers/notifications/IntegrationNotifier' +import {createMeetingMember} from '../../mutations/joinMeeting' import {MutationResolvers} from '../resolverTypes' const startCheckIn: MutationResolvers['startCheckIn'] = async ( @@ -72,22 +71,22 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( dataLoader.clearAll('newMeetings') const agendaItems = await dataLoader.get('agendaItemsByTeamId').load(teamId) const agendaItemIds = agendaItems.map(({id}) => id) - - const updates = { - lastMeetingType: meetingType - } + const meetingMember = createMeetingMember(meeting, { + userId: viewerId, + teamId, + isSpectatingPoker: false + }) await Promise.all([ - r - .table('MeetingMember') - .insert(new ActionMeetingMember({meetingId, userId: viewerId, teamId})) - .run(), - updateTeamByTeamId(updates, teamId), + pg + .with('TeamUpdates', (qb) => + qb.updateTable('Team').set({lastMeetingType: meetingType}).where('id', '=', teamId) + ) + .insertInto('MeetingMember') + .values(meetingMember) + .execute(), + r.table('MeetingMember').insert(meetingMember).run(), agendaItemIds.length && - getKysely() - .updateTable('AgendaItem') - .set({meetingId}) - .where('id', 'in', agendaItemIds) - .execute() + pg.updateTable('AgendaItem').set({meetingId}).where('id', 'in', agendaItemIds).execute() ]) IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting) diff --git a/packages/server/graphql/public/mutations/startRetrospective.ts b/packages/server/graphql/public/mutations/startRetrospective.ts index 6f82f89d7ef..8ab00de43e2 100644 --- a/packages/server/graphql/public/mutations/startRetrospective.ts +++ b/packages/server/graphql/public/mutations/startRetrospective.ts @@ -1,9 +1,7 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../../database/rethinkDriver' -import RetroMeetingMember from '../../../database/types/RetroMeetingMember' import getKysely from '../../../postgres/getKysely' import updateMeetingTemplateLastUsedAt from '../../../postgres/queries/updateMeetingTemplateLastUsedAt' -import updateTeamByTeamId from '../../../postgres/queries/updateTeamByTeamId' import {MeetingTypeEnum} from '../../../postgres/types/Meeting' import {analytics} from '../../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../../utils/authorization' @@ -14,6 +12,7 @@ import {createMeetingSeriesTitle} from '../../mutations/helpers/createMeetingSer import isStartMeetingLocked from '../../mutations/helpers/isStartMeetingLocked' import {IntegrationNotifier} from '../../mutations/helpers/notifications/IntegrationNotifier' import safeCreateRetrospective from '../../mutations/helpers/safeCreateRetrospective' +import {createMeetingMember} from '../../mutations/joinMeeting' import {MutationResolvers} from '../resolverTypes' import {startNewMeetingSeries} from './updateRecurrenceSettings' @@ -84,18 +83,21 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( return {error: {message: 'Meeting already started'}} } - const updates = { - lastMeetingType: meetingType - } + const meetingMember = createMeetingMember(meeting, { + userId: viewerId, + teamId, + isSpectatingPoker: false + }) const [meetingSeries] = await Promise.all([ rrule && startNewMeetingSeries(meeting, rrule, meetingSeriesName), - r - .table('MeetingMember') - .insert( - new RetroMeetingMember({meetingId, userId: viewerId, teamId, votesRemaining: totalVotes}) + pg + .with('TeamUpdates', (qb) => + qb.updateTable('Team').set({lastMeetingType: meetingType}).where('id', '=', teamId) ) - .run(), - updateTeamByTeamId(updates, teamId), + .insertInto('MeetingMember') + .values(meetingMember) + .execute(), + r.table('MeetingMember').insert(meetingMember).run(), videoMeetingURL && pg .updateTable('MeetingSettings') diff --git a/packages/server/graphql/public/types/ActionMeeting.ts b/packages/server/graphql/public/types/ActionMeeting.ts index 4e2928042e7..5a4e5ef7644 100644 --- a/packages/server/graphql/public/types/ActionMeeting.ts +++ b/packages/server/graphql/public/types/ActionMeeting.ts @@ -1,4 +1,5 @@ import toTeamMemberId from '../../../../client/utils/relay/toTeamMemberId' +import {ActionMeetingMember} from '../../../postgres/types/Meeting' import {getUserId} from '../../../utils/authorization' import filterTasksByMeeting from '../../../utils/filterTasksByMeeting' import {ActionMeetingResolvers} from '../resolverTypes' @@ -20,8 +21,10 @@ const ActionMeeting: ActionMeetingResolvers = { // only populated after the meeting has been completed (not killed) return commentCount || 0 }, - meetingMembers: ({id: meetingId}, _args, {dataLoader}) => { - return dataLoader.get('meetingMembersByMeetingId').load(meetingId) + meetingMembers: async ({id: meetingId}, _args, {dataLoader}) => { + return (await dataLoader + .get('meetingMembersByMeetingId') + .load(meetingId)) as ActionMeetingMember[] }, taskCount: async ({taskCount}) => { // only populated after the meeting has been completed (not killed) @@ -38,7 +41,7 @@ const ActionMeeting: ActionMeetingResolvers = { const viewerId = getUserId(authToken) const meetingMemberId = toTeamMemberId(meetingId, viewerId) const meetingMember = await dataLoader.get('meetingMembers').load(meetingMemberId) - return meetingMember || null + return (meetingMember as ActionMeetingMember) || null } } diff --git a/packages/server/graphql/public/types/MeetingSeries.ts b/packages/server/graphql/public/types/MeetingSeries.ts index bf66c401b52..f70571873ce 100644 --- a/packages/server/graphql/public/types/MeetingSeries.ts +++ b/packages/server/graphql/public/types/MeetingSeries.ts @@ -1,6 +1,5 @@ import MeetingSeriesId from 'parabol-client/shared/gqlIds/MeetingSeriesId' import {selectNewMeetings} from '../../../postgres/select' -import {AnyMeeting} from '../../../postgres/types/Meeting' import {MeetingSeriesResolvers} from '../resolverTypes' const MeetingSeries: MeetingSeriesResolvers = { @@ -16,7 +15,6 @@ const MeetingSeries: MeetingSeriesResolvers = { .where('meetingSeriesId', '=', meetingSeriesId) .orderBy(['endedAt desc', 'createdAt desc']) .limit(1) - .$narrowType() .executeTakeFirstOrThrow() return meeting } diff --git a/packages/server/graphql/public/types/RetrospectiveMeeting.ts b/packages/server/graphql/public/types/RetrospectiveMeeting.ts index 6b1fd9e1fd5..8d77b876562 100644 --- a/packages/server/graphql/public/types/RetrospectiveMeeting.ts +++ b/packages/server/graphql/public/types/RetrospectiveMeeting.ts @@ -1,9 +1,8 @@ import toTeamMemberId from '../../../../client/utils/relay/toTeamMemberId' -import RetroMeetingMember from '../../../database/types/RetroMeetingMember' +import {RetroMeetingMember} from '../../../postgres/types/Meeting' import {getUserId} from '../../../utils/authorization' import filterTasksByMeeting from '../../../utils/filterTasksByMeeting' import getPhase from '../../../utils/getPhase' -import {GQLContext} from '../../graphql' import {resolveForSU} from '../../resolvers' import {RetrospectiveMeetingResolvers} from '../resolverTypes' @@ -11,10 +10,9 @@ const RetrospectiveMeeting: RetrospectiveMeetingResolvers = { autoGroupThreshold: resolveForSU('autoGroupThreshold'), commentCount: ({commentCount}) => commentCount || 0, disableAnonymity: ({disableAnonymity}) => disableAnonymity ?? false, - meetingMembers: ({id: meetingId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('meetingMembersByMeetingId').load(meetingId) as Promise< - RetroMeetingMember[] - > + meetingMembers: async ({id: meetingId}, _args, {dataLoader}) => { + const res = await dataLoader.get('meetingMembersByMeetingId').load(meetingId) + return res as RetroMeetingMember[] }, reflectionCount: ({reflectionCount}) => reflectionCount || 0, reflectionGroup: async ({id: meetingId}, {reflectionGroupId}, {dataLoader}) => { @@ -50,7 +48,7 @@ const RetrospectiveMeeting: RetrospectiveMeetingResolvers = { return reflectionGroups }, taskCount: ({taskCount}) => taskCount || 0, - tasks: async ({id: meetingId}, _args: unknown, {authToken, dataLoader}) => { + tasks: async ({id: meetingId}, _args, {authToken, dataLoader}) => { const viewerId = getUserId(authToken) const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) const {teamId} = meeting @@ -58,17 +56,13 @@ const RetrospectiveMeeting: RetrospectiveMeetingResolvers = { return filterTasksByMeeting(teamTasks, meetingId, viewerId) }, topicCount: ({topicCount}) => topicCount || 0, - votesRemaining: async ({id: meetingId}, _args: unknown, {dataLoader}) => { + votesRemaining: async ({id: meetingId}, _args, {dataLoader}) => { const meetingMembers = (await dataLoader .get('meetingMembersByMeetingId') .load(meetingId)) as RetroMeetingMember[] return meetingMembers.reduce((sum, member) => sum + member.votesRemaining, 0) }, - viewerMeetingMember: async ( - {id: meetingId}, - _args: unknown, - {authToken, dataLoader}: GQLContext - ) => { + viewerMeetingMember: async ({id: meetingId}, _args, {authToken, dataLoader}) => { const viewerId = getUserId(authToken) const meetingMemberId = toTeamMemberId(meetingId, viewerId) const meetingMember = (await dataLoader diff --git a/packages/server/graphql/public/types/TeamMember.ts b/packages/server/graphql/public/types/TeamMember.ts index 477914e7a21..9d86d955e04 100644 --- a/packages/server/graphql/public/types/TeamMember.ts +++ b/packages/server/graphql/public/types/TeamMember.ts @@ -36,7 +36,7 @@ const TeamMember: TeamMemberResolvers = { meetingMember: async ({userId}, {meetingId}, {dataLoader}) => { const meetingMemberId = MeetingMemberId.join(meetingId, userId) - return meetingId ? dataLoader.get('meetingMembers').load(meetingMemberId) : undefined + return meetingId ? dataLoader.get('meetingMembers').load(meetingMemberId) : null }, prevUsedRepoIntegrations: async ({teamId, userId}, {first}, context) => { diff --git a/packages/server/graphql/public/types/User.ts b/packages/server/graphql/public/types/User.ts index 736e6b9b102..7dd6d2d68f5 100644 --- a/packages/server/graphql/public/types/User.ts +++ b/packages/server/graphql/public/types/User.ts @@ -13,7 +13,6 @@ import { import groupReflections from '../../../../client/utils/smartGroup/groupReflections' import getRethink from '../../../database/rethinkDriver' import {RDatum, RValue} from '../../../database/stricterR' -import MeetingMemberType from '../../../database/types/MeetingMember' import MeetingTemplate from '../../../database/types/MeetingTemplate' import Task from '../../../database/types/Task' import getKysely from '../../../postgres/getKysely' @@ -302,10 +301,7 @@ const User: ReqResolvers<'User'> = { lastMetAt: async ({id: userId}, _args, {dataLoader}) => { const meetingMembers = await dataLoader.get('meetingMembersByUserId').load(userId) - const lastMetAt = Math.max( - 0, - ...meetingMembers.map(({updatedAt}: MeetingMemberType) => updatedAt.getTime()) - ) + const lastMetAt = Math.max(0, ...meetingMembers.map(({updatedAt}) => updatedAt.getTime())) return lastMetAt ? new Date(lastMetAt) : null }, @@ -317,7 +313,7 @@ const User: ReqResolvers<'User'> = { monthlyStreakMax: async ({id: userId}, _args, {dataLoader}) => { const meetingMembers = await dataLoader.get('meetingMembersByUserId').load(userId) const meetingDates = meetingMembers - .map(({updatedAt}: MeetingMemberType) => updatedAt.getTime()) + .map(({updatedAt}) => updatedAt.getTime()) .sort((a, b) => (a < b ? 1 : -1)) return getMonthlyStreak(meetingDates) @@ -326,7 +322,7 @@ const User: ReqResolvers<'User'> = { monthlyStreakCurrent: async ({id: userId}, _args, {dataLoader}) => { const meetingMembers = await dataLoader.get('meetingMembersByUserId').load(userId) const meetingDates = meetingMembers - .map(({updatedAt}: MeetingMemberType) => updatedAt.getTime()) + .map(({updatedAt}) => updatedAt.getTime()) .sort((a, b) => (a < b ? 1 : -1)) return getMonthlyStreak(meetingDates, true) }, @@ -427,7 +423,7 @@ const User: ReqResolvers<'User'> = { meetingMember: async ({id: userId}, {meetingId}, {dataLoader}) => { const meetingMemberId = toTeamMemberId(meetingId, userId) - return meetingId ? dataLoader.get('meetingMembers').load(meetingMemberId) : undefined + return meetingId ? dataLoader.get('meetingMembers').load(meetingMemberId) : null }, organizationUser: async ({id: userId}, {orgId}, {authToken, dataLoader}) => { diff --git a/packages/server/postgres/migrations/1727893031268_MeetingMember-phase1.ts b/packages/server/postgres/migrations/1727893031268_MeetingMember-phase1.ts new file mode 100644 index 00000000000..d0c0eabfabb --- /dev/null +++ b/packages/server/postgres/migrations/1727893031268_MeetingMember-phase1.ts @@ -0,0 +1,49 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS "MeetingMember" ( + "id" VARCHAR(100) PRIMARY KEY, + "meetingType" "MeetingTypeEnum" NOT NULL, + "meetingId" VARCHAR(100) NOT NULL, + "teamId" VARCHAR(100) NOT NULL, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "userId" VARCHAR(100) NOT NULL, + "isSpectating" BOOLEAN, + "votesRemaining" SMALLINT, + CONSTRAINT "fk_meetingId" + FOREIGN KEY("meetingId") + REFERENCES "NewMeeting"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_teamId" + FOREIGN KEY("teamId") + REFERENCES "Team"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_userId" + FOREIGN KEY("userId") + REFERENCES "User"("id") + ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS "idx_MeetingMember_meetingId" ON "MeetingMember"("meetingId"); + CREATE INDEX IF NOT EXISTS "idx_MeetingMember_teamId" ON "MeetingMember"("teamId"); + CREATE INDEX IF NOT EXISTS "idx_MeetingMember_userId" ON "MeetingMember"("userId"); + DROP TRIGGER IF EXISTS "update_MeetingMember_updatedAt" ON "MeetingMember"; + CREATE TRIGGER "update_MeetingMember_updatedAt" BEFORE UPDATE ON "MeetingMember" 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 IF EXISTS "MeetingMember"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index d8baac2ce3d..b0c9a168e85 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -2,8 +2,8 @@ import type {JSONContent} from '@tiptap/core' import {NotNull, sql} from 'kysely' import {NewMeetingPhaseTypeEnum} from '../graphql/public/resolverTypes' import getKysely from './getKysely' -import {AutogroupReflectionGroupType, ReactjiDB, TranscriptBlock, UsedReactjis} from './types' -import type {NewMeetingPhase} from './types/NewMeetingPhase' +import {ReactjiDB} from './types' +import {AnyMeeting, AnyMeetingMember} from './types/Meeting' export const selectTimelineEvent = () => { return getKysely().selectFrom('TimelineEvent').selectAll().$narrowType< | { @@ -269,13 +269,13 @@ export const selectNewMeetings = () => 'videoMeetingURL', 'templateRefId', 'meetingPrompt', - fn('to_json', ['phases']).as('phases'), - fn('to_json', ['usedReactjis']).as('usedReactjis'), - fn('to_json', ['transcription']).as('transcription'), - fn('to_json', ['autogroupReflectionGroups']).as( - 'autogroupReflectionGroups' - ), - fn('to_json', ['resetReflectionGroups']).as( - 'resetReflectionGroups' - ) + fn('to_json', ['phases']).as('phases'), + fn('to_json', ['usedReactjis']).as('usedReactjis'), + fn('to_json', ['transcription']).as('transcription'), + fn('to_json', ['autogroupReflectionGroups']).as('autogroupReflectionGroups'), + fn('to_json', ['resetReflectionGroups']).as('resetReflectionGroups') ]) + .$narrowType() + +export const selectMeetingMembers = () => + getKysely().selectFrom('MeetingMember').selectAll().$narrowType() diff --git a/packages/server/postgres/types/Meeting.d.ts b/packages/server/postgres/types/Meeting.d.ts index b4d38c02e0e..4dfa0d3e4f5 100644 --- a/packages/server/postgres/types/Meeting.d.ts +++ b/packages/server/postgres/types/Meeting.d.ts @@ -1,12 +1,7 @@ +import {Selectable} from 'kysely' import {NonNullableProps} from '../../../client/types/generics' -import ActionMeetingMember from '../../database/types/ActionMeetingMember' -import PokerMeetingMember from '../../database/types/PokerMeetingMember' -import RetroMeetingMember from '../../database/types/RetroMeetingMember' -import TeamPromptMeetingMember from '../../database/types/TeamPromptMeetingMember' -import {NewMeeting as NewMeetingDB} from '../pg' -import {NewMeeting} from './index.d' - -import {Insertable} from 'kysely' +import {NewMeeting as NewMeetingPG} from '../pg.d' +import {AutogroupReflectionGroupType, UsedReactjis} from './index.d' import { CheckInMeetingPhase, NewMeetingPhase, @@ -15,6 +10,7 @@ import { TeamPromptPhase } from './NewMeetingPhase' +type NewMeeting = Selectable export type MeetingTypeEnum = NewMeeting['meetingType'] type BaseNewMeeting = Pick< @@ -38,19 +34,9 @@ type BaseNewMeeting = Pick< | 'scheduledEndTime' | 'summary' | 'sentimentScore' - | 'usedReactjis' | 'slackTs' | 'engagement' -> & {phases: NewMeetingPhase[]} - -type InsertableRetrospectiveMeeting = Insertable & { - meetingType: 'retrospective' - phases: RetroMeetingPhase[] - totalVotes: number - maxVotesPerGroup: number - disableAnonymity: boolean - templateId: string -} +> & {phases: NewMeetingPhase[]; usedReactjis: UsedReactjis | null} export type RetrospectiveMeeting = BaseNewMeeting & NonNullableProps< @@ -62,14 +48,14 @@ export type RetrospectiveMeeting = BaseNewMeeting & | 'taskCount' | 'topicCount' | 'reflectionCount' - | 'transcription' | 'recallBotId' | 'videoMeetingURL' - | 'autogroupReflectionGroups' - | 'resetReflectionGroups' > & { meetingType: 'retrospective' phases: RetroMeetingPhase[] + autogroupReflectionGroups: AutogroupReflectionGroupType[] | null + resetReflectionGroups: AutogroupReflectionGroupType[] | null + transcription: TranscriptBlock[] | null } export type PokerMeeting = BaseNewMeeting & @@ -93,7 +79,32 @@ export type TeamPromptMeeting = BaseNewMeeting & export type AnyMeeting = RetrospectiveMeeting | PokerMeeting | CheckInMeeting | TeamPromptMeeting -export type AnyMeetingTeamMember = +export interface MeetingMember { + id: string + meetingType: MeetingTypeEnum + meetingId: string + teamId: string + updatedAt: Date + userId: string +} + +export interface ActionMeetingMember extends MeetingMember { + meetingType: 'action' +} + +export interface TeamPromptMeetingMember extends MeetingMember { + meetingType: 'teamPrompt' +} +export interface RetroMeetingMember extends MeetingMember { + meetingType: 'retrospective' + votesRemaining: number +} + +export type PokerMeetingMember = MeetingMember & { + meetingType: 'poker' + isSpectating: boolean +} +export type AnyMeetingMember = | PokerMeetingMember | RetroMeetingMember | ActionMeetingMember diff --git a/packages/server/utils/analytics/analytics.ts b/packages/server/utils/analytics/analytics.ts index c5767be4733..0c32e1b0881 100644 --- a/packages/server/utils/analytics/analytics.ts +++ b/packages/server/utils/analytics/analytics.ts @@ -3,14 +3,18 @@ import type {UpgradeCTALocationEnumType} from '../../../client/shared/UpgradeCTA import TeamPromptResponseId from '../../../client/shared/gqlIds/TeamPromptResponseId' import {PARABOL_AI_USER_ID} from '../../../client/utils/constants' import {TeamLimitsEmailType} from '../../billing/helpers/sendTeamsLimitEmail' -import MeetingMember from '../../database/types/MeetingMember' import MeetingTemplate from '../../database/types/MeetingTemplate' import {TaskServiceEnum} from '../../database/types/Task' import {DataLoaderWorker} from '../../graphql/graphql' import {ModifyType, ReactableEnum} from '../../graphql/public/resolverTypes' import {IntegrationProviderServiceEnumType} from '../../graphql/types/IntegrationProviderServiceEnum' import {SlackNotification, TeamPromptResponse, TemplateScale} from '../../postgres/types' -import {AnyMeeting, MeetingTypeEnum, RetrospectiveMeeting} from '../../postgres/types/Meeting' +import { + AnyMeeting, + AnyMeetingMember, + MeetingTypeEnum, + RetrospectiveMeeting +} from '../../postgres/types/Meeting' import {MeetingSeries} from '../../postgres/types/MeetingSeries' import {AmplitudeAnalytics} from './amplitude/AmplitudeAnalytics' import {createMeetingProperties} from './helpers' @@ -191,7 +195,7 @@ class Analytics { // meeting teamPromptEnd = async ( completedMeeting: AnyMeeting, - meetingMembers: MeetingMember[], + meetingMembers: AnyMeetingMember[], responses: TeamPromptResponse[], dataLoader: DataLoaderWorker ) => { @@ -218,7 +222,7 @@ class Analytics { checkInEnd = async ( completedMeeting: AnyMeeting, - meetingMembers: MeetingMember[], + meetingMembers: AnyMeetingMember[], dataLoader: DataLoaderWorker ) => Promise.all( @@ -236,7 +240,7 @@ class Analytics { retrospectiveEnd = async ( completedMeeting: RetrospectiveMeeting, - meetingMembers: MeetingMember[], + meetingMembers: AnyMeetingMember[], template: MeetingTemplate, dataLoader: DataLoaderWorker ) => { @@ -259,7 +263,7 @@ class Analytics { sprintPokerEnd = ( completedMeeting: AnyMeeting, - meetingMembers: MeetingMember[], + meetingMembers: AnyMeetingMember[], template: MeetingTemplate, dataLoader: DataLoaderWorker ) => { @@ -280,7 +284,7 @@ class Analytics { dataloader: DataLoaderWorker, userId: string, completedMeeting: AnyMeeting, - meetingMembers: MeetingMember[], + meetingMembers: AnyMeetingMember[], template?: MeetingTemplate, meetingSpecificProperties?: any ) => { diff --git a/packages/server/utils/analytics/helpers.ts b/packages/server/utils/analytics/helpers.ts index 1e9f965b50c..38313c235a7 100644 --- a/packages/server/utils/analytics/helpers.ts +++ b/packages/server/utils/analytics/helpers.ts @@ -1,11 +1,10 @@ import {CHECKIN} from '../../../client/utils/constants' -import MeetingMember from '../../database/types/MeetingMember' import MeetingTemplate from '../../database/types/MeetingTemplate' -import {AnyMeeting} from '../../postgres/types/Meeting' +import {AnyMeeting, AnyMeetingMember} from '../../postgres/types/Meeting' export const createMeetingProperties = ( meeting: AnyMeeting, - meetingMembers?: MeetingMember[], + meetingMembers?: AnyMeetingMember[], template?: MeetingTemplate ) => { const {id: meetingId, teamId, facilitatorUserId, meetingType, phases} = meeting From f8080940015f66ad47853e48dd562c34501be6d2 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 15:20:05 -0700 Subject: [PATCH 499/529] chore(release): release v7.50.3 (#10310) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 021ff70bd94..592c1063c19 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.50.2" + ".": "7.50.3" } diff --git a/CHANGELOG.md b/CHANGELOG.md index a4f4da458e0..708cb195d1e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.50.3](https://github.com/ParabolInc/parabol/compare/v7.50.2...v7.50.3) (2024-10-08) + + +### Fixed + +* **webserver:** exits with code 0 when SIGTERM is handled ([#10301](https://github.com/ParabolInc/parabol/issues/10301)) ([de317d2](https://github.com/ParabolInc/parabol/commit/de317d2dabcf6834b0564f7aab1af5ad02443d66)) + + +### Changed + +* **rethinkdb:** MeetingMember: Phase 1 ([#10289](https://github.com/ParabolInc/parabol/issues/10289)) ([abd8281](https://github.com/ParabolInc/parabol/commit/abd8281cc7e637311ea0920f21b8822b42396acd)) +* **rethinkdb:** NewMeeting: Phase 3 ([#10273](https://github.com/ParabolInc/parabol/issues/10273)) ([1667810](https://github.com/ParabolInc/parabol/commit/1667810a192e82bd8d2cd49b2ed0ba138559458f)) + ## [7.50.2](https://github.com/ParabolInc/parabol/compare/v7.50.1...v7.50.2) (2024-10-07) diff --git a/package.json b/package.json index a448f4d9418..f2a58965007 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.50.2", + "version": "7.50.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 31081b2540c..7533c63d598 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.50.2", + "version": "7.50.3", "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.50.2" + "parabol-server": "7.50.3" } } diff --git a/packages/client/package.json b/packages/client/package.json index e71fba695ff..51fe9c164b7 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.50.2", + "version": "7.50.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 02de87fa8ea..e1c812cb800 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.50.2", + "version": "7.50.3", "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 26f0bf114e8..e5ad7ec4504 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.50.2", + "version": "7.50.3", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.50.2", - "parabol-server": "7.50.2", + "parabol-client": "7.50.3", + "parabol-server": "7.50.3", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 3bf110af28f..0db43982d1b 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.50.2", + "version": "7.50.3", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 8ed83890863..b1613f7eacc 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.50.2", + "version": "7.50.3", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.50.2", + "parabol-client": "7.50.3", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From af50d0a1420ae21b4e48ddc040c0d79f9d81e2cf Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 8 Oct 2024 15:32:05 -0700 Subject: [PATCH 500/529] chore(rethinkdb): MeetingMember: Phase 2 (#10294) Signed-off-by: Matt Krick --- .../1727995266026_MeetingMember-phase2.ts | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 packages/server/postgres/migrations/1727995266026_MeetingMember-phase2.ts diff --git a/packages/server/postgres/migrations/1727995266026_MeetingMember-phase2.ts b/packages/server/postgres/migrations/1727995266026_MeetingMember-phase2.ts new file mode 100644 index 00000000000..650075cde3c --- /dev/null +++ b/packages/server/postgres/migrations/1727995266026_MeetingMember-phase2.ts @@ -0,0 +1,117 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + try { + console.log('Adding index') + await r + .table('MeetingMember') + .indexCreate('updatedAtId', (row: any) => [row('updatedAt'), row('id')]) + .run() + await r.table('MeetingMember').indexWait().run() + } catch { + // index already exists + } + + console.log('Adding index complete') + + const MAX_PG_PARAMS = 65545 + const PG_COLS = [ + 'id', + 'meetingType', + 'meetingId', + 'teamId', + 'updatedAt', + 'userId', + 'isSpectating', + 'votesRemaining' + ] as const + type MeetingMember = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + + let curUpdatedAt = r.minval + let curId = r.minval + + const insertRow = async (row) => { + if (!row.teamId) { + console.log('MeetingMember has no teamId, skipping insert', row.id) + return + } + try { + await pg + .insertInto('MeetingMember') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_teamId') { + console.log('MeetingMember has no team, skipping insert', row.id) + return + } + if (e.constraint === 'fk_meetingId') { + console.log('MeetingMember has no meeting, skipping insert', row.id) + return + } + if (e.constraint === 'fk_userId') { + console.log('MeetingMember has no user, skipping insert', row.id) + return + } + throw e + } + } + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, String(curUpdatedAt), String(curId)) + const rawRowsToInsert = (await r + .table('MeetingMember') + .between([curUpdatedAt, curId], [r.maxval, r.maxval], { + index: 'updatedAtId', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'updatedAtId'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as MeetingMember[] + + const rowsToInsert = rawRowsToInsert.map((row) => { + const {id, meetingType, meetingId, teamId, updatedAt, userId, isSpectating, votesRemaining} = + row as any + return { + id, + meetingType, + meetingId, + teamId, + updatedAt, + userId, + isSpectating, + votesRemaining + } + }) + + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curUpdatedAt = lastRow.updatedAt + curId = lastRow.id + await Promise.all(rowsToInsert.map(async (row) => insertRow(row))) + } +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql`TRUNCATE TABLE "MeetingMember" CASCADE`.execute(pg) +} From dee4e0f0119bd46167175043405bc24abd49d88b Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 8 Oct 2024 15:50:29 -0700 Subject: [PATCH 501/529] chore(rethinkdb): MeetingMember: Phase 3 (#10298) Signed-off-by: Matt Krick --- packages/server/database/rethinkDriver.ts | 5 - .../dataloader/foreignKeyLoaderMakers.ts | 8 +- .../dataloader/primaryKeyLoaderMakers.ts | 2 +- .../rethinkForeignKeyLoaderMakers.ts | 17 --- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../mutations/helpers/removeTeamMember.ts | 6 - .../mutations/helpers/safelyCastVote.ts | 95 ++++--------- .../mutations/helpers/safelyWithdrawVote.ts | 81 ++++++----- .../server/graphql/mutations/joinMeeting.ts | 7 - .../graphql/mutations/newMeetingCheckIn.ts | 25 ---- .../resetRetroMeetingToGroupStage.ts | 7 +- .../graphql/mutations/setPokerSpectate.ts | 7 +- .../graphql/mutations/startSprintPoker.ts | 17 +-- .../graphql/mutations/updateRetroMaxVotes.ts | 130 ++++-------------- .../mutations/voteForReflectionGroup.ts | 17 +-- .../private/mutations/hardDeleteUser.ts | 1 - .../graphql/private/queries/orgActivities.ts | 26 ++-- .../graphql/public/mutations/startCheckIn.ts | 3 - .../public/mutations/startRetrospective.ts | 3 - .../subscriptions/meetingSubscription.ts | 9 +- .../graphql/public/typeDefs/Mutation.graphql | 21 --- .../public/types/NewMeetingTeamMemberStage.ts | 2 +- .../server/graphql/public/types/TeamMember.ts | 2 +- packages/server/graphql/public/types/User.ts | 2 +- .../helpers/getActiveTeamCountByTeamIds.ts | 9 +- packages/server/graphql/rootMutation.ts | 2 - .../server/graphql/types/MeetingMember.ts | 8 -- .../graphql/types/NewMeetingCheckInPayload.ts | 25 ---- 28 files changed, 138 insertions(+), 400 deletions(-) delete mode 100644 packages/server/graphql/mutations/newMeetingCheckIn.ts delete mode 100644 packages/server/graphql/types/MeetingMember.ts delete mode 100644 packages/server/graphql/types/NewMeetingCheckInPayload.ts diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index f1049f80727..b76040d74d6 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -1,6 +1,5 @@ import {MasterPool, r} from 'rethinkdb-ts' import TeamInvitation from '../database/types/TeamInvitation' -import {AnyMeetingMember} from '../postgres/types/Meeting' import getRethinkConfig from './getRethinkConfig' import {R} from './stricterR' import MassInvitation from './types/MassInvitation' @@ -21,10 +20,6 @@ export type RethinkSchema = { type: MassInvitation index: 'teamMemberId' } - MeetingMember: { - type: AnyMeetingMember - index: 'meetingId' | 'teamId' | 'userId' - } NewFeature: { type: any index: '' diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index be87e4620ea..d1e8d1060f2 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -253,16 +253,16 @@ export const completedMeetingsByTeamId = foreignKeyLoaderMaker( } ) -export const _pgmeetingMembersByMeetingId = foreignKeyLoaderMaker( - '_pgmeetingMembers', +export const meetingMembersByMeetingId = foreignKeyLoaderMaker( + 'meetingMembers', 'meetingId', async (meetingIds) => { return selectMeetingMembers().where('meetingId', 'in', meetingIds).execute() } ) -export const _pgmeetingMembersByUserId = foreignKeyLoaderMaker( - '_pgmeetingMembers', +export const meetingMembersByUserId = foreignKeyLoaderMaker( + 'meetingMembers', 'userId', async (userIds) => { return selectMeetingMembers().where('userId', 'in', userIds).execute() diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 09053b04b43..b7d005d1f6f 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -126,6 +126,6 @@ export const newMeetings = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectNewMeetings().where('id', 'in', ids).execute() }) -export const _pgmeetingMembers = primaryKeyLoaderMaker((ids: readonly string[]) => { +export const meetingMembers = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectMeetingMembers().where('id', 'in', ids).execute() }) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index 06741f45f69..32bf2c76d6c 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -14,23 +14,6 @@ export const massInvitationsByTeamMemberId = new RethinkForeignKeyLoaderMaker( .run() } ) -export const meetingMembersByMeetingId = new RethinkForeignKeyLoaderMaker( - 'meetingMembers', - 'meetingId', - async (meetingIds) => { - const r = await getRethink() - return r.table('MeetingMember').getAll(r.args(meetingIds), {index: 'meetingId'}).run() - } -) - -export const meetingMembersByUserId = new RethinkForeignKeyLoaderMaker( - 'meetingMembers', - 'userId', - async (userIds) => { - const r = await getRethink() - return r.table('MeetingMember').getAll(r.args(userIds), {index: 'userId'}).run() - } -) export const tasksByDiscussionId = new RethinkForeignKeyLoaderMaker( 'tasks', diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index 0bef3795af3..a0b698abd6e 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -4,7 +4,6 @@ import RethinkPrimaryKeyLoaderMaker from './RethinkPrimaryKeyLoaderMaker' * all rethink dataloader types which also must exist in {@link rethinkDriver/RethinkSchema} */ export const massInvitations = new RethinkPrimaryKeyLoaderMaker('MassInvitation') -export const meetingMembers = new RethinkPrimaryKeyLoaderMaker('MeetingMember') export const newFeatures = new RethinkPrimaryKeyLoaderMaker('NewFeature') export const notifications = new RethinkPrimaryKeyLoaderMaker('Notification') export const tasks = new RethinkPrimaryKeyLoaderMaker('Task') diff --git a/packages/server/graphql/mutations/helpers/removeTeamMember.ts b/packages/server/graphql/mutations/helpers/removeTeamMember.ts index 49fd21ce16e..48e7551fa50 100644 --- a/packages/server/graphql/mutations/helpers/removeTeamMember.ts +++ b/packages/server/graphql/mutations/helpers/removeTeamMember.ts @@ -135,12 +135,6 @@ const removeTeamMember = async ( // Reassign facilitator for meetings this user is facilitating. if (meetingIds.length > 0) { - await r - .table('MeetingMember') - .getAll(r.args(meetingIds), {index: 'meetingId'}) - .filter({userId}) - .delete() - .run() const facilitatingMeetings = await pg .with('DeleteMeetingMembers', (qb) => qb diff --git a/packages/server/graphql/mutations/helpers/safelyCastVote.ts b/packages/server/graphql/mutations/helpers/safelyCastVote.ts index edac81955fb..d8393a14671 100644 --- a/packages/server/graphql/mutations/helpers/safelyCastVote.ts +++ b/packages/server/graphql/mutations/helpers/safelyCastVote.ts @@ -1,88 +1,49 @@ import {sql} from 'kysely' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' -import getRethink from '../../../database/rethinkDriver' -import {RValue} from '../../../database/stricterR' -import AuthToken from '../../../database/types/AuthToken' import getKysely from '../../../postgres/getKysely' -import {getUserId} from '../../../utils/authorization' -import standardError from '../../../utils/standardError' -const safelyCastVote = async ( - authToken: AuthToken, - meetingId: string, - userId: string, - reflectionGroupId: string, - maxVotesPerGroup: number -) => { +const safelyCastVote = async (meetingId: string, userId: string, reflectionGroupId: string) => { const meetingMemberId = toTeamMemberId(meetingId, userId) - const r = await getRethink() const pg = getKysely() - const now = new Date() - const viewerId = getUserId(authToken) - const isVoteRemovedFromUser = await r - .table('MeetingMember') - .get(meetingMemberId) - .update((member: RValue) => { - // go atomic. no cheating allowed - return r.branch( - member('votesRemaining').ge(1), - { - updatedAt: now, - votesRemaining: member('votesRemaining').sub(1) - }, - {} - ) - })('replaced') - .eq(1) - .run() - if (!isVoteRemovedFromUser) { - return standardError(new Error('No votes remaining'), {userId: viewerId}) - } - - // in a transaction add to the reflection group and the meeting member try { await pg.transaction().execute(async (trx) => { - const res = await trx + // Lock the rows here in case updateRetroMaxVotes gets called, which could use stale values + const [meetingMember, reflectionGroup] = await Promise.all([ + trx + .selectFrom('MeetingMember') + .select('id') + .where('id', '=', meetingMemberId) + .where('votesRemaining', '>', 0) + .forUpdate() + .executeTakeFirst(), + trx + .selectFrom('RetroReflectionGroup') + .select('id') + .where('id', '=', reflectionGroupId) + .where('isActive', '=', true) + .where(({eb, selectFrom}) => + eb( + sql`COALESCE(array_length(array_positions("voterIds", ${userId}),1),0)`, + '<', + selectFrom('NewMeeting').select('maxVotesPerGroup').where('id', '=', meetingId) + ) + ) + .forUpdate() + .executeTakeFirst() + ]) + if (!meetingMember) throw new Error('No votes remaining') + if (!reflectionGroup) throw new Error('Max votes per group reached') + await trx .with('MeetingMemberUpdate', (qb) => qb .updateTable('MeetingMember') .set((eb) => ({votesRemaining: eb('votesRemaining', '-', 1)})) .where('id', '=', meetingMemberId) - .returning('id') ) .updateTable('RetroReflectionGroup') .set({voterIds: sql`ARRAY_APPEND("voterIds",${userId})`}) .where('id', '=', reflectionGroupId) - .where( - sql`COALESCE(array_length(array_positions("voterIds", ${userId}),1),0)`, - '<', - maxVotesPerGroup - ) - .returning((eb) => [ - 'id', - eb.selectFrom('MeetingMemberUpdate').select('id').as('meetingMemberUpdate') - ]) .executeTakeFirst() - - if (!res) { - await r - .table('MeetingMember') - .get(meetingMemberId) - .update((member: RValue) => ({ - votesRemaining: member('votesRemaining').add(1) - })) - .run() - throw new Error('Max votes per group exceeded') - } - if (!res.meetingMemberUpdate) { - // just for phase 2, make sure the row exists in the DB - const hasMember = await trx - .selectFrom('MeetingMember') - .select('id') - .where('id', '=', meetingMemberId) - .executeTakeFirst() - if (hasMember) throw new Error('No votes remaining') - } }) } catch (e) { return {error: {message: (e as Error).message}} diff --git a/packages/server/graphql/mutations/helpers/safelyWithdrawVote.ts b/packages/server/graphql/mutations/helpers/safelyWithdrawVote.ts index b91452779b3..9a3a49e485b 100644 --- a/packages/server/graphql/mutations/helpers/safelyWithdrawVote.ts +++ b/packages/server/graphql/mutations/helpers/safelyWithdrawVote.ts @@ -1,51 +1,58 @@ import {sql} from 'kysely' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' -import getRethink from '../../../database/rethinkDriver' -import {RValue} from '../../../database/stricterR' -import AuthToken from '../../../database/types/AuthToken' import getKysely from '../../../postgres/getKysely' -import {getUserId} from '../../../utils/authorization' -import standardError from '../../../utils/standardError' -const safelyWithdrawVote = async ( - authToken: AuthToken, - meetingId: string, - userId: string, - reflectionGroupId: string -) => { +const safelyWithdrawVote = async (meetingId: string, userId: string, reflectionGroupId: string) => { const meetingMemberId = toTeamMemberId(meetingId, userId) - const r = await getRethink() const pg = getKysely() - const now = new Date() - const viewerId = getUserId(authToken) - const voteRemovedResult = await pg - .updateTable('RetroReflectionGroup') - .set({ - voterIds: sql`array_cat( + try { + await pg.transaction().execute(async (trx) => { + // Lock the rows here in case updateRetroMaxVotes gets called, which could use stale values + const [meetingMember, reflectionGroup] = await Promise.all([ + trx + .selectFrom('MeetingMember') + .select('id') + .where('id', '=', meetingMemberId) + .where(({eb, selectFrom}) => + eb( + 'votesRemaining', + '<', + selectFrom('NewMeeting').select('totalVotes').where('id', '=', meetingId) + ) + ) + .forUpdate() + .executeTakeFirst(), + trx + .selectFrom('RetroReflectionGroup') + .select('id') + .where('id', '=', reflectionGroupId) + .where('isActive', '=', true) + .where(sql`${userId}`, '=', sql`ANY("voterIds")`) + .forUpdate() + .executeTakeFirst() + ]) + if (!meetingMember) throw new Error('Vote already withdrawn') + if (!reflectionGroup) throw new Error('Group vote already withdrawn') + await trx + .with('MeetingMemberUpdate', (qb) => + qb + .updateTable('MeetingMember') + .set((eb) => ({votesRemaining: eb('votesRemaining', '+', 1)})) + .where('id', '=', meetingMemberId) + ) + .updateTable('RetroReflectionGroup') + .set({ + voterIds: sql`array_cat( "voterIds"[1:array_position("voterIds",${userId})-1], "voterIds"[array_position("voterIds",${userId})+1:] )` + }) + .where('id', '=', reflectionGroupId) + .execute() }) - .where('id', '=', reflectionGroupId) - .where(sql`${userId}`, '=', sql`ANY("voterIds")`) - .executeTakeFirst() - const isVoteRemovedFromGroup = voteRemovedResult.numUpdatedRows === BigInt(1) - if (!isVoteRemovedFromGroup) { - return standardError(new Error('Already removed vote'), {userId: viewerId}) + } catch (e) { + return {error: {message: (e as Error).message}} } - await r - .table('MeetingMember') - .get(meetingMemberId) - .update((member: RValue) => ({ - updatedAt: now, - votesRemaining: member('votesRemaining').add(1) - })) - .run() - await pg - .updateTable('MeetingMember') - .set((eb) => ({votesRemaining: eb('votesRemaining', '+', 1)})) - .where('id', '=', meetingMemberId) - .execute() return undefined } diff --git a/packages/server/graphql/mutations/joinMeeting.ts b/packages/server/graphql/mutations/joinMeeting.ts index 2d7cbb7002d..9ae3eec2766 100644 --- a/packages/server/graphql/mutations/joinMeeting.ts +++ b/packages/server/graphql/mutations/joinMeeting.ts @@ -3,7 +3,6 @@ import {Insertable} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import MeetingMemberId from '../../../client/shared/gqlIds/MeetingMemberId' import toTeamMemberId from '../../../client/utils/relay/toTeamMemberId' -import getRethink from '../../database/rethinkDriver' import CheckInStage from '../../database/types/CheckInStage' import TeamPromptResponseStage from '../../database/types/TeamPromptResponseStage' import UpdatesStage from '../../database/types/UpdatesStage' @@ -51,7 +50,6 @@ const joinMeeting = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { const pg = getKysely() - const r = await getRethink() const viewerId = getUserId(authToken) const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -73,11 +71,6 @@ const joinMeeting = { const teamMemberId = toTeamMemberId(teamId, viewerId) const teamMember = await dataLoader.get('teamMembers').loadNonNull(teamMemberId) const meetingMember = createMeetingMember(meeting, teamMember) - const {errors} = await r.table('MeetingMember').insert(meetingMember).run() - // if this is called concurrently, only 1 will be error free - if (errors > 0) { - return {error: {message: 'Already joined meeting'}} - } await pg.insertInto('MeetingMember').values(meetingMember).execute() const addStageToPhase = async ( stage: CheckInStage | UpdatesStage | TeamPromptResponseStage, diff --git a/packages/server/graphql/mutations/newMeetingCheckIn.ts b/packages/server/graphql/mutations/newMeetingCheckIn.ts deleted file mode 100644 index e8ee23122f1..00000000000 --- a/packages/server/graphql/mutations/newMeetingCheckIn.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {GraphQLBoolean, GraphQLID, GraphQLNonNull} from 'graphql' -import NewMeetingCheckInPayload from '../types/NewMeetingCheckInPayload' - -export default { - type: NewMeetingCheckInPayload, - description: 'Check a member in as present or absent', - deprecationReason: 'Members now join lazily and joining means they are present', - args: { - userId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The id of the user being marked present or absent' - }, - meetingId: { - type: new GraphQLNonNull(GraphQLID), - description: 'the meeting currently in progress' - }, - isCheckedIn: { - type: GraphQLBoolean, - description: 'true if the member is present, false if absent, null if undecided' - } - }, - async resolve(_source: unknown, {userId, meetingId}: {userId: string; meetingId: string}) { - return {meetingId, userId} - } -} diff --git a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts index fd89e91f137..6bd36cb6eb1 100644 --- a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts +++ b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts @@ -124,12 +124,7 @@ const resetRetroMeetingToGroupStage = { .set({phases: JSON.stringify(newPhases)}) .where('id', '=', meetingId) .execute(), - r.table('Task').getAll(r.args(discussionIdsToDelete), {index: 'discussionId'}).delete().run(), - r - .table('MeetingMember') - .getAll(meetingId, {index: 'meetingId'}) - .update({votesRemaining: meeting.totalVotes}) - .run() + r.table('Task').getAll(r.args(discussionIdsToDelete), {index: 'discussionId'}).delete().run() ]) dataLoader.clearAll([ 'newMeetings', diff --git a/packages/server/graphql/mutations/setPokerSpectate.ts b/packages/server/graphql/mutations/setPokerSpectate.ts index 173b63720a2..9dfe7099fec 100644 --- a/packages/server/graphql/mutations/setPokerSpectate.ts +++ b/packages/server/graphql/mutations/setPokerSpectate.ts @@ -1,7 +1,6 @@ import {GraphQLBoolean, GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import toTeamMemberId from '../../../client/utils/relay/toTeamMemberId' -import getRethink from '../../database/rethinkDriver' import EstimateStage from '../../database/types/EstimateStage' import getKysely from '../../postgres/getKysely' import {getUserId} from '../../utils/authorization' @@ -28,7 +27,6 @@ const setPokerSpectate = { {meetingId, isSpectating}: {meetingId: string; isSpectating: boolean}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { - const r = await getRethink() const pg = getKysely() const viewerId = getUserId(authToken) const operationId = dataLoader.share() @@ -37,7 +35,7 @@ const setPokerSpectate = { //AUTH const meetingMemberId = toTeamMemberId(meetingId, viewerId) const [meetingMember, meeting] = await Promise.all([ - dataLoader.get('meetingMembers').load(meetingMemberId), + dataLoader.get('meetingMembers').loadNonNull(meetingMemberId), dataLoader.get('newMeetings').load(meetingId) ]) if (!meeting) { @@ -71,9 +69,6 @@ const setPokerSpectate = { .set({isSpectatingPoker: isSpectating}) .where('id', '=', teamMemberId) .execute() - await r({ - meetingMember: r.table('MeetingMember').get(meetingMemberId).update({isSpectating}) - }).run() dataLoader.clearAll('teamMembers') // mutate the dataLoader cache meetingMember.isSpectating = isSpectating diff --git a/packages/server/graphql/mutations/startSprintPoker.ts b/packages/server/graphql/mutations/startSprintPoker.ts index bef930b4c60..3396c6921bc 100644 --- a/packages/server/graphql/mutations/startSprintPoker.ts +++ b/packages/server/graphql/mutations/startSprintPoker.ts @@ -1,7 +1,6 @@ import {GraphQLID, GraphQLNonNull, GraphQLString} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import toTeamMemberId from '../../../client/utils/relay/toTeamMemberId' -import getRethink from '../../database/rethinkDriver' import MeetingPoker from '../../database/types/MeetingPoker' import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' @@ -97,7 +96,6 @@ export default { {authToken, socketId: mutatorId, dataLoader}: GQLContext ) { const pg = getKysely() - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const viewerId = getUserId(authToken) @@ -161,15 +159,12 @@ export default { const teamMemberId = toTeamMemberId(teamId, viewerId) const teamMember = await dataLoader.get('teamMembers').loadNonNull(teamMemberId) const meetingMember = createMeetingMember(meeting, teamMember) - await Promise.all([ - pg - .with('MeetingMemberInsert', (qb) => qb.insertInto('MeetingMember').values(meetingMember)) - .updateTable('Team') - .set({lastMeetingType: meetingType}) - .where('id', '=', teamId) - .execute(), - r.table('MeetingMember').insert(meetingMember).run() - ]) + await pg + .with('MeetingMemberInsert', (qb) => qb.insertInto('MeetingMember').values(meetingMember)) + .updateTable('Team') + .set({lastMeetingType: meetingType}) + .where('id', '=', teamId) + .execute() IntegrationNotifier.startMeeting(dataLoader, meetingId, teamId) analytics.meetingStarted(viewer, meeting, template) const {error} = await createGcalEvent({ diff --git a/packages/server/graphql/mutations/updateRetroMaxVotes.ts b/packages/server/graphql/mutations/updateRetroMaxVotes.ts index 847a480ba37..4e4bc0c91ea 100644 --- a/packages/server/graphql/mutations/updateRetroMaxVotes.ts +++ b/packages/server/graphql/mutations/updateRetroMaxVotes.ts @@ -1,9 +1,7 @@ import {GraphQLID, GraphQLInt, GraphQLNonNull} from 'graphql' import {MeetingSettingsThreshold, SubscriptionChannel} from 'parabol-client/types/constEnums' import isPhaseComplete from 'parabol-client/utils/meetings/isPhaseComplete' -import mode from 'parabol-client/utils/mode' -import getRethink from '../../database/rethinkDriver' -import {RValue} from '../../database/stricterR' +import mode from '../../../client/utils/mode' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -38,7 +36,6 @@ const updateRetroMaxVotes = { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) => { const pg = getKysely() - const r = await getRethink() const viewerId = getUserId(authToken) const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -89,107 +86,46 @@ const updateRetroMaxVotes = { } const delta = totalVotes - oldTotalVotes - // this isn't 100% atomic, but it's done in a single call, so it's pretty close - // eventual consistancy is OK, it's just possible for a client to get a bad data in between the 2 updates - // if votesRemaining goes negative for any user, we know we can't decrease any more - const hasError = await r - .table('MeetingMember') - .getAll(meetingId, {index: 'meetingId'}) - .update( - (member: RValue) => ({ - votesRemaining: member('votesRemaining').add(delta) - }), - {returnChanges: true} - )('changes')('new_val')('votesRemaining') - .min() - .lt(0) - .default(false) - .do((undo: RValue) => { - return r.branch( - undo, - r - .table('MeetingMember') - .getAll(meetingId, {index: 'meetingId'}) - .update((member: RValue) => ({ - votesRemaining: member('votesRemaining').add(-delta) - })), - null - ) - }) - .run() - - if (hasError) { - return {error: {message: 'Your team has already spent their votes'}} - } - - if (maxVotesPerGroup < oldMaxVotesPerGroup) { - const reflectionGroups = await dataLoader - .get('retroReflectionGroupsByMeetingId') - .load(meetingId) - - const maxVotesByASingleUser = Math.max( - ...reflectionGroups.map(({voterIds}) => mode(voterIds)[0]) - ) - if (maxVotesByASingleUser > maxVotesPerGroup) { - return {error: {message: 'Your team has already spent their votes'}} - } - } // RESOLUTION try { await pg.transaction().execute(async (trx) => { - const canChangeMaxVotesPerGroup = - maxVotesPerGroup >= oldMaxVotesPerGroup - ? true - : await trx - .with('GroupVotes', (qb) => - qb - .selectFrom('RetroReflectionGroup') - .where('meetingId', '=', meetingId) - .where('isActive', '=', true) - .select(({fn}) => ['id', fn('unnest', ['voterIds']).as('userIds')]) - ) - .with('GroupVoteCount', (qb) => - qb - .selectFrom('GroupVotes') - .select(({fn}) => ['id', fn.count('userIds').as('mode')]) - .groupBy(['id', 'userIds']) - ) - .selectFrom('GroupVoteCount') - .select(({eb, fn}) => eb(fn.max('mode'), '<', maxVotesPerGroup).as('isValid')) - .executeTakeFirst() - if (!canChangeMaxVotesPerGroup) { - throw new Error('A topic already has too many votes') + if (maxVotesPerGroup < oldMaxVotesPerGroup) { + const reflectionGroups = await trx + .selectFrom('RetroReflectionGroup') + .select('voterIds') + .where('meetingId', '=', meetingId) + .where('isActive', '=', true) + .forUpdate() + .execute() + const maxVotesPerGroupSpent = Math.max( + ...reflectionGroups.map(({voterIds}) => mode(voterIds)[0]) + ) + if (maxVotesPerGroupSpent > maxVotesPerGroup) + throw new Error('A topic already has too many votes') + } + if (delta < 0) { + const res = await trx + .selectFrom('MeetingMember') + .select('votesRemaining') + .where('meetingId', '=', meetingId) + .forUpdate() + .execute() + const min = Math.min(...res.map((m) => Number(m.votesRemaining!))) + if (min < -delta) throw new Error('Your team has already spent their votes') } - const res = await trx + await trx .with('MeetingMemberUpdates', (qb) => qb .updateTable('MeetingMember') .set((eb) => ({votesRemaining: eb('votesRemaining', '+', delta)})) .where('meetingId', '=', meetingId) - // TURN THIS ON IN PHASE 2 - // .$if(delta < 0, (qb) => - // qb.where(({selectFrom, eb}) => - // eb( - // selectFrom('MeetingMember') - // .select((eb) => eb.fn('min', ['votesRemaining']).as('min')) - // .where('meetingId', '=', meetingId), - // '>', - // -delta - // ) - // ) - // ) - .returning('id') ) - .with( - 'NewMeetingUpdates', - (qb) => - qb - .updateTable('NewMeeting') - .set({totalVotes, maxVotesPerGroup}) - .where('id', '=', meetingId) - // TURN THIS ON IN PHASE 2 - // .where(({exists, selectFrom}) => exists(selectFrom('MeetingMemberUpdates').select('id'))) + .with('NewMeetingUpdates', (qb) => + qb + .updateTable('NewMeeting') + .set({totalVotes, maxVotesPerGroup}) + .where('id', '=', meetingId) ) .updateTable('MeetingSettings') .set({ @@ -198,13 +134,7 @@ const updateRetroMaxVotes = { }) .where('teamId', '=', teamId) .where('meetingType', '=', 'retrospective') - // TURN THIS ON IN PHASE 2 - // .where(({exists, selectFrom}) => exists(selectFrom('MeetingMemberUpdates').select('id'))) .executeTakeFirstOrThrow() - - if (res.numUpdatedRows === BigInt(0)) { - throw new Error('Your team has already spent their votes') - } }) } catch (e) { return {error: {message: (e as Error).message}} diff --git a/packages/server/graphql/mutations/voteForReflectionGroup.ts b/packages/server/graphql/mutations/voteForReflectionGroup.ts index d848cebed3b..c0fb162bb65 100644 --- a/packages/server/graphql/mutations/voteForReflectionGroup.ts +++ b/packages/server/graphql/mutations/voteForReflectionGroup.ts @@ -45,7 +45,7 @@ export default { if (meeting.meetingType !== 'retrospective') { return {error: {message: 'Meeting type is not retrospective'}} } - const {endedAt, phases, maxVotesPerGroup, teamId} = meeting + const {endedAt, phases, teamId} = meeting if (!isTeamMember(authToken, teamId)) { return standardError(new Error('Team not found'), {userId: viewerId}) } @@ -64,21 +64,10 @@ export default { // RESOLUTION dataLoader.get('retroReflectionGroups').clear(reflectionGroupId) if (isUnvote) { - const votingError = await safelyWithdrawVote( - authToken, - meetingId, - viewerId, - reflectionGroupId - ) + const votingError = await safelyWithdrawVote(meetingId, viewerId, reflectionGroupId) if (votingError) return votingError } else { - const votingError = await safelyCastVote( - authToken, - meetingId, - viewerId, - reflectionGroupId, - maxVotesPerGroup - ) + const votingError = await safelyCastVote(meetingId, viewerId, reflectionGroupId) if (votingError) return votingError } dataLoader.clearAll('meetingMembers') diff --git a/packages/server/graphql/private/mutations/hardDeleteUser.ts b/packages/server/graphql/private/mutations/hardDeleteUser.ts index b4c4903891e..8effc9a13ad 100644 --- a/packages/server/graphql/private/mutations/hardDeleteUser.ts +++ b/packages/server/graphql/private/mutations/hardDeleteUser.ts @@ -86,7 +86,6 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( .where('createdBy', '=', userIdToDelete) .execute() await r({ - meetingMember: r.table('MeetingMember').getAll(userIdToDelete, {index: 'userId'}).delete(), notification: r.table('Notification').getAll(userIdToDelete, {index: 'userId'}).delete(), createdTasks: r .table('Task') diff --git a/packages/server/graphql/private/queries/orgActivities.ts b/packages/server/graphql/private/queries/orgActivities.ts index 9b61012cb60..ea97e24a00e 100644 --- a/packages/server/graphql/private/queries/orgActivities.ts +++ b/packages/server/graphql/private/queries/orgActivities.ts @@ -1,6 +1,4 @@ import {sql} from 'kysely' -import getRethink from '../../../database/rethinkDriver' -import {RValue} from '../../../database/stricterR' import getKysely from '../../../postgres/getKysely' import {OrgActivityRow, QueryResolvers} from '../resolverTypes' @@ -46,7 +44,6 @@ const orgActivities: QueryResolvers['orgActivities'] = async (_source, {startDat ]) .orderBy('monthStart') - const r = await getRethink() try { const [signupsResult, rawMeetingResult] = await Promise.all([ query.execute(), @@ -62,20 +59,13 @@ const orgActivities: QueryResolvers['orgActivities'] = async (_source, {startDat .execute() ]) const meetingIds = rawMeetingResult.flatMap((row) => row.meetingIds) - const participantCounts = (await ( - r - .table('MeetingMember') - .getAll(r.args(meetingIds), {index: 'meetingId'}) - .group('meetingId') as any - ) - .count() - .ungroup() - .map((group: RValue) => ({ - meetingId: group('group'), - participantCount: group('reduction') - })) - .run()) as {meetingId: string; participantCount: number}[] - // Combine PostgreSQL and RethinkDB results + const participantCounts = await pg + .selectFrom('MeetingMember') + .select(({fn}) => ['meetingId', fn.count('id').as('participantCount')]) + .where('meetingId', 'in', meetingIds) + .groupBy('meetingId') + .execute() + // Combine results const combinedResults = signupsResult.reduce( (acc, signupRow) => { const epochMonthStart = signupRow.monthStart.getTime() @@ -98,7 +88,7 @@ const orgActivities: QueryResolvers['orgActivities'] = async (_source, {startDat const meetingData = rawMeetingResult.find((r) => r.monthStart.getTime() === epochMonthStart) const participantCount = participantCounts .filter((pc) => meetingData?.meetingIds.includes(pc.meetingId)) - .map((pc) => pc.participantCount) + .map((pc) => Number(pc.participantCount)) .reduce((a, b) => a + b, 0) acc[monthKey].participantCount = participantCount acc[monthKey].meetingCount = meetingData?.meetingIds.length ?? 0 diff --git a/packages/server/graphql/public/mutations/startCheckIn.ts b/packages/server/graphql/public/mutations/startCheckIn.ts index ffe6ab4c87d..eab8efe5fb5 100644 --- a/packages/server/graphql/public/mutations/startCheckIn.ts +++ b/packages/server/graphql/public/mutations/startCheckIn.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' import MeetingAction from '../../../database/types/MeetingAction' import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' @@ -22,7 +21,6 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( context ) => { const pg = getKysely() - const r = await getRethink() const {authToken, socketId: mutatorId, dataLoader} = context const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -84,7 +82,6 @@ const startCheckIn: MutationResolvers['startCheckIn'] = async ( .insertInto('MeetingMember') .values(meetingMember) .execute(), - r.table('MeetingMember').insert(meetingMember).run(), agendaItemIds.length && pg.updateTable('AgendaItem').set({meetingId}).where('id', 'in', agendaItemIds).execute() ]) diff --git a/packages/server/graphql/public/mutations/startRetrospective.ts b/packages/server/graphql/public/mutations/startRetrospective.ts index 8ab00de43e2..f45d27aae82 100644 --- a/packages/server/graphql/public/mutations/startRetrospective.ts +++ b/packages/server/graphql/public/mutations/startRetrospective.ts @@ -1,5 +1,4 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' import updateMeetingTemplateLastUsedAt from '../../../postgres/queries/updateMeetingTemplateLastUsedAt' import {MeetingTypeEnum} from '../../../postgres/types/Meeting' @@ -21,7 +20,6 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( {teamId, name, rrule, gcalInput}, {authToken, socketId: mutatorId, dataLoader} ) => { - const r = await getRethink() const pg = getKysely() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -97,7 +95,6 @@ const startRetrospective: MutationResolvers['startRetrospective'] = async ( .insertInto('MeetingMember') .values(meetingMember) .execute(), - r.table('MeetingMember').insert(meetingMember).run(), videoMeetingURL && pg .updateTable('MeetingSettings') diff --git a/packages/server/graphql/public/subscriptions/meetingSubscription.ts b/packages/server/graphql/public/subscriptions/meetingSubscription.ts index b7c5e354770..3a2dba1855c 100644 --- a/packages/server/graphql/public/subscriptions/meetingSubscription.ts +++ b/packages/server/graphql/public/subscriptions/meetingSubscription.ts @@ -1,6 +1,6 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' -import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {getUserId} from '../../../utils/authorization' import getPubSub from '../../../utils/getPubSub' import {SubscriptionResolvers} from '../resolverTypes' @@ -8,10 +8,13 @@ import {SubscriptionResolvers} from '../resolverTypes' const meetingSubscription: SubscriptionResolvers['meetingSubscription'] = { subscribe: async (_source, {meetingId}, {authToken}) => { // AUTH - const r = await getRethink() const viewerId = getUserId(authToken) const meetingMemberId = toTeamMemberId(meetingId, viewerId) - const meetingMember = await r.table('MeetingMember').get(meetingMemberId).run() + const meetingMember = await getKysely() + .selectFrom('MeetingMember') + .select('id') + .where('id', '=', meetingMemberId) + .executeTakeFirst() if (!meetingMember) { throw new Error('Not invited to the meeting. Cannot subscribe') } diff --git a/packages/server/graphql/public/typeDefs/Mutation.graphql b/packages/server/graphql/public/typeDefs/Mutation.graphql index 8e63179b3a9..b71bc91fe3c 100644 --- a/packages/server/graphql/public/typeDefs/Mutation.graphql +++ b/packages/server/graphql/public/typeDefs/Mutation.graphql @@ -470,27 +470,6 @@ type Mutation { meetingId: ID! ): NavigateMeetingPayload! - """ - Check a member in as present or absent - """ - newMeetingCheckIn( - """ - The id of the user being marked present or absent - """ - userId: ID! - - """ - the meeting currently in progress - """ - meetingId: ID! - - """ - true if the member is present, false if absent, null if undecided - """ - isCheckedIn: Boolean - ): NewMeetingCheckInPayload - @deprecated(reason: "Members now join lazily and joining means they are present") - """ Increment the count of times the org has clicked pay later """ diff --git a/packages/server/graphql/public/types/NewMeetingTeamMemberStage.ts b/packages/server/graphql/public/types/NewMeetingTeamMemberStage.ts index 05d63550f50..f2c754657e4 100644 --- a/packages/server/graphql/public/types/NewMeetingTeamMemberStage.ts +++ b/packages/server/graphql/public/types/NewMeetingTeamMemberStage.ts @@ -6,7 +6,7 @@ const NewMeetingTeamMemberStage: NewMeetingTeamMemberStageResolvers = { meetingMember: async ({meetingId, teamMemberId}, _args, {dataLoader}) => { const {userId} = fromTeamMemberId(teamMemberId) const meetingMemberId = toTeamMemberId(meetingId, userId) - return dataLoader.get('meetingMembers').load(meetingMemberId) + return dataLoader.get('meetingMembers').loadNonNull(meetingMemberId) }, teamMember: async ({teamMemberId}, _args, {dataLoader}) => { return dataLoader.get('teamMembers').loadNonNull(teamMemberId) diff --git a/packages/server/graphql/public/types/TeamMember.ts b/packages/server/graphql/public/types/TeamMember.ts index 9d86d955e04..cb414ea2558 100644 --- a/packages/server/graphql/public/types/TeamMember.ts +++ b/packages/server/graphql/public/types/TeamMember.ts @@ -36,7 +36,7 @@ const TeamMember: TeamMemberResolvers = { meetingMember: async ({userId}, {meetingId}, {dataLoader}) => { const meetingMemberId = MeetingMemberId.join(meetingId, userId) - return meetingId ? dataLoader.get('meetingMembers').load(meetingMemberId) : null + return meetingId ? dataLoader.get('meetingMembers').loadNonNull(meetingMemberId) : null }, prevUsedRepoIntegrations: async ({teamId, userId}, {first}, context) => { diff --git a/packages/server/graphql/public/types/User.ts b/packages/server/graphql/public/types/User.ts index 7dd6d2d68f5..cb65785f3e7 100644 --- a/packages/server/graphql/public/types/User.ts +++ b/packages/server/graphql/public/types/User.ts @@ -423,7 +423,7 @@ const User: ReqResolvers<'User'> = { meetingMember: async ({id: userId}, {meetingId}, {dataLoader}) => { const meetingMemberId = toTeamMemberId(meetingId, userId) - return meetingId ? dataLoader.get('meetingMembers').load(meetingMemberId) : null + return meetingId ? dataLoader.get('meetingMembers').loadNonNull(meetingMemberId) : null }, organizationUser: async ({id: userId}, {orgId}, {authToken, dataLoader}) => { diff --git a/packages/server/graphql/public/types/helpers/getActiveTeamCountByTeamIds.ts b/packages/server/graphql/public/types/helpers/getActiveTeamCountByTeamIds.ts index 72a04e06aa0..c4acbd51737 100644 --- a/packages/server/graphql/public/types/helpers/getActiveTeamCountByTeamIds.ts +++ b/packages/server/graphql/public/types/helpers/getActiveTeamCountByTeamIds.ts @@ -1,6 +1,6 @@ import {Threshold} from '~/types/constEnums' -import getRethink from '../../../../database/rethinkDriver' import getKysely from '../../../../postgres/getKysely' +import {selectMeetingMembers} from '../../../../postgres/select' // Uncomment for easier testing //import { ThresholdTest as Threshold } from "~/types/constEnums"; @@ -10,7 +10,6 @@ import getKysely from '../../../../postgres/getKysely' // TODO: store all calculations in the database, e.g. meeting.attendeeCount (see #7975) const getActiveTeamCountByTeamIds = async (teamIds: string[]) => { if (!teamIds.length) return 0 - const r = await getRethink() const pg = getKysely() const meetingIdsByTeamId = await pg .selectFrom('NewMeeting') @@ -19,10 +18,8 @@ const getActiveTeamCountByTeamIds = async (teamIds: string[]) => { .groupBy('teamId') .execute() const meetingIds = meetingIdsByTeamId.flatMap((row) => row.meetingIds) - const meetingMembers = await r - .table('MeetingMember') - .getAll(r.args(meetingIds), {index: 'meetingId'}) - .run() + if (!meetingIds.length) return 0 + const meetingMembers = await selectMeetingMembers().where('id', 'in', meetingIds).execute() const teamsIdsWithMinMeetingsAndMembers = meetingIdsByTeamId .map(({teamId, meetingIds}) => ({ teamId, diff --git a/packages/server/graphql/rootMutation.ts b/packages/server/graphql/rootMutation.ts index 918463c1dae..c86f83691fc 100644 --- a/packages/server/graphql/rootMutation.ts +++ b/packages/server/graphql/rootMutation.ts @@ -42,7 +42,6 @@ import movePokerTemplateDimension from './mutations/movePokerTemplateDimension' import movePokerTemplateScaleValue from './mutations/movePokerTemplateScaleValue' import moveTeamToOrg from './mutations/moveTeamToOrg' import navigateMeeting from './mutations/navigateMeeting' -import newMeetingCheckIn from './mutations/newMeetingCheckIn' import oldUpdateCreditCard from './mutations/oldUpdateCreditCard' import oldUpgradeToTeamTier from './mutations/oldUpgradeToTeamTier' import payLater from './mutations/payLater' @@ -143,7 +142,6 @@ export default new GraphQLObjectType({ movePokerTemplateDimension, moveTeamToOrg, navigateMeeting, - newMeetingCheckIn, payLater, persistJiraSearchQuery, pushInvitation, diff --git a/packages/server/graphql/types/MeetingMember.ts b/packages/server/graphql/types/MeetingMember.ts deleted file mode 100644 index 496875361ad..00000000000 --- a/packages/server/graphql/types/MeetingMember.ts +++ /dev/null @@ -1,8 +0,0 @@ -import {GraphQLInterfaceType} from 'graphql' - -const MeetingMember: GraphQLInterfaceType = new GraphQLInterfaceType({ - name: 'MeetingMember', - fields: {} -}) - -export default MeetingMember diff --git a/packages/server/graphql/types/NewMeetingCheckInPayload.ts b/packages/server/graphql/types/NewMeetingCheckInPayload.ts deleted file mode 100644 index b5010e1079c..00000000000 --- a/packages/server/graphql/types/NewMeetingCheckInPayload.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {GraphQLObjectType} from 'graphql' -import {GQLContext} from '../graphql' -import {resolveMeetingMember, resolveNewMeeting} from '../resolvers' -import MeetingMember from './MeetingMember' -import NewMeeting from './NewMeeting' -import StandardMutationError from './StandardMutationError' - -const NewMeetingCheckInPayload = new GraphQLObjectType({ - name: 'NewMeetingCheckInPayload', - fields: () => ({ - error: { - type: StandardMutationError - }, - meetingMember: { - type: MeetingMember, - resolve: resolveMeetingMember - }, - meeting: { - type: NewMeeting, - resolve: resolveNewMeeting - } - }) -}) - -export default NewMeetingCheckInPayload From fff7383a9da013bcfdf1cdb3f6bc606f834462a0 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:29:46 -0700 Subject: [PATCH 502/529] chore(release): release v7.50.4 (#10314) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 592c1063c19..4140dc174d1 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.50.3" + ".": "7.50.4" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 708cb195d1e..4bea3a9bf7d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.50.4](https://github.com/ParabolInc/parabol/compare/v7.50.3...v7.50.4) (2024-10-08) + + +### Changed + +* **rethinkdb:** MeetingMember: Phase 2 ([#10294](https://github.com/ParabolInc/parabol/issues/10294)) ([af50d0a](https://github.com/ParabolInc/parabol/commit/af50d0a1420ae21b4e48ddc040c0d79f9d81e2cf)) +* **rethinkdb:** MeetingMember: Phase 3 ([#10298](https://github.com/ParabolInc/parabol/issues/10298)) ([dee4e0f](https://github.com/ParabolInc/parabol/commit/dee4e0f0119bd46167175043405bc24abd49d88b)) + ## [7.50.3](https://github.com/ParabolInc/parabol/compare/v7.50.2...v7.50.3) (2024-10-08) diff --git a/package.json b/package.json index f2a58965007..11f224fde70 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.50.3", + "version": "7.50.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 7533c63d598..776c3dc0104 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.50.3", + "version": "7.50.4", "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.50.3" + "parabol-server": "7.50.4" } } diff --git a/packages/client/package.json b/packages/client/package.json index 51fe9c164b7..c6321564a88 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.50.3", + "version": "7.50.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index e1c812cb800..f6488317b1e 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.50.3", + "version": "7.50.4", "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 e5ad7ec4504..99395b0046c 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.50.3", + "version": "7.50.4", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.50.3", - "parabol-server": "7.50.3", + "parabol-client": "7.50.4", + "parabol-server": "7.50.4", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 0db43982d1b..2b95bd6844c 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.50.3", + "version": "7.50.4", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index b1613f7eacc..7aa8be70f04 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.50.3", + "version": "7.50.4", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.50.3", + "parabol-client": "7.50.4", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From de9df6ca85e9187a87fb3585279a295d29665220 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 8 Oct 2024 16:45:39 -0700 Subject: [PATCH 503/529] fix: timeRemaining (#10316) --- .../server/graphql/public/types/NewMeetingStage.ts | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/packages/server/graphql/public/types/NewMeetingStage.ts b/packages/server/graphql/public/types/NewMeetingStage.ts index 9a32aba3862..9950e3cb878 100644 --- a/packages/server/graphql/public/types/NewMeetingStage.ts +++ b/packages/server/graphql/public/types/NewMeetingStage.ts @@ -33,8 +33,15 @@ const NewMeetingStage: NewMeetingStageResolvers = { return readyToAdvance.filter((userId: string) => userId !== facilitatorUserId).length }, - timeRemaining: ({scheduledEndTime}) => { - return scheduledEndTime ? scheduledEndTime.valueOf() - Date.now() : null + timeRemaining: ({scheduledEndTime, id, meetingId}) => { + if (scheduledEndTime) { + if (!(scheduledEndTime instanceof Date)) { + console.log('ENDTIME NOT DATE', scheduledEndTime, id, meetingId) + return null + } + return scheduledEndTime.getTime() - Date.now() + } + return null } } From 3476051bd5d7edfb279fc42c52cfb27fd1693c4f Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 8 Oct 2024 16:50:35 -0700 Subject: [PATCH 504/529] chore(release): release v7.50.5 (#10317) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 4140dc174d1..2027b33158e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.50.4" + ".": "7.50.5" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4bea3a9bf7d..9cfa5c098f0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.50.5](https://github.com/ParabolInc/parabol/compare/v7.50.4...v7.50.5) (2024-10-08) + + +### Fixed + +* timeRemaining ([#10316](https://github.com/ParabolInc/parabol/issues/10316)) ([de9df6c](https://github.com/ParabolInc/parabol/commit/de9df6ca85e9187a87fb3585279a295d29665220)) + ## [7.50.4](https://github.com/ParabolInc/parabol/compare/v7.50.3...v7.50.4) (2024-10-08) diff --git a/package.json b/package.json index 11f224fde70..1affe5064e6 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.50.4", + "version": "7.50.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 776c3dc0104..17c0593b1bb 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.50.4", + "version": "7.50.5", "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.50.4" + "parabol-server": "7.50.5" } } diff --git a/packages/client/package.json b/packages/client/package.json index c6321564a88..aa6b8ea5d7a 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.50.4", + "version": "7.50.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index f6488317b1e..2bce0f461dc 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.50.4", + "version": "7.50.5", "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 99395b0046c..8dbceffc01f 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.50.4", + "version": "7.50.5", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.50.4", - "parabol-server": "7.50.4", + "parabol-client": "7.50.5", + "parabol-server": "7.50.5", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 2b95bd6844c..50000f137a5 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.50.4", + "version": "7.50.5", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 7aa8be70f04..e35c368f291 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.50.4", + "version": "7.50.5", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.50.4", + "parabol-client": "7.50.5", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 887abd49d6467207c60ade8f62b4e496fa9378b6 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 9 Oct 2024 10:30:23 -0700 Subject: [PATCH 505/529] fix: catch error if user tries to join meeting twice (#10320) --- packages/client/hooks/useAutoCheckIn.ts | 6 ++++-- packages/server/graphql/mutations/joinMeeting.ts | 7 ++++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/client/hooks/useAutoCheckIn.ts b/packages/client/hooks/useAutoCheckIn.ts index cf6e6776d80..218f8273eeb 100644 --- a/packages/client/hooks/useAutoCheckIn.ts +++ b/packages/client/hooks/useAutoCheckIn.ts @@ -1,5 +1,5 @@ import graphql from 'babel-plugin-relay/macro' -import {useEffect} from 'react' +import {useEffect, useRef} from 'react' import {readInlineData} from 'relay-runtime' import {useAutoCheckIn_meeting$key} from '~/__generated__/useAutoCheckIn_meeting.graphql' import JoinMeetingMutation from '../mutations/JoinMeetingMutation' @@ -12,6 +12,7 @@ const useAutoCheckIn = (meetingRef: useAutoCheckIn_meeting$key) => { const {history, location} = useRouter() const router = {history, location} const queryKey = 'useAutoCheckIn' + const hasCalledJoinedRef = useRef(false) useEffect(() => { const meeting = readInlineData( graphql` @@ -33,7 +34,8 @@ const useAutoCheckIn = (meetingRef: useAutoCheckIn_meeting$key) => { } if (viewerMeetingMember) { subscribeToMeeting() - } else if (!endedAt) { + } else if (!endedAt && !hasCalledJoinedRef.current) { + hasCalledJoinedRef.current = true JoinMeetingMutation( atmosphere, {meetingId}, diff --git a/packages/server/graphql/mutations/joinMeeting.ts b/packages/server/graphql/mutations/joinMeeting.ts index 9ae3eec2766..d904089a878 100644 --- a/packages/server/graphql/mutations/joinMeeting.ts +++ b/packages/server/graphql/mutations/joinMeeting.ts @@ -71,7 +71,12 @@ const joinMeeting = { const teamMemberId = toTeamMemberId(teamId, viewerId) const teamMember = await dataLoader.get('teamMembers').loadNonNull(teamMemberId) const meetingMember = createMeetingMember(meeting, teamMember) - await pg.insertInto('MeetingMember').values(meetingMember).execute() + try { + await pg.insertInto('MeetingMember').values(meetingMember).execute() + } catch { + return {error: {message: 'Could not join meeting'}} + } + const addStageToPhase = async ( stage: CheckInStage | UpdatesStage | TeamPromptResponseStage, phaseType: NewMeetingPhase['phaseType'] From 8e1222fa88368d1296d125e8236d2992c05294b4 Mon Sep 17 00:00:00 2001 From: Dale Bumblis <135627447+dbumblis-parabol@users.noreply.github.com> Date: Wed, 9 Oct 2024 10:55:49 -0700 Subject: [PATCH 506/529] chore: update snyk workflow to use node20 (#10324) --- .github/workflows/synk-yarn-lock-commit.yml | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/synk-yarn-lock-commit.yml b/.github/workflows/synk-yarn-lock-commit.yml index 6ac67f37aa9..cd1273c6cf0 100644 --- a/.github/workflows/synk-yarn-lock-commit.yml +++ b/.github/workflows/synk-yarn-lock-commit.yml @@ -11,10 +11,10 @@ jobs: steps: - name: Checkout the repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: ref: ${{ github.head_ref }} - token: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, no need to create one + token: ${{ secrets.GITHUB_TOKEN }} # This token is provided by Actions, no need to create one - name: Setup environment variables run: | @@ -26,7 +26,7 @@ jobs: echo "DOCKER_REPOSITORY_FOR_REF=${{ secrets.GCP_AR_PARABOL_DEV }}" >> $GITHUB_ENV - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version-file: package.json # Caching yarn dir & running yarn install is too slow @@ -34,7 +34,7 @@ jobs: - name: Get cached node modules id: cache - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: | **/node_modules From fc1ef4d44568193f018a31c378181b5df92da355 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 9 Oct 2024 12:48:22 -0700 Subject: [PATCH 507/529] chore(rethinkdb): MassInvitation: OneShot (#10311) Signed-off-by: Matt Krick --- packages/server/database/rethinkDriver.ts | 5 -- .../dataloader/foreignKeyLoaderMakers.ts | 12 ++++ .../dataloader/primaryKeyLoaderMakers.ts | 5 ++ .../rethinkForeignKeyLoaderMakers.ts | 13 ---- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../graphql/mutations/createMassInvitation.ts | 21 ++++-- packages/server/graphql/types/Team.ts | 22 +++--- .../1728411506375_MassInvitation-1shot.ts | 71 +++++++++++++++++++ packages/server/postgres/select.ts | 2 + 9 files changed, 116 insertions(+), 36 deletions(-) create mode 100644 packages/server/postgres/migrations/1728411506375_MassInvitation-1shot.ts diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index b76040d74d6..ffe5a5ca287 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -2,7 +2,6 @@ import {MasterPool, r} from 'rethinkdb-ts' import TeamInvitation from '../database/types/TeamInvitation' import getRethinkConfig from './getRethinkConfig' import {R} from './stricterR' -import MassInvitation from './types/MassInvitation' import NotificationKickedOut from './types/NotificationKickedOut' import NotificationMeetingStageTimeLimitEnd from './types/NotificationMeetingStageTimeLimitEnd' import NotificationMentioned from './types/NotificationMentioned' @@ -16,10 +15,6 @@ import NotificationTeamInvitation from './types/NotificationTeamInvitation' import Task from './types/Task' export type RethinkSchema = { - MassInvitation: { - type: MassInvitation - index: 'teamMemberId' - } NewFeature: { type: any index: '' diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index d1e8d1060f2..1d2ace54f0b 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -3,6 +3,7 @@ import {getTeamPromptResponsesByMeetingIds} from '../postgres/queries/getTeamPro import { selectAgendaItems, selectComments, + selectMassInvitations, selectMeetingMembers, selectNewMeetings, selectOrganizations, @@ -268,3 +269,14 @@ export const meetingMembersByUserId = foreignKeyLoaderMaker( return selectMeetingMembers().where('userId', 'in', userIds).execute() } ) + +export const massInvitationsByTeamMemberId = foreignKeyLoaderMaker( + 'massInvitations', + 'teamMemberId', + async (teamMemberIds) => { + return selectMassInvitations() + .where('teamMemberId', 'in', teamMemberIds) + .orderBy('expiration desc') + .execute() + } +) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index b7d005d1f6f..6905e1ffa18 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -8,6 +8,7 @@ import {getUsersByIds} from '../postgres/queries/getUsersByIds' import { selectAgendaItems, selectComments, + selectMassInvitations, selectMeetingMembers, selectMeetingSettings, selectNewMeetings, @@ -129,3 +130,7 @@ export const newMeetings = primaryKeyLoaderMaker((ids: readonly string[]) => { export const meetingMembers = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectMeetingMembers().where('id', 'in', ids).execute() }) + +export const massInvitations = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectMassInvitations().where('id', 'in', ids).execute() +}) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index 32bf2c76d6c..d558c0f190c 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -2,19 +2,6 @@ import getRethink from '../database/rethinkDriver' import {RDatum} from '../database/stricterR' import RethinkForeignKeyLoaderMaker from './RethinkForeignKeyLoaderMaker' -export const massInvitationsByTeamMemberId = new RethinkForeignKeyLoaderMaker( - 'massInvitations', - 'teamMemberId', - async (teamMemberIds) => { - const r = await getRethink() - return r - .table('MassInvitation') - .getAll(r.args(teamMemberIds), {index: 'teamMemberId'}) - .orderBy(r.desc('expiration')) - .run() - } -) - export const tasksByDiscussionId = new RethinkForeignKeyLoaderMaker( 'tasks', 'discussionId', diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index a0b698abd6e..695470a8201 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -3,7 +3,6 @@ import RethinkPrimaryKeyLoaderMaker from './RethinkPrimaryKeyLoaderMaker' /** * all rethink dataloader types which also must exist in {@link rethinkDriver/RethinkSchema} */ -export const massInvitations = new RethinkPrimaryKeyLoaderMaker('MassInvitation') export const newFeatures = new RethinkPrimaryKeyLoaderMaker('NewFeature') export const notifications = new RethinkPrimaryKeyLoaderMaker('Notification') export const tasks = new RethinkPrimaryKeyLoaderMaker('Task') diff --git a/packages/server/graphql/mutations/createMassInvitation.ts b/packages/server/graphql/mutations/createMassInvitation.ts index 32fd444f548..2a2b41dfb2c 100644 --- a/packages/server/graphql/mutations/createMassInvitation.ts +++ b/packages/server/graphql/mutations/createMassInvitation.ts @@ -1,7 +1,8 @@ import {GraphQLBoolean, GraphQLID, GraphQLNonNull} from 'graphql' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' -import getRethink from '../../database/rethinkDriver' -import MassInvitation from '../../database/types/MassInvitation' +import {Security, Threshold} from '../../../client/types/constEnums' +import generateRandomString from '../../generateRandomString' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import {GQLContext} from '../graphql' import CreateMassInvitationPayload from '../types/CreateMassInvitationPayload' @@ -32,7 +33,7 @@ const createMassInvitation = { }: {meetingId?: string | null; teamId: string; voidOld?: boolean | null}, {authToken}: GQLContext ) => { - const r = await getRethink() + const pg = getKysely() const viewerId = getUserId(authToken) //AUTH @@ -43,11 +44,17 @@ const createMassInvitation = { // RESOLUTION const teamMemberId = toTeamMemberId(teamId, viewerId) if (voidOld) { - await r.table('MassInvitation').getAll(teamMemberId, {index: 'teamMemberId'}).delete().run() + await pg.deleteFrom('MassInvitation').where('teamMemberId', '=', teamMemberId).execute() } - const massInvitation = new MassInvitation({meetingId: meetingId ?? undefined, teamMemberId}) - - await r.table('MassInvitation').insert(massInvitation, {conflict: 'replace'}).run() + await pg + .insertInto('MassInvitation') + .values({ + id: generateRandomString(Security.MASS_INVITATION_TOKEN_LENGTH), + meetingId, + teamMemberId, + expiration: new Date(Date.now() + Threshold.MASS_INVITATION_TOKEN_LIFESPAN) + }) + .execute() return {teamId} } } diff --git a/packages/server/graphql/types/Team.ts b/packages/server/graphql/types/Team.ts index 385be345be2..7a92925c44a 100644 --- a/packages/server/graphql/types/Team.ts +++ b/packages/server/graphql/types/Team.ts @@ -9,10 +9,11 @@ import { } from 'graphql' import isTaskPrivate from 'parabol-client/utils/isTaskPrivate' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' -import getRethink from '../../database/rethinkDriver' -import MassInvitationDB from '../../database/types/MassInvitation' +import {Security, Threshold} from '../../../client/types/constEnums' import Task from '../../database/types/Task' import ITeam from '../../database/types/Team' +import generateRandomString from '../../generateRandomString' +import getKysely from '../../postgres/getKysely' import {getUserId, isSuperUser, isTeamMember, isUserBillingLeader} from '../../utils/authorization' import standardError from '../../utils/standardError' import isValid from '../isValid' @@ -75,7 +76,7 @@ const Team: GraphQLObjectType = new GraphQLObjectType({ {authToken, dataLoader}: GQLContext ) => { if (!isTeamMember(authToken, teamId)) return null - const r = await getRethink() + const pg = getKysely() const viewerId = getUserId(authToken) const teamMemberId = toTeamMemberId(teamId, viewerId) const invitationTokens = await dataLoader @@ -87,14 +88,15 @@ const Team: GraphQLObjectType = new GraphQLObjectType({ return newestInvitationToken // if the token is not valid, delete it to keep the table clean of expired things if (newestInvitationToken) { - await r - .table('MassInvitation') - .getAll(teamMemberId, {index: 'teamMemberId'}) - .delete() - .run() + await pg.deleteFrom('MassInvitation').where('teamMemberId', '=', teamMemberId).execute() } - const massInvitation = new MassInvitationDB({meetingId, teamMemberId}) - await r.table('MassInvitation').insert(massInvitation, {conflict: 'replace'}).run() + const massInvitation = { + id: generateRandomString(Security.MASS_INVITATION_TOKEN_LENGTH), + meetingId, + teamMemberId, + expiration: new Date(Date.now() + Threshold.MASS_INVITATION_TOKEN_LIFESPAN) + } + await pg.insertInto('MassInvitation').values(massInvitation).execute() invitationTokens.length = 1 invitationTokens[0] = massInvitation return massInvitation diff --git a/packages/server/postgres/migrations/1728411506375_MassInvitation-1shot.ts b/packages/server/postgres/migrations/1728411506375_MassInvitation-1shot.ts new file mode 100644 index 00000000000..09a93e8853a --- /dev/null +++ b/packages/server/postgres/migrations/1728411506375_MassInvitation-1shot.ts @@ -0,0 +1,71 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {Client} from 'pg' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' +import getPgConfig from '../getPgConfig' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS "MassInvitation" ( + "id" CHAR(12) NOT NULL PRIMARY KEY, + "expiration" TIMESTAMP WITH TIME ZONE NOT NULL, + "meetingId" VARCHAR(100), + "teamMemberId" VARCHAR(100) NOT NULL, + CONSTRAINT "fk_meetingId" + FOREIGN KEY("meetingId") + REFERENCES "NewMeeting"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_teamMemberId" + FOREIGN KEY("teamMemberId") + REFERENCES "TeamMember"("id") + ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS "idx_MassInvitation_meetingId" ON "MassInvitation"("meetingId") WHERE "meetingId" IS NOT NULL; + CREATE INDEX IF NOT EXISTS "idx_MassInvitation_teamMemberId" ON "MassInvitation"("teamMemberId"); + END $$; +`.execute(pg) + + const rRequests = await r + .table('MassInvitation') + .filter((row) => row('expiration').ge(r.now())) + .coerceTo('array') + .run() + + const insertRow = async (row) => { + try { + await pg + .insertInto('MassInvitation') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_meetingId') { + return insertRow({...row, meetingId: null}) + } + if (e.constraint === 'fk_teamMemberId') { + console.log('MassInvitation has no teamMember, skipping insert', row.id) + return + } + throw e + } + } + await Promise.all(rRequests.map(async (row) => insertRow(row))) +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE IF EXISTS "MassInvitation"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index b0c9a168e85..45e15ee777c 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -279,3 +279,5 @@ export const selectNewMeetings = () => export const selectMeetingMembers = () => getKysely().selectFrom('MeetingMember').selectAll().$narrowType() + +export const selectMassInvitations = () => getKysely().selectFrom('MassInvitation').selectAll() From 8b9a2c7e9b07c34bd14501cabacac51dc9833f5a Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 16:45:48 -0700 Subject: [PATCH 508/529] chore(release): release v7.50.6 (#10322) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 13 +++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 25 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 2027b33158e..0877bdaaac8 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.50.5" + ".": "7.50.6" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cfa5c098f0..beae3250882 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,19 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.50.6](https://github.com/ParabolInc/parabol/compare/v7.50.5...v7.50.6) (2024-10-09) + + +### Fixed + +* catch error if user tries to join meeting twice ([#10320](https://github.com/ParabolInc/parabol/issues/10320)) ([887abd4](https://github.com/ParabolInc/parabol/commit/887abd49d6467207c60ade8f62b4e496fa9378b6)) + + +### Changed + +* **rethinkdb:** MassInvitation: OneShot ([#10311](https://github.com/ParabolInc/parabol/issues/10311)) ([fc1ef4d](https://github.com/ParabolInc/parabol/commit/fc1ef4d44568193f018a31c378181b5df92da355)) +* update snyk workflow to use node20 ([#10324](https://github.com/ParabolInc/parabol/issues/10324)) ([8e1222f](https://github.com/ParabolInc/parabol/commit/8e1222fa88368d1296d125e8236d2992c05294b4)) + ## [7.50.5](https://github.com/ParabolInc/parabol/compare/v7.50.4...v7.50.5) (2024-10-08) diff --git a/package.json b/package.json index 1affe5064e6..570475330ff 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.50.5", + "version": "7.50.6", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 17c0593b1bb..1e16f6c09eb 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.50.5", + "version": "7.50.6", "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.50.5" + "parabol-server": "7.50.6" } } diff --git a/packages/client/package.json b/packages/client/package.json index aa6b8ea5d7a..8800043f036 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.50.5", + "version": "7.50.6", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 2bce0f461dc..3c037b2910c 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.50.5", + "version": "7.50.6", "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 8dbceffc01f..b0c99dec35b 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.50.5", + "version": "7.50.6", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.50.5", - "parabol-server": "7.50.5", + "parabol-client": "7.50.6", + "parabol-server": "7.50.6", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 50000f137a5..5b3d7267bbf 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.50.5", + "version": "7.50.6", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index e35c368f291..3939752121a 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.50.5", + "version": "7.50.6", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.50.5", + "parabol-client": "7.50.6", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 92deddf30645ad4d479f29eb7ab66737a3218946 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 9 Oct 2024 17:00:26 -0700 Subject: [PATCH 509/529] chore(rethinkdb): NewFeature: OneShot (#10312) Signed-off-by: Matt Krick --- codegen.json | 2 + packages/server/database/types/User.ts | 2 +- .../dataloader/primaryKeyLoaderMakers.ts | 5 ++ .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../private/mutations/addNewFeature.ts | 23 ++++----- .../public/types/NewFeatureBroadcast.ts | 7 +++ packages/server/graphql/public/types/User.ts | 2 +- .../graphql/types/NewFeatureBroadcast.ts | 26 ---------- .../1728418948136_NewFeature-oneshot.ts | 48 +++++++++++++++++++ packages/server/postgres/select.ts | 2 + packages/server/postgres/types/index.d.ts | 2 + 11 files changed, 77 insertions(+), 43 deletions(-) create mode 100644 packages/server/graphql/public/types/NewFeatureBroadcast.ts delete mode 100644 packages/server/graphql/types/NewFeatureBroadcast.ts create mode 100644 packages/server/postgres/migrations/1728418948136_NewFeature-oneshot.ts diff --git a/codegen.json b/codegen.json index 08c29e714e5..ceaf9293682 100644 --- a/codegen.json +++ b/codegen.json @@ -25,6 +25,7 @@ "GenerateMeetingSummarySuccess": "./types/GenerateMeetingSummarySuccess#GenerateMeetingSummarySuccessSource", "LoginsPayload": "./types/LoginsPayload#LoginsPayloadSource", "MeetingTemplate": "../../database/types/MeetingTemplate#default as IMeetingTemplate", + "NewFeatureBroadcast": "../../postgres/types/index#NewFeature", "NewMeeting": "../../postgres/types/Meeting#AnyMeeting", "Organization": "../../postgres/types/index#Organization as OrganizationDB", "PingableServices": "./types/PingableServices#PingableServicesSource", @@ -50,6 +51,7 @@ "contextType": "../graphql#GQLContext", "mappers": { "ReflectTemplatePromptUpdateDescriptionPayload": "./types/ReflectTemplatePromptUpdateDescriptionPayload#ReflectTemplatePromptUpdateDescriptionPayloadSource", + "NewFeatureBroadcast": "../../postgres/types/index#NewFeature", "ReflectTemplatePromptUpdateGroupColorPayload": "./types/ReflectTemplatePromptUpdateGroupColorPayload#ReflectTemplatePromptUpdateGroupColorPayloadSource", "RemoveReflectTemplatePromptPayload": "./types/RemoveReflectTemplatePromptPayload#RemoveReflectTemplatePromptPayloadSource", "RenameReflectTemplatePromptPayload": "./types/RenameReflectTemplatePromptPayload#RenameReflectTemplatePromptPayloadSource", diff --git a/packages/server/database/types/User.ts b/packages/server/database/types/User.ts index 3c278e3816a..84e3ffea909 100644 --- a/packages/server/database/types/User.ts +++ b/packages/server/database/types/User.ts @@ -32,7 +32,7 @@ export default class User { lastSeenAt: Date lastSeenAtURLs: string[] | null updatedAt: Date - newFeatureId?: string | null + newFeatureId?: number | null overLimitCopy?: string | null picture: string inactive: boolean diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 6905e1ffa18..813fb985c8f 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -11,6 +11,7 @@ import { selectMassInvitations, selectMeetingMembers, selectMeetingSettings, + selectNewFeatures, selectNewMeetings, selectOrganizations, selectReflectPrompts, @@ -134,3 +135,7 @@ export const meetingMembers = primaryKeyLoaderMaker((ids: readonly string[]) => export const massInvitations = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectMassInvitations().where('id', 'in', ids).execute() }) + +export const newFeatures = primaryKeyLoaderMaker((ids: readonly number[]) => { + return selectNewFeatures().where('id', 'in', ids).execute() +}) diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index 695470a8201..d0f67f23712 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -3,7 +3,6 @@ import RethinkPrimaryKeyLoaderMaker from './RethinkPrimaryKeyLoaderMaker' /** * all rethink dataloader types which also must exist in {@link rethinkDriver/RethinkSchema} */ -export const newFeatures = new RethinkPrimaryKeyLoaderMaker('NewFeature') export const notifications = new RethinkPrimaryKeyLoaderMaker('Notification') export const tasks = new RethinkPrimaryKeyLoaderMaker('Task') export const teamInvitations = new RethinkPrimaryKeyLoaderMaker('TeamInvitation') diff --git a/packages/server/graphql/private/mutations/addNewFeature.ts b/packages/server/graphql/private/mutations/addNewFeature.ts index b612061ee9d..ca084664f0c 100644 --- a/packages/server/graphql/private/mutations/addNewFeature.ts +++ b/packages/server/graphql/private/mutations/addNewFeature.ts @@ -1,6 +1,4 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' -import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' import getRedis from '../../../utils/getRedis' import publish from '../../../utils/publish' @@ -12,7 +10,6 @@ const addNewFeature: MutationResolvers['addNewFeature'] = async ( {actionButtonCopy, snackbarMessage, url}, {dataLoader} ) => { - const r = await getRethink() const redis = getRedis() const pg = getKysely() @@ -21,18 +18,16 @@ const addNewFeature: MutationResolvers['addNewFeature'] = async ( const subOptions = {operationId} // RESOLUTION - const newFeatureId = generateUID() - const newFeature = { - id: newFeatureId, - actionButtonCopy, - snackbarMessage, - url - } - await Promise.all([ - r.table('NewFeature').insert(newFeature).run(), - pg.updateTable('User').set({newFeatureId}).execute() - ]) + const newFeatureRes = await pg + .with('NewFeatureInsert', (qb) => + qb.insertInto('NewFeature').values({actionButtonCopy, snackbarMessage, url}).returning('id') + ) + .updateTable('User') + .set((eb) => ({newFeatureId: eb.selectFrom('NewFeatureInsert').select('NewFeatureInsert.id')})) + .returning((eb) => [eb.selectFrom('NewFeatureInsert').select('NewFeatureInsert.id').as('id')]) + .executeTakeFirstOrThrow() + const newFeature = {actionButtonCopy, snackbarMessage, url, id: newFeatureRes.id!} const onlineUserIds = new Set() const stream = redis.scanStream({match: 'presence:*'}) stream.on('data', (keys) => { diff --git a/packages/server/graphql/public/types/NewFeatureBroadcast.ts b/packages/server/graphql/public/types/NewFeatureBroadcast.ts new file mode 100644 index 00000000000..5ca7f15ec80 --- /dev/null +++ b/packages/server/graphql/public/types/NewFeatureBroadcast.ts @@ -0,0 +1,7 @@ +import {NewFeatureBroadcastResolvers} from '../resolverTypes' + +const NewFeatureBroadcast: NewFeatureBroadcastResolvers = { + id: ({id}) => `NewFeature:${id}` +} + +export default NewFeatureBroadcast diff --git a/packages/server/graphql/public/types/User.ts b/packages/server/graphql/public/types/User.ts index cb65785f3e7..d736cb65a26 100644 --- a/packages/server/graphql/public/types/User.ts +++ b/packages/server/graphql/public/types/User.ts @@ -411,7 +411,7 @@ const User: ReqResolvers<'User'> = { }, newFeature: ({newFeatureId}, _args, {dataLoader}) => { - return newFeatureId ? dataLoader.get('newFeatures').load(newFeatureId) : null + return newFeatureId ? dataLoader.get('newFeatures').loadNonNull(newFeatureId) : null }, lastSeenAtURLs: async ({id: userId}) => { diff --git a/packages/server/graphql/types/NewFeatureBroadcast.ts b/packages/server/graphql/types/NewFeatureBroadcast.ts deleted file mode 100644 index 069525b2694..00000000000 --- a/packages/server/graphql/types/NewFeatureBroadcast.ts +++ /dev/null @@ -1,26 +0,0 @@ -import {GraphQLID, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql' -import {GQLContext} from '../graphql' - -const NewFeatureBroadcast = new GraphQLObjectType({ - name: 'NewFeatureBroadcast', - description: 'The latest feature released by Parabol', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID) - }, - actionButtonCopy: { - type: new GraphQLNonNull(GraphQLString), - description: 'The text of the action button in the snackbar' - }, - snackbarMessage: { - type: new GraphQLNonNull(GraphQLString), - description: 'The description of the new feature' - }, - url: { - type: new GraphQLNonNull(GraphQLString), - description: 'The permalink to the blog post describing the new feature' - } - }) -}) - -export default NewFeatureBroadcast diff --git a/packages/server/postgres/migrations/1728418948136_NewFeature-oneshot.ts b/packages/server/postgres/migrations/1728418948136_NewFeature-oneshot.ts new file mode 100644 index 00000000000..2b6e137980f --- /dev/null +++ b/packages/server/postgres/migrations/1728418948136_NewFeature-oneshot.ts @@ -0,0 +1,48 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {Client} from 'pg' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' +import getPgConfig from '../getPgConfig' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS "NewFeature" ( + "id" INT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY, + "actionButtonCopy" VARCHAR(50) NOT NULL, + "snackbarMessage" VARCHAR(255) NOT NULL, + "url" VARCHAR(2056) NOT NULL + ); + END $$; +`.execute(pg) + + // empty out old new features, do not migrate them over + await pg.updateTable('User').set({newFeatureId: null}).execute() + await pg.schema + .alterTable('User') + .alterColumn('newFeatureId', (builder) => + builder.setDataType(sql`INTEGER USING "newFeatureId"::integer`) + ) + .execute() + await pg.schema + .alterTable('User') + .addForeignKeyConstraint('fk_newFeatureId', ['newFeatureId'], 'NewFeature', ['id']) + .onDelete('set null') + .execute() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE IF EXISTS "NewFeature"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index 45e15ee777c..1f2461e231a 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -281,3 +281,5 @@ export const selectMeetingMembers = () => getKysely().selectFrom('MeetingMember').selectAll().$narrowType() export const selectMassInvitations = () => getKysely().selectFrom('MassInvitation').selectAll() + +export const selectNewFeatures = () => getKysely().selectFrom('NewFeature').selectAll() diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index 2186bdc5ee3..3db0a425a1b 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -8,6 +8,7 @@ import { selectAgendaItems, selectComments, selectMeetingSettings, + selectNewFeatures, selectNewMeetings, selectOrganizations, selectReflectPrompts, @@ -72,3 +73,4 @@ export type Comment = ExtractTypeFromQueryBuilderSelect export type ReflectPrompt = ExtractTypeFromQueryBuilderSelect export type NewMeeting = ExtractTypeFromQueryBuilderSelect +export type NewFeature = ExtractTypeFromQueryBuilderSelect From 72ea4e1bde79cdec9d9d4eda80932e32e880d19c Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 9 Oct 2024 17:10:19 -0700 Subject: [PATCH 510/529] chore(rethinkdb): TeamInvitation: Phase 1 (#10325) Signed-off-by: Matt Krick --- codegen.json | 2 +- packages/server/database/rethinkDriver.ts | 2 +- .../dataloader/foreignKeyLoaderMakers.ts | 14 +++++ .../dataloader/primaryKeyLoaderMakers.ts | 5 ++ .../helpers/handleMassInviteToken.ts | 18 ++++-- .../helpers/handleTeamInviteToken.ts | 2 +- .../mutations/helpers/inviteToTeamHelper.ts | 28 +++++---- .../types/AcceptTeamInvitationPayload.ts | 2 +- .../1728425112300_TeamInvitation-phase1.ts | 60 +++++++++++++++++++ packages/server/postgres/select.ts | 2 + packages/server/postgres/types/index.d.ts | 2 + .../safeMutations/acceptTeamInvitation.ts | 9 ++- .../server/safeMutations/safeArchiveTeam.ts | 8 +++ .../server/utils/getBestInvitationMeeting.ts | 2 +- 14 files changed, 133 insertions(+), 23 deletions(-) create mode 100644 packages/server/postgres/migrations/1728425112300_TeamInvitation-phase1.ts diff --git a/codegen.json b/codegen.json index ceaf9293682..77d538e22cc 100644 --- a/codegen.json +++ b/codegen.json @@ -180,7 +180,7 @@ "Team": "../../postgres/types/index#Team as TeamDB", "TeamHealthPhase": "./types/TeamHealthPhase#TeamHealthPhaseSource", "TeamHealthStage": "./types/TeamHealthStage#TeamHealthStageSource", - "TeamInvitation": "../../database/types/TeamInvitation#default", + "TeamInvitation": "../../postgres/types/index/#TeamInvitation", "TeamMember": "../../postgres/types/index#TeamMember as TeamMember", "TeamMemberIntegrationAuthOAuth1": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", "TeamMemberIntegrationAuthOAuth2": "../../postgres/queries/getTeamMemberIntegrationAuth#TeamMemberIntegrationAuth", diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index ffe5a5ca287..dd498997ecf 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -1,5 +1,5 @@ import {MasterPool, r} from 'rethinkdb-ts' -import TeamInvitation from '../database/types/TeamInvitation' +import {TeamInvitation} from '../postgres/types/index' import getRethinkConfig from './getRethinkConfig' import {R} from './stricterR' import NotificationKickedOut from './types/NotificationKickedOut' diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 1d2ace54f0b..522dbcd1ba2 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -1,3 +1,4 @@ +import {sql} from 'kysely' import getKysely from '../postgres/getKysely' import {getTeamPromptResponsesByMeetingIds} from '../postgres/queries/getTeamPromptResponsesByMeetingIds' import { @@ -12,6 +13,7 @@ import { selectSlackAuths, selectSlackNotifications, selectSuggestedAction, + selectTeamInvitations, selectTeams, selectTemplateDimension, selectTemplateScale, @@ -280,3 +282,15 @@ export const massInvitationsByTeamMemberId = foreignKeyLoaderMaker( .execute() } ) + +export const _pgteamInvitationsByTeamId = foreignKeyLoaderMaker( + '_pgteamInvitations', + 'teamId', + async (teamIds) => { + return selectTeamInvitations() + .where('teamId', 'in', teamIds) + .where('acceptedAt', 'is', null) + .where('expiresAt', '>=', sql`CURRENT_TIMESTAMP`) + .execute() + } +) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 813fb985c8f..811069fb210 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -19,6 +19,7 @@ import { selectSlackAuths, selectSlackNotifications, selectSuggestedAction, + selectTeamInvitations, selectTeamPromptResponses, selectTeams, selectTemplateDimension, @@ -139,3 +140,7 @@ export const massInvitations = primaryKeyLoaderMaker((ids: readonly string[]) => export const newFeatures = primaryKeyLoaderMaker((ids: readonly number[]) => { return selectNewFeatures().where('id', 'in', ids).execute() }) + +export const _pgteamInvitations = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectTeamInvitations().where('id', 'in', ids).execute() +}) diff --git a/packages/server/graphql/mutations/helpers/handleMassInviteToken.ts b/packages/server/graphql/mutations/helpers/handleMassInviteToken.ts index c8ac2e69502..8c6d0200bae 100644 --- a/packages/server/graphql/mutations/helpers/handleMassInviteToken.ts +++ b/packages/server/graphql/mutations/helpers/handleMassInviteToken.ts @@ -1,6 +1,7 @@ import {InvitationTokenError} from 'parabol-client/types/constEnums' import getRethink from '../../../database/rethinkDriver' -import TeamInvitation from '../../../database/types/TeamInvitation' +import generateUID from '../../../generateUID' +import getKysely from '../../../postgres/getKysely' import {verifyMassInviteToken} from '../../../utils/massInviteToken' import {DataLoaderWorker} from '../../graphql' @@ -17,15 +18,22 @@ const handleMassInviteToken = async ( if (tms?.includes(teamId)) { return {error: InvitationTokenError.ALREADY_ACCEPTED, teamId, meetingId} } - const invitation = new TeamInvitation({ + const invitation = { + id: generateUID(), token: invitationToken, invitedBy, meetingId, teamId, expiresAt, - email - }) - await r.table('TeamInvitation').insert(invitation).run() + email, + isMassInvite: true, + acceptedAt: null + } + await getKysely().insertInto('TeamInvitation').values(invitation).execute() + await r + .table('TeamInvitation') + .insert({...invitation, createdAt: new Date()}) + .run() return {invitation} } diff --git a/packages/server/graphql/mutations/helpers/handleTeamInviteToken.ts b/packages/server/graphql/mutations/helpers/handleTeamInviteToken.ts index bfed33eb19c..544dc5b1a83 100644 --- a/packages/server/graphql/mutations/helpers/handleTeamInviteToken.ts +++ b/packages/server/graphql/mutations/helpers/handleTeamInviteToken.ts @@ -1,6 +1,6 @@ import {InvitationTokenError} from 'parabol-client/types/constEnums' import getRethink from '../../../database/rethinkDriver' -import TeamInvitation from '../../../database/types/TeamInvitation' +import {TeamInvitation} from '../../../postgres/types' const handleTeamInviteToken = async ( invitationToken: string, diff --git a/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts b/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts index 916faae658a..561febbd547 100644 --- a/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts +++ b/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts @@ -7,9 +7,10 @@ import {isNotNull} from '../../../../client/utils/predicates' import appOrigin from '../../../appOrigin' import getRethink from '../../../database/rethinkDriver' import NotificationTeamInvitation from '../../../database/types/NotificationTeamInvitation' -import TeamInvitation from '../../../database/types/TeamInvitation' import getMailManager from '../../../email/getMailManager' import teamInviteEmailCreator from '../../../email/teamInviteEmailCreator' +import generateUID from '../../../generateUID' +import getKysely from '../../../postgres/getKysely' import {getUsersByEmails} from '../../../postgres/queries/getUsersByEmails' import removeSuggestedAction from '../../../safeMutations/removeSuggestedAction' import {analytics} from '../../../utils/analytics/analytics' @@ -34,6 +35,7 @@ const inviteToTeamHelper = async ( const {authToken, dataLoader, socketId: mutatorId} = context const viewerId = getUserId(authToken) const r = await getRethink() + const pg = getKysely() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -112,18 +114,20 @@ const inviteToTeamHelper = async ( ) const expiresAt = new Date(Date.now() + Threshold.TEAM_INVITATION_LIFESPAN) // insert invitation records - const teamInvitationsToInsert = newAllowedInvitees.map((email, idx) => { - return new TeamInvitation({ - expiresAt, - email, - invitedBy: viewerId, - meetingId: meetingId ?? undefined, - teamId, - token: tokens[idx]! - }) - }) + const teamInvitationsToInsert = newAllowedInvitees.map((email, idx) => ({ + id: generateUID(), + expiresAt, + email, + invitedBy: viewerId, + meetingId: meetingId ?? undefined, + teamId, + token: tokens[idx]!, + isMassInvite: false, + createdAt: new Date(), + acceptedAt: null + })) await r.table('TeamInvitation').insert(teamInvitationsToInsert).run() - + await pg.insertInto('TeamInvitation').values(teamInvitationsToInsert).execute() // remove suggested action, if any let removedSuggestedActionId if (isOnboardTeam) { diff --git a/packages/server/graphql/public/types/AcceptTeamInvitationPayload.ts b/packages/server/graphql/public/types/AcceptTeamInvitationPayload.ts index 1590a4b281b..ff21995cf78 100644 --- a/packages/server/graphql/public/types/AcceptTeamInvitationPayload.ts +++ b/packages/server/graphql/public/types/AcceptTeamInvitationPayload.ts @@ -6,7 +6,7 @@ import isValid from '../../isValid' import {AcceptTeamInvitationPayloadResolvers} from '../resolverTypes' export type AcceptTeamInvitationPayloadSource = { - meetingId?: string + meetingId?: string | null teamId?: string teamMemberId?: string invitationNotificationIds?: string[] diff --git a/packages/server/postgres/migrations/1728425112300_TeamInvitation-phase1.ts b/packages/server/postgres/migrations/1728425112300_TeamInvitation-phase1.ts new file mode 100644 index 00000000000..875feca6b68 --- /dev/null +++ b/packages/server/postgres/migrations/1728425112300_TeamInvitation-phase1.ts @@ -0,0 +1,60 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {Client} from 'pg' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' +import getPgConfig from '../getPgConfig' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql` + DO $$ + BEGIN + CREATE TABLE IF NOT EXISTS "TeamInvitation" ( + "id" VARCHAR(100) NOT NULL PRIMARY KEY, + "acceptedAt" TIMESTAMP WITH TIME ZONE, + "acceptedBy" VARCHAR(100), + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "expiresAt" TIMESTAMP WITH TIME ZONE NOT NULL, + "email" "citext" NOT NULL, + "invitedBy" VARCHAR(100) NOT NULL, + "isMassInvite" BOOLEAN NOT NULL DEFAULT FALSE, + "meetingId" VARCHAR(100), + "teamId" VARCHAR(100) NOT NULL, + "token" VARCHAR(200) NOT NULL, + CONSTRAINT "fk_meetingId" + FOREIGN KEY("meetingId") + REFERENCES "NewMeeting"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_invitedBy" + FOREIGN KEY("invitedBy") + REFERENCES "User"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_acceptedBy" + FOREIGN KEY("acceptedBy") + REFERENCES "User"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_teamId" + FOREIGN KEY("teamId") + REFERENCES "Team"("id") + ON DELETE CASCADE + ); + CREATE INDEX IF NOT EXISTS "idx_TeamInvitation_email" ON "TeamInvitation"("email"); + CREATE INDEX IF NOT EXISTS "idx_TeamInvitation_teamId" ON "TeamInvitation"("teamId"); + CREATE INDEX IF NOT EXISTS "idx_TeamInvitation_token" ON "TeamInvitation"("token"); + END $$; +`.execute(pg) +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE IF EXISTS "TeamInvitation"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index 1f2461e231a..2b3c4421d72 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -283,3 +283,5 @@ export const selectMeetingMembers = () => export const selectMassInvitations = () => getKysely().selectFrom('MassInvitation').selectAll() export const selectNewFeatures = () => getKysely().selectFrom('NewFeature').selectAll() + +export const selectTeamInvitations = () => getKysely().selectFrom('TeamInvitation').selectAll() diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index 3db0a425a1b..587e2372564 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -16,6 +16,7 @@ import { selectSlackAuths, selectSlackNotifications, selectSuggestedAction, + selectTeamInvitations, selectTeamPromptResponses, selectTeams, selectTemplateScale, @@ -74,3 +75,4 @@ export type ReflectPrompt = ExtractTypeFromQueryBuilderSelect export type NewFeature = ExtractTypeFromQueryBuilderSelect +export type TeamInvitation = ExtractTypeFromQueryBuilderSelect diff --git a/packages/server/safeMutations/acceptTeamInvitation.ts b/packages/server/safeMutations/acceptTeamInvitation.ts index 63171c108eb..8c7f3a425c0 100644 --- a/packages/server/safeMutations/acceptTeamInvitation.ts +++ b/packages/server/safeMutations/acceptTeamInvitation.ts @@ -93,6 +93,14 @@ const acceptTeamInvitation = async (team: Team, userId: string, dataLoader: Data .set({tms: sql`arr_append_uniq("tms", ${teamId})`}) .where('id', '=', userId) ) + .with('TeamInvitationUpdate', (qb) => + // redeem all invitations, otherwise if they have 2 someone could join after they've been kicked out + qb + .updateTable('TeamInvitation') + .set({acceptedAt: sql`CURRENT_TIMESTAMP`, acceptedBy: userId}) + .where('email', '=', email) + .where('teamId', '=', teamId) + ) .insertInto('TeamMember') .values({ id: TeamMemberId.join(teamId, userId), @@ -108,7 +116,6 @@ const acceptTeamInvitation = async (team: Team, userId: string, dataLoader: Data r .table('TeamInvitation') .getAll(teamId, {index: 'teamId'}) - // redeem all invitations, otherwise if they have 2 someone could join after they've been kicked out .filter({email}) .update({ acceptedAt: now, diff --git a/packages/server/safeMutations/safeArchiveTeam.ts b/packages/server/safeMutations/safeArchiveTeam.ts index ea18bb807da..8194b4c2577 100644 --- a/packages/server/safeMutations/safeArchiveTeam.ts +++ b/packages/server/safeMutations/safeArchiveTeam.ts @@ -1,3 +1,4 @@ +import {sql} from 'kysely' import getRethink from '../database/rethinkDriver' import {RDatum} from '../database/stricterR' import {DataLoaderWorker} from '../graphql/graphql' @@ -32,6 +33,13 @@ const safeArchiveTeam = async (teamId: string, dataLoader: DataLoaderWorker) => .returningAll() .executeTakeFirst(), pg + .with('TeamInvitationUpdate', (qb) => + qb + .updateTable('TeamInvitation') + .set({expiresAt: sql`CURRENT_TIMESTAMP`}) + .where('teamId', '=', teamId) + .where('acceptedAt', 'is', null) + ) .updateTable('User') .set(({fn, ref, val}) => ({tms: fn('ARRAY_REMOVE', [ref('tms'), val(teamId)])})) .where('id', 'in', userIds) diff --git a/packages/server/utils/getBestInvitationMeeting.ts b/packages/server/utils/getBestInvitationMeeting.ts index 3b36f940ebf..8138915f558 100644 --- a/packages/server/utils/getBestInvitationMeeting.ts +++ b/packages/server/utils/getBestInvitationMeeting.ts @@ -2,7 +2,7 @@ import {DataLoaderWorker} from '../graphql/graphql' const getBestInvitationMeeting = async ( teamId: string, - maybeMeetingId: string | undefined, + maybeMeetingId: string | undefined | null, dataLoader: DataLoaderWorker ) => { const activeMeetings = await dataLoader.get('activeMeetingsByTeamId').load(teamId) From 8f6049e85ea87ad5f20061685d2d577c5ab831e7 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:20:11 -0700 Subject: [PATCH 511/529] chore(release): release v7.50.7 (#10331) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 0877bdaaac8..a4bad28fe0d 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.50.6" + ".": "7.50.7" } diff --git a/CHANGELOG.md b/CHANGELOG.md index beae3250882..7d14d34f5b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.50.7](https://github.com/ParabolInc/parabol/compare/v7.50.6...v7.50.7) (2024-10-10) + + +### Changed + +* **rethinkdb:** NewFeature: OneShot ([#10312](https://github.com/ParabolInc/parabol/issues/10312)) ([92deddf](https://github.com/ParabolInc/parabol/commit/92deddf30645ad4d479f29eb7ab66737a3218946)) +* **rethinkdb:** TeamInvitation: Phase 1 ([#10325](https://github.com/ParabolInc/parabol/issues/10325)) ([72ea4e1](https://github.com/ParabolInc/parabol/commit/72ea4e1bde79cdec9d9d4eda80932e32e880d19c)) + ## [7.50.6](https://github.com/ParabolInc/parabol/compare/v7.50.5...v7.50.6) (2024-10-09) diff --git a/package.json b/package.json index 570475330ff..8571d7491ae 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.50.6", + "version": "7.50.7", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 1e16f6c09eb..01a3e27b0db 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.50.6", + "version": "7.50.7", "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.50.6" + "parabol-server": "7.50.7" } } diff --git a/packages/client/package.json b/packages/client/package.json index 8800043f036..413b2d7217a 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.50.6", + "version": "7.50.7", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 3c037b2910c..6be11f0574c 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.50.6", + "version": "7.50.7", "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 b0c99dec35b..8324ce6fc1a 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.50.6", + "version": "7.50.7", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.50.6", - "parabol-server": "7.50.6", + "parabol-client": "7.50.7", + "parabol-server": "7.50.7", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 5b3d7267bbf..c38c25f69f1 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.50.6", + "version": "7.50.7", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 3939752121a..93f878bdff9 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.50.6", + "version": "7.50.7", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.50.6", + "parabol-client": "7.50.7", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From f8486bae6feeeb7a69088a864e8277c53d052061 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 9 Oct 2024 17:22:49 -0700 Subject: [PATCH 512/529] chore(rethinkdb): TeamInvitation: Phase 2 (#10326) Signed-off-by: Matt Krick --- .../1728496970486_TeamInvitation-phase2.ts | 134 ++++++++++++++++++ 1 file changed, 134 insertions(+) create mode 100644 packages/server/postgres/migrations/1728496970486_TeamInvitation-phase2.ts diff --git a/packages/server/postgres/migrations/1728496970486_TeamInvitation-phase2.ts b/packages/server/postgres/migrations/1728496970486_TeamInvitation-phase2.ts new file mode 100644 index 00000000000..54271a6721e --- /dev/null +++ b/packages/server/postgres/migrations/1728496970486_TeamInvitation-phase2.ts @@ -0,0 +1,134 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + try { + console.log('Adding index') + await r + .table('TeamInvitation') + .indexCreate('updatedAtId', (row: any) => [row('expiresAt'), row('id')]) + .run() + await r.table('TeamInvitation').indexWait().run() + } catch { + // index already exists + } + + console.log('Adding index complete') + + const MAX_PG_PARAMS = 65545 + const PG_COLS = [ + 'id', + 'acceptedAt', + 'acceptedBy', + 'createdAt', + 'expiresAt', + 'email', + 'invitedBy', + 'isMassInvite', + 'meetingId', + 'teamId', + 'token' + ] as const + type TeamInvitation = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + + let curUpdatedAt = new Date() + let curId = r.minval + + const insertRow = async (row) => { + try { + await pg + .insertInto('TeamInvitation') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_teamId') { + console.log('TeamInvitation has no team, skipping insert', row.id) + return + } + if (e.constraint === 'fk_meetingId') { + console.log('TeamInvitation has no meeting, skipping insert', row.id) + return + } + if (e.constraint === 'fk_acceptedBy') { + console.log('TeamInvitation has no acceptedBy user, skipping insert', row.id) + return + } + if (e.constraint === 'fk_invitedBy') { + console.log('TeamInvitation has no invitedBy user, skipping insert', row.id) + return + } + throw e + } + } + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, String(curUpdatedAt), String(curId)) + const rawRowsToInsert = (await r + .table('TeamInvitation') + .between([curUpdatedAt, curId], [r.maxval, r.maxval], { + index: 'updatedAtId', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'updatedAtId'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as TeamInvitation[] + + const rowsToInsert = rawRowsToInsert.map((row) => { + const { + id, + acceptedAt, + acceptedBy, + createdAt, + expiresAt, + email, + invitedBy, + isMassInvite, + meetingId, + teamId, + token + } = row as any + return { + id, + acceptedAt, + acceptedBy, + createdAt, + expiresAt, + email, + invitedBy, + isMassInvite, + meetingId, + teamId, + token + } + }) + + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curUpdatedAt = lastRow.expiresAt + curId = lastRow.id + await Promise.all(rowsToInsert.map(async (row) => insertRow(row))) + } +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql`TRUNCATE TABLE "TeamInvitation" CASCADE`.execute(pg) +} From 16c2b35b2e95fecb7273aeb4ff85e40a5e4e144c Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 9 Oct 2024 17:31:22 -0700 Subject: [PATCH 513/529] chore(release): release v7.50.8 (#10333) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a4bad28fe0d..7568c2e9275 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.50.7" + ".": "7.50.8" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 7d14d34f5b6..818f6408ed9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.50.8](https://github.com/ParabolInc/parabol/compare/v7.50.7...v7.50.8) (2024-10-10) + + +### Changed + +* **rethinkdb:** TeamInvitation: Phase 2 ([#10326](https://github.com/ParabolInc/parabol/issues/10326)) ([f8486ba](https://github.com/ParabolInc/parabol/commit/f8486bae6feeeb7a69088a864e8277c53d052061)) + ## [7.50.7](https://github.com/ParabolInc/parabol/compare/v7.50.6...v7.50.7) (2024-10-10) diff --git a/package.json b/package.json index 8571d7491ae..b2743425d01 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.50.7", + "version": "7.50.8", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 01a3e27b0db..77138df621c 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.50.7", + "version": "7.50.8", "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.50.7" + "parabol-server": "7.50.8" } } diff --git a/packages/client/package.json b/packages/client/package.json index 413b2d7217a..0022e0094f6 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.50.7", + "version": "7.50.8", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 6be11f0574c..4eec50e308b 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.50.7", + "version": "7.50.8", "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 8324ce6fc1a..1caf2edbeda 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.50.7", + "version": "7.50.8", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^4.2.0", - "parabol-client": "7.50.7", - "parabol-server": "7.50.7", + "parabol-client": "7.50.8", + "parabol-server": "7.50.8", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index c38c25f69f1..e35f9ef7062 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.50.7", + "version": "7.50.8", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 93f878bdff9..2e677fa2099 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.50.7", + "version": "7.50.8", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.50.7", + "parabol-client": "7.50.8", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 9b52d4958d8aace5c3adc2322ede58f638a947cd Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Thu, 10 Oct 2024 09:36:07 -0700 Subject: [PATCH 514/529] chore(rethinkdb): TeamInvitation: Phase 3 (#10327) Signed-off-by: Matt Krick --- packages/server/database/rethinkDriver.ts | 11 +---- .../server/database/types/TeamInvitation.ts | 42 ------------------- packages/server/dataloader/RethinkDBCache.ts | 2 +- .../dataloader/foreignKeyLoaderMakers.ts | 4 +- .../dataloader/primaryKeyLoaderMakers.ts | 2 +- .../rethinkForeignKeyLoaderMakers.ts | 15 ------- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../helpers/handleMassInviteToken.ts | 9 +--- .../helpers/handleTeamInviteToken.ts | 15 +++---- .../mutations/helpers/inviteToTeamHelper.ts | 23 ++++++---- .../private/mutations/hardDeleteUser.ts | 12 +----- .../graphql/queries/verifiedInvitation.ts | 17 ++++---- .../safeMutations/acceptTeamInvitation.ts | 12 +----- .../server/safeMutations/safeArchiveTeam.ts | 15 +------ 14 files changed, 40 insertions(+), 140 deletions(-) delete mode 100644 packages/server/database/types/TeamInvitation.ts diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index dd498997ecf..649fd167cc7 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -1,5 +1,4 @@ import {MasterPool, r} from 'rethinkdb-ts' -import {TeamInvitation} from '../postgres/types/index' import getRethinkConfig from './getRethinkConfig' import {R} from './stricterR' import NotificationKickedOut from './types/NotificationKickedOut' @@ -15,10 +14,6 @@ import NotificationTeamInvitation from './types/NotificationTeamInvitation' import Task from './types/Task' export type RethinkSchema = { - NewFeature: { - type: any - index: '' - } Notification: { type: | NotificationTaskInvolves @@ -44,14 +39,10 @@ export type RethinkSchema = { | 'userId' | 'integrationHash' } - TeamInvitation: { - type: TeamInvitation - index: 'email' | 'teamId' | 'token' - } } export type DBType = { - [P in keyof RethinkSchema]: RethinkSchema[P]['type'] + [P in keyof RethinkSchema]: any } export type ParabolR = R diff --git a/packages/server/database/types/TeamInvitation.ts b/packages/server/database/types/TeamInvitation.ts deleted file mode 100644 index ebfc1a41765..00000000000 --- a/packages/server/database/types/TeamInvitation.ts +++ /dev/null @@ -1,42 +0,0 @@ -import generateUID from '../../generateUID' -import getIsMassInviteToken from '../../graphql/mutations/helpers/getIsMassInviteToken' - -interface Input { - id?: string - acceptedAt?: Date - acceptedBy?: string - expiresAt: Date - email: string - invitedBy: string - meetingId?: string - teamId: string - token: string -} -export default class TeamInvitation { - id: string - acceptedAt: Date | null - acceptedBy?: string - createdAt: Date - expiresAt: Date - email: string - invitedBy: string - isMassInvite: boolean - meetingId?: string - teamId: string - token: string - constructor(input: Input) { - const {teamId, acceptedAt, acceptedBy, email, expiresAt, id, invitedBy, meetingId, token} = - input - this.id = id || generateUID() - this.acceptedAt = acceptedAt || null - this.acceptedBy = acceptedBy - this.createdAt = new Date() - this.expiresAt = expiresAt - this.email = email - this.invitedBy = invitedBy - this.isMassInvite = getIsMassInviteToken(token) - this.meetingId = meetingId - this.teamId = teamId - this.token = token - } -} diff --git a/packages/server/dataloader/RethinkDBCache.ts b/packages/server/dataloader/RethinkDBCache.ts index 7bf0ced8be7..b33a05a28fd 100644 --- a/packages/server/dataloader/RethinkDBCache.ts +++ b/packages/server/dataloader/RethinkDBCache.ts @@ -51,7 +51,7 @@ export default class RethinkDBCache { .table(table) .get(id) // "always" will return the document whether it has changed or not - .update(updater, {returnChanges: 'always'})('changes')(0)('new_val') + .update(updater as any, {returnChanges: 'always'})('changes')(0)('new_val') .default(null) ) }) diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 522dbcd1ba2..5f9131e0708 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -283,8 +283,8 @@ export const massInvitationsByTeamMemberId = foreignKeyLoaderMaker( } ) -export const _pgteamInvitationsByTeamId = foreignKeyLoaderMaker( - '_pgteamInvitations', +export const teamInvitationsByTeamId = foreignKeyLoaderMaker( + 'teamInvitations', 'teamId', async (teamIds) => { return selectTeamInvitations() diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index 811069fb210..fe94fd0ea23 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -141,6 +141,6 @@ export const newFeatures = primaryKeyLoaderMaker((ids: readonly number[]) => { return selectNewFeatures().where('id', 'in', ids).execute() }) -export const _pgteamInvitations = primaryKeyLoaderMaker((ids: readonly string[]) => { +export const teamInvitations = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectTeamInvitations().where('id', 'in', ids).execute() }) diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts index d558c0f190c..e7d6a9342b7 100644 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts @@ -30,18 +30,3 @@ export const tasksByTeamId = new RethinkForeignKeyLoaderMaker( .run() } ) - -export const teamInvitationsByTeamId = new RethinkForeignKeyLoaderMaker( - 'teamInvitations', - 'teamId', - async (teamIds) => { - const r = await getRethink() - const now = new Date() - return r - .table('TeamInvitation') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter({acceptedAt: null}) - .filter((row: RDatum) => row('expiresAt').ge(now)) - .run() - } -) diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index d0f67f23712..973c2c959d0 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -5,4 +5,3 @@ import RethinkPrimaryKeyLoaderMaker from './RethinkPrimaryKeyLoaderMaker' */ export const notifications = new RethinkPrimaryKeyLoaderMaker('Notification') export const tasks = new RethinkPrimaryKeyLoaderMaker('Task') -export const teamInvitations = new RethinkPrimaryKeyLoaderMaker('TeamInvitation') diff --git a/packages/server/graphql/mutations/helpers/handleMassInviteToken.ts b/packages/server/graphql/mutations/helpers/handleMassInviteToken.ts index 8c6d0200bae..4a4fa2d588a 100644 --- a/packages/server/graphql/mutations/helpers/handleMassInviteToken.ts +++ b/packages/server/graphql/mutations/helpers/handleMassInviteToken.ts @@ -1,5 +1,4 @@ import {InvitationTokenError} from 'parabol-client/types/constEnums' -import getRethink from '../../../database/rethinkDriver' import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' import {verifyMassInviteToken} from '../../../utils/massInviteToken' @@ -13,7 +12,6 @@ const handleMassInviteToken = async ( ) => { const validToken = await verifyMassInviteToken(invitationToken, dataLoader) if ('error' in validToken) return {error: validToken.error} - const r = await getRethink() const {teamId, userId: invitedBy, exp: expiresAt, meetingId} = validToken if (tms?.includes(teamId)) { return {error: InvitationTokenError.ALREADY_ACCEPTED, teamId, meetingId} @@ -26,14 +24,9 @@ const handleMassInviteToken = async ( teamId, expiresAt, email, - isMassInvite: true, - acceptedAt: null + isMassInvite: true } await getKysely().insertInto('TeamInvitation').values(invitation).execute() - await r - .table('TeamInvitation') - .insert({...invitation, createdAt: new Date()}) - .run() return {invitation} } diff --git a/packages/server/graphql/mutations/helpers/handleTeamInviteToken.ts b/packages/server/graphql/mutations/helpers/handleTeamInviteToken.ts index 544dc5b1a83..8205be8c520 100644 --- a/packages/server/graphql/mutations/helpers/handleTeamInviteToken.ts +++ b/packages/server/graphql/mutations/helpers/handleTeamInviteToken.ts @@ -1,6 +1,6 @@ import {InvitationTokenError} from 'parabol-client/types/constEnums' import getRethink from '../../../database/rethinkDriver' -import {TeamInvitation} from '../../../postgres/types' +import getKysely from '../../../postgres/getKysely' const handleTeamInviteToken = async ( invitationToken: string, @@ -9,12 +9,13 @@ const handleTeamInviteToken = async ( notificationId?: string ) => { const r = await getRethink() - const invitation = (await r - .table('TeamInvitation') - .getAll(invitationToken, {index: 'token'}) - .nth(0) - .default(null) - .run()) as TeamInvitation + const pg = getKysely() + const invitation = await pg + .selectFrom('TeamInvitation') + .selectAll() + .where('token', '=', invitationToken) + .limit(1) + .executeTakeFirst() if (!invitation) return {error: InvitationTokenError.NOT_FOUND} const {expiresAt} = invitation if (expiresAt.getTime() < Date.now()) { diff --git a/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts b/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts index 561febbd547..81688c94fbc 100644 --- a/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts +++ b/packages/server/graphql/mutations/helpers/inviteToTeamHelper.ts @@ -39,15 +39,21 @@ const inviteToTeamHelper = async ( const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} - const [total, pending] = await Promise.all([ - r.table('TeamInvitation').getAll(teamId, {index: 'teamId'}).count().run(), - r - .table('TeamInvitation') - .getAll(teamId, {index: 'teamId'}) - .filter({acceptedAt: null}) - .count() - .run() + const [totalRes, pendingRes] = await Promise.all([ + pg + .selectFrom('TeamInvitation') + .select(({fn}) => fn.count('id').as('count')) + .where('teamId', '=', teamId) + .executeTakeFirstOrThrow(), + pg + .selectFrom('TeamInvitation') + .select(({fn}) => fn.count('id').as('count')) + .where('teamId', '=', teamId) + .where('acceptedAt', 'is', null) + .executeTakeFirstOrThrow() ]) + const total = Number(totalRes.count) + const pending = Number(pendingRes.count) const accepted = total - pending // if no one has accepted one of their 100+ invites, don't trust them if (accepted === 0 && total + invitees.length >= 100) { @@ -126,7 +132,6 @@ const inviteToTeamHelper = async ( createdAt: new Date(), acceptedAt: null })) - await r.table('TeamInvitation').insert(teamInvitationsToInsert).run() await pg.insertInto('TeamInvitation').values(teamInvitationsToInsert).execute() // remove suggested action, if any let removedSuggestedActionId diff --git a/packages/server/graphql/private/mutations/hardDeleteUser.ts b/packages/server/graphql/private/mutations/hardDeleteUser.ts index 8effc9a13ad..63f9571af59 100644 --- a/packages/server/graphql/private/mutations/hardDeleteUser.ts +++ b/packages/server/graphql/private/mutations/hardDeleteUser.ts @@ -91,17 +91,7 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( .table('Task') .getAll(r.args(teamIds), {index: 'teamId'}) .filter((row: RValue) => row('createdBy').eq(userIdToDelete)) - .delete(), - invitedByTeamInvitation: r - .table('TeamInvitation') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((row: RValue) => row('invitedBy').eq(userIdToDelete)) - .delete(), - createdByTeamInvitations: r - .table('TeamInvitation') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((row: RValue) => row('acceptedBy').eq(userIdToDelete)) - .update({acceptedBy: ''}) + .delete() }).run() // now postgres, after FKs are added then triggers should take care of children diff --git a/packages/server/graphql/queries/verifiedInvitation.ts b/packages/server/graphql/queries/verifiedInvitation.ts index c4f959b4bc4..1eae17749ef 100644 --- a/packages/server/graphql/queries/verifiedInvitation.ts +++ b/packages/server/graphql/queries/verifiedInvitation.ts @@ -3,7 +3,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {InvitationTokenError} from 'parabol-client/types/constEnums' import util from 'util' import {AuthIdentityTypeEnum} from '../../../client/types/constEnums' -import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserByEmail} from '../../postgres/queries/getUsersByEmails' import IUser from '../../postgres/types/IUser' import getBestInvitationMeeting from '../../utils/getBestInvitationMeeting' @@ -41,14 +41,15 @@ export default { }, resolve: rateLimit({perMinute: 60, perHour: 1800})( async (_source: unknown, {token}, {dataLoader}: GQLContext) => { - const r = await getRethink() + const pg = getKysely() const now = new Date() - const teamInvitation = await r - .table('TeamInvitation') - .getAll(token, {index: 'token'}) - .nth(0) - .default(null) - .run() + const teamInvitation = await pg + .selectFrom('TeamInvitation') + .selectAll() + .where('token', '=', token) + .limit(1) + .executeTakeFirst() + if (!teamInvitation) return {errorType: InvitationTokenError.NOT_FOUND} const { email, diff --git a/packages/server/safeMutations/acceptTeamInvitation.ts b/packages/server/safeMutations/acceptTeamInvitation.ts index 8c7f3a425c0..c2cd852458d 100644 --- a/packages/server/safeMutations/acceptTeamInvitation.ts +++ b/packages/server/safeMutations/acceptTeamInvitation.ts @@ -63,7 +63,6 @@ const handleFirstAcceptedInvitation = async ( const acceptTeamInvitation = async (team: Team, userId: string, dataLoader: DataLoaderWorker) => { const r = await getRethink() const pg = getKysely() - const now = new Date() const {id: teamId, orgId} = team const [user, organizationUser] = await Promise.all([ dataLoader.get('users').loadNonNull(userId), @@ -112,16 +111,7 @@ const acceptTeamInvitation = async (team: Team, userId: string, dataLoader: Data openDrawer: 'manageTeam' }) .onConflict((oc) => oc.column('id').doUpdateSet({isNotRemoved: true, isLead: false})) - .execute(), - r - .table('TeamInvitation') - .getAll(teamId, {index: 'teamId'}) - .filter({email}) - .update({ - acceptedAt: now, - acceptedBy: userId - }) - .run() + .execute() ]) dataLoader.clearAll(['teamMembers', 'users']) if (!organizationUser) { diff --git a/packages/server/safeMutations/safeArchiveTeam.ts b/packages/server/safeMutations/safeArchiveTeam.ts index 8194b4c2577..ea2a4ef5b78 100644 --- a/packages/server/safeMutations/safeArchiveTeam.ts +++ b/packages/server/safeMutations/safeArchiveTeam.ts @@ -1,25 +1,13 @@ import {sql} from 'kysely' -import getRethink from '../database/rethinkDriver' -import {RDatum} from '../database/stricterR' import {DataLoaderWorker} from '../graphql/graphql' import getKysely from '../postgres/getKysely' const safeArchiveTeam = async (teamId: string, dataLoader: DataLoaderWorker) => { - const r = await getRethink() const pg = getKysely() const now = new Date() const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) const userIds = teamMembers.map((tm) => tm.userId) - const [rethinkResult, removedSuggestedActions, team] = await Promise.all([ - r({ - invitations: r - .table('TeamInvitation') - .getAll(teamId, {index: 'teamId'}) - .filter({acceptedAt: null}) - .update((invitation: RDatum) => ({ - expiresAt: r.min([invitation('expiresAt'), now]) - })) as unknown as null - }).run(), + const [removedSuggestedActions, team] = await Promise.all([ pg .updateTable('SuggestedAction') .set({removedAt: now}) @@ -48,7 +36,6 @@ const safeArchiveTeam = async (teamId: string, dataLoader: DataLoaderWorker) => dataLoader.clearAll(['teamMembers', 'users', 'teams']) const users = await Promise.all(userIds.map((userId) => dataLoader.get('users').load(userId))) return { - invitations: rethinkResult.invitations, removedSuggestedActionIds: removedSuggestedActions.map(({id}) => id), team: team ?? null, users From a3aba863580fb866c4affad42f03454afd0d77e8 Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:23:26 +0100 Subject: [PATCH 515/529] fix(dd-trace): upgrade to v5.0.0 (#10343) --- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 2 +- packages/server/package.json | 2 +- yarn.lock | 260 +++++++++++++++++------------ 4 files changed, 154 insertions(+), 112 deletions(-) diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 4eec50e308b..4fa483ec110 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -36,7 +36,7 @@ "ts-node-dev": "^1.0.0-pre.44" }, "dependencies": { - "dd-trace": "^4.2.0", + "dd-trace": "^5.0.0", "franc-min": "^5.0.0", "ms": "^2.1.3", "redlock": "^5.0.0-beta.2" diff --git a/packages/gql-executor/package.json b/packages/gql-executor/package.json index 1caf2edbeda..5f86c18f014 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -25,7 +25,7 @@ "ts-node-dev": "^1.0.0-pre.44" }, "dependencies": { - "dd-trace": "^4.2.0", + "dd-trace": "^5.0.0", "parabol-client": "7.50.8", "parabol-server": "7.50.8", "undici": "^5.26.2" diff --git a/packages/server/package.json b/packages/server/package.json index 2e677fa2099..100d44e09b6 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -92,7 +92,7 @@ "bcryptjs": "^2.4.3", "cheerio": "^1.0.0-rc.10", "dataloader": "^2.0.0", - "dd-trace": "^4.2.0", + "dd-trace": "^5.0.0", "dotenv": "8.6.0", "dotenv-expand": "5.1.0", "draft-js": "https://github.com/mattkrick/draft-js/tarball/559a21968370c4944511657817d601a6c4ade0f6", diff --git a/yarn.lock b/yarn.lock index 23fa16e2ab5..d6e21700c55 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3765,24 +3765,25 @@ xml2js "0.5.0" yamux-js "0.1.2" -"@datadog/native-appsec@^3.2.0": - version "3.2.0" - resolved "https://registry.yarnpkg.com/@datadog/native-appsec/-/native-appsec-3.2.0.tgz#ddeca06cbaba9c6905903d09d18013f81eedc8c3" - integrity sha512-biAa7EFfuavjSWgSQaCit9CqGzr6Af5nhzfNNGJ38Y/Y387hDvLivAR374kK1z6XoxGZEOa+XPbVogmV/2Bcjw== +"@datadog/native-appsec@8.1.1": + version "8.1.1" + resolved "https://registry.yarnpkg.com/@datadog/native-appsec/-/native-appsec-8.1.1.tgz#76aa34697e6ecbd3d9ef7e6938d3cdcfa689b1f3" + integrity sha512-mf+Ym/AzET4FeUTXOs8hz0uLOSsVIUnavZPUx8YoKWK5lKgR2L+CLfEzOpjBwgFpDgbV8I1/vyoGelgGpsMKHA== dependencies: node-gyp-build "^3.9.0" -"@datadog/native-iast-rewriter@2.0.1": - version "2.0.1" - resolved "https://registry.yarnpkg.com/@datadog/native-iast-rewriter/-/native-iast-rewriter-2.0.1.tgz#dc4a23796870f2d840053ae879c61547eda6bb89" - integrity sha512-Mm+FG3XxEbPrAfJQPOMHts7iZZXRvg9gnGeeFRGkyirmRcQcOpZO4wFe/8K61DUVa5pXpgAJQ2ZkBGYF1O9STg== +"@datadog/native-iast-rewriter@2.4.1": + version "2.4.1" + resolved "https://registry.yarnpkg.com/@datadog/native-iast-rewriter/-/native-iast-rewriter-2.4.1.tgz#e8211f78c818906513fb96a549374da0382c7623" + integrity sha512-j3auTmyyn63e2y+SL28CGNy/l+jXQyh+pxqoGTacWaY5FW/dvo5nGQepAismgJ3qJ8VhQfVWRdxBSiT7wu9clw== dependencies: + lru-cache "^7.14.0" node-gyp-build "^4.5.0" -"@datadog/native-iast-taint-tracking@^1.4.1": - version "1.5.0" - resolved "https://registry.yarnpkg.com/@datadog/native-iast-taint-tracking/-/native-iast-taint-tracking-1.5.0.tgz#1a55eca6692079ac6167696682acb972aa0b0181" - integrity sha512-SOWIk1M6PZH0osNB191Voz2rKBPoF5hISWVSK9GiJPrD40+xjib1Z/bFDV7EkDn3kjOyordSBdNPG5zOqZJdyg== +"@datadog/native-iast-taint-tracking@3.1.0": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@datadog/native-iast-taint-tracking/-/native-iast-taint-tracking-3.1.0.tgz#7b2ed7f8fad212d65e5ab03bcdea8b42a3051b2e" + integrity sha512-rw6qSjmxmu1yFHVvZLXFt/rVq2tUZXocNogPLB8n7MPpA0jijNGb109WokWw5ITImiW91GcGDuBW6elJDVKouQ== dependencies: node-gyp-build "^3.9.0" @@ -3794,18 +3795,16 @@ node-addon-api "^6.1.0" node-gyp-build "^3.9.0" -"@datadog/pprof@^2.2.1": - version "2.2.2" - resolved "https://registry.yarnpkg.com/@datadog/pprof/-/pprof-2.2.2.tgz#5cc8aa2c198bb594bc8ecd85c94adbfac6e75563" - integrity sha512-6FVmgQoYvHVnpnAzfTHRIONJQprEJ6PdrfA3Kn4dfVEXZMH42PBRLSNWe4qoi5AKmr4SoIc6Ay7VAlHb/cDNjA== +"@datadog/pprof@5.3.0": + version "5.3.0" + resolved "https://registry.yarnpkg.com/@datadog/pprof/-/pprof-5.3.0.tgz#c2f58d328ecced7f99887f1a559d7fe3aecb9219" + integrity sha512-53z2Q3K92T6Pf4vz4Ezh8kfkVEvLzbnVqacZGgcbkP//q0joFzO8q00Etw1S6NdnCX0XmX08ULaF4rUI5r14mw== dependencies: delay "^5.0.0" - node-gyp-build "^3.9.0" + node-gyp-build "<4.0" p-limit "^3.1.0" - pify "^5.0.0" - pprof-format "^2.0.7" - source-map "^0.7.3" - split "^1.0.1" + pprof-format "^2.1.0" + source-map "^0.7.4" "@datadog/sketches-js@^2.1.0": version "2.1.0" @@ -5169,6 +5168,16 @@ resolved "https://registry.yarnpkg.com/@jsdevtools/ono/-/ono-7.1.3.tgz#9df03bbd7c696a5c58885c34aa06da41c8543796" integrity sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg== +"@jsep-plugin/assignment@^1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@jsep-plugin/assignment/-/assignment-1.2.1.tgz#07277bdd7862451a865d391e2142efba33f46c9b" + integrity sha512-gaHqbubTi29aZpVbBlECRpmdia+L5/lh2BwtIJTmtxdbecEyyX/ejAOg7eQDGNvGOUmPY7Z2Yxdy9ioyH/VJeA== + +"@jsep-plugin/regex@^1.0.3": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@jsep-plugin/regex/-/regex-1.0.3.tgz#3aeaa2e5fa45d89de116aeafbfa41c95935b7f6d" + integrity sha512-XfZgry4DwEZvSFtS/6Y+R48D7qJYJK6R9/yJFyUFHCIUMEEHuJ4X95TDgJp5QkmzfLYvapMPzskV5HpIDrREug== + "@kwsites/file-exists@^1.1.1": version "1.1.1" resolved "https://registry.yarnpkg.com/@kwsites/file-exists/-/file-exists-1.1.1.tgz#ad1efcac13e1987d8dbaf235ef3be5b0d96faa99" @@ -6376,22 +6385,22 @@ dependencies: "@octokit/openapi-types" "^13.10.0" -"@opentelemetry/api@^1.0.0": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.4.1.tgz#ff22eb2e5d476fbc2450a196e40dd243cc20c28f" - integrity sha512-O2yRJce1GOc6PAy3QxFM4NzFiWzvScDC1/5ihYBL6BUEVdq0XMWN01sppE+H6bBXbaFYipjwFLEWLg5PaSOThA== +"@opentelemetry/api@>=1.0.0 <1.9.0": + version "1.8.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.8.0.tgz#5aa7abb48f23f693068ed2999ae627d2f7d902ec" + integrity sha512-I/s6F7yKUDdtMsoBWXJe8Qz40Tui5vsuKCWJEWVL+5q9sSWRzzx6v2KeNsOBEwd94j0eWkpWCH4yB6rZg9Mf0w== -"@opentelemetry/core@<1.4.0": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.3.1.tgz#6eef5c5efca9a4cd7daa0cd4c7ff28ca2317c8d7" - integrity sha512-k7lOC86N7WIyUZsUuSKZfFIrUtINtlauMGQsC1r7jNmcr0vVJGqK1ROBvt7WWMxLbpMnt1q2pXJO8tKu0b9auA== +"@opentelemetry/core@^1.14.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.26.0.tgz#7d84265aaa850ed0ca5813f97d831155be42b328" + integrity sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ== dependencies: - "@opentelemetry/semantic-conventions" "1.3.1" + "@opentelemetry/semantic-conventions" "1.27.0" -"@opentelemetry/semantic-conventions@1.3.1": - version "1.3.1" - resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.3.1.tgz#ba07b864a3c955f061aa30ea3ef7f4ae4449794a" - integrity sha512-wU5J8rUoo32oSef/rFpOT1HIjLjAv3qIDHkw1QIhODV3OpAVHi5oVzlouozg9obUmZKtbZ0qUe/m7FP0y0yBzA== +"@opentelemetry/semantic-conventions@1.27.0": + version "1.27.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz#1a857dcc95a5ab30122e04417148211e6f945e6c" + integrity sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg== "@parcel/watcher@2.0.4": version "2.0.4" @@ -9850,11 +9859,6 @@ acorn-globals@^7.0.0: acorn "^8.1.0" acorn-walk "^8.0.2" -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== - acorn-import-attributes@^1.9.5: version "1.9.5" resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" @@ -12136,40 +12140,47 @@ dayjs@~1.8.24: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.8.36.tgz#be36e248467afabf8f5a86bae0de0cdceecced50" integrity sha512-3VmRXEtw7RZKAf+4Tv1Ym9AGeo8r8+CjDi26x+7SYQil1UqtqdaokhzoEJohqlzt0m5kacJSDhJQkG/LWhpRBw== -dd-trace@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/dd-trace/-/dd-trace-4.2.0.tgz#276bcdec564b7a4edd3fbe73d4db5833380f83de" - integrity sha512-YDR31HLjd4reAoFcOFnTQFEVKO/GtugsotDOoDVeuUtPs2Z9Awcy10m3/bEl91ipUt2x72jedchEUBNcAbRVFw== +dc-polyfill@^0.1.4: + version "0.1.6" + resolved "https://registry.yarnpkg.com/dc-polyfill/-/dc-polyfill-0.1.6.tgz#c2940fa68ffb24a7bf127cc6cfdd15b39f0e7f02" + integrity sha512-UV33cugmCC49a5uWAApM+6Ev9ZdvIUMTrtCO9fj96TPGOQiea54oeO3tiEVdVeo3J9N2UdJEmbS4zOkkEA35uQ== + +dd-trace@^5.0.0: + version "5.23.1" + resolved "https://registry.yarnpkg.com/dd-trace/-/dd-trace-5.23.1.tgz#62418c3a7d5292e58bcd459785fa60bdf5b30f45" + integrity sha512-cdpJuiP1sYVgyagTt+BBjN3Wfa24a/fZw2L1oXg885uUy3bK9GYTiX0U6njlXY4krDiCZt+hii1sGEQPLYK6TQ== dependencies: - "@datadog/native-appsec" "^3.2.0" - "@datadog/native-iast-rewriter" "2.0.1" - "@datadog/native-iast-taint-tracking" "^1.4.1" + "@datadog/native-appsec" "8.1.1" + "@datadog/native-iast-rewriter" "2.4.1" + "@datadog/native-iast-taint-tracking" "3.1.0" "@datadog/native-metrics" "^2.0.0" - "@datadog/pprof" "^2.2.1" + "@datadog/pprof" "5.3.0" "@datadog/sketches-js" "^2.1.0" - "@opentelemetry/api" "^1.0.0" - "@opentelemetry/core" "<1.4.0" + "@opentelemetry/api" ">=1.0.0 <1.9.0" + "@opentelemetry/core" "^1.14.0" crypto-randomuuid "^1.0.0" - diagnostics_channel "^1.1.0" - ignore "^5.2.0" - import-in-the-middle "^1.3.5" - ipaddr.js "^2.0.1" + dc-polyfill "^0.1.4" + ignore "^5.2.4" + import-in-the-middle "1.11.2" + int64-buffer "^0.1.9" istanbul-lib-coverage "3.2.0" + jest-docblock "^29.7.0" + jsonpath-plus "^9.0.0" koalas "^1.0.2" - limiter "^1.1.4" - lodash.kebabcase "^4.1.1" - lodash.pick "^4.4.0" + limiter "1.1.5" lodash.sortby "^4.7.0" - lodash.uniq "^4.5.0" lru-cache "^7.14.0" - methods "^1.1.2" module-details-from-path "^1.0.3" - node-abort-controller "^3.0.1" + msgpack-lite "^0.1.26" opentracing ">=0.12.1" - path-to-regexp "^0.1.2" - protobufjs "^7.1.2" - retry "^0.10.1" - semver "^7.3.8" + path-to-regexp "^0.1.10" + pprof-format "^2.1.0" + protobufjs "^7.2.5" + retry "^0.13.1" + rfdc "^1.3.1" + semver "^7.5.4" + shell-quote "^1.8.1" + tlhunter-sorted-set "^0.1.0" debounce@^1.2.0: version "1.2.1" @@ -12455,11 +12466,6 @@ dezalgo@^1.0.0: asap "^2.0.0" wrappy "1" -diagnostics_channel@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/diagnostics_channel/-/diagnostics_channel-1.1.0.tgz#bd66c49124ce3bac697dff57466464487f57cea5" - integrity sha512-OE1ngLDjSBPG6Tx0YATELzYzy3RKHC+7veQ8gLa8yS7AAgw65mFbVdcsu3501abqOZCEZqZyAIemB0zXlqDSuw== - didyoumean@^1.2.2: version "1.2.2" resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" @@ -13211,6 +13217,11 @@ event-emitter@^0.3.5: d "1" es5-ext "~0.10.14" +event-lite@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/event-lite/-/event-lite-0.1.3.tgz#3dfe01144e808ac46448f0c19b4ab68e403a901d" + integrity sha512-8qz9nOz5VeD2z96elrEKD2U433+L3DWdUdDkOINLGOJvx1GsMBbMn0aCeu28y8/e85A6mCigBiFlYMnTBEGlSw== + event-target-shim@^5.0.0: version "5.0.1" resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" @@ -14908,7 +14919,7 @@ idb@^7.0.1: resolved "https://registry.yarnpkg.com/idb/-/idb-7.1.1.tgz#d910ded866d32c7ced9befc5bfdf36f572ced72b" integrity sha512-gchesWBzyvGHRO9W8tzUWFDycow5gwjvFKfyV9FF32Y7F50yZMp7mP+T2mJIWFx49zicqyC4uefHM17o6xKIVQ== -ieee754@^1.1.13: +ieee754@^1.1.13, ieee754@^1.1.8: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" integrity sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA== @@ -14937,7 +14948,7 @@ ignore@^5.0.4, ignore@^5.1.4, ignore@^5.2.0: resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.0.tgz#6d3bac8fa7fe0d45d9f9be7bac2fc279577e345a" integrity sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ== -ignore@^5.3.1: +ignore@^5.2.4, ignore@^5.3.1: version "5.3.2" resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.3.2.tgz#3cd40e729f3643fd87cb04e50bf0eb722bc596f5" integrity sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g== @@ -14980,13 +14991,13 @@ import-from@4.0.0: resolved "https://registry.yarnpkg.com/import-from/-/import-from-4.0.0.tgz#2710b8d66817d232e16f4166e319248d3d5492e2" integrity sha512-P9J71vT5nLlDeV8FHs5nNxaLbrpfAV5cF5srvbZfpwpcJoM/xZR3hiv+q+SAnuSmuGbXMWud063iIMx/V/EWZQ== -import-in-the-middle@^1.3.5: - version "1.4.2" - resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.4.2.tgz#2a266676e3495e72c04bbaa5ec14756ba168391b" - integrity sha512-9WOz1Yh/cvO/p69sxRmhyQwrIGGSp7EIdcb+fFNVi7CzQGQB8U1/1XrKVSbEd/GNOAeM0peJtmi7+qphe7NvAw== +import-in-the-middle@1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.11.2.tgz#dd848e72b63ca6cd7c34df8b8d97fc9baee6174f" + integrity sha512-gK6Rr6EykBcc6cVWRSBR5TWf8nn6hZMYSRYqCcHa0l0d1fPK7JSYo6+Mlmck76jIX9aL/IZ71c06U2VpFwl1zA== dependencies: acorn "^8.8.2" - acorn-import-assertions "^1.9.0" + acorn-import-attributes "^1.9.5" cjs-module-lexer "^1.2.2" module-details-from-path "^1.0.3" @@ -15091,6 +15102,11 @@ inquirer@^8.0.0, inquirer@^8.2.4, inquirer@^8.2.5: through "^2.3.6" wrap-ansi "^6.0.1" +int64-buffer@^0.1.9: + version "0.1.10" + resolved "https://registry.yarnpkg.com/int64-buffer/-/int64-buffer-0.1.10.tgz#277b228a87d95ad777d07c13832022406a473423" + integrity sha512-v7cSY1J8ydZ0GyjUHqF+1bshJ6cnEVLo9EnjB8p+4HDRPZc9N5jjmvUV7NvEsqQOKyH0pmIBFWXVQbiS0+OBbA== + internal-slot@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.3.tgz#7347e307deeea2faac2ac6205d4bc7d34967f59c" @@ -15159,11 +15175,6 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -ipaddr.js@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0" - integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng== - ipaddr.js@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.1.0.tgz#2119bc447ff8c257753b196fc5f1ce08a4cdf39f" @@ -15560,7 +15571,7 @@ isarray@0.0.1: resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" integrity sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ== -isarray@~1.0.0: +isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -15755,6 +15766,13 @@ jest-docblock@^29.4.3: dependencies: detect-newline "^3.0.0" +jest-docblock@^29.7.0: + version "29.7.0" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-29.7.0.tgz#8fddb6adc3cdc955c93e2a87f61cfd350d5d119a" + integrity sha512-q617Auw3A612guyaFgsbFeYpNP5t2aoUNLwBUbc/0kD1R4t9ixDbyFTHd1nok4epoVFpr7PmeWHrhvuV3XaJ4g== + dependencies: + detect-newline "^3.0.0" + jest-each@^29.5.0: version "29.5.0" resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-29.5.0.tgz#fc6e7014f83eac68e22b7195598de8554c2e5c06" @@ -16175,6 +16193,11 @@ jsdom@^20.0.0: ws "^8.9.0" xml-name-validator "^4.0.0" +jsep@^1.3.8: + version "1.3.9" + resolved "https://registry.yarnpkg.com/jsep/-/jsep-1.3.9.tgz#8ce42df80ee9c1b39e52d0dd062a465342f35440" + integrity sha512-i1rBX5N7VPl0eYb6+mHNp52sEuaS2Wi8CDYx1X5sn9naevL78+265XJqy1qENEk7mRKwS06NHpUqiBwR7qeodw== + jsesc@^2.5.1: version "2.5.2" resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4" @@ -16352,6 +16375,15 @@ jsonpath-plus@^7.2.0: resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-7.2.0.tgz#7ad94e147b3ed42f7939c315d2b9ce490c5a3899" integrity sha512-zBfiUPM5nD0YZSBT/o/fbCUlCcepMIdP0CJZxM1+KgA4f2T206f6VAg9e7mX35+KlMaIc5qXW34f3BnwJ3w+RA== +jsonpath-plus@^9.0.0: + version "9.0.0" + resolved "https://registry.yarnpkg.com/jsonpath-plus/-/jsonpath-plus-9.0.0.tgz#bb8703ee481531142bca8dee9a42fe72b8358a7f" + integrity sha512-bqE77VIDStrOTV/czspZhTn+o27Xx9ZJRGVkdVShEtPoqsIx5yALv3lWVU6y+PqYvWPJNWE7ORCQheQkEe0DDA== + dependencies: + "@jsep-plugin/assignment" "^1.2.1" + "@jsep-plugin/regex" "^1.0.3" + jsep "^1.3.8" + jsonpointer@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.0.tgz#f802669a524ec4805fa7389eadbc9921d5dc8072" @@ -16604,7 +16636,7 @@ lilconfig@^2.0.5, lilconfig@^2.0.6: resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== -limiter@^1.1.4: +limiter@1.1.5: version "1.1.5" resolved "https://registry.yarnpkg.com/limiter/-/limiter-1.1.5.tgz#8f92a25b3b16c6131293a0cc834b4a838a2aa7c2" integrity sha512-FWWMIEOxz3GwUI4Ts/IvgVy6LPvoMPgjMdQ185nN6psJyBJ4yOpzqm695/h5umdLJg2vW3GR5iG11MAkR2AzJA== @@ -16838,11 +16870,6 @@ lodash.isstring@^4.0.1: resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE= -lodash.kebabcase@^4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" - integrity sha1-hImxyw0p/4gZXM7KRI/21swpXDY= - lodash.memoize@4.x, lodash.memoize@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" @@ -16883,11 +16910,6 @@ lodash.toarray@^4.4.0: resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" integrity sha512-QyffEA3i5dma5q2490+SgCvDN0pXLmRGSyAANuVi0HQ01Pkfr9fuoKQW8wm1wGBnJITs/mS7wQvS6VshUEBFCw== -lodash.uniq@^4.5.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" - integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= - lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.17.4, lodash@^4.17.5, lodash@~4.17.0: version "4.17.21" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" @@ -17230,7 +17252,7 @@ meros@^1.1.4, meros@^1.2.1: resolved "https://registry.yarnpkg.com/meros/-/meros-1.2.1.tgz#056f7a76e8571d0aaf3c7afcbe7eb6407ff7329e" integrity sha512-R2f/jxYqCAGI19KhAvaxSOxALBMkaXWH2a7rOyqQw+ZmizX5bKkEYWLzdhC+U82ZVVPVp6MCXe3EkVligh+12g== -methods@^1.1.2, methods@~1.1.2: +methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -17537,6 +17559,16 @@ ms@2.1.3, ms@^2.0.0, ms@^2.1.1, ms@^2.1.3: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== +msgpack-lite@^0.1.26: + version "0.1.26" + resolved "https://registry.yarnpkg.com/msgpack-lite/-/msgpack-lite-0.1.26.tgz#dd3c50b26f059f25e7edee3644418358e2a9ad89" + integrity sha512-SZ2IxeqZ1oRFGo0xFGbvBJWMp3yLIY9rlIJyxy8CGrwZn1f0ZK4r6jV/AM1r0FZMDUkWkglOk/eeKIL9g77Nxw== + dependencies: + event-lite "^0.1.1" + ieee754 "^1.1.8" + int64-buffer "^0.1.9" + isarray "^1.0.0" + multicast-dns@^7.2.5: version "7.2.5" resolved "https://registry.yarnpkg.com/multicast-dns/-/multicast-dns-7.2.5.tgz#77eb46057f4d7adbd16d9290fa7299f6fa64cced" @@ -17745,7 +17777,7 @@ node-forge@^0.10.0, node-forge@^1, node-forge@^1.2.1, node-forge@^1.3.1: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-1.3.1.tgz#be8da2af243b2417d5f646a770663a92b7e9ded3" integrity sha512-dPEtOeMvF9VMcYV/1Wb8CPoVAXtp6MKMlcbAt4ddqmGqUJ6fQZFXkNZNkNlfevtNkGtaSoXf/vNNNSvgrdXwtA== -node-gyp-build@^3.9.0: +node-gyp-build@<4.0, node-gyp-build@^3.9.0: version "3.9.0" resolved "https://registry.yarnpkg.com/node-gyp-build/-/node-gyp-build-3.9.0.tgz#53a350187dd4d5276750da21605d1cb681d09e25" integrity sha512-zLcTg6P4AbcHPq465ZMFNXx7XpKKJh+7kkN699NiQWisR2uWYOWNWqRHAmbnmKiL4e9aLSlmy5U7rEMUXV59+A== @@ -18794,10 +18826,10 @@ path-to-regexp@0.1.10: resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.10.tgz#67e9108c5c0551b9e5326064387de4763c4d5f8b" integrity sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w== -path-to-regexp@^0.1.2: - version "0.1.7" - resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" - integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w= +path-to-regexp@^0.1.10: + version "0.1.11" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.11.tgz#a527e662c89efc4646dbfa8100bf3e847e495761" + integrity sha512-c0t+KCuUkO/YDLPG4WWzEwx3J5F/GHXsD1h/SNZfySqAIKe/BaP95x8fWtOfRJokpS5yYHRJjMtYlXD8jxnpbw== path-to-regexp@^1.7.0: version "1.8.0" @@ -19208,10 +19240,10 @@ postgres-interval@^1.1.0: dependencies: xtend "^4.0.0" -pprof-format@^2.0.7: - version "2.0.7" - resolved "https://registry.yarnpkg.com/pprof-format/-/pprof-format-2.0.7.tgz#526e4361f8b37d16b2ec4bb0696b5292de5046a4" - integrity sha512-1qWaGAzwMpaXJP9opRa23nPnt2Egi7RMNoNBptEE/XwHbcn4fC2b/4U4bKc5arkGkIh2ZabpF2bEb+c5GNHEKA== +pprof-format@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/pprof-format/-/pprof-format-2.1.0.tgz#acc8d7773bcf4faf0a3d3df11bceefba7ac06664" + integrity sha512-0+G5bHH0RNr8E5hoZo/zJYsL92MhkZjwrHp3O2IxmY8RJL9ooKeuZ8Tm0ZNBw5sGZ9TiM71sthTjWoR2Vf5/xw== prebuild-install@^7.1.1: version "7.1.1" @@ -19538,7 +19570,7 @@ protobufjs@7.2.6, protobufjs@^7.2.5: "@types/node" ">=13.7.0" long "^5.0.0" -protobufjs@^7.1.2, protobufjs@^7.2.4: +protobufjs@^7.2.4: version "7.2.5" resolved "https://registry.yarnpkg.com/protobufjs/-/protobufjs-7.2.5.tgz#45d5c57387a6d29a17aab6846dcc283f9b8e7f2d" integrity sha512-gGXRSXvxQ7UiPgfw8gevrfRWcTlSbOFg+p/N+JVJEK5VhueL2miT6qTymqAmjr1Q5WbOCyJbyrk6JfWKwlFn6A== @@ -20563,11 +20595,6 @@ retry@0.12.0, retry@^0.12.0: resolved "https://registry.yarnpkg.com/retry/-/retry-0.12.0.tgz#1b42a6266a21f07421d1b0b54b7dc167b01c013b" integrity sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs= -retry@^0.10.1: - version "0.10.1" - resolved "https://registry.yarnpkg.com/retry/-/retry-0.10.1.tgz#e76388d217992c252750241d3d3956fed98d8ff4" - integrity sha1-52OI0heZLCUnUCQdPTlW/tmNj/Q= - retry@^0.13.1: version "0.13.1" resolved "https://registry.yarnpkg.com/retry/-/retry-0.13.1.tgz#185b1587acf67919d63b357349e03537b2484658" @@ -20588,6 +20615,11 @@ rfdc@^1.3.0: resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.3.0.tgz#d0b7c441ab2720d05dc4cf26e01c89631d9da08b" integrity sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA== +rfdc@^1.3.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + rimraf@^2.6.1, rimraf@^2.6.3, rimraf@^2.7.1: version "2.7.1" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" @@ -21325,6 +21357,11 @@ source-map@^0.7.3: resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== +source-map@^0.7.4: + version "0.7.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656" + integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA== + source-map@^0.8.0-beta.0: version "0.8.0-beta.0" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.8.0-beta.0.tgz#d4c1bb42c3f7ee925f005927ba10709e0d1d1f11" @@ -21408,7 +21445,7 @@ split2@^4.1.0: resolved "https://registry.yarnpkg.com/split2/-/split2-4.1.0.tgz#101907a24370f85bb782f08adaabe4e281ecf809" integrity sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ== -split@^1.0.0, split@^1.0.1: +split@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/split/-/split-1.0.1.tgz#605bd9be303aa59fb35f9229fbea0ddec9ea07d9" integrity sha512-mTyOoPbrivtXnwnIxZRFYRrPNtEFKlpB2fvjSnCQUiAA6qAZzqwna5envK4uk6OIeP17CsdF3rSBGYVBsU0Tkg== @@ -22211,6 +22248,11 @@ tlds@^1.192.0: resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.228.0.tgz#416ab76ac1a06aad0b5d6b484a13bf5a0ad63f39" integrity sha512-Q0TU9zh5hDs2CpRFNM7SOW3K7OSgUgJC/cMrq9t44ei4tu+G3KV8BZyIJuYVvryJHH96mKgc9WXdhgKVvGD7jg== +tlhunter-sorted-set@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/tlhunter-sorted-set/-/tlhunter-sorted-set-0.1.0.tgz#1c3eae28c0fa4dff97e9501d2e3c204b86406f4b" + integrity sha512-eGYW4bjf1DtrHzUYxYfAcSytpOkA44zsr7G2n3PV7yOUR23vmkGe3LL4R+1jL9OsXtbsFOwe8XtbCrabeaEFnw== + tmp-promise@^3.0.3: version "3.0.3" resolved "https://registry.yarnpkg.com/tmp-promise/-/tmp-promise-3.0.3.tgz#60a1a1cc98c988674fcbfd23b6e3367bdeac4ce7" From f20d36435c8e379b337af076eb9cf5a4806ce951 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 18:36:22 +0100 Subject: [PATCH 516/529] chore(release): release v7.50.9 (#10335) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 12 ++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 24 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 7568c2e9275..d8013ec9eb9 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.50.8" + ".": "7.50.9" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 818f6408ed9..1dac840a5d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.50.9](https://github.com/ParabolInc/parabol/compare/v7.50.8...v7.50.9) (2024-10-15) + + +### Fixed + +* **dd-trace:** upgrade to v5.0.0 ([#10343](https://github.com/ParabolInc/parabol/issues/10343)) ([a3aba86](https://github.com/ParabolInc/parabol/commit/a3aba863580fb866c4affad42f03454afd0d77e8)) + + +### Changed + +* **rethinkdb:** TeamInvitation: Phase 3 ([#10327](https://github.com/ParabolInc/parabol/issues/10327)) ([9b52d49](https://github.com/ParabolInc/parabol/commit/9b52d4958d8aace5c3adc2322ede58f638a947cd)) + ## [7.50.8](https://github.com/ParabolInc/parabol/compare/v7.50.7...v7.50.8) (2024-10-10) diff --git a/package.json b/package.json index b2743425d01..634a16652e1 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.50.8", + "version": "7.50.9", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 77138df621c..b54ba27fa03 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.50.8", + "version": "7.50.9", "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.50.8" + "parabol-server": "7.50.9" } } diff --git a/packages/client/package.json b/packages/client/package.json index 0022e0094f6..4884bcdbf1c 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.50.8", + "version": "7.50.9", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 4fa483ec110..cd083b5f562 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.50.8", + "version": "7.50.9", "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 5f86c18f014..24792c28f6c 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.50.8", + "version": "7.50.9", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^5.0.0", - "parabol-client": "7.50.8", - "parabol-server": "7.50.8", + "parabol-client": "7.50.9", + "parabol-server": "7.50.9", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index e35f9ef7062..03b9b4c8c0c 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.50.8", + "version": "7.50.9", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 100d44e09b6..cb973dc5fef 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.50.8", + "version": "7.50.9", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.50.8", + "parabol-client": "7.50.9", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 6c8d420fa3d29243a141862fee095a3e05fed7df Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 15 Oct 2024 11:28:50 -0700 Subject: [PATCH 517/529] chore: remove old invite notifications (#10345) Signed-off-by: Matt Krick --- .../1728496980486_removeOldInviteNotifs.ts | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 packages/server/postgres/migrations/1728496980486_removeOldInviteNotifs.ts diff --git a/packages/server/postgres/migrations/1728496980486_removeOldInviteNotifs.ts b/packages/server/postgres/migrations/1728496980486_removeOldInviteNotifs.ts new file mode 100644 index 00000000000..a0e42195d52 --- /dev/null +++ b/packages/server/postgres/migrations/1728496980486_removeOldInviteNotifs.ts @@ -0,0 +1,17 @@ +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' + +export async function up() { + await connectRethinkDB() + const almostAMonthAgo = new Date(Date.now() - 29 * 24 * 60 * 60 * 1000) + await r + .table('Notification') + .filter({type: 'TEAM_INVITATION'}) + .filter((row) => row('createdAt').le(almostAMonthAgo)) + .delete() + .run() +} + +export async function down() { + // noop +} From 197bbc5c65f54a12070ba12ed8e0bd6f4a38fc06 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 19:36:08 +0100 Subject: [PATCH 518/529] chore(release): release v7.50.10 (#10346) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index d8013ec9eb9..5f0db771d60 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.50.9" + ".": "7.50.10" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 1dac840a5d3..c5c6558181a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.50.10](https://github.com/ParabolInc/parabol/compare/v7.50.9...v7.50.10) (2024-10-15) + + +### Changed + +* remove old invite notifications ([#10345](https://github.com/ParabolInc/parabol/issues/10345)) ([6c8d420](https://github.com/ParabolInc/parabol/commit/6c8d420fa3d29243a141862fee095a3e05fed7df)) + ## [7.50.9](https://github.com/ParabolInc/parabol/compare/v7.50.8...v7.50.9) (2024-10-15) diff --git a/package.json b/package.json index 634a16652e1..4dfd42922a8 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.50.9", + "version": "7.50.10", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index b54ba27fa03..fd8627ac0f6 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.50.9", + "version": "7.50.10", "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.50.9" + "parabol-server": "7.50.10" } } diff --git a/packages/client/package.json b/packages/client/package.json index 4884bcdbf1c..8d71f3c70a5 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.50.9", + "version": "7.50.10", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index cd083b5f562..74da73462d5 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.50.9", + "version": "7.50.10", "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 24792c28f6c..3354db4ae95 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.50.9", + "version": "7.50.10", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^5.0.0", - "parabol-client": "7.50.9", - "parabol-server": "7.50.9", + "parabol-client": "7.50.10", + "parabol-server": "7.50.10", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 03b9b4c8c0c..86b050c9805 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.50.9", + "version": "7.50.10", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index cb973dc5fef..8e4d836a7de 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.50.9", + "version": "7.50.10", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.50.9", + "parabol-client": "7.50.10", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 09328223cf0170ef7f1df94e6e326b5fdcdc0a93 Mon Sep 17 00:00:00 2001 From: Rafa <101704572+rafaelromcar-parabol@users.noreply.github.com> Date: Tue, 15 Oct 2024 19:57:30 +0100 Subject: [PATCH 519/529] chore(deployment): PR title for the PR that deploys to production states its purpose (#10348) --- .github/workflows/release-to-staging.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release-to-staging.yml b/.github/workflows/release-to-staging.yml index c36292228bf..d33ed976e51 100644 --- a/.github/workflows/release-to-staging.yml +++ b/.github/workflows/release-to-staging.yml @@ -110,7 +110,7 @@ jobs: --assignee ${{ github.actor }} \ --base production \ --head release/v${{ env.ACTION_VERSION }} \ - --title "chore(release): Test v${{ env.ACTION_VERSION }}" \ + --title "chore(release): Test and deploy to Production v${{ env.ACTION_VERSION }}" \ --body "$BODY" - name: Poll Staging Release uses: artiz/poll-endpoint@1.0.2 From 5202a3b59c1183e8ca7eb6c0906dc2a6baf5b82e Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 15 Oct 2024 14:05:22 -0700 Subject: [PATCH 520/529] chore(rethinkdb): Task: Phase 1 (#10336) Signed-off-by: Matt Krick --- .../dataloader/foreignKeyLoaderMakers.ts | 18 ++++ .../dataloader/primaryKeyLoaderMakers.ts | 5 + .../graphql/mutations/changeTaskTeam.ts | 25 ++++- .../server/graphql/mutations/createTask.ts | 7 +- .../mutations/createTaskIntegration.ts | 15 ++- .../server/graphql/mutations/deleteTask.ts | 5 +- .../server/graphql/mutations/endCheckIn.ts | 6 ++ .../graphql/mutations/helpers/addSeedTasks.ts | 4 +- .../mutations/helpers/bootstrapNewUser.ts | 7 +- .../mutations/helpers/importTasksForPoker.ts | 6 ++ .../mutations/helpers/removeEmptyTasks.ts | 3 + .../mutations/helpers/removeTeamMember.ts | 19 +++- .../resetRetroMeetingToGroupStage.ts | 3 + .../server/graphql/mutations/updateTask.ts | 18 +++- .../graphql/mutations/updateTaskDueDate.ts | 7 +- .../migrations/1728578190454_Task-phase1.ts | 97 +++++++++++++++++++ packages/server/postgres/select.ts | 26 +++++ .../postgres/types/TaskIntegration.d.ts | 44 +++++++++ .../server/safeMutations/archiveTasksForDB.ts | 11 +++ 19 files changed, 309 insertions(+), 17 deletions(-) create mode 100644 packages/server/postgres/migrations/1728578190454_Task-phase1.ts create mode 100644 packages/server/postgres/types/TaskIntegration.d.ts diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 5f9131e0708..0c296d73386 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -13,6 +13,7 @@ import { selectSlackAuths, selectSlackNotifications, selectSuggestedAction, + selectTasks, selectTeamInvitations, selectTeams, selectTemplateDimension, @@ -294,3 +295,20 @@ export const teamInvitationsByTeamId = foreignKeyLoaderMaker( .execute() } ) + +export const _pgtasksByDiscussionId = foreignKeyLoaderMaker( + '_pgtasks', + 'discussionId', + async (discusisonIds) => { + // include archived cards in the conversation, since it's persistent + return selectTasks().where('discussionId', 'in', discusisonIds).execute() + } +) + +export const _pgtasksByTeamId = foreignKeyLoaderMaker('_pgtasks', 'teamId', async (teamIds) => { + // waraning! contains private tasks + return selectTasks() + .where('teamId', 'in', teamIds) + .where(sql`'archived' = ANY(tags)`) + .execute() +}) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index fe94fd0ea23..f53c962ba2d 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -19,6 +19,7 @@ import { selectSlackAuths, selectSlackNotifications, selectSuggestedAction, + selectTasks, selectTeamInvitations, selectTeamPromptResponses, selectTeams, @@ -144,3 +145,7 @@ export const newFeatures = primaryKeyLoaderMaker((ids: readonly number[]) => { export const teamInvitations = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectTeamInvitations().where('id', 'in', ids).execute() }) + +export const _pgtasks = primaryKeyLoaderMaker((ids: readonly string[]) => { + return selectTasks().where('id', 'in', ids).execute() +}) diff --git a/packages/server/graphql/mutations/changeTaskTeam.ts b/packages/server/graphql/mutations/changeTaskTeam.ts index e229536106c..8b7bc3566ec 100644 --- a/packages/server/graphql/mutations/changeTaskTeam.ts +++ b/packages/server/graphql/mutations/changeTaskTeam.ts @@ -3,6 +3,7 @@ import {SubscriptionChannel} from 'parabol-client/types/constEnums' import removeEntityKeepText from 'parabol-client/utils/draftjs/removeEntityKeepText' import getRethink from '../../database/rethinkDriver' import Task from '../../database/types/Task' +import getKysely from '../../postgres/getKysely' import {AtlassianAuth} from '../../postgres/queries/getAtlassianAuthByUserIdTeamId' import {GitHubAuth} from '../../postgres/queries/getGitHubAuthByUserIdTeamId' import upsertAtlassianAuths from '../../postgres/queries/upsertAtlassianAuths' @@ -33,6 +34,7 @@ export default { {taskId, teamId}: {taskId: string; teamId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { + const pg = getKysely() const r = await getRethink() const now = new Date() const operationId = dataLoader.share() @@ -43,7 +45,7 @@ export default { if (!isTeamMember(authToken, teamId)) { return standardError(new Error('Team not found'), {userId: viewerId}) } - const task = await r.table('Task').get(taskId).run() + const task = await dataLoader.get('tasks').load(taskId) if (!task) { return standardError(new Error('Task not found'), {userId: viewerId}) } @@ -155,7 +157,24 @@ export default { .default(null), newTask: r.table('Task').get(taskId).update(updates) }).run() - + if (task.integrationHash) { + await pg + .deleteFrom('Task') + .where('integrationHash', '=', task.integrationHash) + .where('teamId', '=', teamId) + .limit(1) + .returning('id') + .execute() + } + await pg + .updateTable('Task') + .set({ + content: rawContent === nextRawContent ? undefined : JSON.stringify(nextRawContent), + teamId, + integration: JSON.stringify(integration) + }) + .where('id', '=', taskId) + .executeTakeFirst() if (deletedConflictingIntegrationTask) { const task = deletedConflictingIntegrationTask as unknown as Task const isPrivate = task.tags.includes('private') @@ -166,7 +185,7 @@ export default { } }) } - + dataLoader.clearAll('tasks') const isPrivate = tags.includes('private') const data = {taskId} const teamMembers = oldTeamMembers.concat(newTeamMembers) diff --git a/packages/server/graphql/mutations/createTask.ts b/packages/server/graphql/mutations/createTask.ts index 3aadc09b20a..8d31cb3e193 100644 --- a/packages/server/graphql/mutations/createTask.ts +++ b/packages/server/graphql/mutations/createTask.ts @@ -8,6 +8,7 @@ import getRethink from '../../database/rethinkDriver' import NotificationTaskInvolves from '../../database/types/NotificationTaskInvolves' import Task, {TaskServiceEnum} from '../../database/types/Task' import updatePrevUsedRepoIntegrationsCache from '../../integrations/updatePrevUsedRepoIntegrationsCache' +import getKysely from '../../postgres/getKysely' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' import publish, {SubOptions} from '../../utils/publish' @@ -152,6 +153,7 @@ export default { info: GraphQLResolveInfo ) { const {authToken, dataLoader, socketId: mutatorId} = context + const pg = getKysely() const r = await getRethink() const operationId = dataLoader.share() const viewerId = getUserId(authToken) @@ -222,7 +224,10 @@ export default { await r({ task: r.table('Task').insert(task) }).run() - + await pg + .insertInto('Task') + .values({...task, integration: JSON.stringify(integration)}) + .execute() handleAddTaskNotifications(teamMembers, task, viewerId, teamId, { operationId, mutatorId diff --git a/packages/server/graphql/mutations/createTaskIntegration.ts b/packages/server/graphql/mutations/createTaskIntegration.ts index d9969f94b9f..4b069473115 100644 --- a/packages/server/graphql/mutations/createTaskIntegration.ts +++ b/packages/server/graphql/mutations/createTaskIntegration.ts @@ -5,6 +5,7 @@ import appOrigin from '../../appOrigin' import getRethink from '../../database/rethinkDriver' import TaskIntegrationManagerFactory from '../../integrations/TaskIntegrationManagerFactory' import updatePrevUsedRepoIntegrationsCache from '../../integrations/updatePrevUsedRepoIntegrationsCache' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import sendToSentry from '../../utils/sendToSentry' @@ -44,7 +45,7 @@ export default { info: GraphQLResolveInfo ) => { const {authToken, dataLoader, socketId: mutatorId} = context - + const pg = getKysely() const r = await getRethink() const now = new Date() const operationId = dataLoader.share() @@ -52,7 +53,7 @@ export default { const viewerId = getUserId(authToken) // AUTH - const task = await r.table('Task').get(taskId).run() + const task = await dataLoader.get('tasks').load(taskId) if (!task) { return standardError(new Error('Task not found'), {userId: viewerId}) } @@ -162,6 +163,16 @@ export default { }) .run() + await pg + .updateTable('Task') + .set({ + integration: JSON.stringify(updateTaskInput.integration), + integrationHash: updateTaskInput.integrationHash + }) + .where('id', '=', taskId) + .execute() + + dataLoader.clearAll('tasks') const data = {taskId} teamMembers.forEach(({userId}) => { publish(SubscriptionChannel.TASK, userId, 'CreateTaskIntegrationPayload', data, subOptions) diff --git a/packages/server/graphql/mutations/deleteTask.ts b/packages/server/graphql/mutations/deleteTask.ts index 04288d2d8d5..e433b53b8b3 100644 --- a/packages/server/graphql/mutations/deleteTask.ts +++ b/packages/server/graphql/mutations/deleteTask.ts @@ -1,6 +1,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -21,13 +22,14 @@ export default { {taskId}: {taskId: string}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { + const pg = getKysely() const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const viewerId = getUserId(authToken) // AUTH - const task = await r.table('Task').get(taskId).run() + const task = await dataLoader.get('tasks').load(taskId) if (!task) { return {error: {message: 'Task not found'}} } @@ -42,6 +44,7 @@ export default { await r({ task: r.table('Task').get(taskId).delete() }).run() + await pg.deleteFrom('Task').where('id', '=', taskId).execute() const {tags, userId: taskUserId} = task const data = {task} diff --git a/packages/server/graphql/mutations/endCheckIn.ts b/packages/server/graphql/mutations/endCheckIn.ts index b5c65d89ec5..7cb747a7144 100644 --- a/packages/server/graphql/mutations/endCheckIn.ts +++ b/packages/server/graphql/mutations/endCheckIn.ts @@ -33,6 +33,7 @@ import updateTeamInsights from './helpers/updateTeamInsights' type SortOrderTask = Pick const updateTaskSortOrders = async (userIds: string[], tasks: SortOrderTask[]) => { + const pg = getKysely() const r = await getRethink() const taskMax = await ( r @@ -61,6 +62,11 @@ const updateTaskSortOrders = async (userIds: string[], tasks: SortOrderTask[]) = }) }) .run() + await Promise.all( + updatedTasks.map((task) => + pg.updateTable('Task').set({sortOrder: task.sortOrder}).where('id', '=', task.id).execute() + ) + ) return tasks } diff --git a/packages/server/graphql/mutations/helpers/addSeedTasks.ts b/packages/server/graphql/mutations/helpers/addSeedTasks.ts index 0bfc175e13c..509ef01f10d 100644 --- a/packages/server/graphql/mutations/helpers/addSeedTasks.ts +++ b/packages/server/graphql/mutations/helpers/addSeedTasks.ts @@ -5,6 +5,7 @@ import appOrigin from '../../../appOrigin' import getRethink from '../../../database/rethinkDriver' import {TaskStatusEnum} from '../../../database/types/Task' import generateUID from '../../../generateUID' +import getKysely from '../../../postgres/getKysely' import {convertHtmlToTaskContent} from '../../../utils/draftjs/convertHtmlToTaskContent' const NORMAL_TASK_STRING = `This is a task card. They can be created here, in a meeting, or via an integration` @@ -37,6 +38,7 @@ function getSeedTasks(teamId: string) { } export default async (userId: string, teamId: string) => { + const pg = getKysely() const r = await getRethink() const now = new Date() @@ -50,6 +52,6 @@ export default async (userId: string, teamId: string) => { userId, updatedAt: now })) - + await pg.insertInto('Task').values(seedTasks).execute() return r.table('Task').insert(seedTasks).run() } diff --git a/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts b/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts index 16bdec6f6b2..3c4dc2e826e 100644 --- a/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts +++ b/packages/server/graphql/mutations/helpers/bootstrapNewUser.ts @@ -124,11 +124,8 @@ const bootstrapNewUser = async ( } const orgName = `${newUser.preferredName}’s Org` await createNewOrg(orgId, orgName, userId, email, dataLoader) - await Promise.all([ - createTeamAndLeader(newUser as IUser, validNewTeam, dataLoader), - addSeedTasks(userId, teamId), - sendPromptToJoinOrg(newUser, dataLoader) - ]) + await createTeamAndLeader(newUser as IUser, validNewTeam, dataLoader) + await Promise.all([addSeedTasks(userId, teamId), sendPromptToJoinOrg(newUser, dataLoader)]) analytics.newOrg(newUser, orgId, teamId, true) } diff --git a/packages/server/graphql/mutations/helpers/importTasksForPoker.ts b/packages/server/graphql/mutations/helpers/importTasksForPoker.ts index 3c7d8858474..9d6d15eb564 100644 --- a/packages/server/graphql/mutations/helpers/importTasksForPoker.ts +++ b/packages/server/graphql/mutations/helpers/importTasksForPoker.ts @@ -2,6 +2,7 @@ import IntegrationHash from 'parabol-client/shared/gqlIds/IntegrationHash' import {isNotNull} from 'parabol-client/utils/predicates' import getRethink from '../../../database/rethinkDriver' import ImportedTask from '../../../database/types/ImportedTask' +import getKysely from '../../../postgres/getKysely' import {TUpdatePokerScopeItemInput} from '../updatePokerScope' const importTasksForPoker = async ( @@ -10,6 +11,7 @@ const importTasksForPoker = async ( userId: string, meetingId: string ) => { + const pg = getKysely() const r = await getRethink() const integratedUpdates = additiveUpdates.filter((update) => update.service !== 'PARABOL') const integrationHashes = integratedUpdates.map((update) => update.serviceTaskId) @@ -45,6 +47,10 @@ const importTasksForPoker = async ( .filter(isNotNull) if (newIntegrationUpdates.length > 0) { + await pg + .insertInto('Task') + .values(tasksToAdd.map((t) => ({...t, integration: JSON.stringify(t.integration)}))) + .execute() await r.table('Task').insert(tasksToAdd).run() } const integratedTasks = [...existingTasks, ...tasksToAdd] diff --git a/packages/server/graphql/mutations/helpers/removeEmptyTasks.ts b/packages/server/graphql/mutations/helpers/removeEmptyTasks.ts index f04bed65ac3..ce4dd8a0d41 100644 --- a/packages/server/graphql/mutations/helpers/removeEmptyTasks.ts +++ b/packages/server/graphql/mutations/helpers/removeEmptyTasks.ts @@ -1,7 +1,9 @@ import extractTextFromDraftString from 'parabol-client/utils/draftjs/extractTextFromDraftString' import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' const removeEmptyTasks = async (meetingId: string, teamId: string) => { + const pg = getKysely() const r = await getRethink() const createdTasks = await r .table('Task') @@ -17,6 +19,7 @@ const removeEmptyTasks = async (meetingId: string, teamId: string) => { .filter(({plaintextContent}) => plaintextContent.length === 0) .map(({id}) => id) if (removedTaskIds.length > 0) { + await pg.deleteFrom('Task').where('id', 'in', removedTaskIds).execute() await r.table('Task').getAll(r.args(removedTaskIds)).delete().run() } return removedTaskIds diff --git a/packages/server/graphql/mutations/helpers/removeTeamMember.ts b/packages/server/graphql/mutations/helpers/removeTeamMember.ts index 48e7551fa50..b7afbd2de7b 100644 --- a/packages/server/graphql/mutations/helpers/removeTeamMember.ts +++ b/packages/server/graphql/mutations/helpers/removeTeamMember.ts @@ -1,3 +1,4 @@ +import {sql} from 'kysely' import fromTeamMemberId from 'parabol-client/utils/relay/fromTeamMemberId' import getRethink from '../../../database/rethinkDriver' import {RDatum} from '../../../database/stricterR' @@ -54,7 +55,12 @@ const removeTeamMember = async ( if (willArchive) { await Promise.all([ // archive single-person teams - pg.updateTable('Team').set({isArchived: true}).where('id', '=', teamId).execute(), + pg + .with('TaskDelete', (qb) => qb.deleteFrom('Task').where('teamId', '=', teamId)) + .updateTable('Team') + .set({isArchived: true}) + .where('id', '=', teamId) + .execute(), // delete all tasks belonging to a 1-person team r.table('Task').getAll(teamId, {index: 'teamId'}).delete() ]) @@ -101,11 +107,20 @@ const removeTeamMember = async ( .default([]) as unknown as Task[] }).run() await pg + .with('TaskReassignment', (qb) => + qb + .updateTable('Task') + .set({userId: nextTeamLead.userId}) + .where('userId', '=', userId) + .where('teamId', '=', teamId) + .where('integration', 'is', null) + .where(sql`'archived' != ANY(tags)`) + ) .updateTable('User') .set(({fn, ref, val}) => ({tms: fn('ARRAY_REMOVE', [ref('tms'), val(teamId)])})) .where('id', '=', userId) .execute() - dataLoader.clearAll(['users', 'teamMembers']) + dataLoader.clearAll(['users', 'teamMembers', 'tasks']) const user = await dataLoader.get('users').load(userId) let notificationId: string | undefined diff --git a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts index 6bd36cb6eb1..73debf3e5f5 100644 --- a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts +++ b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts @@ -108,6 +108,9 @@ const resetRetroMeetingToGroupStage = { .with('DeleteComments', (qb) => qb.deleteFrom('Comment').where('discussionId', 'in', discussionIdsToDelete) ) + .with('DeleteTasks', (qb) => + qb.deleteFrom('Task').where('discussionId', 'in', discussionIdsToDelete) + ) .with('ResetGroups', (qb) => qb .updateTable('RetroReflectionGroup') diff --git a/packages/server/graphql/mutations/updateTask.ts b/packages/server/graphql/mutations/updateTask.ts index 63c868fc6dd..b8471bf00b8 100644 --- a/packages/server/graphql/mutations/updateTask.ts +++ b/packages/server/graphql/mutations/updateTask.ts @@ -4,6 +4,7 @@ import extractTextFromDraftString from 'parabol-client/utils/draftjs/extractText import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' import getRethink from '../../database/rethinkDriver' import Task, {AreaEnum as TAreaEnum, TaskStatusEnum} from '../../database/types/Task' +import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -46,6 +47,7 @@ export default { {updatedTask}: UpdateTaskMutationVariables, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { + const pg = getKysely() const r = await getRethink() const now = new Date() const operationId = dataLoader.share() @@ -56,7 +58,7 @@ export default { const {id: taskId, userId: inputUserId, status, sortOrder, content} = updatedTask const validContent = normalizeRawDraftJS(content) const [task, viewer] = await Promise.all([ - r.table('Task').get(taskId).run(), + dataLoader.get('tasks').load(taskId), dataLoader.get('users').loadNonNull(viewerId) ]) if (!task) { @@ -94,6 +96,20 @@ export default { .update(nextTask, {returnChanges: true})('changes')(0)('new_val') .default(null) as unknown as Task }).run() + await pg + .updateTable('Task') + .set({ + content: content ? validContent : undefined, + plaintextContent: content + ? extractTextFromDraftString(validContent) + : task.plaintextContent, + sortOrder: sortOrder || undefined, + status: status || undefined, + userId: inputUserId || undefined + }) + .where('id', '=', taskId) + .execute() + dataLoader.clearAll('tasks') // TODO: get users in the same location const usersToIgnore = await getUsersToIgnore(viewerId, teamId) if (!newTask) return standardError(new Error('Already updated task'), {userId: viewerId}) diff --git a/packages/server/graphql/mutations/updateTaskDueDate.ts b/packages/server/graphql/mutations/updateTaskDueDate.ts index 40b3a2e85c0..7660dab5521 100644 --- a/packages/server/graphql/mutations/updateTaskDueDate.ts +++ b/packages/server/graphql/mutations/updateTaskDueDate.ts @@ -2,6 +2,7 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import isValidDate from 'parabol-client/utils/isValidDate' import getRethink from '../../database/rethinkDriver' +import getKysely from '../../postgres/getKysely' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -28,6 +29,7 @@ export default { {taskId, dueDate}: {taskId: string; dueDate: string | null}, {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { + const pg = getKysely() const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -39,7 +41,7 @@ export default { const formattedDueDate = dueDate && new Date(dueDate) const nextDueDate = isValidDate(formattedDueDate) ? formattedDueDate : null const [task, viewer] = await Promise.all([ - r.table('Task').get(taskId).run(), + dataLoader.get('tasks').load(taskId), dataLoader.get('users').loadNonNull(viewerId) ]) if (!task || !isTeamMember(authToken, task.teamId)) { @@ -55,6 +57,9 @@ export default { }) .run() + await pg.updateTable('Task').set({dueDate: nextDueDate}).where('id', '=', taskId).execute() + + dataLoader.clearAll('tasks') const data = {taskId} // send task updated messages diff --git a/packages/server/postgres/migrations/1728578190454_Task-phase1.ts b/packages/server/postgres/migrations/1728578190454_Task-phase1.ts new file mode 100644 index 00000000000..7fba8b32c38 --- /dev/null +++ b/packages/server/postgres/migrations/1728578190454_Task-phase1.ts @@ -0,0 +1,97 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {Client} from 'pg' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' +import getPgConfig from '../getPgConfig' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql` + DO $$ + BEGIN + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'TaskStatusEnum') THEN + CREATE TYPE "TaskStatusEnum" AS ENUM ( + 'active', + 'stuck', + 'done', + 'future' + ); + END IF; + IF NOT EXISTS (SELECT 1 FROM pg_type WHERE typname = 'TaskTagEnum') THEN + CREATE TYPE "TaskTagEnum" AS ENUM ( + 'private', + 'archived' + ); + END IF; + CREATE TABLE IF NOT EXISTS "Task" ( + "id" VARCHAR(100) NOT NULL PRIMARY KEY, + "content" JSONB NOT NULL, + "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "createdBy" VARCHAR(100) NOT NULL, + "doneMeetingId" VARCHAR(100), + "dueDate" TIMESTAMP WITH TIME ZONE, + "integration" JSONB, + "integrationHash" VARCHAR(200), + "meetingId" VARCHAR(100), + "plaintextContent" VARCHAR(10000) NOT NULL, + "sortOrder" DOUBLE PRECISION NOT NULL DEFAULT 0, + "status" "TaskStatusEnum" NOT NULL DEFAULT 'active', + "tags" "TaskTagEnum"[] NOT NULL DEFAULT ARRAY[]::"TaskTagEnum"[], + "teamId" VARCHAR(100) NOT NULL, + "discussionId" VARCHAR(100), + "threadParentId" VARCHAR(100), + "threadSortOrder" INTEGER, + "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT NOW(), + "userId" VARCHAR(100), + CONSTRAINT "fk_createdBy" + FOREIGN KEY("createdBy") + REFERENCES "User"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_doneMeetingId" + FOREIGN KEY("doneMeetingId") + REFERENCES "NewMeeting"("id") + ON DELETE SET NULL, + CONSTRAINT "fk_meetingId" + FOREIGN KEY("meetingId") + REFERENCES "NewMeeting"("id") + ON DELETE SET NULL, + CONSTRAINT "fk_teamId" + FOREIGN KEY("teamId") + REFERENCES "Team"("id") + ON DELETE CASCADE, + CONSTRAINT "fk_discussionId" + FOREIGN KEY("discussionId") + REFERENCES "Discussion"("id") + ON DELETE SET NULL, + CONSTRAINT "fk_userId" + FOREIGN KEY("userId") + REFERENCES "User"("id") + ON DELETE SET NULL + ); + CREATE INDEX IF NOT EXISTS "idx_Task_createdBy" ON "Task"("createdBy"); + CREATE INDEX IF NOT EXISTS "idx_Task_discussionId" ON "Task"("discussionId") WHERE "discussionId" IS NOT NULL; + CREATE INDEX IF NOT EXISTS "idx_Task_integrationHash" ON "Task"("integrationHash") WHERE "integrationHash" IS NOT NULL; + CREATE INDEX IF NOT EXISTS "idx_Task_meetingId" ON "Task"("meetingId") WHERE "meetingId" IS NOT NULL; + CREATE INDEX IF NOT EXISTS "idx_Task_teamId_updatedAt" ON "Task"("teamId", "updatedAt" DESC); + CREATE INDEX IF NOT EXISTS "idx_Task_threadParentId" ON "Task"("threadParentId") WHERE "threadParentId" IS NOT NULL; + CREATE INDEX IF NOT EXISTS "idx_Task_userId" ON "Task"("userId") WHERE "userId" IS NOT NULL; + DROP TRIGGER IF EXISTS "update_Task_updatedAt" ON "Task"; + CREATE TRIGGER "update_Task_updatedAt" BEFORE UPDATE ON "Task" FOR EACH ROW EXECUTE PROCEDURE "set_updatedAt"(); + + END $$; +`.execute(pg) +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + DROP TABLE IF EXISTS "Task"; + ` /* Do undo magic */) + await client.end() +} diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index 2b3c4421d72..7a679f8b91e 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -4,6 +4,7 @@ import {NewMeetingPhaseTypeEnum} from '../graphql/public/resolverTypes' import getKysely from './getKysely' import {ReactjiDB} from './types' import {AnyMeeting, AnyMeetingMember} from './types/Meeting' +import {AnyTaskIntegration} from './types/TaskIntegration' export const selectTimelineEvent = () => { return getKysely().selectFrom('TimelineEvent').selectAll().$narrowType< | { @@ -285,3 +286,28 @@ export const selectMassInvitations = () => getKysely().selectFrom('MassInvitatio export const selectNewFeatures = () => getKysely().selectFrom('NewFeature').selectAll() export const selectTeamInvitations = () => getKysely().selectFrom('TeamInvitation').selectAll() + +export const selectTasks = () => + getKysely() + .selectFrom('Task') + .select(({fn}) => [ + 'id', + 'content', + 'createdAt', + 'createdBy', + 'doneMeetingId', + 'dueDate', + 'integrationHash', + 'meetingId', + 'plaintextContent', + 'sortOrder', + 'status', + 'tags', + 'teamId', + 'discussionId', + 'threadParentId', + 'threadSortOrder', + 'updatedAt', + 'userId', + fn('to_json', ['integration']).as('integration') + ]) diff --git a/packages/server/postgres/types/TaskIntegration.d.ts b/packages/server/postgres/types/TaskIntegration.d.ts new file mode 100644 index 00000000000..75ca80ad75f --- /dev/null +++ b/packages/server/postgres/types/TaskIntegration.d.ts @@ -0,0 +1,44 @@ +interface BaseTaskIntegration { + service: 'jira' | 'jiraServer' | 'github' | 'gitlab' | 'azureDevOps' + accessUserId: string +} + +interface TaskIntegrationJira extends BaseTaskIntegration { + service: 'jira' + cloudId: string + issueKey: string + projectKey: string +} + +interface TaskIntegrationJiraServer extends BaseTaskIntegration { + service: 'jiraServer' + providerId: number + issueId: string + repositoryId: string +} + +interface TaskIntegrationGitHub extends BaseTaskIntegration { + service: 'github' + nameWithOwner: string + issueNumber: number +} + +interface TaskIntegrationGitLab extends BaseTaskIntegration { + service: 'gitlab' + providerId: string + gid: string +} + +interface TaskIntegrationAzureDevOps extends BaseTaskIntegration { + service: 'azureDevOps' + instanceId: string + issueKey: string + projectKey: string +} + +export type AnyTaskIntegration = + | TaskIntegrationJira + | TaskIntegrationJiraServer + | TaskIntegrationGitHub + | TaskIntegrationGitLab + | TaskIntegrationAzureDevOps diff --git a/packages/server/safeMutations/archiveTasksForDB.ts b/packages/server/safeMutations/archiveTasksForDB.ts index 8efd00a6692..4197a64d685 100644 --- a/packages/server/safeMutations/archiveTasksForDB.ts +++ b/packages/server/safeMutations/archiveTasksForDB.ts @@ -3,9 +3,11 @@ import addTagToTask from 'parabol-client/utils/draftjs/addTagToTask' import getTagsFromEntityMap from 'parabol-client/utils/draftjs/getTagsFromEntityMap' import getRethink from '../database/rethinkDriver' import Task from '../database/types/Task' +import getKysely from '../postgres/getKysely' const archiveTasksForDB = async (tasks: Task[], doneMeetingId?: string) => { if (!tasks || tasks.length === 0) return [] + const pg = getKysely() const r = await getRethink() const tasksToArchive = tasks.map((task) => { const contentState = convertFromRaw(JSON.parse(task.content)) @@ -25,6 +27,15 @@ const archiveTasksForDB = async (tasks: Task[], doneMeetingId?: string) => { id: task.id } }) + await Promise.all( + tasksToArchive.map((t) => + pg + .updateTable('Task') + .set({content: t.content, tags: t.tags, doneMeetingId: t.doneMeetingId}) + .where('id', '=', t.id) + .execute() + ) + ) return r(tasksToArchive) .forEach((task) => { return r From b152a8f8f9912dc9bd01396e40c033279a2d40f0 Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:10:48 -0700 Subject: [PATCH 521/529] chore(release): release v7.50.11 (#10349) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 8 ++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 20 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 5f0db771d60..88420408d5b 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.50.10" + ".": "7.50.11" } diff --git a/CHANGELOG.md b/CHANGELOG.md index c5c6558181a..4e915998619 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,14 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.50.11](https://github.com/ParabolInc/parabol/compare/v7.50.10...v7.50.11) (2024-10-15) + + +### Changed + +* **deployment:** PR title for the PR that deploys to production states its purpose ([#10348](https://github.com/ParabolInc/parabol/issues/10348)) ([0932822](https://github.com/ParabolInc/parabol/commit/09328223cf0170ef7f1df94e6e326b5fdcdc0a93)) +* **rethinkdb:** Task: Phase 1 ([#10336](https://github.com/ParabolInc/parabol/issues/10336)) ([5202a3b](https://github.com/ParabolInc/parabol/commit/5202a3b59c1183e8ca7eb6c0906dc2a6baf5b82e)) + ## [7.50.10](https://github.com/ParabolInc/parabol/compare/v7.50.9...v7.50.10) (2024-10-15) diff --git a/package.json b/package.json index 4dfd42922a8..d9d10a76a8c 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.50.10", + "version": "7.50.11", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index fd8627ac0f6..aba812c182f 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.50.10", + "version": "7.50.11", "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.50.10" + "parabol-server": "7.50.11" } } diff --git a/packages/client/package.json b/packages/client/package.json index 8d71f3c70a5..48349a59cd8 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.50.10", + "version": "7.50.11", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 74da73462d5..095d9f24f7d 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.50.10", + "version": "7.50.11", "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 3354db4ae95..da01e1f830a 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.50.10", + "version": "7.50.11", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^5.0.0", - "parabol-client": "7.50.10", - "parabol-server": "7.50.10", + "parabol-client": "7.50.11", + "parabol-server": "7.50.11", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 86b050c9805..7a6af4d1d72 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.50.10", + "version": "7.50.11", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 8e4d836a7de..1863bc3e288 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.50.10", + "version": "7.50.11", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.50.10", + "parabol-client": "7.50.11", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From 62977aed62f8b749476541adb924f8542934fb38 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Tue, 15 Oct 2024 17:18:20 -0700 Subject: [PATCH 522/529] chore(rethinkdb): Task: Phase 2 (#10338) Signed-off-by: Matt Krick --- .../migrations/1728595090540_Task-phase2.ts | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 packages/server/postgres/migrations/1728595090540_Task-phase2.ts diff --git a/packages/server/postgres/migrations/1728595090540_Task-phase2.ts b/packages/server/postgres/migrations/1728595090540_Task-phase2.ts new file mode 100644 index 00000000000..e5e0b160b66 --- /dev/null +++ b/packages/server/postgres/migrations/1728595090540_Task-phase2.ts @@ -0,0 +1,170 @@ +import {Kysely, PostgresDialect, sql} from 'kysely' +import {r} from 'rethinkdb-ts' +import connectRethinkDB from '../../database/connectRethinkDB' +import getPg from '../getPg' + +export async function up() { + await connectRethinkDB() + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + + try { + console.log('Adding index') + await r + .table('Task') + .indexCreate('updatedAtId', (row: any) => [row('updatedAt'), row('id')]) + .run() + await r.table('Task').indexWait().run() + } catch { + // index already exists + } + + console.log('Adding index complete') + + const MAX_PG_PARAMS = 65545 + const PG_COLS = [ + 'id', + 'content', + 'createdAt', + 'createdBy', + 'doneMeetingId', + 'dueDate', + 'integration', + 'integrationHash', + 'meetingId', + 'plaintextContent', + 'sortOrder', + 'status', + 'tags', + 'teamId', + 'discussionId', + 'threadParentId', + 'threadSortOrder', + 'updatedAt', + 'userId' + ] as const + type Task = { + [K in (typeof PG_COLS)[number]]: any + } + const BATCH_SIZE = Math.trunc(MAX_PG_PARAMS / PG_COLS.length) + + let curUpdatedAt = r.minval + let curId = r.minval + + const insertRow = async (row) => { + try { + await pg + .insertInto('Task') + .values(row) + .onConflict((oc) => oc.doNothing()) + .execute() + } catch (e) { + if (e.constraint === 'fk_teamId') { + console.log('Task has no team, skipping insert', row.id) + return + } + if (e.constraint === 'fk_meetingId') { + console.log('Task has no meeting, skipping insert', row.id) + return + } + if (e.constraint === 'fk_discussionId') { + console.log('Task has no discussionId, skipping insert', row.id) + return + } + if (e.constraint === 'fk_createdBy') { + console.log('Task has no createdBy user, skipping insert', row.id) + return + } + if (e.constraint === 'fk_doneMeetingId') { + console.log('Task has no doneMeetingId user, skipping insert', row.id) + return + } + if (e.constraint === 'fk_userId') { + console.log('Task has no userId user, skipping insert', row.id) + return + } + if (e.message.includes('invalid input value for enum "TaskTagEnum"')) { + console.log('Task has invalid enum, skipping insert', row.id) + return + } + throw e + } + } + for (let i = 0; i < 1e6; i++) { + console.log('inserting row', i * BATCH_SIZE, String(curUpdatedAt), String(curId)) + const rawRowsToInsert = (await r + .table('Task') + .between([curUpdatedAt, curId], [r.maxval, r.maxval], { + index: 'updatedAtId', + leftBound: 'open', + rightBound: 'closed' + }) + .orderBy({index: 'updatedAtId'}) + .limit(BATCH_SIZE) + .pluck(...PG_COLS) + .run()) as Task[] + + const rowsToInsert = rawRowsToInsert.map((row) => { + const { + id, + content, + createdAt, + createdBy, + doneMeetingId, + dueDate, + integration, + integrationHash, + meetingId, + plaintextContent, + sortOrder, + status, + tags, + teamId, + discussionId, + threadParentId, + threadSortOrder, + updatedAt, + userId + } = row as any + return { + id, + content: JSON.stringify(content), + createdAt, + createdBy, + doneMeetingId, + dueDate, + integration: JSON.stringify(integration), + integrationHash, + meetingId, + plaintextContent: plaintextContent.slice(0, 2000), + sortOrder, + status, + tags, + teamId, + discussionId, + threadParentId, + threadSortOrder: threadSortOrder ? Math.round(threadSortOrder) : null, + updatedAt, + userId + } + }) + + if (rowsToInsert.length === 0) break + const lastRow = rowsToInsert[rowsToInsert.length - 1] + curUpdatedAt = lastRow.updatedAt + curId = lastRow.id + await Promise.all(rowsToInsert.map(async (row) => insertRow(row))) + } +} + +export async function down() { + const pg = new Kysely({ + dialect: new PostgresDialect({ + pool: getPg() + }) + }) + await sql`TRUNCATE TABLE "Task" CASCADE`.execute(pg) +} From 3d4e1366520eba07ba2f7e11d43507dc4aef6ccd Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Tue, 15 Oct 2024 17:24:32 -0700 Subject: [PATCH 523/529] chore(release): release v7.50.12 (#10352) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 7 +++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 19 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index 88420408d5b..a9bdd96471e 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.50.11" + ".": "7.50.12" } diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e915998619..fdd6390a0f5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,13 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.50.12](https://github.com/ParabolInc/parabol/compare/v7.50.11...v7.50.12) (2024-10-16) + + +### Changed + +* **rethinkdb:** Task: Phase 2 ([#10338](https://github.com/ParabolInc/parabol/issues/10338)) ([62977ae](https://github.com/ParabolInc/parabol/commit/62977aed62f8b749476541adb924f8542934fb38)) + ## [7.50.11](https://github.com/ParabolInc/parabol/compare/v7.50.10...v7.50.11) (2024-10-15) diff --git a/package.json b/package.json index d9d10a76a8c..dde7aa8cf65 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.50.11", + "version": "7.50.12", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index aba812c182f..2562d054e07 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.50.11", + "version": "7.50.12", "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.50.11" + "parabol-server": "7.50.12" } } diff --git a/packages/client/package.json b/packages/client/package.json index 48349a59cd8..cc0ed6b3754 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.50.11", + "version": "7.50.12", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 095d9f24f7d..5f1f318abb9 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.50.11", + "version": "7.50.12", "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 da01e1f830a..40cfec39792 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.50.11", + "version": "7.50.12", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^5.0.0", - "parabol-client": "7.50.11", - "parabol-server": "7.50.11", + "parabol-client": "7.50.12", + "parabol-server": "7.50.12", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 7a6af4d1d72..7640239fdc0 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.50.11", + "version": "7.50.12", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 1863bc3e288..283dcb7a546 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.50.11", + "version": "7.50.12", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.50.11", + "parabol-client": "7.50.12", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2", From aa8e9313603d081e445d5b6193059d27dfdcb268 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Wed, 16 Oct 2024 17:47:24 +0100 Subject: [PATCH 524/529] feat: add Insights UI skeleton (#10254) Co-authored-by: Terry Acker --- .../TeamDashHeader/TeamDashHeader.tsx | 26 +++++++--- .../TeamDashInsightsTab.tsx | 21 +++++++++ .../TeamDashInsightsTab/TeamInsights.tsx | 47 +++++++++++++++++++ .../TeamDashInsightsTab/TeamInsightsRoot.tsx | 23 +++++++++ .../components/TeamDashMain/TeamDashMain.tsx | 4 ++ 5 files changed, 114 insertions(+), 7 deletions(-) create mode 100644 packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamDashInsightsTab.tsx create mode 100644 packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsights.tsx create mode 100644 packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsightsRoot.tsx diff --git a/packages/client/modules/teamDashboard/components/TeamDashHeader/TeamDashHeader.tsx b/packages/client/modules/teamDashboard/components/TeamDashHeader/TeamDashHeader.tsx index d4b427dcffd..f1c764adfaf 100644 --- a/packages/client/modules/teamDashboard/components/TeamDashHeader/TeamDashHeader.tsx +++ b/packages/client/modules/teamDashboard/components/TeamDashHeader/TeamDashHeader.tsx @@ -3,7 +3,6 @@ import styled from '@emotion/styled' import graphql from 'babel-plugin-relay/macro' import React from 'react' import {useFragment} from 'react-relay' -import {useRouteMatch} from 'react-router' import {NavLink} from 'react-router-dom' import DashboardAvatars from '~/components/DashboardAvatars/DashboardAvatars' import AgendaToggle from '~/modules/teamDashboard/components/AgendaToggle/AgendaToggle' @@ -112,10 +111,19 @@ const TeamDashHeader = (props: Props) => { ) const {organization, id: teamId, name: teamName, teamMembers} = team const {name: orgName, id: orgId} = organization - const isTasks = useRouteMatch('/team/:teamId/tasks') - const isIntegrations = useRouteMatch('/team/:teamId/integrations') const {history} = useRouter() + const tabs = [ + {label: 'Activity', path: 'activity'}, + {label: 'Tasks', path: 'tasks'}, + {label: 'Integrations', path: 'integrations'}, + {label: 'Insights', path: 'insights'} + ] + + const activePath = location.pathname.split('/').pop() + const activeTab = tabs.find((tab) => tab.path === activePath) ? activePath : 'activity' + const activeIdx = tabs.findIndex((tab) => tab.path === activeTab) + return ( @@ -158,12 +166,16 @@ const TeamDashHeader = (props: Props) => { - history.push(`/team/${teamId}/activity`)} /> - history.push(`/team/${teamId}/tasks`)} /> - history.push(`/team/${teamId}/integrations`)} /> + {tabs.map((tab) => ( + history.push(`/team/${teamId}/${tab.path}`)} + /> + ))} ) diff --git a/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamDashInsightsTab.tsx b/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamDashInsightsTab.tsx new file mode 100644 index 00000000000..3b1ae503875 --- /dev/null +++ b/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamDashInsightsTab.tsx @@ -0,0 +1,21 @@ +import React, {lazy} from 'react' + +interface Props { + teamId: string +} + +const TeamInsightsRoot = lazy( + () => import(/* webpackChunkName: 'InsightsRoot' */ './TeamInsightsRoot') +) + +const TeamDashInsightsTab = (props: Props) => { + const {teamId} = props + return ( +
+
+ +
+
+ ) +} +export default TeamDashInsightsTab diff --git a/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsights.tsx b/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsights.tsx new file mode 100644 index 00000000000..b10f1eb9238 --- /dev/null +++ b/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsights.tsx @@ -0,0 +1,47 @@ +import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome' +import graphql from 'babel-plugin-relay/macro' +import React from 'react' +import {PreloadedQuery, usePreloadedQuery} from 'react-relay' +import {TeamInsightsQuery} from '../../../../__generated__/TeamInsightsQuery.graphql' + +interface Props { + queryRef: PreloadedQuery +} + +const query = graphql` + query TeamInsightsQuery($teamId: ID!) { + viewer { + team(teamId: $teamId) { + id + name + } + } + } +` + +const Insights = (props: Props) => { + const {queryRef} = props + const data = usePreloadedQuery(query, queryRef) + // TODO: use the query rather than just console logging it + console.log('🚀 ~ data:', data) + + return ( +
+ ) +} + +export default Insights diff --git a/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsightsRoot.tsx b/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsightsRoot.tsx new file mode 100644 index 00000000000..3d5590049e1 --- /dev/null +++ b/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsightsRoot.tsx @@ -0,0 +1,23 @@ +import React, {Suspense} from 'react' +import teamInsightsQuery, { + TeamInsightsQuery +} from '../../../../__generated__/TeamInsightsQuery.graphql' +import useQueryLoaderNow from '../../../../hooks/useQueryLoaderNow' +import TeamInsights from './TeamInsights' + +interface Props { + teamId: string +} + +const TeamInsightsRoot = ({teamId}: Props) => { + const queryRef = useQueryLoaderNow(teamInsightsQuery, { + teamId + }) + return ( +
+ {queryRef && } +
+ ) +} + +export default TeamInsightsRoot diff --git a/packages/client/modules/teamDashboard/components/TeamDashMain/TeamDashMain.tsx b/packages/client/modules/teamDashboard/components/TeamDashMain/TeamDashMain.tsx index 49165b16447..2b4d80ef024 100644 --- a/packages/client/modules/teamDashboard/components/TeamDashMain/TeamDashMain.tsx +++ b/packages/client/modules/teamDashboard/components/TeamDashMain/TeamDashMain.tsx @@ -9,6 +9,7 @@ import useDocumentTitle from '../../../../hooks/useDocumentTitle' import getTeamIdFromPathname from '../../../../utils/getTeamIdFromPathname' import TeamTasksHeaderContainer from '../../containers/TeamTasksHeader/TeamTasksHeaderContainer' import TeamDashActivityTab from '../TeamDashActivityTab/TeamDashActivityTab' +import TeamDashInsightsTab from '../TeamDashInsightsTab/TeamDashInsightsTab' import TeamDashIntegrationsTab from '../TeamDashIntegrationsTab/TeamDashIntegrationsTab' import TeamDashTasksTab from '../TeamDashTasksTab/TeamDashTasksTab' import TeamDrawer from './TeamDrawer' @@ -59,6 +60,9 @@ const TeamDashMain = (props: Props) => { + + + {/*Fall back to activity view if nothing is specified*/} From f99e63a3f05a4f2b863e4b3052950de7d02cce5e Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Wed, 16 Oct 2024 18:06:10 +0100 Subject: [PATCH 525/529] feat: show default insight (#10283) Co-authored-by: Terry Acker --- .../WholeMeetingSummaryResult.tsx | 2 +- .../TeamDashHeader/TeamDashHeader.tsx | 16 ++- .../TeamInsightContent.tsx | 121 ++++++++++++++++++ .../TeamInsightEmptyState.tsx | 78 +++++++++++ .../TeamDashInsightsTab/TeamInsights.tsx | 43 +++++-- .../mutations/EndRetrospectiveMutation.ts | 1 + .../mutations/GenerateInsightMutation.ts | 10 +- packages/client/shared/gqlIds/InsightId.ts | 7 + packages/client/styles/theme/global.css | 2 +- .../client/subscriptions/TeamSubscription.ts | 3 + .../server/dataloader/customLoaderMakers.ts | 21 ++- .../public/mutations/generateInsight.ts | 40 +++++- .../typeDefs/GenerateInsightPayload.graphql | 4 + .../typeDefs/GenerateInsightSuccess.graphql | 3 + .../graphql/public/typeDefs/Insight.graphql | 39 ++++++ .../graphql/public/typeDefs/Mutation.graphql | 11 ++ .../graphql/public/typeDefs/Team.graphql | 10 ++ .../typeDefs/TeamSubscriptionPayload.graphql | 1 + .../public/typeDefs/generateInsight.graphql | 34 ----- .../public/types/GenerateInsightSuccess.ts | 12 +- packages/server/graphql/public/types/Team.ts | 14 ++ ...1728596433080_addMeetingsCountToInsight.ts | 22 ++++ packages/server/postgres/types/index.d.ts | 2 + .../illustrations/insights-empty-state.png | Bin 0 -> 53959 bytes 24 files changed, 429 insertions(+), 67 deletions(-) create mode 100644 packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsightContent.tsx create mode 100644 packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsightEmptyState.tsx create mode 100644 packages/client/shared/gqlIds/InsightId.ts create mode 100644 packages/server/graphql/public/typeDefs/GenerateInsightPayload.graphql create mode 100644 packages/server/graphql/public/typeDefs/GenerateInsightSuccess.graphql create mode 100644 packages/server/graphql/public/typeDefs/Insight.graphql delete mode 100644 packages/server/graphql/public/typeDefs/generateInsight.graphql create mode 100644 packages/server/postgres/migrations/1728596433080_addMeetingsCountToInsight.ts create mode 100644 static/images/illustrations/insights-empty-state.png diff --git a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/WholeMeetingSummaryResult.tsx b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/WholeMeetingSummaryResult.tsx index 1f1c899ef7b..ca4705f9733 100644 --- a/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/WholeMeetingSummaryResult.tsx +++ b/packages/client/modules/email/components/SummaryEmail/MeetingSummaryEmail/WholeMeetingSummaryResult.tsx @@ -93,7 +93,7 @@ const WholeMeetingSummaryResult = ({meetingRef}: Props) => { diff --git a/packages/client/modules/teamDashboard/components/TeamDashHeader/TeamDashHeader.tsx b/packages/client/modules/teamDashboard/components/TeamDashHeader/TeamDashHeader.tsx index f1c764adfaf..91805c5cf19 100644 --- a/packages/client/modules/teamDashboard/components/TeamDashHeader/TeamDashHeader.tsx +++ b/packages/client/modules/teamDashboard/components/TeamDashHeader/TeamDashHeader.tsx @@ -95,10 +95,14 @@ const TeamDashHeader = (props: Props) => { ...DashboardAvatars_team id name + hasInsightsFlag: featureFlag(featureName: "insights") organization { id name } + viewerTeamMember { + isLead + } teamMembers(sortBy: "preferredName") { ...InviteTeamMemberAvatar_teamMembers ...DashboardAvatar_teamMember @@ -109,15 +113,23 @@ const TeamDashHeader = (props: Props) => { `, teamRef ) - const {organization, id: teamId, name: teamName, teamMembers} = team + const { + organization, + id: teamId, + name: teamName, + teamMembers, + viewerTeamMember, + hasInsightsFlag + } = team const {name: orgName, id: orgId} = organization + const canViewInsights = viewerTeamMember?.isLead && hasInsightsFlag const {history} = useRouter() const tabs = [ {label: 'Activity', path: 'activity'}, {label: 'Tasks', path: 'tasks'}, {label: 'Integrations', path: 'integrations'}, - {label: 'Insights', path: 'insights'} + ...(canViewInsights ? [{label: 'Insights', path: 'insights'}] : []) ] const activePath = location.pathname.split('/').pop() diff --git a/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsightContent.tsx b/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsightContent.tsx new file mode 100644 index 00000000000..849181d07e6 --- /dev/null +++ b/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsightContent.tsx @@ -0,0 +1,121 @@ +import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome' +import graphql from 'babel-plugin-relay/macro' +import dayjs from 'dayjs' +import {marked} from 'marked' +import React from 'react' +import {useFragment} from 'react-relay' +import sanitizeHtml from 'sanitize-html' +import {TeamInsightContent_team$key} from '../../../../__generated__/TeamInsightContent_team.graphql' + +interface Props { + teamName: string + insightRef: TeamInsightContent_team$key +} + +const TeamInsightContent = (props: Props) => { + const {insightRef, teamName} = props + const insight = useFragment( + graphql` + fragment TeamInsightContent_team on Insight { + meetingsCount + wins + challenges + startDateTime + endDateTime + } + `, + insightRef + ) + const {meetingsCount, wins, challenges} = insight + + const renderMarkdown = (text: string) => { + const renderedText = marked(text, { + gfm: true, + breaks: true + }) as string + return sanitizeHtml(renderedText, { + allowedTags: sanitizeHtml.defaults.allowedTags.concat(['a']), + allowedAttributes: { + ...sanitizeHtml.defaults.allowedAttributes, + a: ['href', 'target', 'rel'] + }, + transformTags: { + a: (tagName, attribs) => { + return { + tagName, + attribs: { + ...attribs, + target: '_blank', + rel: 'noopener noreferrer' + } + } + } + } + }) + } + + const formatDateRange = (start: string, end: string) => { + const startDate = dayjs(start) + const endDate = dayjs(end) + + if (startDate.year() === endDate.year()) { + return `${startDate.format('MMM')} to ${endDate.format('MMM YYYY')}` + } else { + return `${startDate.format('MMM YYYY')} to ${endDate.format('MMM YYYY')}` + } + } + + const dateRange = insight + ? formatDateRange(insight.startDateTime, insight.endDateTime) + : 'Date range not available' + + return ( +
+

+ + Insights - {dateRange} +

+

Summarized {meetingsCount} meetings

+ + {wins && wins.length > 0 && ( + <> +

Wins

+

+ What wins has "{teamName}" seen during this timeframe? +

+
    + {wins.map((win, index) => ( +
  • + +
  • + ))} +
+ + )} + + {challenges && challenges.length > 0 && ( + <> +

Challenges

+

+ What challenges has "{teamName}" faced during this timeframe? +

+
    + {challenges.map((challenge, index) => ( +
  • + +
  • + ))} +
+ + )} +
+ ) +} + +export default TeamInsightContent diff --git a/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsightEmptyState.tsx b/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsightEmptyState.tsx new file mode 100644 index 00000000000..9a4c13487e3 --- /dev/null +++ b/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsightEmptyState.tsx @@ -0,0 +1,78 @@ +import AddIcon from '@mui/icons-material/Add' +import React from 'react' +import insightsEmptyStateImg from '../../../../../../static/images/illustrations/insights-empty-state.png' +import useAtmosphere from '../../../../hooks/useAtmosphere' +import useMutationProps from '../../../../hooks/useMutationProps' +import GenerateInsightMutation from '../../../../mutations/GenerateInsightMutation' +import plural from '../../../../utils/plural' + +interface Props { + meetingsCount?: number + teamId?: string +} + +const TeamInsightEmptyState = (props: Props) => { + const {meetingsCount, teamId} = props + const atmosphere = useAtmosphere() + const {onError, error, onCompleted, submitMutation, submitting} = useMutationProps() + + if (meetingsCount === undefined || !teamId) return null + + const canGenerateInsight = meetingsCount > 1 + + const handleGenerateInsight = () => { + if (submitting) return + submitMutation() + const now = new Date() + const threeMonthsAgo = new Date(now.getFullYear(), now.getMonth() - 3, now.getDate()) + GenerateInsightMutation( + atmosphere, + { + teamId, + startDate: threeMonthsAgo.toISOString(), // TODO: let users choose date range + endDate: now.toISOString() + }, + {onError, onCompleted} + ) + } + + return ( +
+ Empty state +
+

+ Your team has completed {meetingsCount}{' '} + {plural(meetingsCount, 'meeting')}. +

+ {canGenerateInsight ? ( +

+ This should be{' '} + + good fodder + {' '} + for some interesting insights! +

+ ) : ( +

+ Create more meetings to start generating insights for your team. +

+ )} +
+ {canGenerateInsight && ( + + )} + {error && ( +
{error.message}
+ )} +
+ ) +} + +export default TeamInsightEmptyState diff --git a/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsights.tsx b/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsights.tsx index b10f1eb9238..9232897bdd8 100644 --- a/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsights.tsx +++ b/packages/client/modules/teamDashboard/components/TeamDashInsightsTab/TeamInsights.tsx @@ -1,8 +1,9 @@ -import AutoAwesomeIcon from '@mui/icons-material/AutoAwesome' import graphql from 'babel-plugin-relay/macro' import React from 'react' import {PreloadedQuery, usePreloadedQuery} from 'react-relay' import {TeamInsightsQuery} from '../../../../__generated__/TeamInsightsQuery.graphql' +import TeamInsightContent from './TeamInsightContent' +import TeamInsightEmptyState from './TeamInsightEmptyState' interface Props { queryRef: PreloadedQuery @@ -12,36 +13,52 @@ const query = graphql` query TeamInsightsQuery($teamId: ID!) { viewer { team(teamId: $teamId) { - id + ...TeamInsights_team @relay(mask: false) name + insight { + wins + ...TeamInsightContent_team + } } } } ` -const Insights = (props: Props) => { +graphql` + fragment TeamInsights_team on Team { + id + retroMeetingsCount + } +` + +const TeamInsights = (props: Props) => { const {queryRef} = props const data = usePreloadedQuery(query, queryRef) - // TODO: use the query rather than just console logging it - console.log('🚀 ~ data:', data) + const {viewer} = data + const {team} = viewer + const {id: teamId, insight, name, retroMeetingsCount} = team ?? {} return (

Only you (as Team Lead) can see Team Insights. Insights are auto-generated.{' '} - + Give us feedback

-
-

- - Insights - Aug to Sep 2024 -

-
+ {insight ? ( + + ) : ( + + )}
) } -export default Insights +export default TeamInsights diff --git a/packages/client/mutations/EndRetrospectiveMutation.ts b/packages/client/mutations/EndRetrospectiveMutation.ts index c7f3d2c1df2..e2c37a4ec49 100644 --- a/packages/client/mutations/EndRetrospectiveMutation.ts +++ b/packages/client/mutations/EndRetrospectiveMutation.ts @@ -52,6 +52,7 @@ graphql` activeMeetings { id } + ...TeamInsights_team insights { ...TeamDashInsights_insights } diff --git a/packages/client/mutations/GenerateInsightMutation.ts b/packages/client/mutations/GenerateInsightMutation.ts index d7bbe485b4f..b0324478e70 100644 --- a/packages/client/mutations/GenerateInsightMutation.ts +++ b/packages/client/mutations/GenerateInsightMutation.ts @@ -5,8 +5,14 @@ import {StandardMutation} from '../types/relayMutations' graphql` fragment GenerateInsightMutation_team on GenerateInsightSuccess { - wins - challenges + team { + id + insight { + wins + challenges + meetingsCount + } + } } ` diff --git a/packages/client/shared/gqlIds/InsightId.ts b/packages/client/shared/gqlIds/InsightId.ts new file mode 100644 index 00000000000..ebe082ca92c --- /dev/null +++ b/packages/client/shared/gqlIds/InsightId.ts @@ -0,0 +1,7 @@ +export const InsightId = { + join: (ownerId: string, databaseId: number) => `insight:${ownerId}:${databaseId}`, + split: (id: string) => { + const [, ownerId, databaseId] = id.split(':') + return {ownerId, databaseId} + } +} diff --git a/packages/client/styles/theme/global.css b/packages/client/styles/theme/global.css index b1a76203c76..4b8d81dbb31 100644 --- a/packages/client/styles/theme/global.css +++ b/packages/client/styles/theme/global.css @@ -188,7 +188,7 @@ @apply m-0 rounded-[1px] border-l-2 border-solid border-l-slate-500 bg-slate-200 py-0 px-[8px] font-mono font-[13px] leading-normal; } -.summary-link-style a { +.link-style a { @apply text-sky-500; text-decoration: underline; } diff --git a/packages/client/subscriptions/TeamSubscription.ts b/packages/client/subscriptions/TeamSubscription.ts index 17527fdee9b..3e7b524d4ab 100644 --- a/packages/client/subscriptions/TeamSubscription.ts +++ b/packages/client/subscriptions/TeamSubscription.ts @@ -46,6 +46,9 @@ const subscription = graphql` subscription TeamSubscription { teamSubscription { fieldName + GenerateInsightSuccess { + ...GenerateInsightMutation_team @relay(mask: false) + } UpdateRecurrenceSettingsSuccess { ...UpdateRecurrenceSettingsMutation_team @relay(mask: false) } diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index dd75986ec34..86cabe39472 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -25,7 +25,7 @@ import getMeetingTaskEstimates, { MeetingTaskEstimatesResult } from '../postgres/queries/getMeetingTaskEstimates' import {selectMeetingSettings, selectNewMeetings, selectTeams} from '../postgres/select' -import {MeetingSettings, OrganizationUser, Team} from '../postgres/types' +import {Insight, MeetingSettings, OrganizationUser, Team} from '../postgres/types' import {AnyMeeting, MeetingTypeEnum} from '../postgres/types/Meeting' import {Logger} from '../utils/Logger' import getRedis from '../utils/getRedis' @@ -832,6 +832,25 @@ export const meetingCount = (parent: RootDataLoader, dependsOn: RegisterDependsO ) } +export const latestInsightByTeamId = (parent: RootDataLoader) => { + return new NullableDataLoader( + async (teamIds) => { + const pg = getKysely() + const insights = await pg + .selectFrom('Insight') + .where('teamId', 'in', teamIds) + .selectAll() + .orderBy('createdAt', 'desc') + .execute() + + return teamIds.map((teamId) => insights.find((insight) => insight.teamId === teamId) || null) + }, + { + ...parent.dataLoaderOptions + } + ) +} + export const featureFlagByOwnerId = (parent: RootDataLoader) => { return new DataLoader<{ownerId: string; featureName: string}, boolean, string>( async (keys) => { diff --git a/packages/server/graphql/public/mutations/generateInsight.ts b/packages/server/graphql/public/mutations/generateInsight.ts index c7ec6cbb459..d463384f32d 100644 --- a/packages/server/graphql/public/mutations/generateInsight.ts +++ b/packages/server/graphql/public/mutations/generateInsight.ts @@ -1,4 +1,8 @@ +import {SubscriptionChannel} from 'parabol-client/types/constEnums' +import toTeamMemberId from '../../../../client/utils/relay/toTeamMemberId' import getKysely from '../../../postgres/getKysely' +import {getUserId} from '../../../utils/authorization' +import publish from '../../../utils/publish' import standardError from '../../../utils/standardError' import {MutationResolvers} from '../resolverTypes' import {getSummaries} from './helpers/getSummaries' @@ -7,8 +11,25 @@ import {getTopics} from './helpers/getTopics' const generateInsight: MutationResolvers['generateInsight'] = async ( _source, {teamId, startDate, endDate, useSummaries = true, prompt}, - {dataLoader} + context ) => { + const {dataLoader, socketId: mutatorId, authToken} = context + const viewerId = getUserId(authToken) + const teamMemberId = toTeamMemberId(teamId, viewerId) + const teamMember = await dataLoader.get('teamMembers').loadNonNull(teamMemberId) + const isLead = teamMember.isLead + if (!isLead) { + return standardError(new Error('Only team leads can generate insights'), {userId: viewerId}) + } + const hasInsightsFlag = await dataLoader + .get('featureFlagByOwnerId') + .load({ownerId: teamId, featureName: 'insights'}) + if (!hasInsightsFlag) { + return standardError(new Error('Insights are not enabled for this team'), {userId: viewerId}) + } + + const operationId = dataLoader.share() + const subOptions = {operationId, mutatorId} if (Number.isNaN(startDate.getTime()) || Number.isNaN(endDate.getTime())) { return standardError( new Error('Invalid date format. Please use ISO 8601 format (e.g., 2024-01-01T00:00:00Z).') @@ -26,21 +47,32 @@ const generateInsight: MutationResolvers['generateInsight'] = async ( if ('error' in response) { return response } - const {wins, challenges} = response + const {wins, challenges, meetingIds} = response const pg = getKysely() - await pg + const [insertedInsight] = await pg .insertInto('Insight') .values({ teamId, wins, challenges, + meetingsCount: meetingIds.length, startDateTime: startDate, endDateTime: endDate }) + .returning(['id']) .execute() - return response + if (!insertedInsight) { + return standardError(new Error('Failed to insert insight')) + } + const data = { + teamId + } + + publish(SubscriptionChannel.TEAM, teamId, 'GenerateInsightSuccess', data, subOptions) + + return data } export default generateInsight diff --git a/packages/server/graphql/public/typeDefs/GenerateInsightPayload.graphql b/packages/server/graphql/public/typeDefs/GenerateInsightPayload.graphql new file mode 100644 index 00000000000..13629c3d050 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/GenerateInsightPayload.graphql @@ -0,0 +1,4 @@ +""" +Return value for generateInsight, which could be an error +""" +union GenerateInsightPayload = ErrorPayload | GenerateInsightSuccess diff --git a/packages/server/graphql/public/typeDefs/GenerateInsightSuccess.graphql b/packages/server/graphql/public/typeDefs/GenerateInsightSuccess.graphql new file mode 100644 index 00000000000..b40ebf68e2d --- /dev/null +++ b/packages/server/graphql/public/typeDefs/GenerateInsightSuccess.graphql @@ -0,0 +1,3 @@ +type GenerateInsightSuccess { + team: Team! +} diff --git a/packages/server/graphql/public/typeDefs/Insight.graphql b/packages/server/graphql/public/typeDefs/Insight.graphql new file mode 100644 index 00000000000..3f5e4d55f33 --- /dev/null +++ b/packages/server/graphql/public/typeDefs/Insight.graphql @@ -0,0 +1,39 @@ +type Insight { + id: ID! + teamId: ID! + + """ + Start date and time of the insight period + """ + startDateTime: DateTime! + + """ + End date and time of the insight period + """ + endDateTime: DateTime! + + """ + List of AI generated wins + """ + wins: [String!]! + + """ + List of AI generated challenges + """ + challenges: [String!]! + + """ + The count of the meetings used to generate the insight + """ + meetingsCount: Int! + + """ + Timestamp when the insight was created + """ + createdAt: DateTime! + + """ + Timestamp when the insight was last updated + """ + updatedAt: DateTime! +} diff --git a/packages/server/graphql/public/typeDefs/Mutation.graphql b/packages/server/graphql/public/typeDefs/Mutation.graphql index b71bc91fe3c..a793fc21632 100644 --- a/packages/server/graphql/public/typeDefs/Mutation.graphql +++ b/packages/server/graphql/public/typeDefs/Mutation.graphql @@ -422,6 +422,17 @@ type Mutation { invitees: [Email!]! ): InviteToTeamPayload! + """ + Generate an insight for a team + """ + generateInsight( + teamId: ID! + startDate: DateTime! + endDate: DateTime! + useSummaries: Boolean + prompt: String + ): GenerateInsightPayload! + """ Move a template dimension """ diff --git a/packages/server/graphql/public/typeDefs/Team.graphql b/packages/server/graphql/public/typeDefs/Team.graphql index 5bed3eae97e..dccbb9e7eb9 100644 --- a/packages/server/graphql/public/typeDefs/Team.graphql +++ b/packages/server/graphql/public/typeDefs/Team.graphql @@ -128,6 +128,11 @@ type Team { ): NewMeeting organization: Organization! + """ + The AI insight for the team. Null if not enough data or feature flag isn't set + """ + insight: Insight + """ The agenda items for the upcoming or current meeting """ @@ -191,4 +196,9 @@ type Team { The team member that is the team lead """ teamLead: TeamMember! + + """ + The number of retro meetings the team has had + """ + retroMeetingsCount: Int! } diff --git a/packages/server/graphql/public/typeDefs/TeamSubscriptionPayload.graphql b/packages/server/graphql/public/typeDefs/TeamSubscriptionPayload.graphql index 98cafd8758b..5d988e06788 100644 --- a/packages/server/graphql/public/typeDefs/TeamSubscriptionPayload.graphql +++ b/packages/server/graphql/public/typeDefs/TeamSubscriptionPayload.graphql @@ -71,4 +71,5 @@ type TeamSubscriptionPayload { UpdateRecurrenceSettingsSuccess: UpdateRecurrenceSettingsSuccess UpdateDimensionFieldSuccess: UpdateDimensionFieldSuccess UpdateTemplateCategorySuccess: UpdateTemplateCategorySuccess + GenerateInsightSuccess: GenerateInsightSuccess } diff --git a/packages/server/graphql/public/typeDefs/generateInsight.graphql b/packages/server/graphql/public/typeDefs/generateInsight.graphql deleted file mode 100644 index d07b656a8f0..00000000000 --- a/packages/server/graphql/public/typeDefs/generateInsight.graphql +++ /dev/null @@ -1,34 +0,0 @@ -extend type Mutation { - """ - Generate an insight for a team - """ - generateInsight( - teamId: ID! - startDate: DateTime! - endDate: DateTime! - useSummaries: Boolean - prompt: String - ): GenerateInsightPayload! -} - -""" -Return value for generateInsight, which could be an error -""" -union GenerateInsightPayload = ErrorPayload | GenerateInsightSuccess - -type GenerateInsightSuccess { - """ - The insights generated focusing on the wins of the team - """ - wins: [String!]! - - """ - The insights generated focusing on the challenges team are facing - """ - challenges: [String!]! - - """ - The meetings that were used to generate the insights - """ - meetings: [RetrospectiveMeeting!]! -} diff --git a/packages/server/graphql/public/types/GenerateInsightSuccess.ts b/packages/server/graphql/public/types/GenerateInsightSuccess.ts index a22c5a7e1cd..53a3ddd4200 100644 --- a/packages/server/graphql/public/types/GenerateInsightSuccess.ts +++ b/packages/server/graphql/public/types/GenerateInsightSuccess.ts @@ -1,18 +1,12 @@ -import isValid from '../../isValid' import {GenerateInsightSuccessResolvers} from '../resolverTypes' export type GenerateInsightSuccessSource = { - wins: string[] - challenges: string[] - meetingIds: string[] + teamId: string } const GenerateInsightSuccess: GenerateInsightSuccessResolvers = { - wins: ({wins}) => wins, - challenges: ({challenges}) => challenges, - meetings: async ({meetingIds}, _args, {dataLoader}) => { - const meetings = await dataLoader.get('newMeetings').loadMany(meetingIds) - return meetings.filter(isValid).filter((m) => m.meetingType === 'retrospective') + team: async ({teamId}, _args, {dataLoader}) => { + return await dataLoader.get('teams').loadNonNull(teamId) } } diff --git a/packages/server/graphql/public/types/Team.ts b/packages/server/graphql/public/types/Team.ts index 8cd5ccbbae9..1c3975fd4dc 100644 --- a/packages/server/graphql/public/types/Team.ts +++ b/packages/server/graphql/public/types/Team.ts @@ -1,4 +1,5 @@ import TeamInsightsId from 'parabol-client/shared/gqlIds/TeamInsightsId' +import {InsightId} from '../../../../client/shared/gqlIds/InsightId' import toTeamMemberId from '../../../../client/utils/relay/toTeamMemberId' import {getUserId, isTeamMember} from '../../../utils/authorization' import {getFeatureTier} from '../../types/helpers/getFeatureTier' @@ -55,6 +56,19 @@ const Team: TeamResolvers = { const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) return teamMembers.find((teamMember) => teamMember.isLead)! }, + retroMeetingsCount: async ({id: teamId}, _args, {dataLoader}) => { + const meetings = await dataLoader.get('completedMeetingsByTeamId').load(teamId) + const retroMeetings = meetings.filter((meeting) => meeting.meetingType === 'retrospective') + return retroMeetings.length + }, + insight: async ({id: teamId}, _args, {dataLoader}) => { + const insight = await dataLoader.get('latestInsightByTeamId').load(teamId) + if (!insight) return null + return { + ...insight, + id: InsightId.join(teamId, insight.id) + } + }, featureFlag: async ({id: teamId}, {featureName}, {dataLoader}) => { return await dataLoader.get('featureFlagByOwnerId').load({ownerId: teamId, featureName}) } diff --git a/packages/server/postgres/migrations/1728596433080_addMeetingsCountToInsight.ts b/packages/server/postgres/migrations/1728596433080_addMeetingsCountToInsight.ts new file mode 100644 index 00000000000..015d54f428a --- /dev/null +++ b/packages/server/postgres/migrations/1728596433080_addMeetingsCountToInsight.ts @@ -0,0 +1,22 @@ +import {Client} from 'pg' +import getPgConfig from '../getPgConfig' + +export async function up() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + ALTER TABLE "Insight" + ADD COLUMN "meetingsCount" INTEGER NOT NULL DEFAULT 0; + `) + await client.end() +} + +export async function down() { + const client = new Client(getPgConfig()) + await client.connect() + await client.query(` + ALTER TABLE "Insight" + DROP COLUMN IF EXISTS "meetingsCount"; + `) + await client.end() +} diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index 587e2372564..11efe61389e 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -1,6 +1,7 @@ import {SelectQueryBuilder, Selectable} from 'kysely' import { Discussion as DiscussionPG, + Insight as InsightPG, OrganizationUser as OrganizationUserPG, TeamMember as TeamMemberPG } from '../pg.d' @@ -72,6 +73,7 @@ export type SlackNotification = ExtractTypeFromQueryBuilderSelect export type ReflectPrompt = ExtractTypeFromQueryBuilderSelect +export type Insight = Selectable export type NewMeeting = ExtractTypeFromQueryBuilderSelect export type NewFeature = ExtractTypeFromQueryBuilderSelect diff --git a/static/images/illustrations/insights-empty-state.png b/static/images/illustrations/insights-empty-state.png new file mode 100644 index 0000000000000000000000000000000000000000..f4f445c4fd57aeb94c8ce47d68581119b6b6d928 GIT binary patch literal 53959 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%0De`YU!-`)8=z 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 E0F}%spa1{> literal 0 HcmV?d00001 From dfdb68cd25c4b9fb49ea1bf6a5e7cfa775151d21 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Wed, 16 Oct 2024 18:06:40 +0100 Subject: [PATCH 526/529] chore: remove feature flag owner (#10319) --- codegen.json | 1 + .../mutations/removeFeatureFlagOwner.ts | 107 ++++++++++++++++++ .../graphql/private/typeDefs/Mutation.graphql | 14 +++ .../RemoveFeatureFlagOwnerSuccess.graphql | 25 ++++ .../typeDefs/RemoveFeatureFlagPayload.graphql | 4 + .../types/RemoveFeatureFlagOwnerSuccess.ts | 31 +++++ 6 files changed, 182 insertions(+) create mode 100644 packages/server/graphql/private/mutations/removeFeatureFlagOwner.ts create mode 100644 packages/server/graphql/private/typeDefs/RemoveFeatureFlagOwnerSuccess.graphql create mode 100644 packages/server/graphql/private/typeDefs/RemoveFeatureFlagPayload.graphql create mode 100644 packages/server/graphql/private/types/RemoveFeatureFlagOwnerSuccess.ts diff --git a/codegen.json b/codegen.json index 77d538e22cc..fc47d10aa3d 100644 --- a/codegen.json +++ b/codegen.json @@ -13,6 +13,7 @@ "mappers": { "AddFeatureFlagSuccess": "./types/AddFeatureFlagSuccess#AddFeatureFlagSuccessSource", "ApplyFeatureFlagSuccess": "./types/ApplyFeatureFlagSuccess#ApplyFeatureFlagSuccessSource", + "RemoveFeatureFlagOwnerSuccess": "./types/RemoveFeatureFlagOwnerSuccess#RemoveFeatureFlagOwnerSuccessSource", "DeleteFeatureFlagSuccess": "./types/DeleteFeatureFlagSuccess#DeleteFeatureFlagSuccessSource", "UpdateFeatureFlagSuccess": "./types/UpdateFeatureFlagSuccess#UpdateFeatureFlagSuccessSource", "ChangeEmailDomainSuccess": "./types/ChangeEmailDomainSuccess#ChangeEmailDomainSuccessSource", diff --git a/packages/server/graphql/private/mutations/removeFeatureFlagOwner.ts b/packages/server/graphql/private/mutations/removeFeatureFlagOwner.ts new file mode 100644 index 00000000000..1abda3f4fbc --- /dev/null +++ b/packages/server/graphql/private/mutations/removeFeatureFlagOwner.ts @@ -0,0 +1,107 @@ +import getKysely from '../../../postgres/getKysely' +import getUsersByDomain from '../../../postgres/queries/getUsersByDomain' +import {getUsersByEmails} from '../../../postgres/queries/getUsersByEmails' +import {getUserId} from '../../../utils/authorization' +import standardError from '../../../utils/standardError' +import {MutationResolvers} from '../resolverTypes' + +const removeFeatureFlagOwner: MutationResolvers['removeFeatureFlagOwner'] = async ( + _source, + {flagName, subjects}, + {authToken} +) => { + const pg = getKysely() + + const viewerId = getUserId(authToken) + + const subjectKeys = Object.keys(subjects) + + if (subjectKeys.length === 0) { + return standardError(new Error('At least one subject type must be provided'), { + userId: viewerId + }) + } + + const featureFlag = await pg + .selectFrom('FeatureFlag') + .select(['id', 'scope']) + .where('featureName', '=', flagName) + .executeTakeFirst() + + if (!featureFlag) { + return standardError(new Error('Feature flag not found'), {userId: viewerId}) + } + + const {id: featureFlagId, scope} = featureFlag + + const userIds: string[] = [] + const teamIds: string[] = [] + const orgIds: string[] = [] + + if (scope === 'User') { + if (subjects.emails) { + const users = await getUsersByEmails(subjects.emails) + userIds.push(...users.map((user) => user.id)) + } + + if (subjects.domains) { + for (const domain of subjects.domains) { + const domainUsers = await getUsersByDomain(domain) + userIds.push(...domainUsers.map((user) => user.id)) + } + } + + if (subjects.userIds) { + userIds.push(...subjects.userIds) + } + } else if (scope === 'Team') { + if (subjects.teamIds) { + teamIds.push(...subjects.teamIds) + } + } else if (scope === 'Organization') { + if (subjects.orgIds) { + orgIds.push(...subjects.orgIds) + } + } + + let deletedCount = 0 + + if (scope === 'User' && userIds.length > 0) { + const result = await pg + .deleteFrom('FeatureFlagOwner') + .where('featureFlagId', '=', featureFlagId) + .where('userId', 'in', userIds) + .executeTakeFirst() + deletedCount = Number(result?.numDeletedRows ?? 0) + } else if (scope === 'Team' && teamIds.length > 0) { + const result = await pg + .deleteFrom('FeatureFlagOwner') + .where('featureFlagId', '=', featureFlagId) + .where('teamId', 'in', teamIds) + .executeTakeFirst() + deletedCount = Number(result?.numDeletedRows ?? 0) + } else if (scope === 'Organization' && orgIds.length > 0) { + const result = await pg + .deleteFrom('FeatureFlagOwner') + .where('featureFlagId', '=', featureFlagId) + .where('orgId', 'in', orgIds) + .executeTakeFirst() + deletedCount = Number(result?.numDeletedRows ?? 0) + } + + if (deletedCount === 0) { + return standardError( + new Error('No feature flag owners were removed. Check the scope and subjects provided.') + ) + } + + return { + featureFlagId, + removedCount: deletedCount, + userIds: scope === 'User' ? userIds : null, + teamIds: scope === 'Team' ? teamIds : null, + orgIds: scope === 'Organization' ? orgIds : null + } +} + +export default removeFeatureFlagOwner diff --git a/packages/server/graphql/private/typeDefs/Mutation.graphql b/packages/server/graphql/private/typeDefs/Mutation.graphql index f27c8f308a8..cc152270cbf 100644 --- a/packages/server/graphql/private/typeDefs/Mutation.graphql +++ b/packages/server/graphql/private/typeDefs/Mutation.graphql @@ -38,6 +38,20 @@ type Mutation { subjects: SubjectsInput! ): ApplyFeatureFlagPayload! + """ + Remove a feature flag from specified subjects (users, teams, or organizations) + """ + removeFeatureFlagOwner( + """ + The name of the feature flag to remove + """ + flagName: String! + """ + The subjects from which the feature flag will be removed + """ + subjects: SubjectsInput! + ): RemoveFeatureFlagOwnerPayload! + """ Delete an existing feature flag """ diff --git a/packages/server/graphql/private/typeDefs/RemoveFeatureFlagOwnerSuccess.graphql b/packages/server/graphql/private/typeDefs/RemoveFeatureFlagOwnerSuccess.graphql new file mode 100644 index 00000000000..49e93ea33a9 --- /dev/null +++ b/packages/server/graphql/private/typeDefs/RemoveFeatureFlagOwnerSuccess.graphql @@ -0,0 +1,25 @@ +""" +Successful result of removing a feature flag owner +""" +type RemoveFeatureFlagOwnerSuccess { + """ + The feature flag that was removed + """ + featureFlag: FeatureFlag! + """ + The feature flag was removed from the following users + """ + users: [User!] + """ + The feature flag was removed from the following teams + """ + teams: [Team!] + """ + The feature flag was removed from the following organizations + """ + organizations: [Organization!] + """ + The number of subjects the feature flag was removed from + """ + removedCount: Int! +} diff --git a/packages/server/graphql/private/typeDefs/RemoveFeatureFlagPayload.graphql b/packages/server/graphql/private/typeDefs/RemoveFeatureFlagPayload.graphql new file mode 100644 index 00000000000..17780293b6b --- /dev/null +++ b/packages/server/graphql/private/typeDefs/RemoveFeatureFlagPayload.graphql @@ -0,0 +1,4 @@ +""" +Return value for removeFeatureFlagOwner, which could be an error or success +""" +union RemoveFeatureFlagOwnerPayload = ErrorPayload | RemoveFeatureFlagOwnerSuccess diff --git a/packages/server/graphql/private/types/RemoveFeatureFlagOwnerSuccess.ts b/packages/server/graphql/private/types/RemoveFeatureFlagOwnerSuccess.ts new file mode 100644 index 00000000000..085ff34f6bf --- /dev/null +++ b/packages/server/graphql/private/types/RemoveFeatureFlagOwnerSuccess.ts @@ -0,0 +1,31 @@ +import isValid from '../../isValid' +import {RemoveFeatureFlagOwnerSuccessResolvers} from '../resolverTypes' + +export type RemoveFeatureFlagOwnerSuccessSource = { + featureFlagId: string + userIds: string[] | null + teamIds: string[] | null + orgIds: string[] | null + removedCount: number +} + +const RemoveFeatureFlagOwnerSuccess: RemoveFeatureFlagOwnerSuccessResolvers = { + featureFlag: async ({featureFlagId}, _args, {dataLoader}) => { + return dataLoader.get('featureFlags').loadNonNull(featureFlagId) + }, + users: async ({userIds}, _args, {dataLoader}) => { + if (!userIds) return null + return (await dataLoader.get('users').loadMany(userIds)).filter(isValid) + }, + teams: async ({teamIds}, _args, {dataLoader}) => { + if (!teamIds) return null + return (await dataLoader.get('teams').loadMany(teamIds)).filter(isValid) + }, + organizations: async ({orgIds}, _args, {dataLoader}) => { + if (!orgIds) return null + return (await dataLoader.get('organizations').loadMany(orgIds)).filter(isValid) + }, + removedCount: ({removedCount}) => removedCount +} + +export default RemoveFeatureFlagOwnerSuccess From ca069dbd97e3f33e81983298676aea748eda0664 Mon Sep 17 00:00:00 2001 From: Bruce Tian Date: Wed, 16 Oct 2024 14:47:53 -0700 Subject: [PATCH 527/529] feat(orgAdmin): Add org admin teaser in org team page for non-enterprise orgs (#10253) --- .../components/OrgTeams/OrgTeams.tsx | 38 +++++++++----- .../components/OrgTeams/TeaserOrgTeamsRow.tsx | 50 +++++++++++++++++++ .../public/typeDefs/Organization.graphql | 5 ++ .../graphql/public/types/Organization.ts | 5 ++ 4 files changed, 85 insertions(+), 13 deletions(-) create mode 100644 packages/client/modules/userDashboard/components/OrgTeams/TeaserOrgTeamsRow.tsx diff --git a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx index 062277dee6c..e16c2371d6a 100644 --- a/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx +++ b/packages/client/modules/userDashboard/components/OrgTeams/OrgTeams.tsx @@ -7,6 +7,7 @@ import {Button} from '../../../../ui/Button/Button' import {useDialogState} from '../../../../ui/Dialog/useDialogState' import plural from '../../../../utils/plural' import OrgTeamsRow from './OrgTeamsRow' +import TeaserOrgTeamsRow from './TeaserOrgTeamsRow' type Props = { organizationRef: OrgTeams_organization$key @@ -18,12 +19,16 @@ const OrgTeams = (props: Props) => { graphql` fragment OrgTeams_organization on Organization { id - isOrgAdmin + tier hasPublicTeamsFlag: featureFlag(featureName: "publicTeams") allTeams { id ...OrgTeamsRow_team } + viewerTeams { + id + } + allTeamsCount } `, organizationRef @@ -35,18 +40,15 @@ const OrgTeams = (props: Props) => { isOpen: isAddTeamDialogOpened } = useDialogState() - const {allTeams, isOrgAdmin, hasPublicTeamsFlag} = organization - const showAllTeams = isOrgAdmin || hasPublicTeamsFlag + const {allTeams, tier, viewerTeams, allTeamsCount, hasPublicTeamsFlag} = organization + const showAllTeams = allTeams.length === allTeamsCount || hasPublicTeamsFlag + const viewerTeamCount = viewerTeams.length + return (

Teams

-

- {!showAllTeams - ? "Only showing teams you're a member of" - : 'Showing all teams in the organization'} -

-
+
- {allTeams.length} {plural(allTeams.length, 'Team')} + {allTeamsCount} {plural(allTeamsCount, 'Team')}{' '} + {!showAllTeams ? `(${allTeamsCount - viewerTeamCount} hidden)` : null}
- {allTeams.map((team) => ( - - ))} +
+ {allTeams.map((team) => ( + + ))} +
+ + {tier !== 'enterprise' && allTeamsCount > viewerTeamCount && !showAllTeams && ( + + )}
{isAddTeamDialogOpened ? ( diff --git a/packages/client/modules/userDashboard/components/OrgTeams/TeaserOrgTeamsRow.tsx b/packages/client/modules/userDashboard/components/OrgTeams/TeaserOrgTeamsRow.tsx new file mode 100644 index 00000000000..7edbe7290b3 --- /dev/null +++ b/packages/client/modules/userDashboard/components/OrgTeams/TeaserOrgTeamsRow.tsx @@ -0,0 +1,50 @@ +import {Lock} from '@mui/icons-material' +import React from 'react' +import {useHistory} from 'react-router' +import plural from '../../../../utils/plural' + +type Props = { + hiddenTeamCount: number + orgId: string +} + +const TeaserOrgTeamsRow = (props: Props) => { + const {hiddenTeamCount, orgId} = props + const history = useHistory() + + const handleParabolEnterpriseClick = () => { + history.push(`/me/organizations/${orgId}/billing`) + } + + return ( +
+
+
+
+ {hiddenTeamCount} {plural(hiddenTeamCount, 'Hidden Team')} +
+
+
+ + Parabol Enterprise + {' '} + includes our Org Admin role, which allows you to see all teams in + your organization. +
+
+
+
+
+ +
+
+
+
+ ) +} + +export default TeaserOrgTeamsRow diff --git a/packages/server/graphql/public/typeDefs/Organization.graphql b/packages/server/graphql/public/typeDefs/Organization.graphql index 2c178205380..85fe5a69614 100644 --- a/packages/server/graphql/public/typeDefs/Organization.graphql +++ b/packages/server/graphql/public/typeDefs/Organization.graphql @@ -52,6 +52,11 @@ type Organization { """ allTeams: [Team!]! + """ + The count of all teams in the organization + """ + allTeamsCount: Int! + """ all the teams the viewer is on in the organization """ diff --git a/packages/server/graphql/public/types/Organization.ts b/packages/server/graphql/public/types/Organization.ts index a6f8bb007da..b705d1c355d 100644 --- a/packages/server/graphql/public/types/Organization.ts +++ b/packages/server/graphql/public/types/Organization.ts @@ -73,6 +73,11 @@ const Organization: OrganizationResolvers = { } }, + allTeamsCount: async ({id: orgId}, _args, {dataLoader}) => { + const allTeamsOnOrg = await dataLoader.get('teamsByOrgIds').load(orgId) + return allTeamsOnOrg?.length ?? 0 + }, + viewerTeams: async ({id: orgId}, _args, {dataLoader, authToken}) => { const allTeamsOnOrg = await dataLoader.get('teamsByOrgIds').load(orgId) return allTeamsOnOrg From 7965ab691f3d4dc9599d9b958ecebc2c46649fad Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Wed, 16 Oct 2024 16:11:25 -0700 Subject: [PATCH 528/529] chore(rethinkdb): Task: Phase 3 (#10339) Signed-off-by: Matt Krick --- codegen.json | 3 +- .../components/TaskIntegrationMenuItem.tsx | 2 +- .../modules/demo/ClientGraphQLServer.ts | 2 +- packages/client/modules/demo/initDB.ts | 2 +- .../mutations/BatchArchiveTasksMutation.ts | 2 +- .../client/mutations/UpdateTaskMutation.ts | 2 +- .../mutations/handlers/handleRemoveTasks.ts | 2 +- .../client/shared/gqlIds/IntegrationHash.ts | 5 +- packages/server/database/rethinkDriver.ts | 12 - .../database/types/BaseTaskIntegration.ts | 29 -- .../server/database/types/ImportedTask.ts | 30 -- packages/server/database/types/Task.ts | 114 ------ .../types/TaskIntegrationAzureDevOps.ts | 22 -- .../database/types/TaskIntegrationGitHub.ts | 19 - .../database/types/TaskIntegrationGitLab.ts | 19 - .../database/types/TaskIntegrationJira.ts | 23 -- .../types/TaskIntegrationJiraServer.ts | 22 -- .../RethinkForeignKeyLoaderMaker.ts | 14 - packages/server/dataloader/RootDataLoader.ts | 19 +- .../server/dataloader/customLoaderMakers.ts | 82 ++--- .../dataloader/foreignKeyLoaderMakers.ts | 16 +- .../dataloader/primaryKeyLoaderMakers.ts | 2 +- .../dataloader/rethinkForeignKeyLoader.ts | 25 -- .../rethinkForeignKeyLoaderMakers.ts | 32 -- .../rethinkPrimaryKeyLoaderMakers.ts | 1 - .../graphql/mutations/changeTaskTeam.ts | 45 +-- .../server/graphql/mutations/createTask.ts | 31 +- .../mutations/createTaskIntegration.ts | 13 - .../server/graphql/mutations/deleteTask.ts | 5 - .../server/graphql/mutations/endCheckIn.ts | 67 ++-- .../graphql/mutations/endSprintPoker.ts | 2 +- .../graphql/mutations/helpers/addSeedTasks.ts | 8 +- .../mutations/helpers/createTaskInService.ts | 26 +- .../mutations/helpers/importTasksForPoker.ts | 58 +-- .../helpers/publishChangeNotifications.ts | 2 +- .../mutations/helpers/pushEstimateToGitHub.ts | 2 +- .../mutations/helpers/pushEstimateToGitLab.ts | 2 +- .../mutations/helpers/removeEmptyTasks.ts | 30 +- .../mutations/helpers/removeTeamMember.ts | 79 ++-- .../mutations/helpers/safeEndRetrospective.ts | 17 +- .../resetRetroMeetingToGroupStage.ts | 48 ++- .../graphql/mutations/setTaskEstimate.ts | 3 +- .../graphql/mutations/updatePokerScope.ts | 2 +- .../server/graphql/mutations/updateTask.ts | 45 +-- .../graphql/mutations/updateTaskDueDate.ts | 11 - .../private/mutations/hardDeleteUser.ts | 8 +- .../public/mutations/batchArchiveTasks.ts | 2 +- .../public/types/ActionMeetingMember.ts | 17 +- .../graphql/public/types/EstimateStage.ts | 14 +- packages/server/graphql/public/types/Task.ts | 229 ++++++++++++ .../graphql/public/types/TaskEstimate.ts | 7 + .../graphql/public/types/TeamPromptMeeting.ts | 16 +- packages/server/graphql/public/types/User.ts | 59 ++- .../queries/helpers/connectionFromTasks.ts | 2 +- packages/server/graphql/resolvers.ts | 3 +- .../resolvers/resolveThreadableConnection.ts | 7 +- .../server/graphql/types/EndCheckInPayload.ts | 4 +- packages/server/graphql/types/PokerMeeting.ts | 2 +- packages/server/graphql/types/Task.ts | 339 +----------------- packages/server/graphql/types/TaskEstimate.ts | 57 --- packages/server/graphql/types/Team.ts | 3 +- .../TaskIntegrationManagerFactory.ts | 4 +- packages/server/postgres/select.ts | 7 +- .../postgres/types/TaskIntegration.d.ts | 2 + packages/server/postgres/types/index.d.ts | 6 + .../server/safeMutations/archiveTasksForDB.ts | 21 +- packages/server/utils/analytics/analytics.ts | 2 +- packages/server/utils/filterTasksByMeeting.ts | 2 +- 68 files changed, 546 insertions(+), 1264 deletions(-) delete mode 100644 packages/server/database/types/BaseTaskIntegration.ts delete mode 100644 packages/server/database/types/ImportedTask.ts delete mode 100644 packages/server/database/types/Task.ts delete mode 100644 packages/server/database/types/TaskIntegrationAzureDevOps.ts delete mode 100644 packages/server/database/types/TaskIntegrationGitHub.ts delete mode 100644 packages/server/database/types/TaskIntegrationGitLab.ts delete mode 100644 packages/server/database/types/TaskIntegrationJira.ts delete mode 100644 packages/server/database/types/TaskIntegrationJiraServer.ts delete mode 100644 packages/server/dataloader/RethinkForeignKeyLoaderMaker.ts delete mode 100644 packages/server/dataloader/rethinkForeignKeyLoader.ts delete mode 100644 packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts create mode 100644 packages/server/graphql/public/types/Task.ts create mode 100644 packages/server/graphql/public/types/TaskEstimate.ts delete mode 100644 packages/server/graphql/types/TaskEstimate.ts diff --git a/codegen.json b/codegen.json index fc47d10aa3d..7eab6a1c503 100644 --- a/codegen.json +++ b/codegen.json @@ -51,6 +51,7 @@ "config": { "contextType": "../graphql#GQLContext", "mappers": { + "TaskEstimate": "../../postgres/types/index#TaskEstimate", "ReflectTemplatePromptUpdateDescriptionPayload": "./types/ReflectTemplatePromptUpdateDescriptionPayload#ReflectTemplatePromptUpdateDescriptionPayloadSource", "NewFeatureBroadcast": "../../postgres/types/index#NewFeature", "ReflectTemplatePromptUpdateGroupColorPayload": "./types/ReflectTemplatePromptUpdateGroupColorPayload#ReflectTemplatePromptUpdateGroupColorPayloadSource", @@ -177,7 +178,7 @@ "StartRetrospectiveSuccess": "./types/StartRetrospectiveSuccess#StartRetrospectiveSuccessSource", "StartTeamPromptSuccess": "./types/StartTeamPromptSuccess#StartTeamPromptSuccessSource", "StripeFailPaymentPayload": "./types/StripeFailPaymentPayload#StripeFailPaymentPayloadSource", - "Task": "../../database/types/Task#default", + "Task": "../../postgres/types/index#Task as TaskDB", "Team": "../../postgres/types/index#Team as TeamDB", "TeamHealthPhase": "./types/TeamHealthPhase#TeamHealthPhaseSource", "TeamHealthStage": "./types/TeamHealthStage#TeamHealthStageSource", diff --git a/packages/client/components/TaskIntegrationMenuItem.tsx b/packages/client/components/TaskIntegrationMenuItem.tsx index c8212b3b687..8e4b47c9ec8 100644 --- a/packages/client/components/TaskIntegrationMenuItem.tsx +++ b/packages/client/components/TaskIntegrationMenuItem.tsx @@ -1,5 +1,5 @@ import React, {forwardRef} from 'react' -import {TaskServiceEnum} from '~/../server/database/types/Task' +import {TaskServiceEnum} from '../__generated__/CreateTaskMutation.graphql' import AzureDevOpsSVG from './AzureDevOpsSVG' import GitHubSVG from './GitHubSVG' import GitLabSVG from './GitLabSVG' diff --git a/packages/client/modules/demo/ClientGraphQLServer.ts b/packages/client/modules/demo/ClientGraphQLServer.ts index 4be77ecd626..0351e4381dc 100644 --- a/packages/client/modules/demo/ClientGraphQLServer.ts +++ b/packages/client/modules/demo/ClientGraphQLServer.ts @@ -10,13 +10,13 @@ import {DragReflectionDropTargetTypeEnum} from '~/__generated__/EndDraggingRefle import {PALETTE} from '~/styles/paletteV3' import GoogleAnalyzedEntity from '../../../server/database/types/GoogleAnalyzedEntity' import ReflectPhase from '../../../server/database/types/ReflectPhase' -import ITask from '../../../server/database/types/Task' import {NewMeetingStage} from '../../../server/graphql/private/resolverTypes' import { DiscussPhase, DiscussStage, NewMeetingPhase } from '../../../server/postgres/types/NewMeetingPhase' +import {Task as ITask} from '../../../server/postgres/types/index.d' import { ExternalLinks, MeetingSettingsThreshold, diff --git a/packages/client/modules/demo/initDB.ts b/packages/client/modules/demo/initDB.ts index 4b7d95391ee..7744fcc783d 100644 --- a/packages/client/modules/demo/initDB.ts +++ b/packages/client/modules/demo/initDB.ts @@ -1,6 +1,6 @@ import {SlackNotificationEventEnum} from '~/__generated__/SlackNotificationList_viewer.graphql' import {PALETTE} from '~/styles/paletteV3' -import ITask from '../../../server/database/types/Task' +import {Task as ITask} from '../../../server/postgres/types/index.d' import {RetrospectiveMeeting} from '../../../server/postgres/types/Meeting' import JiraProjectId from '../../shared/gqlIds/JiraProjectId' import demoUserAvatar from '../../styles/theme/images/avatar-user.svg' diff --git a/packages/client/mutations/BatchArchiveTasksMutation.ts b/packages/client/mutations/BatchArchiveTasksMutation.ts index 4963f72303f..f95935aa9ec 100644 --- a/packages/client/mutations/BatchArchiveTasksMutation.ts +++ b/packages/client/mutations/BatchArchiveTasksMutation.ts @@ -1,7 +1,7 @@ import graphql from 'babel-plugin-relay/macro' import {commitMutation} from 'react-relay' import {BatchArchiveTasksMutation_tasks$data} from '~/__generated__/BatchArchiveTasksMutation_tasks.graphql' -import ITask from '../../server/database/types/Task' +import {Task as ITask} from '../../server/postgres/types/index.d' import {BatchArchiveTasksMutation as TBatchArchiveTasksMutation} from '../__generated__/BatchArchiveTasksMutation.graphql' import {SharedUpdater, StandardMutation} from '../types/relayMutations' import getTagsFromEntityMap from '../utils/draftjs/getTagsFromEntityMap' diff --git a/packages/client/mutations/UpdateTaskMutation.ts b/packages/client/mutations/UpdateTaskMutation.ts index d66706865bd..c9ae0540336 100644 --- a/packages/client/mutations/UpdateTaskMutation.ts +++ b/packages/client/mutations/UpdateTaskMutation.ts @@ -1,6 +1,6 @@ import graphql from 'babel-plugin-relay/macro' import {commitMutation} from 'react-relay' -import ITask from '../../server/database/types/Task' +import {Task as ITask} from '../../server/postgres/types/index.d' import {UpdateTaskMutation as TUpdateTaskMutation} from '../__generated__/UpdateTaskMutation.graphql' import {UpdateTaskMutation_task$data} from '../__generated__/UpdateTaskMutation_task.graphql' import { diff --git a/packages/client/mutations/handlers/handleRemoveTasks.ts b/packages/client/mutations/handlers/handleRemoveTasks.ts index 3c517948455..1ee5667aad8 100644 --- a/packages/client/mutations/handlers/handleRemoveTasks.ts +++ b/packages/client/mutations/handlers/handleRemoveTasks.ts @@ -2,8 +2,8 @@ import {RecordSourceSelectorProxy} from 'relay-runtime' import {handleRemoveReply} from '~/mutations/DeleteCommentMutation' import getDiscussionThreadConn from '~/mutations/connections/getDiscussionThreadConn' import {parseQueryParams} from '~/utils/useQueryParameterParser' -import ITask from '../../../server/database/types/Task' import IUser from '../../../server/database/types/User' +import {Task as ITask} from '../../../server/postgres/types/index.d' import safeRemoveNodeFromArray from '../../utils/relay/safeRemoveNodeFromArray' import safeRemoveNodeFromConn from '../../utils/relay/safeRemoveNodeFromConn' import getArchivedTasksConn from '../connections/getArchivedTasksConn' diff --git a/packages/client/shared/gqlIds/IntegrationHash.ts b/packages/client/shared/gqlIds/IntegrationHash.ts index ec3b33ae0c6..4ad7f2ed493 100644 --- a/packages/client/shared/gqlIds/IntegrationHash.ts +++ b/packages/client/shared/gqlIds/IntegrationHash.ts @@ -1,4 +1,5 @@ -import {TaskIntegration, TaskServiceEnum} from '../../../server/database/types/Task' +import {AnyTaskIntegration} from '../../../server/postgres/types/TaskIntegration' +import {TaskServiceEnum} from '../../__generated__/CreateTaskMutation.graphql' import AzureDevOpsIssueId from './AzureDevOpsIssueId' import GitHubIssueId from './GitHubIssueId' import GitLabIssueId from './GitLabIssueId' @@ -6,7 +7,7 @@ import JiraIssueId from './JiraIssueId' import JiraServerIssueId from './JiraServerIssueId' const IntegrationHash = { - join: (integration: TaskIntegration) => { + join: (integration: AnyTaskIntegration) => { switch (integration.service) { case 'github': return GitHubIssueId.join(integration.nameWithOwner, integration.issueNumber) diff --git a/packages/server/database/rethinkDriver.ts b/packages/server/database/rethinkDriver.ts index 649fd167cc7..e909172b381 100644 --- a/packages/server/database/rethinkDriver.ts +++ b/packages/server/database/rethinkDriver.ts @@ -11,7 +11,6 @@ import NotificationResponseReplied from './types/NotificationResponseReplied' import NotificationTaskInvolves from './types/NotificationTaskInvolves' import NotificationTeamArchived from './types/NotificationTeamArchived' import NotificationTeamInvitation from './types/NotificationTeamInvitation' -import Task from './types/Task' export type RethinkSchema = { Notification: { @@ -28,17 +27,6 @@ export type RethinkSchema = { | NotificationMentioned index: 'userId' } - Task: { - type: Task - index: - | 'integrationId' - | 'tags' - | 'teamId' - | 'teamIdUpdatedAt' - | 'discussionId' - | 'userId' - | 'integrationHash' - } } export type DBType = { diff --git a/packages/server/database/types/BaseTaskIntegration.ts b/packages/server/database/types/BaseTaskIntegration.ts deleted file mode 100644 index 859289051da..00000000000 --- a/packages/server/database/types/BaseTaskIntegration.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - The goal of a TaskIntegration is to store the smallest amount of information possible - in order to fetch the issue from the integration. - This includes: - - service: the name of the integration - - accessUserId: The user that first accessed the issue (we can use their accecss token) - Class extensions will probably include one or more of the following: - - tenant (cloudId, providerId, etc.) - - repo (repoName, project, etc.) - - issue (issueId, issueNumber, issueKey, etc.) - As a rule of thumb, if the property isn't required to fetch the issue, don't include it -*/ - -import {TaskServiceEnum} from './Task' - -interface Input { - accessUserId: string - service: TaskServiceEnum -} - -export default abstract class BaseTaskIntegration { - service: TaskServiceEnum - accessUserId: string - constructor(input: Input) { - const {accessUserId, service} = input - this.accessUserId = accessUserId - this.service = service - } -} diff --git a/packages/server/database/types/ImportedTask.ts b/packages/server/database/types/ImportedTask.ts deleted file mode 100644 index 89d687c295e..00000000000 --- a/packages/server/database/types/ImportedTask.ts +++ /dev/null @@ -1,30 +0,0 @@ -import IntegrationHash from 'parabol-client/shared/gqlIds/IntegrationHash' -import dndNoise from 'parabol-client/utils/dndNoise' -import convertToTaskContent from 'parabol-client/utils/draftjs/convertToTaskContent' -import Task, {TaskIntegration} from './Task' - -interface Input { - meetingId: string - teamId: string - integration: TaskIntegration - userId: string -} - -export default class ImportedTask extends Task { - integrationHash!: string - integration!: TaskIntegration - constructor(input: Input) { - const {meetingId, teamId, integration, userId} = input - super({ - content: convertToTaskContent(`Task imported from ${integration.service} #archived`), - createdBy: userId, - meetingId, - sortOrder: dndNoise(), - status: 'future', - teamId, - integrationHash: IntegrationHash.join(integration), - integration, - userId - }) - } -} diff --git a/packages/server/database/types/Task.ts b/packages/server/database/types/Task.ts deleted file mode 100644 index 44d795cf3b3..00000000000 --- a/packages/server/database/types/Task.ts +++ /dev/null @@ -1,114 +0,0 @@ -import dndNoise from 'parabol-client/utils/dndNoise' -import extractTextFromDraftString from 'parabol-client/utils/draftjs/extractTextFromDraftString' -import getTagsFromEntityMap from 'parabol-client/utils/draftjs/getTagsFromEntityMap' -import generateUID from '../../generateUID' -import TaskIntegrationAzureDevOps from './TaskIntegrationAzureDevOps' -import TaskIntegrationGitHub from './TaskIntegrationGitHub' -import TaskIntegrationGitLab from './TaskIntegrationGitLab' -import TaskIntegrationJira from './TaskIntegrationJira' -import TaskIntegrationJiraServer from './TaskIntegrationJiraServer' - -export type AreaEnum = 'meeting' | 'teamDash' | 'userDash' -export type TaskStatusEnum = 'active' | 'stuck' | 'done' | 'future' -export type TaskTagEnum = 'private' | 'archived' -export type TaskServiceEnum = - | 'PARABOL' - | 'github' - | 'jira' - | 'jiraServer' - | 'gitlab' - | 'azureDevOps' - -export type TaskIntegration = - | TaskIntegrationJira - | TaskIntegrationGitHub - | TaskIntegrationJiraServer - | TaskIntegrationGitLab - | TaskIntegrationAzureDevOps -export interface TaskInput { - id?: string - content: string - createdAt?: Date | null - createdBy: string - doneMeetingId?: string - dueDate?: Date | null - integration?: TaskIntegration - - integrationHash?: string - meetingId?: string | null - plaintextContent?: string - sortOrder?: number | null - status: TaskStatusEnum - teamId: string - discussionId?: string | null - threadParentId?: string | null - threadSortOrder?: number | null - updatedAt?: Date - userId?: string | null -} - -export default class Task { - id: string - content: string - createdAt: Date - createdBy: string - doneMeetingId?: string - dueDate?: Date | null - integration?: TaskIntegration - integrationHash?: string - meetingId?: string - plaintextContent: string - sortOrder: number - status: TaskStatusEnum - tags: TaskTagEnum[] - teamId: string - discussionId?: string - threadParentId?: string - threadSortOrder?: number | null - updatedAt: Date - userId: string | null - - constructor(input: TaskInput) { - const { - id, - userId, - meetingId, - teamId, - content, - createdAt, - createdBy, - doneMeetingId, - dueDate, - integration, - integrationHash, - plaintextContent, - sortOrder, - status, - threadParentId, - threadSortOrder, - discussionId, - updatedAt - } = input - const {entityMap} = JSON.parse(content) - const tags = getTagsFromEntityMap(entityMap) - this.id = id || generateUID() - this.discussionId = discussionId || undefined - this.content = content - this.createdAt = createdAt || new Date() - this.createdBy = createdBy - this.doneMeetingId = doneMeetingId - this.dueDate = dueDate || undefined - this.integration = integration || undefined - this.integrationHash = integrationHash - this.meetingId = meetingId || undefined - this.plaintextContent = plaintextContent || extractTextFromDraftString(content) - this.sortOrder = sortOrder || dndNoise() - this.status = status - this.tags = tags - this.teamId = teamId - this.threadSortOrder = threadSortOrder || undefined - this.threadParentId = threadParentId || undefined - this.updatedAt = updatedAt || new Date() - this.userId = userId || null - } -} diff --git a/packages/server/database/types/TaskIntegrationAzureDevOps.ts b/packages/server/database/types/TaskIntegrationAzureDevOps.ts deleted file mode 100644 index 9f0804e426e..00000000000 --- a/packages/server/database/types/TaskIntegrationAzureDevOps.ts +++ /dev/null @@ -1,22 +0,0 @@ -import BaseTaskIntegration from './BaseTaskIntegration' - -interface Input { - accessUserId: string - instanceId: string - projectKey: string - issueKey: string -} - -export default class TaskIntegrationAzureDevOps extends BaseTaskIntegration { - instanceId: string - issueKey: string - projectKey: string - service!: 'azureDevOps' - constructor(input: Input) { - const {accessUserId, instanceId, projectKey, issueKey} = input - super({accessUserId, service: 'azureDevOps'}) - this.projectKey = projectKey - this.instanceId = instanceId - this.issueKey = issueKey - } -} diff --git a/packages/server/database/types/TaskIntegrationGitHub.ts b/packages/server/database/types/TaskIntegrationGitHub.ts deleted file mode 100644 index 7942b147066..00000000000 --- a/packages/server/database/types/TaskIntegrationGitHub.ts +++ /dev/null @@ -1,19 +0,0 @@ -import BaseTaskIntegration from './BaseTaskIntegration' - -interface Input { - accessUserId: string - nameWithOwner: string - issueNumber: number -} - -export default class TaskIntegrationGitHub extends BaseTaskIntegration { - nameWithOwner: string - issueNumber: number - service!: 'github' - constructor(input: Input) { - const {accessUserId, nameWithOwner, issueNumber} = input - super({accessUserId, service: 'github'}) - this.nameWithOwner = nameWithOwner - this.issueNumber = issueNumber - } -} diff --git a/packages/server/database/types/TaskIntegrationGitLab.ts b/packages/server/database/types/TaskIntegrationGitLab.ts deleted file mode 100644 index b21840c9d75..00000000000 --- a/packages/server/database/types/TaskIntegrationGitLab.ts +++ /dev/null @@ -1,19 +0,0 @@ -import BaseTaskIntegration from './BaseTaskIntegration' - -interface Input { - accessUserId: string - providerId: string - gid: string -} - -export default class TaskIntegrationGitLab extends BaseTaskIntegration { - providerId: string - gid: string - service!: 'gitlab' - constructor(input: Input) { - const {accessUserId, providerId, gid} = input - super({accessUserId, service: 'gitlab'}) - this.providerId = providerId - this.gid = gid - } -} diff --git a/packages/server/database/types/TaskIntegrationJira.ts b/packages/server/database/types/TaskIntegrationJira.ts deleted file mode 100644 index 43872ef499c..00000000000 --- a/packages/server/database/types/TaskIntegrationJira.ts +++ /dev/null @@ -1,23 +0,0 @@ -import JiraProjectKeyId from '../../../client/shared/gqlIds/JiraProjectKeyId' -import BaseTaskIntegration from './BaseTaskIntegration' - -interface Input { - accessUserId: string - cloudId: string - issueKey: string -} - -export default class TaskIntegrationJira extends BaseTaskIntegration { - cloudId: string - issueKey: string - projectKey: string - service!: 'jira' - constructor(input: Input) { - const {accessUserId, cloudId, issueKey} = input - super({accessUserId, service: 'jira'}) - const projectKey = JiraProjectKeyId.join(issueKey) - this.projectKey = projectKey - this.cloudId = cloudId - this.issueKey = issueKey - } -} diff --git a/packages/server/database/types/TaskIntegrationJiraServer.ts b/packages/server/database/types/TaskIntegrationJiraServer.ts deleted file mode 100644 index 54cc6bc24a7..00000000000 --- a/packages/server/database/types/TaskIntegrationJiraServer.ts +++ /dev/null @@ -1,22 +0,0 @@ -import BaseTaskIntegration from './BaseTaskIntegration' - -interface Input { - accessUserId: string - providerId: number - issueId: string - repositoryId: string -} - -export default class TaskIntegrationJiraServer extends BaseTaskIntegration { - providerId: number - issueId: string - repositoryId: string - service!: 'jiraServer' - constructor(input: Input) { - const {accessUserId, providerId, issueId, repositoryId} = input - super({accessUserId, service: 'jiraServer'}) - this.providerId = providerId - this.issueId = issueId - this.repositoryId = repositoryId - } -} diff --git a/packages/server/dataloader/RethinkForeignKeyLoaderMaker.ts b/packages/server/dataloader/RethinkForeignKeyLoaderMaker.ts deleted file mode 100644 index 72570f2644e..00000000000 --- a/packages/server/dataloader/RethinkForeignKeyLoaderMaker.ts +++ /dev/null @@ -1,14 +0,0 @@ -import * as rethinkPrimaryKeyLoaderMakers from './rethinkPrimaryKeyLoaderMakers' - -/** - * Load rethink entities by foreign key - */ -export default class RethinkForeignKeyLodaerMaker< - T extends keyof typeof rethinkPrimaryKeyLoaderMakers -> { - constructor( - public pk: T, - public field: string, - public fetch: (ids: readonly string[]) => Promise - ) {} -} diff --git a/packages/server/dataloader/RootDataLoader.ts b/packages/server/dataloader/RootDataLoader.ts index 039146acd95..9bf6bd6a34e 100644 --- a/packages/server/dataloader/RootDataLoader.ts +++ b/packages/server/dataloader/RootDataLoader.ts @@ -1,5 +1,4 @@ import DataLoader from 'dataloader' -import RethinkForeignKeyLoaderMaker from './RethinkForeignKeyLoaderMaker' import RethinkPrimaryKeyLoaderMaker from './RethinkPrimaryKeyLoaderMaker' import * as atlassianLoaders from './atlassianLoaders' import * as azureDevOpsLoaders from './azureDevOpsLoaders' @@ -12,8 +11,6 @@ import * as integrationAuthLoaders from './integrationAuthLoaders' import * as jiraServerLoaders from './jiraServerLoaders' import * as pollLoaders from './pollsLoaders' import * as primaryKeyLoaderMakers from './primaryKeyLoaderMakers' -import rethinkForeignKeyLoader from './rethinkForeignKeyLoader' -import * as rethinkForeignKeyLoaderMakers from './rethinkForeignKeyLoaderMakers' import rethinkPrimaryKeyLoader from './rethinkPrimaryKeyLoader' import * as rethinkPrimaryKeyLoaderMakers from './rethinkPrimaryKeyLoaderMakers' @@ -23,7 +20,6 @@ interface LoaderDict { // Register all loaders const loaderMakers = { - ...rethinkForeignKeyLoaderMakers, ...rethinkPrimaryKeyLoaderMakers, ...primaryKeyLoaderMakers, ...foreignKeyLoaderMakers, @@ -56,17 +52,7 @@ interface GenericDataLoader { : // can delete below this line after RethinkDB is gone Loader extends RethinkPrimaryKeyLoaderMaker ? ReturnType> - : Loader extends RethinkForeignKeyLoaderMaker - ? ReturnType< - typeof rethinkForeignKeyLoader< - (typeof rethinkPrimaryKeyLoaderMakers)[U] extends RethinkPrimaryKeyLoaderMaker< - infer V - > - ? V - : never - > - > - : never + : never } export type DataLoaderInstance = GenericDataLoader @@ -105,9 +91,6 @@ export default class RootDataLoader< if (loaderMaker instanceof RethinkPrimaryKeyLoaderMaker) { const {table} = loaderMaker loader = rethinkPrimaryKeyLoader(this.dataLoaderOptions, table) - } else if (loaderMaker instanceof RethinkForeignKeyLoaderMaker) { - const {fetch, field, pk} = loaderMaker - loader = rethinkForeignKeyLoader(this, dependsOn, pk, field, fetch) } else { loader = (loaderMaker as any)(this, dependsOn) } diff --git a/packages/server/dataloader/customLoaderMakers.ts b/packages/server/dataloader/customLoaderMakers.ts index 86cabe39472..20ce92a0f0c 100644 --- a/packages/server/dataloader/customLoaderMakers.ts +++ b/packages/server/dataloader/customLoaderMakers.ts @@ -1,10 +1,7 @@ import DataLoader from 'dataloader' import {Selectable, SqlBool, sql} from 'kysely' import {PARABOL_AI_USER_ID} from '../../client/utils/constants' -import getRethink from '../database/rethinkDriver' -import {RDatum} from '../database/stricterR' import MeetingTemplate from '../database/types/MeetingTemplate' -import Task, {TaskStatusEnum} from '../database/types/Task' import getFileStoreManager from '../fileStorage/getFileStoreManager' import {ReactableEnum} from '../graphql/public/resolverTypes' import {SAMLSource} from '../graphql/public/types/SAML' @@ -24,8 +21,13 @@ import getLatestTaskEstimates from '../postgres/queries/getLatestTaskEstimates' import getMeetingTaskEstimates, { MeetingTaskEstimatesResult } from '../postgres/queries/getMeetingTaskEstimates' -import {selectMeetingSettings, selectNewMeetings, selectTeams} from '../postgres/select' -import {Insight, MeetingSettings, OrganizationUser, Team} from '../postgres/types' +import { + selectMeetingSettings, + selectNewMeetings, + selectTasks, + selectTeams +} from '../postgres/select' +import {Insight, MeetingSettings, OrganizationUser, Task, Team} from '../postgres/types' import {AnyMeeting, MeetingTypeEnum} from '../postgres/types/Meeting' import {Logger} from '../utils/Logger' import getRedis from '../utils/getRedis' @@ -33,7 +35,6 @@ import isUserVerified from '../utils/isUserVerified' import NullableDataLoader from './NullableDataLoader' import RootDataLoader, {RegisterDependsOn} from './RootDataLoader' import normalizeArrayResults from './normalizeArrayResults' - export interface MeetingSettingsKey { teamId: string meetingType: MeetingTypeEnum @@ -55,7 +56,7 @@ export interface UserTasksKey { userIds: string[] teamIds: string[] archived?: boolean - statusFilters?: TaskStatusEnum[] | null + statusFilters?: Task['status'][] | null filterQuery?: string | null includeUnassigned?: boolean } @@ -130,7 +131,6 @@ export const userTasks = (parent: RootDataLoader, dependsOn: RegisterDependsOn) dependsOn('tasks') return new DataLoader( async (keys) => { - const r = await getRethink() const uniqKeys = [] as UserTasksKey[] const keySet = new Set() keys.forEach((key) => { @@ -154,42 +154,23 @@ export const userTasks = (parent: RootDataLoader, dependsOn: RegisterDependsOn) filterQuery, includeUnassigned } = key - const dbAfter = after ? new Date(after) : r.maxval - - let teamTaskPartial = r.table('Task').getAll(r.args(teamIds), {index: 'teamId'}) - if (userIds?.length) { - teamTaskPartial = teamTaskPartial.filter((row: RDatum) => - r(userIds).contains(row('userId')) - ) - } - if (statusFilters?.length) { - teamTaskPartial = teamTaskPartial.filter((row: RDatum) => - r(statusFilters).contains(row('status')) - ) - } - if (filterQuery) { - // TODO: deal with tags like #archived and #private. should strip out of plaintextContent?? - teamTaskPartial = teamTaskPartial.filter( - (row: RDatum) => row('plaintextContent').match(filterQuery) as any - ) - } - + const hasUserIds = userIds?.length > 0 + const hasStatusFilters = statusFilters ? statusFilters.length > 0 : false + const teamTasks = await selectTasks() + .where('teamId', 'in', teamIds) + .$if(hasUserIds, (qb) => qb.where('userId', 'in', userIds)) + .$if(hasStatusFilters, (qb) => qb.where('status', 'in', statusFilters!)) + .$if(!!filterQuery, (qb) => qb.where('plaintextContent', 'ilike', `%${filterQuery}%`)) + .$if(!!after, (qb) => qb.where('updatedAt', '<', after!)) + .$if(!!archived, (qb) => qb.where(sql`'archived' = ANY(tags)`)) + .$if(!archived, (qb) => qb.where(sql`'archived' != ALL(tags)`)) + .$if(!includeUnassigned, (qb) => qb.where('userId', 'is not', null)) + .orderBy('updatedAt desc') + .limit(first + 1) + .execute() return { key: serializeUserTasksKey(key), - data: await teamTaskPartial - .filter((task: RDatum) => task('updatedAt').lt(dbAfter)) - .filter((task: RDatum) => - archived - ? task('tags').contains('archived') - : task('tags').contains('archived').not() - ) - .filter((task: RDatum) => { - if (includeUnassigned) return true - return task('userId').ne(null) - }) - .orderBy(r.desc('updatedAt')) - .limit(first + 1) - .run() + data: teamTasks } }) ) @@ -535,16 +516,17 @@ export const taskIdsByTeamAndGitHubRepo = ( dependsOn('tasks') return new DataLoader<{teamId: string; nameWithOwner: string}, string[], string>( async (keys) => { - const r = await getRethink() const res = await Promise.all( - keys.map((key) => { + keys.map(async (key) => { const {teamId, nameWithOwner} = key - // This is very expensive! We should move tasks to PG ASAP - return r - .table('Task') - .getAll(teamId, {index: 'teamId'}) - .filter((row: RDatum) => row('integration')('nameWithOwner').eq(nameWithOwner))('id') - .run() + const res = await getKysely() + .selectFrom('Task') + .select('id') + .where('teamId', '=', teamId) + .where('integration', 'is not', null) + .where(sql`"integration"->>'nameWithOwner' = ${nameWithOwner}`) + .execute() + return res.map(({id}) => id) }) ) return res diff --git a/packages/server/dataloader/foreignKeyLoaderMakers.ts b/packages/server/dataloader/foreignKeyLoaderMakers.ts index 0c296d73386..eff5c1d3ab2 100644 --- a/packages/server/dataloader/foreignKeyLoaderMakers.ts +++ b/packages/server/dataloader/foreignKeyLoaderMakers.ts @@ -296,8 +296,8 @@ export const teamInvitationsByTeamId = foreignKeyLoaderMaker( } ) -export const _pgtasksByDiscussionId = foreignKeyLoaderMaker( - '_pgtasks', +export const tasksByDiscussionId = foreignKeyLoaderMaker( + 'tasks', 'discussionId', async (discusisonIds) => { // include archived cards in the conversation, since it's persistent @@ -305,10 +305,18 @@ export const _pgtasksByDiscussionId = foreignKeyLoaderMaker( } ) -export const _pgtasksByTeamId = foreignKeyLoaderMaker('_pgtasks', 'teamId', async (teamIds) => { +export const tasksByTeamId = foreignKeyLoaderMaker('tasks', 'teamId', async (teamIds) => { // waraning! contains private tasks return selectTasks() .where('teamId', 'in', teamIds) - .where(sql`'archived' = ANY(tags)`) + .where(sql`'archived' != ALL(tags)`) + .execute() +}) + +export const tasksByMeetingId = foreignKeyLoaderMaker('tasks', 'meetingId', async (meetingIds) => { + // waraning! contains private tasks + return selectTasks() + .where('meetingId', 'in', meetingIds) + .where(sql`'archived' != ALL(tags)`) .execute() }) diff --git a/packages/server/dataloader/primaryKeyLoaderMakers.ts b/packages/server/dataloader/primaryKeyLoaderMakers.ts index f53c962ba2d..47ee113669d 100644 --- a/packages/server/dataloader/primaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/primaryKeyLoaderMakers.ts @@ -146,6 +146,6 @@ export const teamInvitations = primaryKeyLoaderMaker((ids: readonly string[]) => return selectTeamInvitations().where('id', 'in', ids).execute() }) -export const _pgtasks = primaryKeyLoaderMaker((ids: readonly string[]) => { +export const tasks = primaryKeyLoaderMaker((ids: readonly string[]) => { return selectTasks().where('id', 'in', ids).execute() }) diff --git a/packages/server/dataloader/rethinkForeignKeyLoader.ts b/packages/server/dataloader/rethinkForeignKeyLoader.ts deleted file mode 100644 index dec36574113..00000000000 --- a/packages/server/dataloader/rethinkForeignKeyLoader.ts +++ /dev/null @@ -1,25 +0,0 @@ -import {DBType} from '../database/rethinkDriver' -import RootDataLoader, {RegisterDependsOn} from './RootDataLoader' -import UpdatableCacheDataLoader from './UpdatableCacheDataLoader' -import * as rethinkPrimaryKeyLoaderMakers from './rethinkPrimaryKeyLoaderMakers' - -const rethinkForeignKeyLoader = ( - parent: RootDataLoader, - dependsOn: RegisterDependsOn, - primaryKeyLoaderName: keyof typeof rethinkPrimaryKeyLoaderMakers, - field: string, - fetchFn: (ids: readonly string[]) => any[] | Promise -) => { - const standardLoader = parent.get(primaryKeyLoaderName) - dependsOn(primaryKeyLoaderName) - const batchFn = async (ids: readonly string[]) => { - const items = await fetchFn(ids) - items.forEach((item) => { - standardLoader.clear(item.id).prime(item.id, item) - }) - return ids.map((id) => items.filter((item) => item[field] === id)) - } - return new UpdatableCacheDataLoader(batchFn, {...parent.dataLoaderOptions}) -} - -export default rethinkForeignKeyLoader diff --git a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts b/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts deleted file mode 100644 index e7d6a9342b7..00000000000 --- a/packages/server/dataloader/rethinkForeignKeyLoaderMakers.ts +++ /dev/null @@ -1,32 +0,0 @@ -import getRethink from '../database/rethinkDriver' -import {RDatum} from '../database/stricterR' -import RethinkForeignKeyLoaderMaker from './RethinkForeignKeyLoaderMaker' - -export const tasksByDiscussionId = new RethinkForeignKeyLoaderMaker( - 'tasks', - 'discussionId', - async (discusisonIds) => { - const r = await getRethink() - return ( - r - .table('Task') - .getAll(r.args(discusisonIds), {index: 'discussionId'}) - // include archived cards in the conversation, since it's persistent - .run() - ) - } -) - -export const tasksByTeamId = new RethinkForeignKeyLoaderMaker( - 'tasks', - 'teamId', - async (teamIds) => { - const r = await getRethink() - // waraning! contains private tasks - return r - .table('Task') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((task: RDatum) => task('tags').contains('archived').not()) - .run() - } -) diff --git a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts index 973c2c959d0..ec24d52c90a 100644 --- a/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts +++ b/packages/server/dataloader/rethinkPrimaryKeyLoaderMakers.ts @@ -4,4 +4,3 @@ import RethinkPrimaryKeyLoaderMaker from './RethinkPrimaryKeyLoaderMaker' * all rethink dataloader types which also must exist in {@link rethinkDriver/RethinkSchema} */ export const notifications = new RethinkPrimaryKeyLoaderMaker('Notification') -export const tasks = new RethinkPrimaryKeyLoaderMaker('Task') diff --git a/packages/server/graphql/mutations/changeTaskTeam.ts b/packages/server/graphql/mutations/changeTaskTeam.ts index 8b7bc3566ec..0fc63d0302a 100644 --- a/packages/server/graphql/mutations/changeTaskTeam.ts +++ b/packages/server/graphql/mutations/changeTaskTeam.ts @@ -1,8 +1,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import removeEntityKeepText from 'parabol-client/utils/draftjs/removeEntityKeepText' -import getRethink from '../../database/rethinkDriver' -import Task from '../../database/types/Task' import getKysely from '../../postgres/getKysely' import {AtlassianAuth} from '../../postgres/queries/getAtlassianAuthByUserIdTeamId' import {GitHubAuth} from '../../postgres/queries/getGitHubAuthByUserIdTeamId' @@ -35,8 +33,6 @@ export default { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const pg = getKysely() - const r = await getRethink() - const now = new Date() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -136,35 +132,26 @@ export default { Boolean(userIdsOnlyOnOldTeam.find((userId) => userId === entity.data.userId)) const {rawContent: nextRawContent} = removeEntityKeepText(rawContent, eqFn) - const updates = { - content: rawContent === nextRawContent ? undefined : JSON.stringify(nextRawContent), - updatedAt: now, - teamId, - integration - } - // If there is a task with the same integration hash in the new team, then delete it first. // This is done so there are no duplicates and also solves the issue of the conflicting task being // private or archived. - const {deletedConflictingIntegrationTask} = await r({ - deletedConflictingIntegrationTask: - task.integrationHash && - r - .table('Task') - .getAll(task.integrationHash, {index: 'integrationHash'}) - .filter({teamId}) - .delete({returnChanges: true})('changes')(0)('old_val') - .default(null), - newTask: r.table('Task').get(taskId).update(updates) - }).run() if (task.integrationHash) { - await pg + const deletedTask = await pg .deleteFrom('Task') .where('integrationHash', '=', task.integrationHash) .where('teamId', '=', teamId) .limit(1) .returning('id') - .execute() + .executeTakeFirst() + if (deletedTask) { + const isPrivate = task.tags.includes('private') + const data = {task} + newTeamMembers.forEach(({userId}) => { + if (!isPrivate || userId === task.userId) { + publish(SubscriptionChannel.TASK, userId, 'DeleteTaskPayload', data, subOptions) + } + }) + } } await pg .updateTable('Task') @@ -175,16 +162,6 @@ export default { }) .where('id', '=', taskId) .executeTakeFirst() - if (deletedConflictingIntegrationTask) { - const task = deletedConflictingIntegrationTask as unknown as Task - const isPrivate = task.tags.includes('private') - const data = {task} - newTeamMembers.forEach(({userId}) => { - if (!isPrivate || userId === task.userId) { - publish(SubscriptionChannel.TASK, userId, 'DeleteTaskPayload', data, subOptions) - } - }) - } dataLoader.clearAll('tasks') const isPrivate = tags.includes('private') const data = {taskId} diff --git a/packages/server/graphql/mutations/createTask.ts b/packages/server/graphql/mutations/createTask.ts index 8d31cb3e193..79cc7b56371 100644 --- a/packages/server/graphql/mutations/createTask.ts +++ b/packages/server/graphql/mutations/createTask.ts @@ -4,11 +4,16 @@ import getTypeFromEntityMap from 'parabol-client/utils/draftjs/getTypeFromEntity import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' import MeetingMemberId from '../../../client/shared/gqlIds/MeetingMemberId' +import dndNoise from '../../../client/utils/dndNoise' +import extractTextFromDraftString from '../../../client/utils/draftjs/extractTextFromDraftString' +import getTagsFromEntityMap from '../../../client/utils/draftjs/getTagsFromEntityMap' import getRethink from '../../database/rethinkDriver' import NotificationTaskInvolves from '../../database/types/NotificationTaskInvolves' -import Task, {TaskServiceEnum} from '../../database/types/Task' +import generateUID from '../../generateUID' import updatePrevUsedRepoIntegrationsCache from '../../integrations/updatePrevUsedRepoIntegrationsCache' import getKysely from '../../postgres/getKysely' +import {Task, TaskTag} from '../../postgres/types/index.d' +import {TaskServiceEnum} from '../../postgres/types/TaskIntegration' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' import publish, {SubOptions} from '../../utils/publish' @@ -58,7 +63,7 @@ const validateTaskDiscussionId = async ( const handleAddTaskNotifications = async ( teamMembers: any[], - task: Task, + task: Pick, viewerId: string, teamId: string, subOptions: SubOptions @@ -154,7 +159,6 @@ export default { ) { const {authToken, dataLoader, socketId: mutatorId} = context const pg = getKysely() - const r = await getRethink() const operationId = dataLoader.share() const viewerId = getUserId(authToken) @@ -205,29 +209,26 @@ export default { if (integrationRepoId) { updatePrevUsedRepoIntegrationsCache(teamId, integrationRepoId, viewerId) } - const task = new Task({ + const task = { + id: generateUID(), content, + plaintextContent: extractTextFromDraftString(content), createdBy: viewerId, meetingId, - sortOrder, + sortOrder: sortOrder || dndNoise(), status, teamId, discussionId, integrationHash, - integration, + integration: JSON.stringify(integration), threadSortOrder, threadParentId, - userId - }) + userId: userId || null, + tags: getTagsFromEntityMap(JSON.parse(content).entityMap) + } const {id: taskId} = task const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) - await r({ - task: r.table('Task').insert(task) - }).run() - await pg - .insertInto('Task') - .values({...task, integration: JSON.stringify(integration)}) - .execute() + await pg.insertInto('Task').values(task).execute() handleAddTaskNotifications(teamMembers, task, viewerId, teamId, { operationId, mutatorId diff --git a/packages/server/graphql/mutations/createTaskIntegration.ts b/packages/server/graphql/mutations/createTaskIntegration.ts index 4b069473115..108ab8ba6d2 100644 --- a/packages/server/graphql/mutations/createTaskIntegration.ts +++ b/packages/server/graphql/mutations/createTaskIntegration.ts @@ -2,7 +2,6 @@ import {GraphQLID, GraphQLNonNull, GraphQLResolveInfo} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import makeAppURL from '~/utils/makeAppURL' import appOrigin from '../../appOrigin' -import getRethink from '../../database/rethinkDriver' import TaskIntegrationManagerFactory from '../../integrations/TaskIntegrationManagerFactory' import updatePrevUsedRepoIntegrationsCache from '../../integrations/updatePrevUsedRepoIntegrationsCache' import getKysely from '../../postgres/getKysely' @@ -46,8 +45,6 @@ export default { ) => { const {authToken, dataLoader, socketId: mutatorId} = context const pg = getKysely() - const r = await getRethink() - const now = new Date() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const viewerId = getUserId(authToken) @@ -153,16 +150,6 @@ export default { } updatePrevUsedRepoIntegrationsCache(teamId, integrationRepoId, viewerId) - - await r - .table('Task') - .get(taskId) - .update({ - ...updateTaskInput, - updatedAt: now - }) - .run() - await pg .updateTable('Task') .set({ diff --git a/packages/server/graphql/mutations/deleteTask.ts b/packages/server/graphql/mutations/deleteTask.ts index e433b53b8b3..98d9d485550 100644 --- a/packages/server/graphql/mutations/deleteTask.ts +++ b/packages/server/graphql/mutations/deleteTask.ts @@ -1,6 +1,5 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' @@ -23,7 +22,6 @@ export default { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const pg = getKysely() - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const viewerId = getUserId(authToken) @@ -41,9 +39,6 @@ export default { // RESOLUTION const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) const subscribedUserIds = teamMembers.map(({userId}) => userId) - await r({ - task: r.table('Task').get(taskId).delete() - }).run() await pg.deleteFrom('Task').where('id', '=', taskId).execute() const {tags, userId: taskUserId} = task diff --git a/packages/server/graphql/mutations/endCheckIn.ts b/packages/server/graphql/mutations/endCheckIn.ts index 7cb747a7144..b212051d08a 100644 --- a/packages/server/graphql/mutations/endCheckIn.ts +++ b/packages/server/graphql/mutations/endCheckIn.ts @@ -1,18 +1,17 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' +import {sql} from 'kysely' import {SubscriptionChannel} from 'parabol-client/types/constEnums' -import {AGENDA_ITEMS, DONE, LAST_CALL} from 'parabol-client/utils/constants' +import {AGENDA_ITEMS, LAST_CALL} from 'parabol-client/utils/constants' import getMeetingPhase from 'parabol-client/utils/getMeetingPhase' import findStageById from 'parabol-client/utils/meetings/findStageById' import {positionAfter} from '../../../client/shared/sortOrder' import {checkTeamsLimit} from '../../billing/helpers/teamLimitsCheck' -import getRethink from '../../database/rethinkDriver' -import {RDatum} from '../../database/stricterR' -import Task from '../../database/types/Task' import TimelineEventCheckinComplete from '../../database/types/TimelineEventCheckinComplete' import {DataLoaderInstance} from '../../dataloader/RootDataLoader' import generateUID from '../../generateUID' import getKysely from '../../postgres/getKysely' -import {AgendaItem} from '../../postgres/types' +import {selectTasks} from '../../postgres/select' +import {AgendaItem, Task} from '../../postgres/types' import {CheckInMeeting} from '../../postgres/types/Meeting' import archiveTasksForDB from '../../safeMutations/archiveTasksForDB' import removeSuggestedAction from '../../safeMutations/removeSuggestedAction' @@ -34,36 +33,20 @@ import updateTeamInsights from './helpers/updateTeamInsights' type SortOrderTask = Pick const updateTaskSortOrders = async (userIds: string[], tasks: SortOrderTask[]) => { const pg = getKysely() - const r = await getRethink() - const taskMax = await ( - r - .table('Task') - .getAll(r.args(userIds), {index: 'userId'}) - .filter((task: RDatum) => task('tags').contains('archived').not()) as any - ) - .max('sortOrder')('sortOrder') - .default(0) - .run() + const taskMaxRes = await pg + .selectFrom('Task') + .select(({fn}) => fn.max('sortOrder').as('maxSortOrder')) + .where('userId', 'in', userIds) + .where(sql`'archived' != ALL(tags)`) + .executeTakeFirst() + const maxSortOrder = Number(taskMaxRes?.maxSortOrder ?? 0) + // mutate what's in the dataloader tasks.forEach((task, idx) => { - task.sortOrder = taskMax + idx + 1 + task.sortOrder = maxSortOrder + idx + 1 }) - const updatedTasks = tasks.map((task) => ({ - id: task.id, - sortOrder: task.sortOrder - })) - await r(updatedTasks) - .forEach((task) => { - return r - .table('Task') - .get(task('id')) - .update({ - sortOrder: task('sortOrder') as unknown as number - }) - }) - .run() await Promise.all( - updatedTasks.map((task) => + tasks.map((task) => pg.updateTable('Task').set({sortOrder: task.sortOrder}).where('id', '=', task.id).execute() ) ) @@ -115,22 +98,14 @@ const summarizeCheckInMeeting = async (meeting: CheckInMeeting, dataLoader: Data const {id: meetingId, teamId, phases} = meeting const pg = getKysely() - const r = await getRethink() const [meetingMembers, tasks, doneTasks, activeAgendaItems] = await Promise.all([ dataLoader.get('meetingMembersByMeetingId').load(meetingId), - r - .table('Task') - .getAll(teamId, {index: 'teamId'}) - .filter({ - meetingId - }) - .run(), - r - .table('Task') - .getAll(teamId, {index: 'teamId'}) - .filter({status: DONE}) - .filter((task: RDatum) => task('tags').contains('archived').not()) - .run(), + dataLoader.get('tasksByMeetingId').load(meetingId), + selectTasks() + .where('teamId', '=', teamId) + .where('status', '=', 'done') + .where(sql`'archived' != ALL(tags)`) + .execute(), dataLoader.get('agendaItemsByTeamId').load(teamId) ]) @@ -227,7 +202,7 @@ export default { dataLoader.get('meetingMembersByMeetingId').load(meetingId), dataLoader.get('teams').loadNonNull(teamId), dataLoader.get('teamMembersByTeamId').load(teamId), - removeEmptyTasks(meetingId, teamId), + removeEmptyTasks(meetingId), updateTeamInsights(teamId, dataLoader) ]) // need to wait for removeEmptyTasks before finishing the meeting diff --git a/packages/server/graphql/mutations/endSprintPoker.ts b/packages/server/graphql/mutations/endSprintPoker.ts index 4f827428b08..4052119850a 100644 --- a/packages/server/graphql/mutations/endSprintPoker.ts +++ b/packages/server/graphql/mutations/endSprintPoker.ts @@ -100,7 +100,7 @@ export default { dataLoader.get('meetingMembersByMeetingId').load(meetingId), dataLoader.get('teams').loadNonNull(teamId), dataLoader.get('teamMembersByTeamId').load(teamId), - removeEmptyTasks(meetingId, teamId), + removeEmptyTasks(meetingId), // technically, this template could have mutated while the meeting was going on. but in practice, probably not dataLoader.get('meetingTemplates').loadNonNull(templateId), updateTeamInsights(teamId, dataLoader) diff --git a/packages/server/graphql/mutations/helpers/addSeedTasks.ts b/packages/server/graphql/mutations/helpers/addSeedTasks.ts index 509ef01f10d..25ade85b187 100644 --- a/packages/server/graphql/mutations/helpers/addSeedTasks.ts +++ b/packages/server/graphql/mutations/helpers/addSeedTasks.ts @@ -2,8 +2,6 @@ import convertToTaskContent from 'parabol-client/utils/draftjs/convertToTaskCont import getTagsFromEntityMap from 'parabol-client/utils/draftjs/getTagsFromEntityMap' import makeAppURL from 'parabol-client/utils/makeAppURL' import appOrigin from '../../../appOrigin' -import getRethink from '../../../database/rethinkDriver' -import {TaskStatusEnum} from '../../../database/types/Task' import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' import {convertHtmlToTaskContent} from '../../../utils/draftjs/convertHtmlToTaskContent' @@ -23,13 +21,13 @@ function getSeedTasks(teamId: string) { return [ { - status: 'active' as TaskStatusEnum, + status: 'active' as const, sortOrder: 1, content: convertToTaskContent(NORMAL_TASK_STRING), plaintextContent: NORMAL_TASK_STRING }, { - status: 'active' as TaskStatusEnum, + status: 'active' as const, sortOrder: 0, content: convertHtmlToTaskContent(integrationTaskHTML), plaintextContent: INTEGRATIONS_TASK_STRING @@ -39,7 +37,6 @@ function getSeedTasks(teamId: string) { export default async (userId: string, teamId: string) => { const pg = getKysely() - const r = await getRethink() const now = new Date() const seedTasks = getSeedTasks(teamId).map((proj) => ({ @@ -53,5 +50,4 @@ export default async (userId: string, teamId: string) => { updatedAt: now })) await pg.insertInto('Task').values(seedTasks).execute() - return r.table('Task').insert(seedTasks).run() } diff --git a/packages/server/graphql/mutations/helpers/createTaskInService.ts b/packages/server/graphql/mutations/helpers/createTaskInService.ts index 378c61ea187..e972d7fb08e 100644 --- a/packages/server/graphql/mutations/helpers/createTaskInService.ts +++ b/packages/server/graphql/mutations/helpers/createTaskInService.ts @@ -7,11 +7,8 @@ import GitHubRepoId from '../../../../client/shared/gqlIds/GitHubRepoId' import IntegrationRepoId from '../../../../client/shared/gqlIds/IntegrationRepoId' import JiraIssueId from '../../../../client/shared/gqlIds/JiraIssueId' import JiraProjectId from '../../../../client/shared/gqlIds/JiraProjectId' +import JiraProjectKeyId from '../../../../client/shared/gqlIds/JiraProjectKeyId' import removeRangesForEntity from '../../../../client/utils/draftjs/removeRangesForEntity' -import TaskIntegrationAzureDevOps from '../../../database/types/TaskIntegrationAzureDevOps' -import TaskIntegrationGitHub from '../../../database/types/TaskIntegrationGitHub' -import TaskIntegrationGitLab from '../../../database/types/TaskIntegrationGitLab' -import TaskIntegrationJira from '../../../database/types/TaskIntegrationJira' import {GQLContext} from '../../graphql' import {CreateTaskIntegrationInput} from '../createTask' import createAzureTask from './createAzureTask' @@ -45,11 +42,13 @@ const createTaskInService = async ( const {issueKey} = jiraTaskRes const integrationRepoId = IntegrationRepoId.join({cloudId, projectKey, service}) return { - integration: new TaskIntegrationJira({ + integration: { + service: 'jira' as const, + projectKey: JiraProjectKeyId.join(issueKey), accessUserId, cloudId, issueKey - }), + }, integrationHash: JiraIssueId.join(cloudId, issueKey), integrationRepoId } @@ -73,11 +72,12 @@ const createTaskInService = async ( const {issueNumber} = githubTaskRes const integrationRepoId = IntegrationRepoId.join({nameWithOwner: serviceProjectHash, service}) return { - integration: new TaskIntegrationGitHub({ + integration: { + service: 'github' as const, accessUserId, nameWithOwner: serviceProjectHash, issueNumber - }), + }, integrationHash: GitHubIssueId.join(serviceProjectHash, issueNumber), integrationRepoId } @@ -101,11 +101,12 @@ const createTaskInService = async ( const integrationRepoId = IntegrationRepoId.join({fullPath, service}) const integrationProviderId = IntegrationProviderId.join(providerId) return { - integration: new TaskIntegrationGitLab({ + integration: { + service: 'gitlab' as const, accessUserId, providerId: integrationProviderId, gid - }), + }, integrationHash: GitLabIssueId.join(integrationProviderId, gid), integrationRepoId } @@ -128,12 +129,13 @@ const createTaskInService = async ( // TODO: fix inconsistencies with projectKey & projectId: https://github.com/ParabolInc/parabol/issues/7073 const integrationRepoId = IntegrationRepoId.join({instanceId, projectId: projectKey, service}) return { - integration: new TaskIntegrationAzureDevOps({ + integration: { + service: 'azureDevOps' as const, instanceId, accessUserId, projectKey, issueKey - }), + }, integrationHash, integrationRepoId } diff --git a/packages/server/graphql/mutations/helpers/importTasksForPoker.ts b/packages/server/graphql/mutations/helpers/importTasksForPoker.ts index 9d6d15eb564..155f5a4740b 100644 --- a/packages/server/graphql/mutations/helpers/importTasksForPoker.ts +++ b/packages/server/graphql/mutations/helpers/importTasksForPoker.ts @@ -1,8 +1,11 @@ import IntegrationHash from 'parabol-client/shared/gqlIds/IntegrationHash' import {isNotNull} from 'parabol-client/utils/predicates' -import getRethink from '../../../database/rethinkDriver' -import ImportedTask from '../../../database/types/ImportedTask' +import dndNoise from '../../../../client/utils/dndNoise' +import convertToTaskContent from '../../../../client/utils/draftjs/convertToTaskContent' +import getTagsFromEntityMap from '../../../../client/utils/draftjs/getTagsFromEntityMap' +import generateUID from '../../../generateUID' import getKysely from '../../../postgres/getKysely' +import {selectTasks} from '../../../postgres/select' import {TUpdatePokerScopeItemInput} from '../updatePokerScope' const importTasksForPoker = async ( @@ -12,14 +15,13 @@ const importTasksForPoker = async ( meetingId: string ) => { const pg = getKysely() - const r = await getRethink() const integratedUpdates = additiveUpdates.filter((update) => update.service !== 'PARABOL') const integrationHashes = integratedUpdates.map((update) => update.serviceTaskId) - const existingTasks = await r - .table('Task') - .getAll(r.args(integrationHashes), {index: 'integrationHash'}) - .filter({teamId, userId}) - .run() + const existingTasks = await selectTasks() + .where('integrationHash', 'in', integrationHashes) + .where('teamId', '=', teamId) + .where('userId', '=', userId) + .execute() const integrationHashToTaskId = {} as Record additiveUpdates.map((update) => { if (update.service === 'PARABOL') { @@ -32,26 +34,32 @@ const importTasksForPoker = async ( const tasksToAdd = newIntegrationUpdates .map((update) => { const {service, serviceTaskId} = update - const integration = IntegrationHash.split(service, serviceTaskId) - if (!integration) return null - return new ImportedTask({ - userId, - integration: { - accessUserId: userId, - ...integration - }, + const integrationSplit = IntegrationHash.split(service, serviceTaskId) + if (!integrationSplit) return null + const integration = { + accessUserId: userId, + ...integrationSplit + } + const integrationHash = IntegrationHash.join(integration) + const plaintextContent = `Task imported from ${integration.service} #archived` + const content = convertToTaskContent(plaintextContent) + return { + id: generateUID(), + content, + plaintextContent, + createdBy: userId, + sortOrder: dndNoise(), + status: 'future' as const, + teamId, + integrationHash, + integration: JSON.stringify(integration), meetingId, - teamId - }) + tags: getTagsFromEntityMap(JSON.parse(content).entityMap) + } }) .filter(isNotNull) - - if (newIntegrationUpdates.length > 0) { - await pg - .insertInto('Task') - .values(tasksToAdd.map((t) => ({...t, integration: JSON.stringify(t.integration)}))) - .execute() - await r.table('Task').insert(tasksToAdd).run() + if (tasksToAdd.length > 0) { + await pg.insertInto('Task').values(tasksToAdd).execute() } const integratedTasks = [...existingTasks, ...tasksToAdd] diff --git a/packages/server/graphql/mutations/helpers/publishChangeNotifications.ts b/packages/server/graphql/mutations/helpers/publishChangeNotifications.ts index 7dcf3cfdc3a..0feaec154fe 100644 --- a/packages/server/graphql/mutations/helpers/publishChangeNotifications.ts +++ b/packages/server/graphql/mutations/helpers/publishChangeNotifications.ts @@ -2,7 +2,7 @@ import {ASSIGNEE, MENTIONEE} from 'parabol-client/utils/constants' import getTypeFromEntityMap from 'parabol-client/utils/draftjs/getTypeFromEntityMap' import getRethink from '../../../database/rethinkDriver' import NotificationTaskInvolves from '../../../database/types/NotificationTaskInvolves' -import Task from '../../../database/types/Task' +import {Task} from '../../../postgres/types' import {analytics} from '../../../utils/analytics/analytics' const publishChangeNotifications = async ( diff --git a/packages/server/graphql/mutations/helpers/pushEstimateToGitHub.ts b/packages/server/graphql/mutations/helpers/pushEstimateToGitHub.ts index 891895d7a71..289f36fa763 100644 --- a/packages/server/graphql/mutations/helpers/pushEstimateToGitHub.ts +++ b/packages/server/graphql/mutations/helpers/pushEstimateToGitHub.ts @@ -41,7 +41,7 @@ const pushEstimateToGitHub = async ( const {dimensionName, taskId, value, meetingId} = taskEstimate const {dataLoader} = context const [task, meeting] = await Promise.all([ - dataLoader.get('tasks').load(taskId), + dataLoader.get('tasks').loadNonNull(taskId), dataLoader.get('newMeetings').load(meetingId) ]) diff --git a/packages/server/graphql/mutations/helpers/pushEstimateToGitLab.ts b/packages/server/graphql/mutations/helpers/pushEstimateToGitLab.ts index 8006cb2b350..32d63499cb2 100644 --- a/packages/server/graphql/mutations/helpers/pushEstimateToGitLab.ts +++ b/packages/server/graphql/mutations/helpers/pushEstimateToGitLab.ts @@ -17,7 +17,7 @@ const pushEstimateToGitLab = async ( const {dimensionName, taskId, value, meetingId} = taskEstimate const {dataLoader} = context const [task, meeting] = await Promise.all([ - dataLoader.get('tasks').load(taskId), + dataLoader.get('tasks').loadNonNull(taskId), dataLoader.get('newMeetings').load(meetingId) ]) if (!meeting) return new Error('Meeting does not exist') diff --git a/packages/server/graphql/mutations/helpers/removeEmptyTasks.ts b/packages/server/graphql/mutations/helpers/removeEmptyTasks.ts index ce4dd8a0d41..7e14376118b 100644 --- a/packages/server/graphql/mutations/helpers/removeEmptyTasks.ts +++ b/packages/server/graphql/mutations/helpers/removeEmptyTasks.ts @@ -1,28 +1,14 @@ -import extractTextFromDraftString from 'parabol-client/utils/draftjs/extractTextFromDraftString' -import getRethink from '../../../database/rethinkDriver' import getKysely from '../../../postgres/getKysely' -const removeEmptyTasks = async (meetingId: string, teamId: string) => { +const removeEmptyTasks = async (meetingId: string) => { const pg = getKysely() - const r = await getRethink() - const createdTasks = await r - .table('Task') - .getAll(teamId, {index: 'teamId'}) - .filter({meetingId}) - .run() - - const removedTaskIds = createdTasks - .map((task) => ({ - id: task.id, - plaintextContent: extractTextFromDraftString(task.content) - })) - .filter(({plaintextContent}) => plaintextContent.length === 0) - .map(({id}) => id) - if (removedTaskIds.length > 0) { - await pg.deleteFrom('Task').where('id', 'in', removedTaskIds).execute() - await r.table('Task').getAll(r.args(removedTaskIds)).delete().run() - } - return removedTaskIds + const removedTasks = await pg + .deleteFrom('Task') + .where('meetingId', '=', meetingId) + .where(({or, eb}) => or([eb('plaintextContent', '=', ''), eb('plaintextContent', 'is', null)])) + .returning('id') + .execute() + return removedTasks.map(({id}) => id) } export default removeEmptyTasks diff --git a/packages/server/graphql/mutations/helpers/removeTeamMember.ts b/packages/server/graphql/mutations/helpers/removeTeamMember.ts index b7afbd2de7b..44ad0e0d858 100644 --- a/packages/server/graphql/mutations/helpers/removeTeamMember.ts +++ b/packages/server/graphql/mutations/helpers/removeTeamMember.ts @@ -1,14 +1,13 @@ import {sql} from 'kysely' import fromTeamMemberId from 'parabol-client/utils/relay/fromTeamMemberId' import getRethink from '../../../database/rethinkDriver' -import {RDatum} from '../../../database/stricterR' import AgendaItemsStage from '../../../database/types/AgendaItemsStage' import CheckInStage from '../../../database/types/CheckInStage' import EstimateStage from '../../../database/types/EstimateStage' import NotificationKickedOut from '../../../database/types/NotificationKickedOut' -import Task from '../../../database/types/Task' import UpdatesStage from '../../../database/types/UpdatesStage' import getKysely from '../../../postgres/getKysely' +import {selectTasks} from '../../../postgres/select' import archiveTasksForDB from '../../../safeMutations/archiveTasksForDB' import errorFilter from '../../errorFilter' import {DataLoaderWorker} from '../../graphql' @@ -53,17 +52,14 @@ const removeTeamMember = async ( : currentTeamLeader if (willArchive) { - await Promise.all([ - // archive single-person teams - pg - .with('TaskDelete', (qb) => qb.deleteFrom('Task').where('teamId', '=', teamId)) - .updateTable('Team') - .set({isArchived: true}) - .where('id', '=', teamId) - .execute(), + await pg // delete all tasks belonging to a 1-person team - r.table('Task').getAll(teamId, {index: 'teamId'}).delete() - ]) + .with('TaskDelete', (qb) => qb.deleteFrom('Task').where('teamId', '=', teamId)) + // archive single-person teams + .updateTable('Team') + .set({isArchived: true}) + .where('id', '=', teamId) + .execute() } else if (isLead) { // assign new leader, remove old leader flag await pg @@ -79,46 +75,26 @@ const removeTeamMember = async ( .where('id', '=', teamMemberId) .execute() // assign active tasks to the team lead - const {integratedTasksToArchive, reassignedTasks} = await r({ - integratedTasksToArchive: r - .table('Task') - .getAll(userId, {index: 'userId'}) - .filter({teamId}) - .filter((task: RDatum) => { - return r.and( - task('tags').contains('archived').not(), - task('integrations').default(null).ne(null) - ) - }) - .coerceTo('array') as unknown as Task[], - reassignedTasks: r - .table('Task') - .getAll(userId, {index: 'userId'}) - .filter({teamId}) - .filter((task: RDatum) => - r.and(task('tags').contains('archived').not(), task('integrations').default(null).eq(null)) - ) - .update( - { - userId: nextTeamLead.userId - }, - {returnChanges: true} - )('changes')('new_val') - .default([]) as unknown as Task[] - }).run() - await pg - .with('TaskReassignment', (qb) => + const integratedTasksToArchive = await selectTasks() + .where('userId', '=', userId) + .where('teamId', '=', teamId) + .where('integration', 'is not', null) + .where(sql`'archived' != ALL(tags)`) + .execute() + const reassignedTasks = await pg + .with('UserUpdate', (qb) => qb - .updateTable('Task') - .set({userId: nextTeamLead.userId}) - .where('userId', '=', userId) - .where('teamId', '=', teamId) - .where('integration', 'is', null) - .where(sql`'archived' != ANY(tags)`) + .updateTable('User') + .set(({fn, ref, val}) => ({tms: fn('ARRAY_REMOVE', [ref('tms'), val(teamId)])})) + .where('id', '=', userId) ) - .updateTable('User') - .set(({fn, ref, val}) => ({tms: fn('ARRAY_REMOVE', [ref('tms'), val(teamId)])})) - .where('id', '=', userId) + .updateTable('Task') + .set({userId: nextTeamLead.userId}) + .where('userId', '=', userId) + .where('teamId', '=', teamId) + .where('integration', 'is', null) + .where(sql`'archived' != ALL(tags)`) + .returning('id') .execute() dataLoader.clearAll(['users', 'teamMembers', 'tasks']) const user = await dataLoader.get('users').load(userId) @@ -130,8 +106,7 @@ const removeTeamMember = async ( await r.table('Notification').insert(notification).run() } - const archivedTasks = await archiveTasksForDB(integratedTasksToArchive) - const archivedTaskIds = archivedTasks.map(({id}) => id) + const archivedTaskIds = await archiveTasksForDB(integratedTasksToArchive) const teamAgendaItems = await dataLoader.get('agendaItemsByTeamId').load(teamId) const agendaItemIds = teamAgendaItems .filter((agendaItem) => agendaItem.teamMemberId === teamMemberId) diff --git a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts index c486815af94..fc91ee11214 100644 --- a/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts +++ b/packages/server/graphql/mutations/helpers/safeEndRetrospective.ts @@ -4,7 +4,6 @@ import {DISCUSS} from 'parabol-client/utils/constants' import getMeetingPhase from 'parabol-client/utils/getMeetingPhase' import findStageById from 'parabol-client/utils/meetings/findStageById' import {checkTeamsLimit} from '../../../billing/helpers/teamLimitsCheck' -import getRethink from '../../../database/rethinkDriver' import TimelineEventRetroComplete from '../../../database/types/TimelineEventRetroComplete' import getKysely from '../../../postgres/getKysely' import {RetrospectiveMeeting} from '../../../postgres/types/Meeting' @@ -38,7 +37,6 @@ const summarizeRetroMeeting = async (meeting: RetrospectiveMeeting, context: Int const {dataLoader} = context const {id: meetingId, phases, facilitatorUserId, teamId, recallBotId} = meeting const pg = getKysely() - const r = await getRethink() const [reflectionGroups, reflections, sentimentScore] = await Promise.all([ dataLoader.get('retroReflectionGroupsByMeetingId').load(meetingId), dataLoader.get('retroReflectionsByMeetingId').load(meetingId), @@ -57,17 +55,16 @@ const summarizeRetroMeeting = async (meeting: RetrospectiveMeeting, context: Int await dataLoader.get('commentCountByDiscussionId').loadMany(discussionIds) ).filter(isValid) const commentCount = commentCounts.reduce((cumSum, count) => cumSum + count, 0) - const taskCount = await r - .table('Task') - .getAll(r.args(discussionIds), {index: 'discussionId'}) - .count() - .default(0) - .run() + const taskCountRes = await pg + .selectFrom('Task') + .select(({fn}) => fn.count('id').as('count')) + .where('discussionId', 'in', discussionIds) + .executeTakeFirst() await pg .updateTable('NewMeeting') .set({ commentCount, - taskCount, + taskCount: Number(taskCountRes?.count ?? 0), topicCount: reflectionGroupIds.length, reflectionCount: reflections.length, sentimentScore, @@ -137,7 +134,7 @@ const safeEndRetrospective = async ({ dataLoader.get('meetingMembersByMeetingId').load(meetingId), dataLoader.get('teams').loadNonNull(teamId), dataLoader.get('teamMembersByTeamId').load(teamId), - removeEmptyTasks(meetingId, teamId), + removeEmptyTasks(meetingId), dataLoader.get('meetingTemplates').loadNonNull(templateId), updateTeamInsights(teamId, dataLoader) ]) diff --git a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts index 73debf3e5f5..5a52a2f05e8 100644 --- a/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts +++ b/packages/server/graphql/mutations/resetRetroMeetingToGroupStage.ts @@ -1,7 +1,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import {CHECKIN, DISCUSS, GROUP, REFLECT, VOTE} from '../../../client/utils/constants' -import getRethink from '../../database/rethinkDriver' import DiscussPhase from '../../database/types/DiscussPhase' import GenericMeetingPhase from '../../database/types/GenericMeetingPhase' import getKysely from '../../postgres/getKysely' @@ -27,7 +26,6 @@ const resetRetroMeetingToGroupStage = { {meetingId}: {meetingId: string}, {authToken, socketId: mutatorId, dataLoader}: GQLContext ) => { - const r = await getRethink() const pg = getKysely() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -103,32 +101,32 @@ const resetRetroMeetingToGroupStage = { // bc we return the reflection groups cached by data loader in the fragment reflectionGroups.forEach((rg) => (rg.voterIds = [])) - await Promise.all([ - pg + if (discussionIdsToDelete.length > 0) { + await pg .with('DeleteComments', (qb) => qb.deleteFrom('Comment').where('discussionId', 'in', discussionIdsToDelete) ) - .with('DeleteTasks', (qb) => - qb.deleteFrom('Task').where('discussionId', 'in', discussionIdsToDelete) - ) - .with('ResetGroups', (qb) => - qb - .updateTable('RetroReflectionGroup') - .set({voterIds: [], discussionPromptQuestion: null}) - .where('id', 'in', reflectionGroupIds) - ) - .with('ResetMeetingMember', (qb) => - qb - .updateTable('MeetingMember') - .set({votesRemaining: meeting.totalVotes}) - .where('meetingId', '=', meetingId) - ) - .updateTable('NewMeeting') - .set({phases: JSON.stringify(newPhases)}) - .where('id', '=', meetingId) - .execute(), - r.table('Task').getAll(r.args(discussionIdsToDelete), {index: 'discussionId'}).delete().run() - ]) + .deleteFrom('Task') + .where('discussionId', 'in', discussionIdsToDelete) + .execute() + } + await pg + .with('ResetGroups', (qb) => + qb + .updateTable('RetroReflectionGroup') + .set({voterIds: [], discussionPromptQuestion: null}) + .where('id', 'in', reflectionGroupIds) + ) + .with('ResetMeetingMember', (qb) => + qb + .updateTable('MeetingMember') + .set({votesRemaining: meeting.totalVotes}) + .where('meetingId', '=', meetingId) + ) + .updateTable('NewMeeting') + .set({phases: JSON.stringify(newPhases)}) + .where('id', '=', meetingId) + .execute() dataLoader.clearAll([ 'newMeetings', 'comments', diff --git a/packages/server/graphql/mutations/setTaskEstimate.ts b/packages/server/graphql/mutations/setTaskEstimate.ts index 1f2bdaea1fa..0b705a9f937 100644 --- a/packages/server/graphql/mutations/setTaskEstimate.ts +++ b/packages/server/graphql/mutations/setTaskEstimate.ts @@ -3,7 +3,6 @@ import {SprintPokerDefaults, SubscriptionChannel, Threshold} from 'parabol-clien import makeAppURL from 'parabol-client/utils/makeAppURL' import JiraProjectKeyId from '../../../client/shared/gqlIds/JiraProjectKeyId' import appOrigin from '../../appOrigin' -import TaskIntegrationJiraServer from '../../database/types/TaskIntegrationJiraServer' import JiraServerRestManager from '../../integrations/jiraServer/JiraServerRestManager' import {IntegrationProviderJiraServer} from '../../postgres/queries/getIntegrationProvidersByIds' import insertTaskEstimate from '../../postgres/queries/insertTaskEstimate' @@ -231,7 +230,7 @@ const setTaskEstimate = { const manager = new JiraServerRestManager(auth, provider as IntegrationProviderJiraServer) - const {providerId, repositoryId: projectId} = integration as TaskIntegrationJiraServer + const {providerId, repositoryId: projectId} = integration! const jiraServerIssue = await dataLoader .get('jiraServerIssue') .load({providerId, teamId, userId: accessUserId, issueId}) diff --git a/packages/server/graphql/mutations/updatePokerScope.ts b/packages/server/graphql/mutations/updatePokerScope.ts index f6d7af19549..a80913a9d47 100644 --- a/packages/server/graphql/mutations/updatePokerScope.ts +++ b/packages/server/graphql/mutations/updatePokerScope.ts @@ -3,9 +3,9 @@ import {Insertable} from 'kysely' import {SubscriptionChannel, Threshold} from 'parabol-client/types/constEnums' import {ESTIMATE_TASK_SORT_ORDER} from '../../../client/utils/constants' import EstimateStage from '../../database/types/EstimateStage' -import {TaskServiceEnum} from '../../database/types/Task' import getKysely from '../../postgres/getKysely' import {Discussion} from '../../postgres/pg' +import {TaskServiceEnum} from '../../postgres/types/TaskIntegration' import RedisLockQueue from '../../utils/RedisLockQueue' import {getUserId, isTeamMember} from '../../utils/authorization' import getPhase from '../../utils/getPhase' diff --git a/packages/server/graphql/mutations/updateTask.ts b/packages/server/graphql/mutations/updateTask.ts index b8471bf00b8..b9ac62a1415 100644 --- a/packages/server/graphql/mutations/updateTask.ts +++ b/packages/server/graphql/mutations/updateTask.ts @@ -2,9 +2,9 @@ import {GraphQLNonNull, GraphQLObjectType} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import extractTextFromDraftString from 'parabol-client/utils/draftjs/extractTextFromDraftString' import normalizeRawDraftJS from 'parabol-client/validation/normalizeRawDraftJS' -import getRethink from '../../database/rethinkDriver' -import Task, {AreaEnum as TAreaEnum, TaskStatusEnum} from '../../database/types/Task' +import getTagsFromEntityMap from '../../../client/utils/draftjs/getTagsFromEntityMap' import getKysely from '../../postgres/getKysely' +import {Task} from '../../postgres/types/index' import {getUserId, isTeamMember} from '../../utils/authorization' import publish from '../../utils/publish' import standardError from '../../utils/standardError' @@ -19,12 +19,12 @@ type UpdateTaskInput = { id: string content?: string | null sortOrder?: number | null - status?: TaskStatusEnum | null + status?: Task['status'] | null userId?: string | null } type UpdateTaskMutationVariables = { updatedTask: UpdateTaskInput - area?: TAreaEnum | null + area?: 'meeting' | 'teamDash' | 'userDash' | null } export default { type: new GraphQLObjectType({ @@ -48,8 +48,6 @@ export default { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const pg = getKysely() - const r = await getRethink() - const now = new Date() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} const viewerId = getUserId(authToken) @@ -76,43 +74,26 @@ export default { } } // RESOLUTION - const isSortOrderUpdate = - updatedTask.sortOrder !== undefined && Object.keys(updatedTask).length === 2 - const nextTask = new Task({ - ...task, - userId: nextUserId, - status: status || task.status, - sortOrder: sortOrder || task.sortOrder, - content: content ? validContent : task.content, - plaintextContent: content ? extractTextFromDraftString(validContent) : task.plaintextContent, - updatedAt: isSortOrderUpdate ? task.updatedAt : now - }) - const teamMembers = await dataLoader.get('teamMembersByTeamId').load(teamId) - const {newTask} = await r({ - newTask: r - .table('Task') - .get(taskId) - .update(nextTask, {returnChanges: true})('changes')(0)('new_val') - .default(null) as unknown as Task - }).run() - await pg + const updateRes = await pg .updateTable('Task') .set({ content: content ? validContent : undefined, - plaintextContent: content - ? extractTextFromDraftString(validContent) - : task.plaintextContent, + plaintextContent: content ? extractTextFromDraftString(validContent) : undefined, sortOrder: sortOrder || undefined, status: status || undefined, - userId: inputUserId || undefined + userId: inputUserId || undefined, + tags: content ? getTagsFromEntityMap(JSON.parse(validContent).entityMap) : undefined }) .where('id', '=', taskId) - .execute() + .executeTakeFirst() + if (Number(updateRes.numChangedRows) === 0) { + return standardError(new Error('Already updated task'), {userId: viewerId}) + } dataLoader.clearAll('tasks') + const newTask = await dataLoader.get('tasks').loadNonNull(taskId) // TODO: get users in the same location const usersToIgnore = await getUsersToIgnore(viewerId, teamId) - if (!newTask) return standardError(new Error('Already updated task'), {userId: viewerId}) // send task updated messages const isPrivate = newTask.tags.includes('private') diff --git a/packages/server/graphql/mutations/updateTaskDueDate.ts b/packages/server/graphql/mutations/updateTaskDueDate.ts index 7660dab5521..9482c532ea7 100644 --- a/packages/server/graphql/mutations/updateTaskDueDate.ts +++ b/packages/server/graphql/mutations/updateTaskDueDate.ts @@ -1,7 +1,6 @@ import {GraphQLID, GraphQLNonNull} from 'graphql' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import isValidDate from 'parabol-client/utils/isValidDate' -import getRethink from '../../database/rethinkDriver' import getKysely from '../../postgres/getKysely' import {analytics} from '../../utils/analytics/analytics' import {getUserId, isTeamMember} from '../../utils/authorization' @@ -30,7 +29,6 @@ export default { {authToken, dataLoader, socketId: mutatorId}: GQLContext ) { const pg = getKysely() - const r = await getRethink() const operationId = dataLoader.share() const subOptions = {mutatorId, operationId} @@ -49,16 +47,7 @@ export default { } // RESOLUTION - await r - .table('Task') - .get(taskId) - .update({ - dueDate: nextDueDate - }) - .run() - await pg.updateTable('Task').set({dueDate: nextDueDate}).where('id', '=', taskId).execute() - dataLoader.clearAll('tasks') const data = {taskId} diff --git a/packages/server/graphql/private/mutations/hardDeleteUser.ts b/packages/server/graphql/private/mutations/hardDeleteUser.ts index 63f9571af59..d2f91d82044 100644 --- a/packages/server/graphql/private/mutations/hardDeleteUser.ts +++ b/packages/server/graphql/private/mutations/hardDeleteUser.ts @@ -1,5 +1,4 @@ import getRethink from '../../../database/rethinkDriver' -import {RValue} from '../../../database/stricterR' import {DataLoaderInstance} from '../../../dataloader/RootDataLoader' import getKysely from '../../../postgres/getKysely' import {getUserByEmail} from '../../../postgres/queries/getUsersByEmails' @@ -86,12 +85,7 @@ const hardDeleteUser: MutationResolvers['hardDeleteUser'] = async ( .where('createdBy', '=', userIdToDelete) .execute() await r({ - notification: r.table('Notification').getAll(userIdToDelete, {index: 'userId'}).delete(), - createdTasks: r - .table('Task') - .getAll(r.args(teamIds), {index: 'teamId'}) - .filter((row: RValue) => row('createdBy').eq(userIdToDelete)) - .delete() + notification: r.table('Notification').getAll(userIdToDelete, {index: 'userId'}).delete() }).run() // now postgres, after FKs are added then triggers should take care of children diff --git a/packages/server/graphql/public/mutations/batchArchiveTasks.ts b/packages/server/graphql/public/mutations/batchArchiveTasks.ts index 66345e4f2fc..5c8e6dd0d5b 100644 --- a/packages/server/graphql/public/mutations/batchArchiveTasks.ts +++ b/packages/server/graphql/public/mutations/batchArchiveTasks.ts @@ -3,7 +3,7 @@ import {getUserId} from '../../../utils/authorization' import {SubscriptionChannel} from 'parabol-client/types/constEnums' import publish from '../../../utils/publish' -import Task from '../../../database/types/Task' +import {Task} from '../../../postgres/types' import archiveTasksForDB from '../../../safeMutations/archiveTasksForDB' import isValid from '../../isValid' import {MutationResolvers} from '../resolverTypes' diff --git a/packages/server/graphql/public/types/ActionMeetingMember.ts b/packages/server/graphql/public/types/ActionMeetingMember.ts index 036dbf7b2b9..ce27294b5db 100644 --- a/packages/server/graphql/public/types/ActionMeetingMember.ts +++ b/packages/server/graphql/public/types/ActionMeetingMember.ts @@ -1,18 +1,17 @@ +import {sql} from 'kysely' import isTaskPrivate from 'parabol-client/utils/isTaskPrivate' -import getRethink from '../../../database/rethinkDriver' -import {RDatum} from '../../../database/stricterR' +import {selectTasks} from '../../../postgres/select' import {ActionMeetingMemberResolvers} from '../resolverTypes' const ActionMeetingMember: ActionMeetingMemberResolvers = { __isTypeOf: ({meetingType}) => meetingType === 'action', doneTasks: async ({meetingId, userId}) => { - const r = await getRethink() - return r - .table('Task') - .getAll(userId, {index: 'userId'}) - .filter({doneMeetingId: meetingId}) - .filter((task: RDatum) => task('tags').contains('private').not()) - .run() + const res = await selectTasks() + .where('userId', '=', userId) + .where('doneMeetingId', '=', meetingId) + .where(sql`'private' != ALL(tags)`) + .execute() + return res }, tasks: async ({meetingId, userId}, _args, {dataLoader}) => { diff --git a/packages/server/graphql/public/types/EstimateStage.ts b/packages/server/graphql/public/types/EstimateStage.ts index dbb12ab652d..a601b2228ad 100644 --- a/packages/server/graphql/public/types/EstimateStage.ts +++ b/packages/server/graphql/public/types/EstimateStage.ts @@ -1,7 +1,5 @@ import JiraProjectKeyId from '../../../../client/shared/gqlIds/JiraProjectKeyId' import {SprintPokerDefaults} from '../../../../client/types/constEnums' -import TaskIntegrationAzureDevOps from '../../../database/types/TaskIntegrationAzureDevOps' -import TaskIntegrationJiraServer from '../../../database/types/TaskIntegrationJiraServer' import GitLabServerManager from '../../../integrations/gitlab/GitLabServerManager' import {getUserId} from '../../../utils/authorization' import getRedis from '../../../utils/getRedis' @@ -63,12 +61,7 @@ const EstimateStage: EstimateStageResolvers = { return {name: SprintPokerDefaults.SERVICE_FIELD_COMMENT, type: 'string'} } if (service === 'jiraServer') { - const { - providerId, - repositoryId: projectId, - issueId, - accessUserId - } = integration as TaskIntegrationJiraServer + const {providerId, repositoryId: projectId, issueId, accessUserId} = integration const dimensionName = await getDimensionName(meetingId) const jiraServerIssue = await dataLoader @@ -89,8 +82,7 @@ const EstimateStage: EstimateStageResolvers = { return {name: SprintPokerDefaults.SERVICE_FIELD_COMMENT, type: 'string'} } if (service === 'azureDevOps') { - const {instanceId, projectKey, issueKey, accessUserId} = - integration as TaskIntegrationAzureDevOps + const {instanceId, projectKey, issueKey, accessUserId} = integration const azureDevOpsWorkItem = await dataLoader.get('azureDevOpsWorkItem').load({ teamId, @@ -225,7 +217,7 @@ const EstimateStage: EstimateStageResolvers = { }, task: async ({taskId}, _args, {dataLoader}) => { - return dataLoader.get('tasks').load(taskId) + return dataLoader.get('tasks').loadNonNull(taskId) } } diff --git a/packages/server/graphql/public/types/Task.ts b/packages/server/graphql/public/types/Task.ts new file mode 100644 index 00000000000..645ea20187b --- /dev/null +++ b/packages/server/graphql/public/types/Task.ts @@ -0,0 +1,229 @@ +import AzureDevOpsIssueId from 'parabol-client/shared/gqlIds/AzureDevOpsIssueId' +import JiraServerIssueId from '~/shared/gqlIds/JiraServerIssueId' +import GitHubRepoId from '../../../../client/shared/gqlIds/GitHubRepoId' +import GitLabServerManager from '../../../integrations/gitlab/GitLabServerManager' +import {IGetLatestTaskEstimatesQueryResult} from '../../../postgres/queries/generated/getLatestTaskEstimatesQuery' +import getSimilarTaskEstimate from '../../../postgres/queries/getSimilarTaskEstimate' +import insertTaskEstimate from '../../../postgres/queries/insertTaskEstimate' +import {GetIssueLabelsQuery, GetIssueLabelsQueryVariables} from '../../../types/githubTypes' +import {getUserId} from '../../../utils/authorization' +import getGitHubRequest from '../../../utils/getGitHubRequest' +import getIssueLabels from '../../../utils/githubQueries/getIssueLabels.graphql' +import sendToSentry from '../../../utils/sendToSentry' +import isValid from '../../isValid' +import {ReqResolvers} from './ReqResolvers' + +const Task: Omit, 'replies'> = { + __isTypeOf: ({status}) => !!status, + agendaItem: async ({discussionId}, _args, {dataLoader}) => { + if (!discussionId) return null + const discussion = await dataLoader.get('discussions').load(discussionId) + if (!discussion) return null + const {discussionTopicId, discussionTopicType} = discussion + if (discussionTopicType !== 'agendaItem') return null + return dataLoader.get('agendaItems').loadNonNull(discussionTopicId) + }, + + taskService: ({integration}, _args) => { + return integration?.service ?? null + }, + + createdByUser: ({createdBy}, _args, {dataLoader}) => { + return dataLoader.get('users').loadNonNull(createdBy) + }, + + estimates: async ({id: taskId, integration, teamId}, _args, context, info) => { + const {dataLoader, authToken} = context + const viewerId = getUserId(authToken) + if (integration?.service === 'jira') { + const {accessUserId, cloudId, issueKey} = integration + // this dataloader has the side effect of guaranteeing fresh estimates + await dataLoader + .get('jiraIssue') + .load({teamId, userId: accessUserId, cloudId, issueKey, taskId, viewerId}) + } else if (integration?.service === 'azureDevOps') { + const {accessUserId, instanceId, projectKey, issueKey} = integration + await dataLoader.get('azureDevOpsWorkItem').load({ + teamId, + userId: accessUserId, + instanceId, + workItemId: issueKey, + taskId, + projectId: projectKey, + viewerId + }) + } else if (integration?.service === 'github') { + const {accessUserId, nameWithOwner, issueNumber} = integration + const [githubAuth, estimates] = await Promise.all([ + dataLoader.get('githubAuth').load({userId: accessUserId, teamId}), + dataLoader.get('latestTaskEstimates').load(taskId) + ]) + if (estimates.length === 0) return estimates + // TODO schedule this work to be done & pump in the updates via subcription + if (!githubAuth) return estimates + // fetch fresh estimates from GH + const {accessToken} = githubAuth + const {repoOwner, repoName} = GitHubRepoId.split(nameWithOwner) + const githubRequest = getGitHubRequest(info, context, {accessToken}) + const [labelsData, labelsError] = await githubRequest< + GetIssueLabelsQuery, + GetIssueLabelsQueryVariables + >(getIssueLabels, { + first: 100, + repoName, + repoOwner, + issueNumber + }) + if (!labelsData) { + if (labelsError) { + sendToSentry(labelsError, {userId: accessUserId}) + } + return estimates + } + const labelNodes = labelsData.repository?.issue?.labels?.nodes + if (!labelNodes) return estimates + const ghIssueLabels = labelNodes.map((node) => node?.name).filter(isValid) + await Promise.all( + estimates.map(async (estimate: IGetLatestTaskEstimatesQueryResult) => { + const {githubLabelName, name: dimensionName} = estimate + const existingLabel = ghIssueLabels.includes(githubLabelName!) + if (existingLabel) return + // VERY EXPENSIVE. We do this only if we're darn sure we need to + const taskIds = await dataLoader + .get('taskIdsByTeamAndGitHubRepo') + .load({teamId, nameWithOwner}) + const similarEstimate = await getSimilarTaskEstimate( + taskIds, + dimensionName, + ghIssueLabels + ) + if (!similarEstimate) return + dataLoader.get('latestTaskEstimates').clear(taskId) + return insertTaskEstimate({ + changeSource: 'external', + // keep the link to the discussion alive, if possible + discussionId: estimate.discussionId, + jiraFieldId: undefined, + label: similarEstimate.label, + name: estimate.name, + meetingId: null, + stageId: null, + taskId, + userId: accessUserId, + githubLabelName: similarEstimate.githubLabelName! + }) + }) + ) + } + return dataLoader.get('latestTaskEstimates').load(taskId) + }, + + editors: () => [], + + integration: async ({integration, integrationHash, teamId, id: taskId}, _args, context, info) => { + const {dataLoader, authToken} = context + const viewerId = getUserId(authToken) + if (!integration) return null + const {accessUserId} = integration + if (integration.service === 'jira') { + const {cloudId, issueKey} = integration + return dataLoader + .get('jiraIssue') + .load({teamId, userId: accessUserId, cloudId, issueKey, taskId, viewerId}) + } else if (integration.service === 'jiraServer') { + const {issueId} = JiraServerIssueId.split(integrationHash!) + const issue = await dataLoader.get('jiraServerIssue').load({ + teamId, + userId: accessUserId, + issueId, + providerId: integration.providerId + }) + return issue + ? { + ...issue, + userId: accessUserId, + teamId + } + : null + } else if (integration.service === 'azureDevOps') { + const {projectKey, issueKey} = integration + const {instanceId} = AzureDevOpsIssueId.split(integrationHash!) + return dataLoader.get('azureDevOpsWorkItem').load({ + teamId, + userId: accessUserId, + instanceId, + projectId: projectKey, + viewerId, + workItemId: issueKey + }) + } else if (integration.service === 'github') { + const githubAuth = await dataLoader.get('githubAuth').load({userId: accessUserId, teamId}) + if (!githubAuth) return null + const {accessToken} = githubAuth + const {nameWithOwner, issueNumber} = integration + const {repoOwner, repoName} = GitHubRepoId.split(nameWithOwner) + const query = ` + { + repository(owner: "${repoOwner}", name: "${repoName}") { + issue(number: ${issueNumber}) { + ...info + } + } + }` + const githubRequest = getGitHubRequest(info, context, {accessToken}) + const [data, error] = await githubRequest(query) + if (error) { + sendToSentry(error, {userId: accessUserId}) + } + return data + } else if (integration.service === 'gitlab') { + const {accessUserId} = integration + const gitlabAuth = await dataLoader + .get('freshGitlabAuth') + .load({teamId, userId: accessUserId}) + if (!gitlabAuth?.accessToken) return null + const {providerId} = gitlabAuth + const provider = await dataLoader.get('integrationProviders').load(providerId) + if (!provider?.serverBaseUrl) return null + const {gid} = integration + const query = ` + query { + issue(id: "${gid}"){ + ...info + } + } + ` + const manager = new GitLabServerManager(gitlabAuth, context, info, provider.serverBaseUrl) + const gitlabRequest = manager.getGitLabRequest(info, context) + const [data, error] = await gitlabRequest(query, {}) + if (error) { + sendToSentry(error, {userId: accessUserId}) + } + return data + } + return null + }, + + team: ({teamId}, _args, {dataLoader}) => { + return dataLoader.get('teams').loadNonNull(teamId) + }, + + title: ({plaintextContent}) => { + const firstBreak = plaintextContent.trim().indexOf('\n') + const endIndex = firstBreak > -1 ? firstBreak : plaintextContent.length + return plaintextContent.slice(0, endIndex) + }, + + user: ({userId}, _args, {dataLoader}) => { + if (!userId) return null + return dataLoader.get('users').loadNonNull(userId) + }, + + isHighlighted: async ({id: taskId}, {meetingId}, {dataLoader}) => { + if (!meetingId) return false + const highlightedTaskId = await dataLoader.get('meetingHighlightedTaskId').load(meetingId) + return taskId === highlightedTaskId + } +} + +export default Task diff --git a/packages/server/graphql/public/types/TaskEstimate.ts b/packages/server/graphql/public/types/TaskEstimate.ts new file mode 100644 index 00000000000..71d5f2900bd --- /dev/null +++ b/packages/server/graphql/public/types/TaskEstimate.ts @@ -0,0 +1,7 @@ +import {TaskEstimateResolvers} from '../resolverTypes' + +const TaskEstimate: TaskEstimateResolvers = { + label: ({label}) => label || '' +} + +export default TaskEstimate diff --git a/packages/server/graphql/public/types/TeamPromptMeeting.ts b/packages/server/graphql/public/types/TeamPromptMeeting.ts index d4c013869a8..87d921b26c7 100644 --- a/packages/server/graphql/public/types/TeamPromptMeeting.ts +++ b/packages/server/graphql/public/types/TeamPromptMeeting.ts @@ -1,4 +1,4 @@ -import getRethink from '../../../database/rethinkDriver' +import getKysely from '../../../postgres/getKysely' import {getTeamPromptResponsesByMeetingId} from '../../../postgres/queries/getTeamPromptResponsesByMeetingIds' import {selectNewMeetings} from '../../../postgres/select' import {TeamPromptMeeting as TeamPromptMeetingSource} from '../../../postgres/types/Meeting' @@ -68,6 +68,7 @@ const TeamPromptMeeting: TeamPromptMeetingResolvers = { }, taskCount: async ({id: meetingId}, _args, {dataLoader}) => { + const pg = getKysely() const meeting = await dataLoader.get('newMeetings').loadNonNull(meetingId) if (meeting.meetingType !== 'teamPrompt') { return 0 @@ -76,13 +77,12 @@ const TeamPromptMeeting: TeamPromptMeetingResolvers = { const discussPhase = getPhase(phases, 'RESPONSES') const {stages} = discussPhase const discussionIds = stages.map((stage) => stage.discussionId) - const r = await getRethink() - return r - .table('Task') - .getAll(r.args(discussionIds), {index: 'discussionId'}) - .count() - .default(0) - .run() + const taskCountRes = await pg + .selectFrom('Task') + .select(({fn}) => fn.count('id').as('count')) + .where('discussionId', 'in', discussionIds) + .executeTakeFirst() + return Number(taskCountRes?.count ?? 0) }, commentCount: async ({id: meetingId}, _args, {dataLoader}) => { diff --git a/packages/server/graphql/public/types/User.ts b/packages/server/graphql/public/types/User.ts index d736cb65a26..ab06417cd2a 100644 --- a/packages/server/graphql/public/types/User.ts +++ b/packages/server/graphql/public/types/User.ts @@ -1,4 +1,5 @@ import base64url from 'base64url' +import {sql} from 'kysely' import ms from 'ms' import DomainJoinRequestId from 'parabol-client/shared/gqlIds/DomainJoinRequestId' import MeetingMemberId from 'parabol-client/shared/gqlIds/MeetingMemberId' @@ -12,10 +13,10 @@ import { } from '../../../../client/utils/constants' import groupReflections from '../../../../client/utils/smartGroup/groupReflections' import getRethink from '../../../database/rethinkDriver' -import {RDatum, RValue} from '../../../database/stricterR' +import {RDatum} from '../../../database/stricterR' import MeetingTemplate from '../../../database/types/MeetingTemplate' -import Task from '../../../database/types/Task' import getKysely from '../../../postgres/getKysely' +import {selectTasks} from '../../../postgres/select' import {getUserId, isSuperUser, isTeamMember} from '../../../utils/authorization' import getDomainFromEmail from '../../../utils/getDomainFromEmail' import getMonthlyStreak from '../../../utils/getMonthlyStreak' @@ -82,8 +83,6 @@ const User: ReqResolvers<'User'> = { }, invoices, archivedTasks: async (_source, {first, after, teamId}, {authToken}) => { - const r = await getRethink() - // AUTH const userId = getUserId(authToken) if (!isTeamMember(authToken, teamId)) { @@ -92,25 +91,14 @@ const User: ReqResolvers<'User'> = { } // RESOLUTION - const teamMemberId = `${userId}::${teamId}` - const dbAfter = after ? new Date(after) : r.maxval - const tasks = await r - .table('Task') - // use a compound index so we can easily paginate later - .between([teamId, r.minval], [teamId, dbAfter], { - index: 'teamIdUpdatedAt' - }) - .filter((task: RValue) => - task('tags') - .contains('archived') - .and( - r.branch(task('tags').contains('private'), task('teamMemberId').eq(teamMemberId), true) - ) - ) - .orderBy(r.desc('updatedAt')) + const tasks = await selectTasks() + .where('teamId', '=', teamId) + .$if(!!after, (qb) => qb.where('updatedAt', '<=', after!)) + .where(sql`'archived' = ANY(tags)`) + .where(({eb, or}) => or([sql`'private' != ALL(tags)`, eb('userId', '=', userId)])) + .orderBy('updatedAt desc') .limit(first + 1) - .coerceTo('array') - .run() + .execute() const nodes = tasks.slice(0, first) const edges = nodes.map((node) => ({ @@ -129,7 +117,7 @@ const User: ReqResolvers<'User'> = { } }, archivedTasksCount: async (_source, {teamId}, {authToken}) => { - const r = await getRethink() + const pg = getKysely() const viewerId = getUserId(authToken) // AUTH @@ -140,21 +128,14 @@ const User: ReqResolvers<'User'> = { } // RESOLUTION - const teamMemberId = `${userId}::${teamId}` - return r - .table('Task') - .between([teamId, r.minval], [teamId, r.maxval], { - index: 'teamIdUpdatedAt' - }) - .filter((task: RValue) => - task('tags') - .contains('archived') - .and( - r.branch(task('tags').contains('private'), task('teamMemberId').eq(teamMemberId), true) - ) - ) - .count() - .run() + const taskCount = await pg + .selectFrom('Task') + .select(({fn}) => fn.count('id').as('count')) + .where('teamId', '=', teamId) + .where(sql`'archived' = ANY(tags)`) + .where(({eb, or}) => or([sql`'private' != ALL(tags)`, eb('userId', '=', userId)])) + .executeTakeFirstOrThrow() + return Number(taskCount.count) }, meeting: async (_source, {meetingId}, {authToken, dataLoader}) => { const viewerId = getUserId(authToken) @@ -253,7 +234,7 @@ const User: ReqResolvers<'User'> = { filterQuery, includeUnassigned }) - const filteredTasks = tasks.filter((task: Task) => { + const filteredTasks = tasks.filter((task) => { if (isTaskPrivate(task.tags) && task.userId !== viewerId) return false return true }) diff --git a/packages/server/graphql/queries/helpers/connectionFromTasks.ts b/packages/server/graphql/queries/helpers/connectionFromTasks.ts index c0301af54c7..b34f65fd485 100644 --- a/packages/server/graphql/queries/helpers/connectionFromTasks.ts +++ b/packages/server/graphql/queries/helpers/connectionFromTasks.ts @@ -1,5 +1,5 @@ import {Threshold} from 'parabol-client/types/constEnums' -import Task from '../../../database/types/Task' +import {Task} from '../../../postgres/types' const connectionFromTasks = ( tasks: T[], diff --git a/packages/server/graphql/resolvers.ts b/packages/server/graphql/resolvers.ts index a038a5f7c95..c1be3d60a38 100644 --- a/packages/server/graphql/resolvers.ts +++ b/packages/server/graphql/resolvers.ts @@ -4,10 +4,9 @@ import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import {NewMeetingPhaseTypeEnum} from '../database/types/GenericMeetingPhase' import GenericMeetingStage from '../database/types/GenericMeetingStage' import Organization from '../database/types/Organization' -import Task from '../database/types/Task' import User from '../database/types/User' import {Loaders} from '../dataloader/RootDataLoader' -import {Team, TeamMember} from '../postgres/types' +import {Task, Team, TeamMember} from '../postgres/types' import {AnyMeeting} from '../postgres/types/Meeting' import {getUserId, isSuperUser, isUserBillingLeader} from '../utils/authorization' import {GQLContext} from './graphql' diff --git a/packages/server/graphql/resolvers/resolveThreadableConnection.ts b/packages/server/graphql/resolvers/resolveThreadableConnection.ts index f553886f470..723f1af3d7f 100644 --- a/packages/server/graphql/resolvers/resolveThreadableConnection.ts +++ b/packages/server/graphql/resolvers/resolveThreadableConnection.ts @@ -1,5 +1,4 @@ -import TaskDB from '../../database/types/Task' -import {Comment} from '../../postgres/types' +import {Comment, Task} from '../../postgres/types' import {ThreadableSource} from '../public/types/Threadable' import {DataLoaderWorker} from './../graphql' @@ -22,7 +21,7 @@ const resolveThreadableConnection = async ( const {threadParentId} = threadable if (!threadParentId) { rootThreadables.push(threadable) - } else if ((threadable as TaskDB).status || (threadable as Comment).isActive) { + } else if ((threadable as Task).status || (threadable as Comment).isActive) { // if it's a task or it's a non-deleted comment, add it threadablesByParentId[threadParentId] = threadablesByParentId[threadParentId] || [] threadablesByParentId[threadParentId]!.push(threadable) @@ -33,7 +32,7 @@ const resolveThreadableConnection = async ( rootThreadables.forEach((threadable) => { const {id: threadableId} = threadable const replies = threadablesByParentId[threadableId] - const isActive = (threadable as TaskDB).status || (threadable as Comment).isActive + const isActive = (threadable as Task).status || (threadable as Comment).isActive // (threadable as Poll).deletedAt === null if (!isActive && !replies) return filteredThreadables.push(threadable) diff --git a/packages/server/graphql/types/EndCheckInPayload.ts b/packages/server/graphql/types/EndCheckInPayload.ts index 82a922729aa..81e648962a9 100644 --- a/packages/server/graphql/types/EndCheckInPayload.ts +++ b/packages/server/graphql/types/EndCheckInPayload.ts @@ -1,8 +1,8 @@ import {GraphQLBoolean, GraphQLID, GraphQLList, GraphQLNonNull, GraphQLObjectType} from 'graphql' import isTaskPrivate from 'parabol-client/utils/isTaskPrivate' import {getUserId} from '../../utils/authorization' -import errorFilter from '../errorFilter' import {GQLContext} from '../graphql' +import isValid from '../isValid' import {resolveNewMeeting} from '../resolvers' import ActionMeeting from './ActionMeeting' import Task from './Task' @@ -51,7 +51,7 @@ export const EndCheckInSuccess = new GraphQLObjectType({ if (!updatedTaskIds) return [] const viewerId = getUserId(authToken) const allUpdatedTasks = (await dataLoader.get('tasks').loadMany(updatedTaskIds)).filter( - errorFilter + isValid ) return allUpdatedTasks.filter((task) => { return isTaskPrivate(task.tags) ? task.userId === viewerId : true diff --git a/packages/server/graphql/types/PokerMeeting.ts b/packages/server/graphql/types/PokerMeeting.ts index bbea73c760f..dfb66332669 100644 --- a/packages/server/graphql/types/PokerMeeting.ts +++ b/packages/server/graphql/types/PokerMeeting.ts @@ -38,7 +38,7 @@ const PokerMeeting = new GraphQLObjectType({ } }, resolve: async ({id: meetingId}, {storyId: taskId}, {dataLoader}) => { - const task = await dataLoader.get('tasks').load(taskId) + const task = await dataLoader.get('tasks').loadNonNull(taskId) if (task.meetingId !== meetingId) { Logger.log('naughty storyId supplied to PokerMeeting') return null diff --git a/packages/server/graphql/types/Task.ts b/packages/server/graphql/types/Task.ts index d2a8b1b7ad8..abcbcee05e7 100644 --- a/packages/server/graphql/types/Task.ts +++ b/packages/server/graphql/types/Task.ts @@ -1,344 +1,14 @@ -import { - GraphQLBoolean, - GraphQLFloat, - GraphQLID, - GraphQLList, - GraphQLNonNull, - GraphQLObjectType, - GraphQLString -} from 'graphql' -import AzureDevOpsIssueId from 'parabol-client/shared/gqlIds/AzureDevOpsIssueId' -import JiraServerIssueId from '~/shared/gqlIds/JiraServerIssueId' -import GitHubRepoId from '../../../client/shared/gqlIds/GitHubRepoId' -import DBTask from '../../database/types/Task' -import GitLabServerManager from '../../integrations/gitlab/GitLabServerManager' -import getSimilarTaskEstimate from '../../postgres/queries/getSimilarTaskEstimate' -import insertTaskEstimate from '../../postgres/queries/insertTaskEstimate' -import {GetIssueLabelsQuery, GetIssueLabelsQueryVariables} from '../../types/githubTypes' -import {getUserId} from '../../utils/authorization' -import getGitHubRequest from '../../utils/getGitHubRequest' -import getIssueLabels from '../../utils/githubQueries/getIssueLabels.graphql' -import sendToSentry from '../../utils/sendToSentry' +import {GraphQLObjectType} from 'graphql' import connectionDefinitions from '../connectionDefinitions' -import {GQLContext} from '../graphql' -import isValid from '../isValid' -import {IGetLatestTaskEstimatesQueryResult} from './../../postgres/queries/generated/getLatestTaskEstimatesQuery' -import AgendaItem from './AgendaItem' import GraphQLISO8601Type from './GraphQLISO8601Type' import PageInfoDateCursor from './PageInfoDateCursor' -import TaskEditorDetails from './TaskEditorDetails' -import TaskEstimate from './TaskEstimate' -import TaskIntegration from './TaskIntegration' -import TaskServiceEnum from './TaskServiceEnum' -import TaskStatusEnum from './TaskStatusEnum' -import Team from './Team' -const Task: GraphQLObjectType = new GraphQLObjectType({ +const Task: GraphQLObjectType = new GraphQLObjectType({ name: 'Task', - description: 'A long-term task shared across the team, assigned to a single user ', - isTypeOf: ({status}) => !!status, - fields: () => ({ - agendaItem: { - type: AgendaItem, - description: 'The agenda item that the task was created in, if any', - resolve: async ({discussionId}, _args: unknown, {dataLoader}) => { - if (!discussionId) return null - const discussion = await dataLoader.get('discussions').load(discussionId) - if (!discussion) return null - const {discussionTopicId, discussionTopicType} = discussion - if (discussionTopicType !== 'agendaItem') return null - return dataLoader.get('agendaItems').load(discussionTopicId) - } - }, - taskService: { - type: TaskServiceEnum, - description: 'Type of the integration if there is one', - resolve: ({integration}: DBTask, _args: unknown) => { - return integration?.service - } - }, - createdBy: { - type: new GraphQLNonNull(GraphQLID), - description: 'The userId that created the item' - }, - createdByUser: { - type: new GraphQLNonNull(require('./User').default), - description: 'The user that created the item', - resolve: ({createdBy}, _args: unknown, {dataLoader}: GQLContext) => { - return dataLoader.get('users').load(createdBy) - } - }, - dueDate: { - type: GraphQLISO8601Type, - description: 'a user-defined due date' - }, - estimates: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(TaskEstimate))), - description: 'A list of the most recent estimates for the task', - resolve: async ({id: taskId, integration, teamId}: DBTask, _args: unknown, context, info) => { - const {dataLoader, authToken} = context - const viewerId = getUserId(authToken) - if (integration?.service === 'jira') { - const {accessUserId, cloudId, issueKey} = integration - // this dataloader has the side effect of guaranteeing fresh estimates - await dataLoader - .get('jiraIssue') - .load({teamId, userId: accessUserId, cloudId, issueKey, taskId, viewerId}) - } else if (integration?.service === 'azureDevOps') { - const {accessUserId, instanceId, projectKey, issueKey} = integration - await dataLoader.get('azureDevOpsWorkItem').load({ - teamId, - userId: accessUserId, - instanceId, - workItemId: issueKey, - taskId, - projectId: projectKey, - viewerId - }) - } else if (integration?.service === 'github') { - const {accessUserId, nameWithOwner, issueNumber} = integration - const [githubAuth, estimates] = await Promise.all([ - dataLoader.get('githubAuth').load({userId: accessUserId, teamId}), - dataLoader.get('latestTaskEstimates').load(taskId) - ]) - if (estimates.length === 0) return estimates - // TODO schedule this work to be done & pump in the updates via subcription - if (!githubAuth) return estimates - // fetch fresh estimates from GH - const {accessToken} = githubAuth - const {repoOwner, repoName} = GitHubRepoId.split(nameWithOwner) - const githubRequest = getGitHubRequest(info, context, {accessToken}) - const [labelsData, labelsError] = await githubRequest< - GetIssueLabelsQuery, - GetIssueLabelsQueryVariables - >(getIssueLabels, { - first: 100, - repoName, - repoOwner, - issueNumber - }) - if (!labelsData) { - if (labelsError) { - sendToSentry(labelsError, {userId: accessUserId}) - } - return estimates - } - const labelNodes = labelsData.repository?.issue?.labels?.nodes - if (!labelNodes) return estimates - const ghIssueLabels = labelNodes.map((node) => node?.name).filter(isValid) - await Promise.all( - estimates.map(async (estimate: IGetLatestTaskEstimatesQueryResult) => { - const {githubLabelName, name: dimensionName} = estimate - const existingLabel = ghIssueLabels.includes(githubLabelName!) - if (existingLabel) return - // VERY EXPENSIVE. We do this only if we're darn sure we need to - const taskIds = await dataLoader - .get('taskIdsByTeamAndGitHubRepo') - .load({teamId, nameWithOwner}) - const similarEstimate = await getSimilarTaskEstimate( - taskIds, - dimensionName, - ghIssueLabels - ) - if (!similarEstimate) return - dataLoader.get('latestTaskEstimates').clear(taskId) - return insertTaskEstimate({ - changeSource: 'external', - // keep the link to the discussion alive, if possible - discussionId: estimate.discussionId, - jiraFieldId: undefined, - label: similarEstimate.label, - name: estimate.name, - meetingId: null, - stageId: null, - taskId, - userId: accessUserId, - githubLabelName: similarEstimate.githubLabelName! - }) - }) - ) - } - return dataLoader.get('latestTaskEstimates').load(taskId) - } - }, - editors: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(TaskEditorDetails))), - description: - 'a list of users currently editing the task (fed by a subscription, so queries return null)', - resolve: (source: any) => source.editors ?? [] - }, - integration: { - type: TaskIntegration, - description: 'The reference to the single source of truth for this task', - resolve: async ( - {integration, integrationHash, teamId, id: taskId}: DBTask, - _args: unknown, - context, - info - ) => { - const {dataLoader, authToken} = context - const viewerId = getUserId(authToken) - if (!integration) return null - const {accessUserId} = integration - if (integration.service === 'jira') { - const {cloudId, issueKey} = integration - return dataLoader - .get('jiraIssue') - .load({teamId, userId: accessUserId, cloudId, issueKey, taskId, viewerId}) - } else if (integration.service === 'jiraServer') { - const {issueId} = JiraServerIssueId.split(integrationHash!) - const issue = await dataLoader.get('jiraServerIssue').load({ - teamId, - userId: accessUserId, - issueId, - providerId: integration.providerId - }) - return issue - ? { - ...issue, - userId: accessUserId, - teamId - } - : null - } else if (integration.service === 'azureDevOps') { - const {projectKey, issueKey} = integration - const {instanceId} = AzureDevOpsIssueId.split(integrationHash!) - return dataLoader.get('azureDevOpsWorkItem').load({ - teamId, - userId: accessUserId, - instanceId, - projectId: projectKey, - viewerId, - workItemId: issueKey - }) - } else if (integration.service === 'github') { - const githubAuth = await dataLoader.get('githubAuth').load({userId: accessUserId, teamId}) - if (!githubAuth) return null - const {accessToken} = githubAuth - const {nameWithOwner, issueNumber} = integration - const {repoOwner, repoName} = GitHubRepoId.split(nameWithOwner) - const query = ` - { - repository(owner: "${repoOwner}", name: "${repoName}") { - issue(number: ${issueNumber}) { - ...info - } - } - }` - const githubRequest = getGitHubRequest(info, context, {accessToken}) - const [data, error] = await githubRequest(query) - if (error) { - sendToSentry(error, {userId: accessUserId}) - } - return data - } else if (integration.service === 'gitlab') { - const {accessUserId} = integration - const gitlabAuth = await dataLoader - .get('freshGitlabAuth') - .load({teamId, userId: accessUserId}) - if (!gitlabAuth?.accessToken) return null - const {providerId} = gitlabAuth - const provider = await dataLoader.get('integrationProviders').load(providerId) - if (!provider?.serverBaseUrl) return null - const {gid} = integration - const query = ` - query { - issue(id: "${gid}"){ - ...info - } - } - ` - const manager = new GitLabServerManager(gitlabAuth, context, info, provider.serverBaseUrl) - const gitlabRequest = manager.getGitLabRequest(info, context) - const [data, error] = await gitlabRequest(query, {}) - if (error) { - sendToSentry(error, {userId: accessUserId}) - } - return data - } - return null - } - }, - integrationHash: { - type: GraphQLID, - description: 'A hash of the integrated task' - }, - meetingId: { - type: GraphQLID, - description: 'the foreign key for the meeting the task was created in' - }, - doneMeetingId: { - type: GraphQLID, - description: 'the foreign key for the meeting the task was marked as complete' - }, - plaintextContent: { - type: new GraphQLNonNull(GraphQLString), - description: 'the plain text content of the task' - }, - sortOrder: { - type: new GraphQLNonNull(GraphQLFloat), - description: 'the shared sort order for tasks on the team dash & user dash' - }, - status: { - type: new GraphQLNonNull(TaskStatusEnum), - description: 'The status of the task' - }, - tags: { - type: new GraphQLNonNull(new GraphQLList(new GraphQLNonNull(GraphQLString))), - description: 'The tags associated with the task' - }, - teamId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The id of the team (indexed). Needed for subscribing to archived tasks' - }, - team: { - type: new GraphQLNonNull(Team), - description: 'The team this task belongs to', - resolve: ({teamId}, _args: unknown, {dataLoader}) => { - return dataLoader.get('teams').load(teamId) - } - }, - title: { - type: new GraphQLNonNull(GraphQLString), - description: 'The first block of the content', - resolve: ({plaintextContent}) => { - const firstBreak = plaintextContent.trim().indexOf('\n') - const endIndex = firstBreak > -1 ? firstBreak : plaintextContent.length - return plaintextContent.slice(0, endIndex) - } - }, - userId: { - type: GraphQLID, - description: - '* The userId, index useful for server-side methods getting all tasks under a user. This can be null if the task is not assigned to anyone.' - }, - user: { - type: require('./User').default, - description: 'The user the task is assigned to. Null if it is not assigned to anyone.', - resolve: ({userId}, _args: unknown, {dataLoader}) => { - if (!userId) return null - return dataLoader.get('users').load(userId) - } - }, - isHighlighted: { - type: new GraphQLNonNull(GraphQLBoolean), - description: 'The owner hovers over the task in their solo update of a checkin', - args: { - meetingId: { - type: GraphQLID, - description: 'Meeting for which the highlight is checked' - } - }, - resolve: async ({id: taskId}, {meetingId}: {meetingId?: string | null}, {dataLoader}) => { - if (!meetingId) return false - const highlightedTaskId = await dataLoader.get('meetingHighlightedTaskId').load(meetingId) - return taskId === highlightedTaskId - } - } - }) + fields: {} }) -const {connectionType, edgeType} = connectionDefinitions({ +const {connectionType} = connectionDefinitions({ name: Task.name, nodeType: Task, edgeFields: () => ({ @@ -355,5 +25,4 @@ const {connectionType, edgeType} = connectionDefinitions({ }) export const TaskConnection = connectionType -export const TaskEdge = edgeType export default Task diff --git a/packages/server/graphql/types/TaskEstimate.ts b/packages/server/graphql/types/TaskEstimate.ts deleted file mode 100644 index dc09b2d4ba9..00000000000 --- a/packages/server/graphql/types/TaskEstimate.ts +++ /dev/null @@ -1,57 +0,0 @@ -import {GraphQLID, GraphQLNonNull, GraphQLObjectType, GraphQLString} from 'graphql' -import {GQLContext} from '../graphql' -import ChangeSourceEnum from './ChangeSourceEnum' -import GraphQLISO8601Type from './GraphQLISO8601Type' - -const TaskEstimate = new GraphQLObjectType({ - name: 'TaskEstimate', - description: 'An estimate for a Task that was voted on and scored in a poker meeting', - fields: () => ({ - id: { - type: new GraphQLNonNull(GraphQLID), - description: 'The ID of the estimate' - }, - createdAt: { - type: new GraphQLNonNull(GraphQLISO8601Type), - description: 'The timestamp the estimate was created' - }, - changeSource: { - type: new GraphQLNonNull(ChangeSourceEnum), - description: 'The source that a change came in through' - }, - name: { - type: new GraphQLNonNull(GraphQLString), - description: 'The name of the estimate dimension' - }, - label: { - type: new GraphQLNonNull(GraphQLString), - description: 'The human-readable label for the estimate', - resolve: ({label}) => label || '' - }, - taskId: { - type: new GraphQLNonNull(GraphQLID), - description: '*The taskId that the estimate refers to' - }, - userId: { - type: new GraphQLNonNull(GraphQLID), - description: 'The userId that added the estimate' - }, - meetingId: { - type: GraphQLID, - description: '*The meetingId that the estimate occured in, if any' - }, - stageId: { - type: GraphQLID, - description: 'The meeting stageId the estimate occurred in, if any' - }, - discussionId: { - type: GraphQLID, - description: 'The discussionId where the estimated was discussed' - }, - jiraFieldId: { - type: GraphQLID, - description: 'If the task comes from jira, this is the jira field that the estimate refers to' - } - }) -}) -export default TaskEstimate diff --git a/packages/server/graphql/types/Team.ts b/packages/server/graphql/types/Team.ts index 7a92925c44a..bf24dfb0207 100644 --- a/packages/server/graphql/types/Team.ts +++ b/packages/server/graphql/types/Team.ts @@ -10,7 +10,6 @@ import { import isTaskPrivate from 'parabol-client/utils/isTaskPrivate' import toTeamMemberId from 'parabol-client/utils/relay/toTeamMemberId' import {Security, Threshold} from '../../../client/types/constEnums' -import Task from '../../database/types/Task' import ITeam from '../../database/types/Team' import generateRandomString from '../../generateRandomString' import getKysely from '../../postgres/getKysely' @@ -296,7 +295,7 @@ const Team: GraphQLObjectType = new GraphQLObjectType({ } const viewerId = getUserId(authToken) const allTasks = await dataLoader.get('tasksByTeamId').load(teamId) - const tasks = allTasks.filter((task: Task) => { + const tasks = allTasks.filter((task) => { if (!task.userId || (isTaskPrivate(task.tags) && task.userId !== viewerId)) return false return true }) diff --git a/packages/server/integrations/TaskIntegrationManagerFactory.ts b/packages/server/integrations/TaskIntegrationManagerFactory.ts index eeaed5683d7..44f67180886 100644 --- a/packages/server/integrations/TaskIntegrationManagerFactory.ts +++ b/packages/server/integrations/TaskIntegrationManagerFactory.ts @@ -1,11 +1,11 @@ import {GraphQLResolveInfo} from 'graphql' -import {TaskIntegration} from '../database/types/Task' import {DataLoaderWorker, GQLContext} from '../graphql/graphql' import {IntegrationProviderServiceEnumType} from '../graphql/types/IntegrationProviderServiceEnum' import { IntegrationProviderAzureDevOps, IntegrationProviderJiraServer } from '../postgres/queries/getIntegrationProvidersByIds' +import {Task} from '../postgres/types' import AzureDevOpsServerManager from '../utils/AzureDevOpsServerManager' import GitHubServerManager from './github/GitHubServerManager' import GitLabServerManager from './gitlab/GitLabServerManager' @@ -18,7 +18,7 @@ export type CreateTaskResponse = // TODO: include issueId for GitHub in hash or store integration.issueId for all integrations // See https://github.com/ParabolInc/parabol/issues/6252 issueId: string - integration: TaskIntegration + integration: NonNullable } | Error diff --git a/packages/server/postgres/select.ts b/packages/server/postgres/select.ts index 7a679f8b91e..f73831e9abe 100644 --- a/packages/server/postgres/select.ts +++ b/packages/server/postgres/select.ts @@ -2,7 +2,7 @@ import type {JSONContent} from '@tiptap/core' import {NotNull, sql} from 'kysely' import {NewMeetingPhaseTypeEnum} from '../graphql/public/resolverTypes' import getKysely from './getKysely' -import {ReactjiDB} from './types' +import {ReactjiDB, TaskTag} from './types' import {AnyMeeting, AnyMeetingMember} from './types/Meeting' import {AnyTaskIntegration} from './types/TaskIntegration' export const selectTimelineEvent = () => { @@ -292,7 +292,6 @@ export const selectTasks = () => .selectFrom('Task') .select(({fn}) => [ 'id', - 'content', 'createdAt', 'createdBy', 'doneMeetingId', @@ -302,12 +301,14 @@ export const selectTasks = () => 'plaintextContent', 'sortOrder', 'status', - 'tags', 'teamId', 'discussionId', 'threadParentId', 'threadSortOrder', 'updatedAt', 'userId', + // this is to match the previous behavior, no reason we couldn't export as json in the future + sql`content::text`.as('content'), + fn('to_json', ['tags']).as('tags'), fn('to_json', ['integration']).as('integration') ]) diff --git a/packages/server/postgres/types/TaskIntegration.d.ts b/packages/server/postgres/types/TaskIntegration.d.ts index 75ca80ad75f..06014b1401e 100644 --- a/packages/server/postgres/types/TaskIntegration.d.ts +++ b/packages/server/postgres/types/TaskIntegration.d.ts @@ -42,3 +42,5 @@ export type AnyTaskIntegration = | TaskIntegrationGitHub | TaskIntegrationGitLab | TaskIntegrationAzureDevOps + +export type TaskServiceEnum = AnyTaskIntegration['service'] | 'PARABOL' diff --git a/packages/server/postgres/types/index.d.ts b/packages/server/postgres/types/index.d.ts index 11efe61389e..a6cf8481192 100644 --- a/packages/server/postgres/types/index.d.ts +++ b/packages/server/postgres/types/index.d.ts @@ -3,6 +3,7 @@ import { Discussion as DiscussionPG, Insight as InsightPG, OrganizationUser as OrganizationUserPG, + TaskEstimate as TaskEstimatePG, TeamMember as TeamMemberPG } from '../pg.d' import { @@ -17,6 +18,7 @@ import { selectSlackAuths, selectSlackNotifications, selectSuggestedAction, + selectTasks, selectTeamInvitations, selectTeamPromptResponses, selectTeams, @@ -41,6 +43,8 @@ export type AutogroupReflectionGroupType = { reflectionIds: string[] } +export type TaskTag = 'private' | 'archived' + export interface Organization extends ExtractTypeFromQueryBuilderSelect {} export type OrganizationUser = Selectable @@ -78,3 +82,5 @@ export type Insight = Selectable export type NewMeeting = ExtractTypeFromQueryBuilderSelect export type NewFeature = ExtractTypeFromQueryBuilderSelect export type TeamInvitation = ExtractTypeFromQueryBuilderSelect +export type Task = ExtractTypeFromQueryBuilderSelect +export type TaskEstimate = Selectable diff --git a/packages/server/safeMutations/archiveTasksForDB.ts b/packages/server/safeMutations/archiveTasksForDB.ts index 4197a64d685..18b005cdf88 100644 --- a/packages/server/safeMutations/archiveTasksForDB.ts +++ b/packages/server/safeMutations/archiveTasksForDB.ts @@ -1,14 +1,12 @@ import {convertFromRaw, convertToRaw} from 'draft-js' import addTagToTask from 'parabol-client/utils/draftjs/addTagToTask' import getTagsFromEntityMap from 'parabol-client/utils/draftjs/getTagsFromEntityMap' -import getRethink from '../database/rethinkDriver' -import Task from '../database/types/Task' import getKysely from '../postgres/getKysely' +import {Task} from '../postgres/types/index.d' const archiveTasksForDB = async (tasks: Task[], doneMeetingId?: string) => { if (!tasks || tasks.length === 0) return [] const pg = getKysely() - const r = await getRethink() const tasksToArchive = tasks.map((task) => { const contentState = convertFromRaw(JSON.parse(task.content)) const nextContentState = addTagToTask(contentState, '#archived') @@ -36,22 +34,7 @@ const archiveTasksForDB = async (tasks: Task[], doneMeetingId?: string) => { .execute() ) ) - return r(tasksToArchive) - .forEach((task) => { - return r - .table('Task') - .get(task('id')) - .update( - { - content: task('content') as unknown, - tags: task('tags'), - doneMeetingId: task('doneMeetingId').default(null) - } as any, - {returnChanges: true} - ) - }) - .default([])('changes')('new_val') - .run() as Promise + return tasksToArchive.map(({id}) => id) } export default archiveTasksForDB diff --git a/packages/server/utils/analytics/analytics.ts b/packages/server/utils/analytics/analytics.ts index 0c32e1b0881..7a5cbb0817e 100644 --- a/packages/server/utils/analytics/analytics.ts +++ b/packages/server/utils/analytics/analytics.ts @@ -4,7 +4,6 @@ import TeamPromptResponseId from '../../../client/shared/gqlIds/TeamPromptRespon import {PARABOL_AI_USER_ID} from '../../../client/utils/constants' import {TeamLimitsEmailType} from '../../billing/helpers/sendTeamsLimitEmail' import MeetingTemplate from '../../database/types/MeetingTemplate' -import {TaskServiceEnum} from '../../database/types/Task' import {DataLoaderWorker} from '../../graphql/graphql' import {ModifyType, ReactableEnum} from '../../graphql/public/resolverTypes' import {IntegrationProviderServiceEnumType} from '../../graphql/types/IntegrationProviderServiceEnum' @@ -16,6 +15,7 @@ import { RetrospectiveMeeting } from '../../postgres/types/Meeting' import {MeetingSeries} from '../../postgres/types/MeetingSeries' +import {TaskServiceEnum} from '../../postgres/types/TaskIntegration' import {AmplitudeAnalytics} from './amplitude/AmplitudeAnalytics' import {createMeetingProperties} from './helpers' export type AnalyticsUser = { diff --git a/packages/server/utils/filterTasksByMeeting.ts b/packages/server/utils/filterTasksByMeeting.ts index 719f534c4ed..e99c5ecba43 100644 --- a/packages/server/utils/filterTasksByMeeting.ts +++ b/packages/server/utils/filterTasksByMeeting.ts @@ -1,5 +1,5 @@ import isTaskPrivate from 'parabol-client/utils/isTaskPrivate' -import Task from '../database/types/Task' +import {Task} from '../postgres/types' const filterTasksByMeeting = (tasks: Task[], meetingId: string, viewerId: string) => { return tasks.filter((task) => { From dd446d050258e35d37a04a863e3131bafc85401c Mon Sep 17 00:00:00 2001 From: "parabol-release-bot[bot]" <150284312+parabol-release-bot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 16:28:09 -0700 Subject: [PATCH 529/529] chore(release): release v7.51.0 (#10355) Co-authored-by: parabol-release-bot[bot] <150284312+parabol-release-bot[bot]@users.noreply.github.com> --- .release-please-manifest.json | 2 +- CHANGELOG.md | 15 +++++++++++++++ package.json | 2 +- packages/chronos/package.json | 4 ++-- packages/client/package.json | 2 +- packages/embedder/package.json | 2 +- packages/gql-executor/package.json | 6 +++--- packages/integration-tests/package.json | 2 +- packages/server/package.json | 4 ++-- 9 files changed, 27 insertions(+), 12 deletions(-) diff --git a/.release-please-manifest.json b/.release-please-manifest.json index a9bdd96471e..12e80a34325 100644 --- a/.release-please-manifest.json +++ b/.release-please-manifest.json @@ -1,3 +1,3 @@ { - ".": "7.50.12" + ".": "7.51.0" } diff --git a/CHANGELOG.md b/CHANGELOG.md index fdd6390a0f5..8ee71fda332 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,21 @@ This project adheres to [Semantic Versioning](http://semver.org/). This CHANGELOG follows conventions [outlined here](http://keepachangelog.com/). +## [7.51.0](https://github.com/ParabolInc/parabol/compare/v7.50.12...v7.51.0) (2024-10-16) + + +### Added + +* add Insights UI skeleton ([#10254](https://github.com/ParabolInc/parabol/issues/10254)) ([aa8e931](https://github.com/ParabolInc/parabol/commit/aa8e9313603d081e445d5b6193059d27dfdcb268)) +* **orgAdmin:** Add org admin teaser in org team page for non-enterprise orgs ([#10253](https://github.com/ParabolInc/parabol/issues/10253)) ([ca069db](https://github.com/ParabolInc/parabol/commit/ca069dbd97e3f33e81983298676aea748eda0664)) +* show default insight ([#10283](https://github.com/ParabolInc/parabol/issues/10283)) ([f99e63a](https://github.com/ParabolInc/parabol/commit/f99e63a3f05a4f2b863e4b3052950de7d02cce5e)) + + +### Changed + +* remove feature flag owner ([#10319](https://github.com/ParabolInc/parabol/issues/10319)) ([dfdb68c](https://github.com/ParabolInc/parabol/commit/dfdb68cd25c4b9fb49ea1bf6a5e7cfa775151d21)) +* **rethinkdb:** Task: Phase 3 ([#10339](https://github.com/ParabolInc/parabol/issues/10339)) ([7965ab6](https://github.com/ParabolInc/parabol/commit/7965ab691f3d4dc9599d9b958ecebc2c46649fad)) + ## [7.50.12](https://github.com/ParabolInc/parabol/compare/v7.50.11...v7.50.12) (2024-10-16) diff --git a/package.json b/package.json index dde7aa8cf65..d554676251d 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.50.12", + "version": "7.51.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/chronos/package.json b/packages/chronos/package.json index 2562d054e07..a20a8acd318 100644 --- a/packages/chronos/package.json +++ b/packages/chronos/package.json @@ -1,6 +1,6 @@ { "name": "chronos", - "version": "7.50.12", + "version": "7.51.0", "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.50.12" + "parabol-server": "7.51.0" } } diff --git a/packages/client/package.json b/packages/client/package.json index cc0ed6b3754..054e0f91bf7 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.50.12", + "version": "7.51.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" diff --git a/packages/embedder/package.json b/packages/embedder/package.json index 5f1f318abb9..57572d3d094 100644 --- a/packages/embedder/package.json +++ b/packages/embedder/package.json @@ -1,6 +1,6 @@ { "name": "parabol-embedder", - "version": "7.50.12", + "version": "7.51.0", "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 40cfec39792..87b811ef647 100644 --- a/packages/gql-executor/package.json +++ b/packages/gql-executor/package.json @@ -1,6 +1,6 @@ { "name": "gql-executor", - "version": "7.50.12", + "version": "7.51.0", "description": "A Stateless GraphQL Executor", "author": "Matt Krick ", "homepage": "https://github.com/ParabolInc/parabol/tree/master/packages/gqlExecutor#readme", @@ -26,8 +26,8 @@ }, "dependencies": { "dd-trace": "^5.0.0", - "parabol-client": "7.50.12", - "parabol-server": "7.50.12", + "parabol-client": "7.51.0", + "parabol-server": "7.51.0", "undici": "^5.26.2" } } diff --git a/packages/integration-tests/package.json b/packages/integration-tests/package.json index 7640239fdc0..027d44cc8a3 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.50.12", + "version": "7.51.0", "description": "", "main": "index.js", "scripts": { diff --git a/packages/server/package.json b/packages/server/package.json index 283dcb7a546..86ca13b2b0e 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.50.12", + "version": "7.51.0", "repository": { "type": "git", "url": "https://github.com/ParabolInc/parabol" @@ -122,7 +122,7 @@ "oauth-1.0a": "^2.2.6", "openai": "^4.53.0", "oy-vey": "^0.12.1", - "parabol-client": "7.50.12", + "parabol-client": "7.51.0", "pg": "^8.5.1", "react": "^17.0.2", "react-dom": "^17.0.2",