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 01/80] 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 02/80] 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 03/80] 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 04/80] 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 05/80] 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 06/80] 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 07/80] 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 08/80] 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 09/80] 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 10/80] 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 11/80] 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 12/80] 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 13/80] 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 14/80] 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 15/80] 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 16/80] 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 17/80] 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 18/80] 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 19/80] 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 20/80] 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 21/80] 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 22/80] 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 23/80] 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 24/80] 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 25/80] 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 26/80] 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 27/80] 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 28/80] 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 29/80] 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 30/80] 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 31/80] 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 32/80] 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 33/80] 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 34/80] 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 35/80] 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 36/80] 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 37/80] 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 38/80] 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 39/80] 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 40/80] 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 41/80] 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 42/80] 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 43/80] 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 44/80] 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 45/80] 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 46/80] 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 47/80] 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 48/80] 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 49/80] 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 50/80] 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 51/80] 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 57/80] 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 58/80] 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 59/80] 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 60/80] 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 61/80] 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 62/80] 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 63/80] 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 64/80] 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 65/80] 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 66/80] 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 67/80] 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 68/80] 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 69/80] 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 70/80] 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 71/80] 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 72/80] 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 73/80] 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 74/80] 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 75/80] 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 76/80] 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 77/80] 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 78/80] 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 79/80] 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 80/80] 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",