From 662ec2bee8ecbb71ce2d6e5718450130eee8bcac Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Mon, 4 Dec 2023 10:32:56 +0000 Subject: [PATCH 1/5] feat: update activity library card UI (#9168) * show title and type below activity card * make activity library card dynamic * add retro background swirls * add background images for all meeting types * fix custom card size * truncate text if there is no space * clean up * use grape 100 * add premortem and postmortem imgs * remove bg from categories themes * move background img to div --- .../ActivityLibrary/ActivityCard.tsx | 77 ++++++++++-------- .../ActivityDetails/ActivityDetails.tsx | 7 +- .../ActivityDetailsCategoryBadge.tsx | 7 +- .../ActivityLibrary/ActivityLibrary.tsx | 6 +- .../ActivityLibrary/ActivityLibraryCard.tsx | 5 +- .../components/ActivityLibrary/Categories.ts | 39 +++++++-- .../CreateNewActivity/CreateNewActivity.tsx | 22 ++--- .../illustrations/estimation-background.png | Bin 0 -> 843 bytes .../illustrations/feedback-background.png | Bin 0 -> 1025 bytes .../illustrations/postmortem-background.png | Bin 0 -> 2119 bytes .../illustrations/premortem-background.png | Bin 0 -> 2158 bytes .../images/illustrations/retro-background.png | Bin 0 -> 2130 bytes .../illustrations/standup-background.png | Bin 0 -> 2371 bytes .../illustrations/strategy-background.png | Bin 0 -> 7818 bytes 14 files changed, 95 insertions(+), 68 deletions(-) create mode 100644 static/images/illustrations/estimation-background.png create mode 100644 static/images/illustrations/feedback-background.png create mode 100644 static/images/illustrations/postmortem-background.png create mode 100644 static/images/illustrations/premortem-background.png create mode 100644 static/images/illustrations/retro-background.png create mode 100644 static/images/illustrations/standup-background.png create mode 100644 static/images/illustrations/strategy-background.png diff --git a/packages/client/components/ActivityLibrary/ActivityCard.tsx b/packages/client/components/ActivityLibrary/ActivityCard.tsx index 185ef06861e..e2699eccb51 100644 --- a/packages/client/components/ActivityLibrary/ActivityCard.tsx +++ b/packages/client/components/ActivityLibrary/ActivityCard.tsx @@ -1,40 +1,38 @@ import clsx from 'clsx' -import React, {ComponentPropsWithoutRef, PropsWithChildren} from 'react' +import React, {PropsWithChildren} from 'react' +import {upperFirst} from '../../utils/upperFirst' +import {MeetingTypeEnum} from '../../__generated__/NewMeetingQuery.graphql' +import {backgroundImgMap, CategoryID, MEETING_TYPE_TO_CATEGORY} from './Categories' +import {twMerge} from 'tailwind-merge' export interface CardTheme { primary: string secondary: string } -export const ActivityCardImage = ( - props: PropsWithChildren> -) => { - const {className, src} = props - - return ( -
- -
- ) +type ActivityCardImageProps = { + className?: string + src: string + category: CategoryID } -const ActivityCardTitle = (props: ComponentPropsWithoutRef<'div'>) => { - const {children, className, ...rest} = props +export const ActivityCardImage = (props: PropsWithChildren) => { + const {className, src, category} = props + const backgroundSrc = backgroundImgMap[category] return (
- {children} + Card Illustration
) } @@ -42,27 +40,38 @@ const ActivityCardTitle = (props: ComponentPropsWithoutRef<'div'>) => { export interface ActivityCardProps { className?: string theme: CardTheme - titleAs?: React.ElementType title?: string badge?: React.ReactNode children?: React.ReactNode + type?: MeetingTypeEnum } export const ActivityCard = (props: ActivityCardProps) => { - const {className, theme, title, titleAs, badge, children} = props - const Title = titleAs ?? ActivityCardTitle + const {className, theme, title, children, type, badge} = props + const category = type && MEETING_TYPE_TO_CATEGORY[type] return ( -
-
- {title} -
-
- {children} -
-
-
{badge}
+
+
+
+ {children} +
{badge}
+
+ {title && category && ( +
+
{title}
+
+ {upperFirst(category)} +
+
+ )}
) } diff --git a/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx b/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx index 789f001dd67..25d60822dd2 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetails.tsx @@ -84,7 +84,7 @@ const ActivityDetails = (props: Props) => { }) }, []) - const {category, illustrationUrl, viewerLowestScope} = activity + const {category, illustrationUrl, viewerLowestScope, type} = activity const prevCategory = history.location.state?.prevCategory const categoryLink = `/activity-library/category/${ prevCategory ?? category ?? QUICK_START_CATEGORY_ID @@ -112,11 +112,12 @@ const ActivityDetails = (props: Props) => { )} > - +
diff --git a/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetailsCategoryBadge.tsx b/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetailsCategoryBadge.tsx index 1cd712c6211..1dea0f2ea10 100644 --- a/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetailsCategoryBadge.tsx +++ b/packages/client/components/ActivityLibrary/ActivityDetails/ActivityDetailsCategoryBadge.tsx @@ -39,7 +39,7 @@ const ActivityDetailsCategoryBadge = (props: Props) => { {CATEGORY_ID_TO_NAME[category]} @@ -59,7 +59,10 @@ const ActivityDetailsCategoryBadge = (props: Props) => { value={categoryId} > {CATEGORY_ID_TO_NAME[categoryId]} diff --git a/packages/client/components/ActivityLibrary/ActivityLibrary.tsx b/packages/client/components/ActivityLibrary/ActivityLibrary.tsx index c11bb1822a5..1cd9bbe61c0 100644 --- a/packages/client/components/ActivityLibrary/ActivityLibrary.tsx +++ b/packages/client/components/ActivityLibrary/ActivityLibrary.tsx @@ -125,7 +125,7 @@ const getTemplateDocumentValue = ( .join('-') const CategoryIDToColorClass = { - [QUICK_START_CATEGORY_ID]: 'bg-grape-700', + [QUICK_START_CATEGORY_ID]: 'grape-700', ...Object.fromEntries( Object.entries(CATEGORY_THEMES).map(([key, value]) => { return [key, value.primary] @@ -167,6 +167,7 @@ const ActivityGrid = ({templates, selectedCategory}: ActivityGridProps) => { key={template.id} theme={CATEGORY_THEMES[template.category as CategoryID]} title={template.name} + type={template.type} badge={ !template.isFree ? ( Premium @@ -176,6 +177,7 @@ const ActivityGrid = ({templates, selectedCategory}: ActivityGridProps) => { { 'flex-shrink-0 cursor-pointer rounded-full py-2 px-4 text-sm text-slate-800', category === selectedCategory && searchQuery.length === 0 ? [ - CategoryIDToColorClass[category], + `bg-${CategoryIDToColorClass[category]}`, 'font-semibold text-white focus:text-white' ] : 'border border-slate-300 bg-white' diff --git a/packages/client/components/ActivityLibrary/ActivityLibraryCard.tsx b/packages/client/components/ActivityLibrary/ActivityLibraryCard.tsx index c42c38ee228..55055d77d94 100644 --- a/packages/client/components/ActivityLibrary/ActivityLibraryCard.tsx +++ b/packages/client/components/ActivityLibrary/ActivityLibraryCard.tsx @@ -7,10 +7,7 @@ export const ActivityLibraryCard = (props: ActivityCardProps) => { return ( ) diff --git a/packages/client/components/ActivityLibrary/Categories.ts b/packages/client/components/ActivityLibrary/Categories.ts index 40283f1f9fe..1fa875b32d0 100644 --- a/packages/client/components/ActivityLibrary/Categories.ts +++ b/packages/client/components/ActivityLibrary/Categories.ts @@ -1,3 +1,11 @@ +import {MeetingTypeEnum} from '../../__generated__/NewMeetingQuery.graphql' +import retroBackgroundSrc from '../../../../static/images/illustrations/retro-background.png' +import standupBackgroundSrc from '../../../../static/images/illustrations/standup-background.png' +import feedbackBackgroundSrc from '../../../../static/images/illustrations/feedback-background.png' +import estimationBackgroundSrc from '../../../../static/images/illustrations/estimation-background.png' +import strategyBackgroundSrc from '../../../../static/images/illustrations/strategy-background.png' +import premortemBackgroundSrc from '../../../../static/images/illustrations/premortem-background.png' +import postmortemBackgroundSrc from '../../../../static/images/illustrations/postmortem-background.png' import {CardTheme} from './ActivityCard' export const MAIN_CATEGORIES = [ @@ -14,13 +22,13 @@ export type CategoryID = typeof MAIN_CATEGORIES[number] export const DEFAULT_CARD_THEME: CardTheme = {primary: 'bg-slate-500', secondary: 'bg-slate-200'} export const CATEGORY_THEMES: Record = { - standup: {primary: 'bg-aqua-400', secondary: 'bg-aqua-100'}, - estimation: {primary: 'bg-tomato-500', secondary: 'bg-tomato-100'}, - retrospective: {primary: 'bg-grape-500', secondary: 'bg-[#F2E1F7]'}, - feedback: {primary: 'bg-jade-400', secondary: 'bg-jade-100'}, - strategy: {primary: 'bg-rose-500', secondary: 'bg-rose-100'}, - premortem: {primary: 'bg-gold-500', secondary: 'bg-gold-100'}, - postmortem: {primary: 'bg-grass-500', secondary: 'bg-grass-100'} + standup: {primary: 'aqua-400', secondary: 'aqua-100'}, + estimation: {primary: 'tomato-500', secondary: 'tomato-100'}, + retrospective: {primary: 'grape-500', secondary: '[#F2E1F7]'}, + feedback: {primary: 'jade-400', secondary: 'jade-100'}, + strategy: {primary: 'rose-500', secondary: 'rose-100'}, + premortem: {primary: 'gold-500', secondary: 'gold-100'}, + postmortem: {primary: 'grass-500', secondary: 'grass-100'} } export const QUICK_START_CATEGORY_ID = 'recommended' @@ -35,3 +43,20 @@ export const CATEGORY_ID_TO_NAME: Record = { + retrospective: 'retrospective', + action: 'feedback', + poker: 'estimation', + teamPrompt: 'standup' +} + +export const backgroundImgMap = { + retrospective: retroBackgroundSrc, + standup: standupBackgroundSrc, + feedback: feedbackBackgroundSrc, + estimation: estimationBackgroundSrc, + strategy: strategyBackgroundSrc, + premortem: premortemBackgroundSrc, + postmortem: postmortemBackgroundSrc +} as const diff --git a/packages/client/components/ActivityLibrary/CreateNewActivity/CreateNewActivity.tsx b/packages/client/components/ActivityLibrary/CreateNewActivity/CreateNewActivity.tsx index ae71b844147..dc9d236279e 100644 --- a/packages/client/components/ActivityLibrary/CreateNewActivity/CreateNewActivity.tsx +++ b/packages/client/components/ActivityLibrary/CreateNewActivity/CreateNewActivity.tsx @@ -39,19 +39,6 @@ const Bold = (props: ComponentPropsWithoutRef<'span'>) => { ) } -const CategoryTitle = (props: ComponentPropsWithoutRef<'div'>) => { - const {children, className, ...rest} = props - - return ( -
- {children} -
- ) -} - type ActivityType = 'retrospective' | 'poker' type SupportedActivity = { @@ -297,15 +284,18 @@ export const CreateNewActivity = (props: Props) => { className='aspect-[320/190] w-80 group-data-[state=checked]:ring-4 group-data-[state=checked]:ring-sky-500 group-data-[state=checked]:ring-offset-4' theme={DEFAULT_CARD_THEME} title={activity.title} - titleAs={CategoryTitle} + type={activity.type} > - +
{activity.includedCategories.map((badge) => ( {CATEGORY_ID_TO_NAME[badge]} diff --git a/static/images/illustrations/estimation-background.png b/static/images/illustrations/estimation-background.png new file mode 100644 index 0000000000000000000000000000000000000000..04a567043f8b3cc3651a8dced4d610216f682575 GIT binary patch literal 843 zcmeAS@N?(olHy`uVBq!ia0y~yU_1cC5**Axkw4R}fhe;8pAc8~0-yi)@Be>z|No;0 z{~ta8kwA3k=KrggK;l65bD%6(@X7uE55OW1?mO-))&ZIzR1)MD%pj03|NQ&^|NkrW zE8M?tFyZ`y-cu!S85o#0dAc};R4~51>zee$L&Wvs_tYtycW3?IZL(Q3ZI)PS-S0cP zGJ88UwDPY8-@KtUwZFHg=IyeYypzt4SMJ$1clxtkD|70qZr!;1?`HZFs~x+aUSGa; zcJnU|i}iVn!|wjuUd8@rW2vm^>#ScNxh(YmSaJ1THBKnKJD>NO@Pjk!S3mf}VR3(1 z=C0UOug8Bgt%Gxy=31>^dC5Gq*x~Az`YTc&bh3CO0<#Zn@nc}*TEM_0;=m~4(7>e8 zz{C;2z#0G+Z+l<6_iMg$)B*-1d5s2; z;j98cL1as@3L?2=RayOSo--3ao?Z9pC==9mgq#eq3gkIl_Sa3_wt1P>)KU{8shp!s z)$Hi50y~q0_JA!Xs zZtqb)5aYvQ{p)JvJjUWXh0Wi}=Y$`KnR7OC>a)w6_b{i)+FSWN_JYKpX%7 literal 0 HcmV?d00001 diff --git a/static/images/illustrations/feedback-background.png b/static/images/illustrations/feedback-background.png new file mode 100644 index 0000000000000000000000000000000000000000..0b8ec6bdbf61bad13f59c94ca7e40bd595df62b4 GIT binary patch literal 1025 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 uIsd>qM(Wg;hw9SiGbdme*-NfJ!#iF5Yi!MyB zS{s|96bVyogfhFeva)Vd<8FpQ<2K%B-u?T1|2XG7pYM5|-}C$Zp6~bfdosN|b`iBG zS`Y*gce`=-Ll7(ikC6rfkfke~>d!Y`d%RtDMeb=Em%kd9wT;PI$K@R|S+h(A-fd%Y zfVYmx0oXb!dnJ>-8j-b4$N~O(Ox_OWkH~%-oopGCy%?DUGaH5{y4oLm0kitC`#lao z9lYE=8cnyTPbL#G7y^NSm%FJKvlW6ge%#G<@{aSJ_&XXImJBYzSX8`Y;t6SGV*&L%C>YK3K_{QPqxpX4Q!dw$;vJ z=@5FX?0w@%b(LSUf{GOCicv*We6XaeewhvND$z}>=*?Z18@s2n`<@JR$fho>5~EkI zmXI2(1xq$_<%5!|6q?kfht1JUTBlNRQT)dh{FCPqtbE$7Aqk?LoF4Tjy?hPdv$*fe z9WXI&UbRi6t7oCwp!@#1?~i>ZWGiJ{6V*w2*-(sZAlu9o4O{d!oV z^Ma#ngu)nnoJPhLIf%0POOSSu=%+MDui`H&7{dy9yd5HHj3=}~6=XM7eMXaFDy<48 zR<8u!@68;p+9NTq4rOobZd zIZ=~}Z1Q%Pm>+^^rC&-y4r7`9-TG^`iht)XIZCynW+7r++Xl>tatgZ@60mJyHQZxGfJjJEW-9WfB2 zWP?;~dFHpdRnS3h87@fV?qm_*gyuzr30Di@`Tg2&Uk%e z$Wvpu*Gp>X9e(ZdE#zs(DuDG^KaTcyS@}8>WNlHRlN5NGJqZ|DlX|x@?Wmaum_NpA zd0j$c;F-KP=fX#EMhGR>x@UMk8V^QZ$^8%NXCIsl$1{aLc=auKQ`P(QIf-}Fg>Lm#BubOB`92V z;q(31JKDhMkWllF^Rce$3k4dd@JyPIc|Vn&~e11UE*@5D- ztGD=LpIed`=2SKW__e`I#0{NuM^Qn37?Fcu7JF?tq1a0TYx)%5!9R?HOs;g5ouNP7 zoVAvm5T;%Dz(xoY{NVv~mwd!8@Q3^Z5VTLaMI%VG>|U(+jgaA7LK89gfj1T%0=avE zYObM`8MpgH20*yB>8{$bOlq>A#`%~d2kDK@L~-on>8|US&t(IewNEn+Q+aW$B*kZ6 zq8L4^b!`D^G<-B)HC6}cfU}~bEP%Cn(PdlAHFacJtf@Q)zT5-WZ3JW$Av@vswtF@x@! zI*V;OL87_@3yAy4Ntai-F7Em|-~?$|YMeE8T{QiFwRZ3yt!Xm~{)g6l%=xatfrhyf z$u5wT)lNHEGq8m-<9;fN#~#8K^uRld^yr}GM4BEq;XUG;`yb>WJH0h*M3yI=Ix*hl zdmsZ=xPlBEytBS41r(fT7A;?ipzgDspuF6qykWPPFs9Xpr40^OobZqGCn?YL1&m{%@D#d z4PCA2#O1N|z~-dDUYT=exw;v2E3KbfSBIxVfgB_{#mugVr54=|($l+Y&2s7^i-+3Q zsz=KyjSHQP*J?of*yNlzb|6u`PmEoC|JC>~i5@SM9{~Kj&K{Y6^tu6XJd~*W`A9J5 zPv|WWye-_RIB^;SnENJzaHLOz+7%eX{dGV>{;&fC&@sWeZ5uGSnM&QRgc=o7##7>6 z8-yZ%cQLgTy(AzpmcITwa^hl`y(MUQo!#e3AG8iV(f9Fkd23*^!xF>wiw|~}a iFNFO_C6c*Yb0niX?wMu<1M%Sc0_}G7;Fde{QvU<=*fxOx literal 0 HcmV?d00001 diff --git a/static/images/illustrations/strategy-background.png b/static/images/illustrations/strategy-background.png new file mode 100644 index 0000000000000000000000000000000000000000..f73eafb65c935df5544f327d607fab9b0fb908f0 GIT binary patch literal 7818 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 Date: Mon, 4 Dec 2023 11:53:11 +0100 Subject: [PATCH 2/5] chore: Cleanup Slack/Mattermost/MSTeams notifiers (#9240) * chore: Cleanup Slack/Mattermost/MSTeams notifiers These will need some refactoring for #8840 and this will hopefully simplify this. * Cleanup --- .../helpers/notifications/MSTeamsNotifier.ts | 90 ++------------ .../notifications/MattermostNotifier.ts | 96 ++------------- .../helpers/notifications/Notifier.ts | 113 +++++++++++++++++- .../helpers/notifications/SlackNotifier.ts | 98 +-------------- 4 files changed, 136 insertions(+), 261 deletions(-) diff --git a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts index 99b6f77670b..a8b4fe0d02c 100644 --- a/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/MSTeamsNotifier.ts @@ -13,8 +13,7 @@ import sendToSentry from '../../../../utils/sendToSentry' import {DataLoaderWorker} from '../../../graphql' import getSummaryText from './getSummaryText' import {NotificationIntegrationHelper} from './NotificationIntegrationHelper' -import {Notifier} from './Notifier' -import {getTeamPromptResponsesByMeetingId} from '../../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {createNotifier} from './Notifier' import {analytics} from '../../../../utils/analytics/analytics' const notifyMSTeams = async ( @@ -346,87 +345,16 @@ async function getMSTeams(dataLoader: DataLoaderWorker, teamId: string, userId: .get('bestTeamIntegrationProviders') .load({service: 'msTeams', teamId, userId}) return provider - ? MSTeamsNotificationHelper({ - ...(provider as IntegrationProviderMSTeams), - userId - }) - : null + ? [ + MSTeamsNotificationHelper({ + ...(provider as IntegrationProviderMSTeams), + userId + }) + ] + : [] } -async function loadMeetingTeam(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { - const [team, meeting] = await Promise.all([ - dataLoader.get('teams').load(teamId), - dataLoader.get('newMeetings').load(meetingId) - ]) - return { - meeting, - team - } -} - -export const MSTeamsNotifier: Notifier = { - async startMeeting(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { - const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) - if (!meeting || !team) return - ;(await getMSTeams(dataLoader, team.id, meeting.facilitatorUserId))?.startMeeting(meeting, team) - }, - - async endMeeting(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { - const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) - if (!meeting || !team) return - ;(await getMSTeams(dataLoader, team.id, meeting.facilitatorUserId))?.endMeeting( - meeting, - team, - null - ) - }, - - async startTimeLimit( - dataLoader: DataLoaderWorker, - scheduledEndTime: Date, - meetingId: string, - teamId: string - ) { - const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) - if (!meeting || !team) return - ;(await getMSTeams(dataLoader, team.id, meeting.facilitatorUserId))?.startTimeLimit( - scheduledEndTime, - meeting, - team - ) - }, - - async endTimeLimit(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { - const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) - if (!meeting || !team) return - ;(await getMSTeams(dataLoader, team.id, meeting.facilitatorUserId))?.endTimeLimit(meeting, team) - }, - - async integrationUpdated(dataLoader: DataLoaderWorker, teamId: string, userId: string) { - ;(await getMSTeams(dataLoader, teamId, userId))?.integrationUpdated() - }, - - async standupResponseSubmitted( - dataLoader: DataLoaderWorker, - meetingId: string, - teamId: string, - userId: string - ) { - const [{meeting, team}, user, responses] = await Promise.all([ - loadMeetingTeam(dataLoader, meetingId, teamId), - dataLoader.get('users').load(userId), - getTeamPromptResponsesByMeetingId(meetingId) - ]) - const response = responses.find(({userId: responseUserId}) => responseUserId === userId) - if (!meeting || !team || !response || !user) return - ;(await getMSTeams(dataLoader, teamId, userId))?.standupResponseSubmitted( - meeting, - team, - user, - response - ) - } -} +export const MSTeamsNotifier = createNotifier(getMSTeams) function GenerateACMeetingTitle(meetingTitle: string) { const titleTextBlock = new AdaptiveCards.TextBlock(meetingTitle) diff --git a/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts index afe69f2e1c2..21c1e9966f3 100644 --- a/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/MattermostNotifier.ts @@ -21,8 +21,7 @@ import { makeHackedFieldButtonValue } from './makeMattermostAttachments' import {NotificationIntegrationHelper} from './NotificationIntegrationHelper' -import {Notifier} from './Notifier' -import {getTeamPromptResponsesByMeetingId} from '../../../../postgres/queries/getTeamPromptResponsesByMeetingIds' +import {createNotifier} from './Notifier' import {analytics} from '../../../../utils/analytics/analytics' const notifyMattermost = async ( @@ -322,90 +321,13 @@ async function getMattermost(dataLoader: DataLoaderWorker, teamId: string, userI .get('bestTeamIntegrationProviders') .load({service: 'mattermost', teamId, userId}) return provider - ? MattermostNotificationHelper({ - ...(provider as IntegrationProviderMattermost), - userId - }) - : null -} - -async function loadMeetingTeam(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { - const [team, meeting] = await Promise.all([ - dataLoader.get('teams').load(teamId), - dataLoader.get('newMeetings').load(meetingId) - ]) - return { - meeting, - team - } + ? [ + MattermostNotificationHelper({ + ...(provider as IntegrationProviderMattermost), + userId + }) + ] + : [] } -export const MattermostNotifier: Notifier = { - async startMeeting(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { - const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) - if (!meeting || !team) return - ;(await getMattermost(dataLoader, team.id, meeting.facilitatorUserId))?.startMeeting( - meeting, - team - ) - }, - - async endMeeting(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { - const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) - if (!meeting || !team) return - ;(await getMattermost(dataLoader, team.id, meeting.facilitatorUserId))?.endMeeting( - meeting, - team, - null - ) - }, - - async startTimeLimit( - dataLoader: DataLoaderWorker, - scheduledEndTime: Date, - meetingId: string, - teamId: string - ) { - const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) - if (!meeting || !team) return - ;(await getMattermost(dataLoader, team.id, meeting.facilitatorUserId))?.startTimeLimit( - scheduledEndTime, - meeting, - team - ) - }, - - async endTimeLimit(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { - const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) - if (!meeting || !team) return - ;(await getMattermost(dataLoader, team.id, meeting.facilitatorUserId))?.endTimeLimit( - meeting, - team - ) - }, - - async integrationUpdated(dataLoader: DataLoaderWorker, teamId: string, userId: string) { - ;(await getMattermost(dataLoader, teamId, userId))?.integrationUpdated() - }, - - async standupResponseSubmitted( - dataLoader: DataLoaderWorker, - meetingId: string, - teamId: string, - userId: string - ) { - const [{meeting, team}, user, responses] = await Promise.all([ - loadMeetingTeam(dataLoader, meetingId, teamId), - dataLoader.get('users').load(userId), - getTeamPromptResponsesByMeetingId(meetingId) - ]) - const response = responses.find(({userId: responseUserId}) => responseUserId === userId) - if (!meeting || !team || !response || !user) return - ;(await getMattermost(dataLoader, teamId, userId))?.standupResponseSubmitted( - meeting, - team, - user, - response - ) - } -} +export const MattermostNotifier = createNotifier(getMattermost) diff --git a/packages/server/graphql/mutations/helpers/notifications/Notifier.ts b/packages/server/graphql/mutations/helpers/notifications/Notifier.ts index 20448ce9bca..3e222e23e4e 100644 --- a/packages/server/graphql/mutations/helpers/notifications/Notifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/Notifier.ts @@ -1,5 +1,7 @@ +import {SlackNotificationEvent} from '../../../../database/types/SlackNotification' +import {getTeamPromptResponsesByMeetingId} from '../../../../postgres/queries/getTeamPromptResponsesByMeetingIds' import {DataLoaderWorker} from '../../../graphql' -import {NotifyResponse} from './NotificationIntegrationHelper' +import {NotificationIntegration, NotifyResponse} from './NotificationIntegrationHelper' export type Notifier = { startMeeting(dataLoader: DataLoaderWorker, meetingId: string, teamId: string): Promise @@ -34,3 +36,112 @@ export type Notifier = { userId: string ): Promise } + +export type NotificationIntegrationLoader = ( + dataLoader: DataLoaderWorker, + teamId: string, + userId: string, + event: SlackNotificationEvent +) => Promise + +async function loadMeetingTeam(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { + const [team, meeting] = await Promise.all([ + dataLoader.get('teams').load(teamId), + dataLoader.get('newMeetings').load(meetingId) + ]) + return { + meeting, + team + } +} + +export const createNotifier = (loader: NotificationIntegrationLoader): Notifier => ({ + async startMeeting(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { + const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) + if (!meeting || !team) return + const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId, 'meetingStart') + notifiers.forEach((notifier) => notifier.startMeeting(meeting, team)) + }, + + async updateMeeting(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { + const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) + if (!meeting || !team) return + const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId, 'meetingStart') + notifiers.forEach((notifier) => notifier.updateMeeting?.(meeting, team)) + }, + + async endMeeting(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { + const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) + if (!meeting || !team) return + const meetingResponses = await getTeamPromptResponsesByMeetingId(meetingId) + const standupResponses = await Promise.all( + meetingResponses.map(async (response) => { + const user = await dataLoader.get('users').loadNonNull(response.userId) + return { + user, + response + } + }) + ) + const notifiers = await loader(dataLoader, team.id, meeting.facilitatorUserId, 'meetingEnd') + notifiers.forEach((notifier) => notifier.endMeeting(meeting, team, standupResponses)) + }, + + async startTimeLimit( + dataLoader: DataLoaderWorker, + scheduledEndTime: Date, + meetingId: string, + teamId: string + ) { + const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) + if (!meeting || !team) return + const notifiers = await loader( + dataLoader, + team.id, + meeting.facilitatorUserId, + 'MEETING_STAGE_TIME_LIMIT_START' + ) + notifiers.forEach((notifier) => notifier.startTimeLimit(scheduledEndTime, meeting, team)) + }, + + async endTimeLimit(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { + const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) + if (!meeting || !team) return + const notifiers = await loader( + dataLoader, + team.id, + meeting.facilitatorUserId, + 'MEETING_STAGE_TIME_LIMIT_END' + ) + notifiers.forEach((notifier) => notifier.endTimeLimit(meeting, team)) + }, + + async integrationUpdated(dataLoader: DataLoaderWorker, teamId: string, userId: string) { + const notifiers = await loader(dataLoader, teamId, userId, 'meetingEnd') + notifiers.forEach((notifier) => notifier.integrationUpdated()) + }, + + async standupResponseSubmitted( + dataLoader: DataLoaderWorker, + meetingId: string, + teamId: string, + userId: string + ) { + const [{meeting, team}, user, responses] = await Promise.all([ + loadMeetingTeam(dataLoader, meetingId, teamId), + dataLoader.get('users').load(userId), + getTeamPromptResponsesByMeetingId(meetingId) + ]) + const response = responses.find(({userId: responseUserId}) => responseUserId === userId) + if (!meeting || !team || !response || !user) return + const notifiers = await loader( + dataLoader, + team.id, + meeting.facilitatorUserId, + 'STANDUP_RESPONSE_SUBMITTED' + ) + notifiers.forEach((notifier) => + notifier.standupResponseSubmitted(meeting, team, user, response) + ) + } +}) diff --git a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts index 14656a2b938..22a6a96701b 100644 --- a/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts +++ b/packages/server/graphql/mutations/helpers/notifications/SlackNotifier.ts @@ -17,7 +17,7 @@ import {DataLoaderWorker} from '../../../graphql' import getSummaryText from './getSummaryText' import {makeButtons, makeHeader, makeSection, makeSections} from './makeSlackBlocks' import {NotificationIntegrationHelper} from './NotificationIntegrationHelper' -import {Notifier} from './Notifier' +import {createNotifier} from './Notifier' import SlackAuth from '../../../../database/types/SlackAuth' import {getTeamPromptResponsesByMeetingId} from '../../../../postgres/queries/getTeamPromptResponsesByMeetingIds' import {ErrorResponse, PostMessageResponse} from '../../../../../client/utils/SlackManager' @@ -456,8 +456,9 @@ export const SlackSingleChannelNotifier: NotificationIntegrationHelper notifier.startMeeting(meeting, team)) - }, - - async updateMeeting(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { - const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) - if (!meeting || !team) return - const notifiers = await getSlack(dataLoader, 'meetingStart', team.id) - notifiers.forEach((notifier) => notifier.updateMeeting?.(meeting, team)) - }, - - async endMeeting(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { - const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) - const meetingResponses = await getTeamPromptResponsesByMeetingId(meetingId) - // const standupResponses: Array<{user: User; response: TeamPromptResponse}> = [] - const standupResponses = await Promise.all( - meetingResponses.map(async (response) => { - const user = await dataLoader.get('users').loadNonNull(response.userId) - return { - user, - response - } - }) - ) - if (!meeting || !team) return - const notifiers = await getSlack(dataLoader, 'meetingEnd', team.id) - notifiers.forEach((notifier) => notifier.endMeeting(meeting, team, standupResponses)) - }, - - async startTimeLimit( - dataLoader: DataLoaderWorker, - scheduledEndTime: Date, - meetingId: string, - teamId: string - ) { - const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) - if (!meeting || !team) return - const notifiers = await getSlack(dataLoader, 'MEETING_STAGE_TIME_LIMIT_START', team.id) - notifiers.forEach((notifier) => notifier.startTimeLimit(scheduledEndTime, meeting, team)) - }, - - async endTimeLimit(dataLoader: DataLoaderWorker, meetingId: string, teamId: string) { - const {meeting, team} = await loadMeetingTeam(dataLoader, meetingId, teamId) - if (!meeting || !team) return - const notifiers = await getSlack(dataLoader, 'MEETING_STAGE_TIME_LIMIT_END', team.id) - notifiers.forEach((notifier) => notifier.endTimeLimit(meeting, team)) - }, - - async integrationUpdated() { - // Slack sends a system message on its own - }, - - async standupResponseSubmitted( - dataLoader: DataLoaderWorker, - meetingId: string, - teamId: string, - userId: string - ) { - const [{meeting, team}, user, responses] = await Promise.all([ - loadMeetingTeam(dataLoader, meetingId, teamId), - dataLoader.get('users').load(userId), - getTeamPromptResponsesByMeetingId(meetingId) - ]) - const response = responses.find(({userId: responseUserId}) => responseUserId === userId) - if (!meeting || !team || !response || !user) { - return - } - - const notifiers = await getSlack(dataLoader, 'STANDUP_RESPONSE_SUBMITTED', team.id) - notifiers.forEach((notifier) => - notifier.standupResponseSubmitted(meeting, team, user, response) - ) - }, - +export const SlackNotifier = { + ...createNotifier(getSlack), async shareTopic( dataLoader: DataLoaderWorker, userId: string, From f8511b21cac91d7bf3cace828ceb337848bb3ee8 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Mon, 4 Dec 2023 12:02:23 +0000 Subject: [PATCH 3/5] feat: add tooltip to activity library card (#9236) * show title and type below activity card * make activity library card dynamic * add retro background swirls * add background images for all meeting types * fix custom card size * truncate text if there is no space * clean up * use grape 100 * add premortem and postmortem imgs * remove bg from categories themes * move background img to div * add dummy tooltip * fix tailwind bg colours * show tooltip after delay * show descriptions in tooltip * clean up * update tooltip position * update CATEGORY_THEMES colours * remove tailwind config safelist --- .../ActivityLibrary/ActivityCard.tsx | 67 ++++++++++++++++++- .../ActivityLibrary/ActivityLibrary.tsx | 7 +- .../ActivityLibraryCardDescription.tsx | 45 ++++++++----- .../components/ActivityLibrary/Categories.ts | 20 +++--- 4 files changed, 105 insertions(+), 34 deletions(-) diff --git a/packages/client/components/ActivityLibrary/ActivityCard.tsx b/packages/client/components/ActivityLibrary/ActivityCard.tsx index e2699eccb51..4885cab1900 100644 --- a/packages/client/components/ActivityLibrary/ActivityCard.tsx +++ b/packages/client/components/ActivityLibrary/ActivityCard.tsx @@ -1,13 +1,21 @@ import clsx from 'clsx' -import React, {PropsWithChildren} from 'react' +import React, {PropsWithChildren, useEffect, useRef, useState} from 'react' import {upperFirst} from '../../utils/upperFirst' import {MeetingTypeEnum} from '../../__generated__/NewMeetingQuery.graphql' +import {ActivityCard_template$key} from '../../__generated__/ActivityCard_template.graphql' import {backgroundImgMap, CategoryID, MEETING_TYPE_TO_CATEGORY} from './Categories' import {twMerge} from 'tailwind-merge' +import {Tooltip} from '../../ui/Tooltip/Tooltip' +import {TooltipTrigger} from '../../ui/Tooltip/TooltipTrigger' +import {TooltipContent} from '../../ui/Tooltip/TooltipContent' +import {useFragment} from 'react-relay' +import graphql from 'babel-plugin-relay/macro' +import {ActivityLibraryCardDescription} from './ActivityLibraryCardDescription' export interface CardTheme { primary: string secondary: string + text: string } type ActivityCardImageProps = { @@ -44,14 +52,51 @@ export interface ActivityCardProps { badge?: React.ReactNode children?: React.ReactNode type?: MeetingTypeEnum + templateRef?: ActivityCard_template$key } export const ActivityCard = (props: ActivityCardProps) => { - const {className, theme, title, children, type, badge} = props + const {className, theme, title, children, type, badge, templateRef} = props const category = type && MEETING_TYPE_TO_CATEGORY[type] + const [showTooltip, setShowTooltip] = useState(false) + const hoverTimeout = useRef(null) + + const template = useFragment( + graphql` + fragment ActivityCard_template on MeetingTemplate { + ...ActivityLibraryCardDescription_template + } + `, + templateRef ?? null + ) + + const handleMouseEnter = () => { + hoverTimeout.current = setTimeout(() => { + setShowTooltip(true) + }, 500) + } + + const handleMouseLeave = () => { + if (hoverTimeout.current) { + clearTimeout(hoverTimeout.current) + } + setShowTooltip(false) + } + + useEffect(() => { + return () => { + if (hoverTimeout.current) { + clearTimeout(hoverTimeout.current) + } + } + }, []) return ( -
+
{ {children}
{badge}
+ {template && ( + + +
+ + +
{title}
+ +
+ + )}
{title && category && (
diff --git a/packages/client/components/ActivityLibrary/ActivityLibrary.tsx b/packages/client/components/ActivityLibrary/ActivityLibrary.tsx index 1cd9bbe61c0..f275cb3f36c 100644 --- a/packages/client/components/ActivityLibrary/ActivityLibrary.tsx +++ b/packages/client/components/ActivityLibrary/ActivityLibrary.tsx @@ -16,7 +16,6 @@ import IconLabel from '../IconLabel' import {ActivityBadge} from './ActivityBadge' import {ActivityCardImage} from './ActivityCard' import {ActivityLibraryCard} from './ActivityLibraryCard' -import {ActivityLibraryCardDescription} from './ActivityLibraryCardDescription' import { CategoryID, CATEGORY_ID_TO_NAME, @@ -70,6 +69,7 @@ graphql` isRecommended isFree ...ActivityLibrary_templateSearchDocument @relay(mask: false) + ...ActivityCard_template ...ActivityLibraryCardDescription_template } ` @@ -168,6 +168,7 @@ const ActivityGrid = ({templates, selectedCategory}: ActivityGridProps) => { theme={CATEGORY_THEMES[template.category as CategoryID]} title={template.name} type={template.type} + templateRef={template} badge={ !template.isFree ? ( Premium @@ -179,10 +180,6 @@ const ActivityGrid = ({templates, selectedCategory}: ActivityGridProps) => { src={template.illustrationUrl} category={template.category as CategoryID} /> - ) diff --git a/packages/client/components/ActivityLibrary/ActivityLibraryCardDescription.tsx b/packages/client/components/ActivityLibrary/ActivityLibraryCardDescription.tsx index 681c85a2539..e9927587efe 100644 --- a/packages/client/components/ActivityLibrary/ActivityLibraryCardDescription.tsx +++ b/packages/client/components/ActivityLibrary/ActivityLibraryCardDescription.tsx @@ -14,18 +14,20 @@ interface RetroDescriptionProps { prompts: ActivityLibraryCardDescription_template$data['prompts'] } -const RetroDescription = (props: RetroDescriptionProps) => { +export const RetroDescription = (props: RetroDescriptionProps) => { const {prompts} = props - return ( <> - {prompts!.map((prompt, index) => ( -
+ {prompts!.map((prompt) => ( +
-
{prompt.question}
+
+
{prompt.question}
+
{prompt.description}
+
))} @@ -38,16 +40,16 @@ interface PokerDescriptionProps { const PokerDescription = (props: PokerDescriptionProps) => { const {dimensions} = props - return ( <> - {dimensions!.map((dimension, index) => ( -
-
+ {dimensions!.map((dimension) => ( +
+
-
- {dimension.name}: {dimension.selectedScale.name} +
+
{dimension.name}
+
{dimension.selectedScale.name}
))} @@ -70,9 +72,11 @@ const ActionDescription = () => { return ( <> {items.map((item, index) => ( -
-
{item.icon}
-
{item.description}
+
+
{item.icon}
+
+
{item.description}
+
))} @@ -90,9 +94,11 @@ const TeamPromptDescription = () => { return ( <> {items.map((item, index) => ( -
-
{item.icon}
-
{item.description}
+
+
{item.icon}
+
+
{item.description}
+
))} @@ -113,6 +119,7 @@ export const ActivityLibraryCardDescription = (props: Props) => { type ... on PokerTemplate { dimensions { + id name description selectedScale { @@ -122,8 +129,10 @@ export const ActivityLibraryCardDescription = (props: Props) => { } ... on ReflectTemplate { prompts { + id question groupColor + description } } } diff --git a/packages/client/components/ActivityLibrary/Categories.ts b/packages/client/components/ActivityLibrary/Categories.ts index 1fa875b32d0..5c02abf0b68 100644 --- a/packages/client/components/ActivityLibrary/Categories.ts +++ b/packages/client/components/ActivityLibrary/Categories.ts @@ -19,16 +19,20 @@ export const MAIN_CATEGORIES = [ ] as const export type CategoryID = typeof MAIN_CATEGORIES[number] -export const DEFAULT_CARD_THEME: CardTheme = {primary: 'bg-slate-500', secondary: 'bg-slate-200'} +export const DEFAULT_CARD_THEME: CardTheme = { + primary: 'bg-slate-500', + secondary: 'bg-slate-200', + text: 'text-slate-500' +} export const CATEGORY_THEMES: Record = { - standup: {primary: 'aqua-400', secondary: 'aqua-100'}, - estimation: {primary: 'tomato-500', secondary: 'tomato-100'}, - retrospective: {primary: 'grape-500', secondary: '[#F2E1F7]'}, - feedback: {primary: 'jade-400', secondary: 'jade-100'}, - strategy: {primary: 'rose-500', secondary: 'rose-100'}, - premortem: {primary: 'gold-500', secondary: 'gold-100'}, - postmortem: {primary: 'grass-500', secondary: 'grass-100'} + standup: {primary: 'bg-aqua-400', secondary: 'bg-aqua-100', text: 'text-aqua-400'}, + estimation: {primary: 'bg-tomato-500', secondary: 'bg-tomato-100', text: 'text-tomato-500'}, + retrospective: {primary: 'bg-grape-500', secondary: 'bg-[#F2E1F7]', text: 'text-grape-500'}, + feedback: {primary: 'bg-jade-400', secondary: 'bg-jade-100', text: 'text-jade-400'}, + strategy: {primary: 'bg-rose-500', secondary: 'bg-rose-100', text: 'text-rose-500'}, + premortem: {primary: 'bg-gold-500', secondary: 'bg-gold-100', text: 'text-gold-500'}, + postmortem: {primary: 'bg-grass-500', secondary: 'bg-grass-100', text: 'text-grass-500'} } export const QUICK_START_CATEGORY_ID = 'recommended' From 1e71688e0d711e945ed373b2448b58c067e2f4b3 Mon Sep 17 00:00:00 2001 From: Nick O'Ferrall Date: Mon, 4 Dec 2023 18:55:10 +0000 Subject: [PATCH 4/5] feat: gcal invite all by default (#9260) --- .../components/GcalModal/GcalModal.tsx | 56 ++++++++++++------- 1 file changed, 35 insertions(+), 21 deletions(-) diff --git a/packages/client/modules/userDashboard/components/GcalModal/GcalModal.tsx b/packages/client/modules/userDashboard/components/GcalModal/GcalModal.tsx index ad076725911..4a1cd0232a3 100644 --- a/packages/client/modules/userDashboard/components/GcalModal/GcalModal.tsx +++ b/packages/client/modules/userDashboard/components/GcalModal/GcalModal.tsx @@ -1,7 +1,7 @@ import styled from '@emotion/styled' import graphql from 'babel-plugin-relay/macro' import dayjs from 'dayjs' -import React, {useState} from 'react' +import React, {useEffect, useState} from 'react' import DialogContent from '../../../../components/DialogContent' import DialogTitle from '../../../../components/DialogTitle' import {DialogActions} from '../../../../ui/Dialog/DialogActions' @@ -82,7 +82,7 @@ const GcalModal = (props: Props) => { const endOfNextHour = dayjs().add(2, 'hour').startOf('hour') const [start, setStart] = useState(startOfNextHour) const [end, setEnd] = useState(endOfNextHour) - const [inviteAll, setInviteAll] = useState(false) + const [inviteAll, setInviteAll] = useState(true) const [inviteError, setInviteError] = useState(null) const [rawInvitees, setRawInvitees] = useState('') const [invitees, setInvitees] = useState([] as string[]) @@ -159,28 +159,42 @@ const GcalModal = (props: Props) => { 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) { - 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]) + addAllTeamMembers() } else { - 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) + removeAllTeamMembers() } setInviteAll((inviteAll) => !inviteAll) } From 619c07ce3c6eb884d3896a53deab210db2feef84 Mon Sep 17 00:00:00 2001 From: Matt Krick Date: Mon, 4 Dec 2023 13:57:36 -0800 Subject: [PATCH 5/5] chore(dx): allow any branch with hotfix prefix to build (#9263) Signed-off-by: Matt Krick --- .github/workflows/release-please.yml | 4 ++-- .github/workflows/release.yml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release-please.yml b/.github/workflows/release-please.yml index ec769544ae4..0ea9c1ee005 100644 --- a/.github/workflows/release-please.yml +++ b/.github/workflows/release-please.yml @@ -2,7 +2,7 @@ on: push: branches: - master - - hotfix-* + - hotfix* name: release-please jobs: release-please: @@ -19,4 +19,4 @@ jobs: command: manifest default-branch: ${{ github.ref_name}} release-type: node - token: ${{ steps.generate_token.outputs.token }} \ No newline at end of file + token: ${{ steps.generate_token.outputs.token }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf585472b50..084909c56f4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -3,7 +3,7 @@ on: pull_request: branches: - master - - hotfix-* + - hotfix* types: [closed] jobs: release: