From 9f0e44c50afbb7f0489c2283416c0d9fb67b840f Mon Sep 17 00:00:00 2001 From: Julien Tant <785518+JulienTant@users.noreply.github.com> Date: Fri, 31 May 2024 16:52:40 -0700 Subject: [PATCH] [MM-47629] Don't crash when a run channel has been deleted (#1908) * don't crash when a run channel has been deleted * i18n * do not set channel * Update webapp/src/components/backstage/playbook_runs/playbook_run/rhs_info_overview.tsx Co-authored-by: Caleb Roseland --------- Co-authored-by: Caleb Roseland --- .../playbooks/runs/rdp_rhs_runinfo_spec.js | 9 +++ server/app/playbook_run_service.go | 25 ++++--- webapp/i18n/en.json | 1 + .../playbook_run/playbook_run.tsx | 5 +- .../playbook_runs/playbook_run/rhs_info.tsx | 2 + .../playbook_run/rhs_info_overview.tsx | 74 ++++++++++++++----- 6 files changed, 84 insertions(+), 32 deletions(-) diff --git a/e2e-tests/tests/integration/playbooks/runs/rdp_rhs_runinfo_spec.js b/e2e-tests/tests/integration/playbooks/runs/rdp_rhs_runinfo_spec.js index 09df49b33e..7e8b0a2531 100644 --- a/e2e-tests/tests/integration/playbooks/runs/rdp_rhs_runinfo_spec.js +++ b/e2e-tests/tests/integration/playbooks/runs/rdp_rhs_runinfo_spec.js @@ -193,6 +193,15 @@ describe('runs > run details page > run info', {testIsolation: true}, () => { cy.url().should('include', `${testTeam.name}/channels/the-run-name`); }); }); + + it('indicates when the channel has been deleted', () => { + cy.apiDeleteChannel(testRun.channel_id).then(() => { + cy.visit(`/playbooks/runs/${testRun.id}`); + + // * Assert channel name + getOverviewEntry('channel').contains('Channel deleted'); + }); + }); }); describe('as viewer', () => { diff --git a/server/app/playbook_run_service.go b/server/app/playbook_run_service.go index 8525c1508f..855d7c4a16 100644 --- a/server/app/playbook_run_service.go +++ b/server/app/playbook_run_service.go @@ -1266,11 +1266,11 @@ func (s *PlaybookRunServiceImpl) GetPlaybookRunMetadata(playbookRunID string) (* // Get main channel details channel, err := s.pluginAPI.Channel.Get(playbookRun.ChannelID) if err != nil { - return nil, errors.Wrapf(err, "failed to retrieve channel id '%s'", playbookRun.ChannelID) + s.pluginAPI.Log.Warn("failed to retrieve channel id", "channel_id", playbookRun.ChannelID) } - team, err := s.pluginAPI.Team.Get(channel.TeamId) + team, err := s.pluginAPI.Team.Get(playbookRun.TeamID) if err != nil { - return nil, errors.Wrapf(err, "failed to retrieve team id '%s'", channel.TeamId) + return nil, errors.Wrapf(err, "failed to retrieve team id '%s'", playbookRun.TeamID) } numParticipants, err := s.store.GetHistoricalPlaybookRunParticipantsCount(playbookRun.ChannelID) @@ -1283,14 +1283,17 @@ func (s *PlaybookRunServiceImpl) GetPlaybookRunMetadata(playbookRunID string) (* return nil, errors.Wrapf(err, "failed to get followers of playbook run %s", playbookRunID) } - return &Metadata{ - ChannelName: channel.Name, - ChannelDisplayName: channel.DisplayName, - TeamName: team.Name, - TotalPosts: channel.TotalMsgCount, - NumParticipants: numParticipants, - Followers: followers, - }, nil + metadata := &Metadata{ + TeamName: team.Name, + NumParticipants: numParticipants, + Followers: followers, + } + if channel != nil { + metadata.ChannelName = channel.Name + metadata.ChannelDisplayName = channel.DisplayName + metadata.TotalPosts = channel.TotalMsgCount + } + return metadata, nil } // GetPlaybookRunsForChannelByUser get the playbookRuns list associated with this channel and user. diff --git a/webapp/i18n/en.json b/webapp/i18n/en.json index d7e6a7edfe..6445e0ff6d 100644 --- a/webapp/i18n/en.json +++ b/webapp/i18n/en.json @@ -115,6 +115,7 @@ "AG7PKJ": "Rename run", "AML4RW": "Task assignments", "AhY0vJ": "Leave and unfollow", + "AkyGP2": "Channel deleted", "AoNLta": "There are no finished runs linked to this channel", "Auj1ap": "Start a trial or upgrade your subscription.", "B3Q5mz": "Trigger", diff --git a/webapp/src/components/backstage/playbook_runs/playbook_run/playbook_run.tsx b/webapp/src/components/backstage/playbook_runs/playbook_run/playbook_run.tsx index 4b92f92384..0fe6dd4f2b 100644 --- a/webapp/src/components/backstage/playbook_runs/playbook_run/playbook_run.tsx +++ b/webapp/src/components/backstage/playbook_runs/playbook_run/playbook_run.tsx @@ -90,7 +90,7 @@ const PlaybookRunDetails = () => { // we must force metadata refetch when participants change (leave&unfollow) const [metadata, metadataResult] = useRunMetadata(playbookRun?.id, [JSON.stringify(playbookRun?.participant_ids)]); const [statusUpdates] = useRunStatusUpdates(playbookRun?.id, [playbookRun?.status_posts.length]); - const [channel] = useChannel(playbookRun?.channel_id ?? ''); + const [channel, channelResult] = useChannel(playbookRun?.channel_id ?? ''); const myUser = useSelector(getCurrentUser); const {options, selectOption, eventsFilter, resetFilters} = useFilter(); const followState = useRunFollowers(metadata?.followers || []); @@ -144,6 +144,8 @@ const PlaybookRunDetails = () => { } }, [urlHash]); + const channelDeleted = Boolean(channel?.delete_at) || channelResult.isErrorCode(404); + // not found or error if (playbookRunResult.error !== null || metadataResult.error !== null) { return ; @@ -176,6 +178,7 @@ const PlaybookRunDetails = () => { role={role} followState={followState} channel={channel} + channelDeleted={channelDeleted} onViewParticipants={() => RHS.open(RHSContent.RunParticipants, RHSParticipantsTitle, playbookRun.name, () => onViewInfo)} onViewTimeline={() => RHS.open(RHSContent.RunTimeline, formatMessage({defaultMessage: 'Timeline'}), playbookRun.name, () => onViewInfo, false)} /> diff --git a/webapp/src/components/backstage/playbook_runs/playbook_run/rhs_info.tsx b/webapp/src/components/backstage/playbook_runs/playbook_run/rhs_info.tsx index 18d8af59e6..a9f8d49d58 100644 --- a/webapp/src/components/backstage/playbook_runs/playbook_run/rhs_info.tsx +++ b/webapp/src/components/backstage/playbook_runs/playbook_run/rhs_info.tsx @@ -17,6 +17,7 @@ interface Props { runMetadata?: Metadata; role: Role; channel: Channel | undefined | null; + channelDeleted: boolean; followState: FollowState; onViewParticipants: () => void; onViewTimeline: () => void; @@ -42,6 +43,7 @@ const RHSInfo = (props: Props) => { onViewParticipants={props.onViewParticipants} editable={editable} channel={props.channel} + channelDeleted={props.channelDeleted} followState={props.followState} playbook={props.playbook} /> diff --git a/webapp/src/components/backstage/playbook_runs/playbook_run/rhs_info_overview.tsx b/webapp/src/components/backstage/playbook_runs/playbook_run/rhs_info_overview.tsx index b2365dd01a..249ce7b0ad 100644 --- a/webapp/src/components/backstage/playbook_runs/playbook_run/rhs_info_overview.tsx +++ b/webapp/src/components/backstage/playbook_runs/playbook_run/rhs_info_overview.tsx @@ -81,6 +81,7 @@ interface Props { runMetadata?: Metadata; editable: boolean; channel: Channel | undefined | null; + channelDeleted: boolean; followState: FollowState; playbook?: PlaybookWithChecklist; role: Role; @@ -91,7 +92,7 @@ const StyledArrowIcon = styled(ArrowForwardIosIcon)` margin-left: 7px; `; -const RHSInfoOverview = ({run, role, channel, runMetadata, followState, editable, playbook, onViewParticipants}: Props) => { +const RHSInfoOverview = ({run, role, channel, channelDeleted, runMetadata, followState, editable, playbook, onViewParticipants}: Props) => { const {formatMessage} = useIntl(); const addToast = useToaster().add; const refreshLHS = useLHSRefresh(); @@ -197,24 +198,13 @@ const RHSInfoOverview = ({run, role, channel, runMetadata, followState, editable icon={ProductChannelsIcon} name={formatMessage({defaultMessage: 'Channel'})} > - {channel && runMetadata ? <> - - - {channel.display_name} - - - - : - {role === Role.Participant ? {formatMessage({defaultMessage: 'Request to Join'})} : null} - {formatMessage({defaultMessage: 'Private'})} - - } + {RequestJoinModal} @@ -277,7 +267,7 @@ const ItemDisabledContent = styled(ItemContent)` color: rgba(var(--center-channel-color-rgb), 0.64); `; -const OverviewRow = styled.div<{onClick?: () => void}>` +const OverviewRow = styled.div<{ onClick?: () => void }>` padding: 10px 24px; height: 44px; display: flex; @@ -321,3 +311,47 @@ const ParticipantsContainer = styled.div` flex-direction: row; align-items: center; `; + +interface ChannelRowProps { + channel: Channel | undefined | null; + channelDeleted: boolean; + runMetadata?: Metadata; + role: Role; + onClickRequestJoin: () => void; +} + +const ChannelRow = ({channel, runMetadata, channelDeleted, role, onClickRequestJoin}: ChannelRowProps) => { + const {formatMessage} = useIntl(); + + if (channelDeleted) { + return ( + + {formatMessage({defaultMessage: 'Channel deleted'})} + + ); + } + + if (channel && runMetadata) { + return ( + + + {channel.display_name} + + + + ); + } + + return ( + + {role === Role.Participant ? {formatMessage({defaultMessage: 'Request to Join'})} : null} + {formatMessage({defaultMessage: 'Private'})} + + ); +};