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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] 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/183] [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/183] 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/183] 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/183] [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/183] 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/183] [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/183] [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/183] 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/183] 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/183] 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/183] 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