diff --git a/app/products/playbooks/screens/navigation.test.ts b/app/products/playbooks/screens/navigation.test.ts index c22121727..eb45b6980 100644 --- a/app/products/playbooks/screens/navigation.test.ts +++ b/app/products/playbooks/screens/navigation.test.ts @@ -6,7 +6,7 @@ import {goToScreen} from '@screens/navigation'; import TestHelper from '@test/test_helper'; import {changeOpacity} from '@utils/theme'; -import {goToPlaybookRuns, goToPlaybookRun, goToSelectUser, goToSelectDate} from './navigation'; +import {goToPlaybookRuns, goToPlaybookRun, goToEditCommand, goToSelectUser, goToSelectDate} from './navigation'; jest.mock('@screens/navigation', () => ({ goToScreen: jest.fn(), @@ -65,6 +65,38 @@ describe('Playbooks Navigation', () => { }); }); + describe('goToEditCommand', () => { + it('should navigate to edit command screen with correct parameters', async () => { + const command = 'test command'; + const channelId = 'channel-id-1'; + const updateCommand = jest.fn(); + + await goToEditCommand(mockIntl, Preferences.THEMES.denim, 'Run 1', command, channelId, updateCommand); + + expect(mockIntl.formatMessage).toHaveBeenCalledWith({ + id: 'playbooks.edit_command.title', + defaultMessage: 'Slash command', + }); + expect(goToScreen).toHaveBeenCalledWith( + Screens.PLAYBOOK_EDIT_COMMAND, + 'Slash command', + { + savedCommand: command, + updateCommand, + channelId, + }, + { + topBar: { + subtitle: { + text: 'Run 1', + color: changeOpacity(Preferences.THEMES.denim.sidebarHeaderTextColor, 0.72), + }, + }, + }, + ); + }); + }); + describe('goToSelectUser', () => { it('should navigate to select user screen with correct parameters when handleRemove is provided', async () => { const title = 'Select User'; @@ -73,7 +105,7 @@ describe('Playbooks Navigation', () => { const handleSelect = jest.fn(); const handleRemove = jest.fn(); - await goToSelectUser(title, participantIds, selected, handleSelect, handleRemove); + await goToSelectUser(Preferences.THEMES.denim, 'Run 1', title, participantIds, selected, handleSelect, handleRemove); expect(goToScreen).toHaveBeenCalledWith( Screens.PLAYBOOK_SELECT_USER, @@ -84,7 +116,14 @@ describe('Playbooks Navigation', () => { handleSelect, handleRemove, }, - {}, + { + topBar: { + subtitle: { + text: 'Run 1', + color: changeOpacity(Preferences.THEMES.denim.sidebarHeaderTextColor, 0.72), + }, + }, + }, ); }); }); @@ -94,7 +133,7 @@ describe('Playbooks Navigation', () => { const onSave = jest.fn(); const selectedDate = 1640995200000; // January 1, 2022 - await goToSelectDate(mockIntl, onSave, selectedDate); + await goToSelectDate(mockIntl, Preferences.THEMES.denim, 'Run 1', onSave, selectedDate); expect(mockIntl.formatMessage).toHaveBeenCalledWith({ id: 'playbooks.select_date.title', @@ -107,14 +146,21 @@ describe('Playbooks Navigation', () => { onSave, selectedDate, }, - {}, + { + topBar: { + subtitle: { + text: 'Run 1', + color: changeOpacity(Preferences.THEMES.denim.sidebarHeaderTextColor, 0.72), + }, + }, + }, ); }); it('should navigate to select date screen without selected date', async () => { const onSave = jest.fn(); - await goToSelectDate(mockIntl, onSave, undefined); + await goToSelectDate(mockIntl, Preferences.THEMES.denim, 'Run 1', onSave, undefined); expect(mockIntl.formatMessage).toHaveBeenCalledWith({ id: 'playbooks.select_date.title', @@ -127,7 +173,14 @@ describe('Playbooks Navigation', () => { onSave, selectedDate: undefined, }, - {}, + { + topBar: { + subtitle: { + text: 'Run 1', + color: changeOpacity(Preferences.THEMES.denim.sidebarHeaderTextColor, 0.72), + }, + }, + }, ); }); }); diff --git a/app/products/playbooks/screens/navigation.ts b/app/products/playbooks/screens/navigation.ts index 2ed55dc8e..cb91e28e4 100644 --- a/app/products/playbooks/screens/navigation.ts +++ b/app/products/playbooks/screens/navigation.ts @@ -6,6 +6,7 @@ import {getThemeFromState, goToScreen} from '@screens/navigation'; import {changeOpacity} from '@utils/theme'; import type {IntlShape} from 'react-intl'; +import type {Options as RNNOptions} from 'react-native-navigation'; export function goToPlaybookRuns(intl: IntlShape, channelId: string, channelName: string) { const theme = getThemeFromState(); @@ -25,29 +26,63 @@ export async function goToPlaybookRun(intl: IntlShape, playbookRunId: string, pl goToScreen(Screens.PLAYBOOK_RUN, title, {playbookRunId, playbookRun}, {}); } +function getSubtitleOptions(theme: Theme, runName: string): RNNOptions { + return { + topBar: { + subtitle: { + color: changeOpacity(theme.sidebarHeaderTextColor, 0.72), + text: runName, + }, + }, + }; +} + +export async function goToEditCommand( + intl: IntlShape, + theme: Theme, + runName: string, + command: string | null, + channelId: string, + updateCommand: (command: string) => void, +) { + const title = intl.formatMessage({id: 'playbooks.edit_command.title', defaultMessage: 'Slash command'}); + const options = getSubtitleOptions(theme, runName); + goToScreen(Screens.PLAYBOOK_EDIT_COMMAND, title, { + savedCommand: command, + updateCommand, + channelId, + }, options); +} + export async function goToSelectUser( + theme: Theme, + runName: string, title: string, participantIds: string[], selected: string | undefined, handleSelect: (user: UserProfile) => void, handleRemove?: () => void, ) { + const options = getSubtitleOptions(theme, runName); goToScreen(Screens.PLAYBOOK_SELECT_USER, title, { participantIds, selected, handleSelect, handleRemove, - }, {}); + }, options); } export async function goToSelectDate( intl: IntlShape, + theme: Theme, + runName: string, onSave: (date: number | undefined) => void, selectedDate: number | undefined, ) { + const options = getSubtitleOptions(theme, runName); const title = intl.formatMessage({id: 'playbooks.select_date.title', defaultMessage: 'Due date'}); goToScreen(Screens.PLAYBOOKS_SELECT_DATE, title, { onSave, selectedDate, - }, {}); + }, options); } diff --git a/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/checklist_item_bottom_sheet.test.tsx b/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/checklist_item_bottom_sheet.test.tsx index 24c56df8d..7d137aeed 100644 --- a/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/checklist_item_bottom_sheet.test.tsx +++ b/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/checklist_item_bottom_sheet.test.tsx @@ -10,16 +10,14 @@ import OptionBox from '@components/option_box'; import OptionItem from '@components/option_item'; import {useIsTablet} from '@hooks/device'; import {setAssignee, setChecklistItemCommand, setDueDate} from '@playbooks/actions/remote/checklist'; -import {goToSelectDate, goToSelectUser} from '@playbooks/screens/navigation'; -import {dismissBottomSheet, goToScreen, openUserProfileModal} from '@screens/navigation'; +import {goToEditCommand, goToSelectDate, goToSelectUser} from '@playbooks/screens/navigation'; +import {dismissBottomSheet, openUserProfileModal} from '@screens/navigation'; import {renderWithIntl} from '@test/intl-test-helper'; import TestHelper from '@test/test_helper'; import {showPlaybookErrorSnackbar} from '@utils/snack_bar'; import ChecklistItemBottomSheet from './checklist_item_bottom_sheet'; -import type EditCommand from '@playbooks/screens/edit_command'; - jest.mock('@hooks/device', () => ({ useIsTablet: jest.fn(), })); @@ -81,6 +79,7 @@ describe('ChecklistItemBottomSheet', () => { function getBaseProps(): ComponentProps { return { runId: 'run-1', + runName: 'Run 1', checklistNumber: 1, itemNumber: 1, channelId: 'channel-1', @@ -400,17 +399,16 @@ describe('ChecklistItemBottomSheet', () => { commandItem.props.action(); }); - expect(goToScreen).toHaveBeenCalledWith( - 'PlaybookEditCommand', - 'Slash command', - { - savedCommand: 'test command', - updateCommand: expect.any(Function), - channelId: 'channel-1', - }, + expect(goToEditCommand).toHaveBeenCalledWith( + expect.anything(), + expect.anything(), + 'Run 1', + 'test command', + 'channel-1', + expect.any(Function), ); - const updateCommand = (jest.mocked(goToScreen).mock.calls[0][2] as ComponentProps).updateCommand; + const updateCommand = jest.mocked(goToEditCommand).mock.calls[0][5]; await act(async () => { updateCommand('new command'); }); @@ -440,11 +438,13 @@ describe('ChecklistItemBottomSheet', () => { }); expect(goToSelectDate).toHaveBeenCalledWith( expect.anything(), + expect.anything(), + 'Run 1', expect.any(Function), 1640995200000, ); - const setDate = jest.mocked(goToSelectDate).mock.calls[0][1]; + const setDate = jest.mocked(goToSelectDate).mock.calls[0][3]; await act(async () => { setDate(1672531200000); }); @@ -501,6 +501,8 @@ describe('ChecklistItemBottomSheet', () => { }); expect(goToSelectUser).toHaveBeenCalledWith( + expect.anything(), + 'Run 1', 'Assignee', ['user-1', 'user-2'], 'user-1', @@ -508,8 +510,8 @@ describe('ChecklistItemBottomSheet', () => { expect.any(Function), ); - const handleSelect = jest.mocked(goToSelectUser).mock.calls[0][3]; - const handleRemove = jest.mocked(goToSelectUser).mock.calls[0][4]; + const handleSelect = jest.mocked(goToSelectUser).mock.calls[0][5]; + const handleRemove = jest.mocked(goToSelectUser).mock.calls[0][6]; await act(async () => { handleSelect(TestHelper.fakeUser({id: 'user-1'})); @@ -552,8 +554,8 @@ describe('ChecklistItemBottomSheet', () => { onPress('user-1'); }); - const handleSelect = jest.mocked(goToSelectUser).mock.calls[0][3]; - const handleRemove = jest.mocked(goToSelectUser).mock.calls[0][4]; + const handleSelect = jest.mocked(goToSelectUser).mock.calls[0][5]; + const handleRemove = jest.mocked(goToSelectUser).mock.calls[0][6]; await act(async () => { handleSelect(TestHelper.fakeUser({id: 'user-1'})); diff --git a/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/checklist_item_bottom_sheet.tsx b/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/checklist_item_bottom_sheet.tsx index f67ced663..e0217781b 100644 --- a/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/checklist_item_bottom_sheet.tsx +++ b/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/checklist_item_bottom_sheet.tsx @@ -11,9 +11,9 @@ import OptionItem, {ITEM_HEIGHT} from '@components/option_item'; import {useServerUrl} from '@context/server'; import {useTheme} from '@context/theme'; import {setAssignee, setChecklistItemCommand, setDueDate} from '@playbooks/actions/remote/checklist'; -import {goToSelectDate, goToSelectUser} from '@playbooks/screens/navigation'; +import {goToEditCommand, goToSelectDate, goToSelectUser} from '@playbooks/screens/navigation'; import {getDueDateString} from '@playbooks/utils/time'; -import {dismissBottomSheet, goToScreen, openUserProfileModal} from '@screens/navigation'; +import {dismissBottomSheet, openUserProfileModal} from '@screens/navigation'; import {showPlaybookErrorSnackbar} from '@utils/snack_bar'; import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme'; import {typography} from '@utils/typography'; @@ -114,6 +114,7 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => ({ type Props = { runId: string; + runName: string; checklistNumber: number; itemNumber: number; channelId: string; @@ -130,6 +131,7 @@ type Props = { const ChecklistItemBottomSheet = ({ runId, + runName, checklistNumber, itemNumber, channelId, @@ -213,16 +215,8 @@ const ChecklistItemBottomSheet = ({ }, [checklistNumber, item.id, itemNumber, runId, serverUrl]); const openEditCommandModal = useCallback(() => { - goToScreen( - 'PlaybookEditCommand', - intl.formatMessage({id: 'playbooks.edit_command.title', defaultMessage: 'Slash command'}), - { - savedCommand: item.command, - updateCommand, - channelId, - }, - ); - }, [intl, item.command, updateCommand, channelId]); + goToEditCommand(intl, theme, runName, item.command, channelId, updateCommand); + }, [intl, theme, runName, item.command, channelId, updateCommand]); const assigneeInfo: ComponentProps['info'] = useMemo(() => { if (!assignee) { @@ -237,10 +231,10 @@ const ChecklistItemBottomSheet = ({ }, [assignee, intl, onUserChipPress, teammateNameDisplay]); const openEditDateModal = useCallback(async () => { - goToSelectDate(intl, (date) => { + goToSelectDate(intl, theme, runName, (date) => { setDueDate(serverUrl, runId, item.id, checklistNumber, itemNumber, date); }, dueDate); - }, [intl, dueDate, serverUrl, runId, item.id, checklistNumber, itemNumber]); + }, [intl, theme, runName, dueDate, serverUrl, runId, item.id, checklistNumber, itemNumber]); const handleSelect = useCallback(async (selected: UserProfile) => { const res = await setAssignee(serverUrl, runId, item.id, checklistNumber, itemNumber, selected.id); @@ -258,13 +252,15 @@ const ChecklistItemBottomSheet = ({ const openUserSelector = useCallback(() => { goToSelectUser( + theme, + runName, intl.formatMessage(messages.assignee), participantIds, assignee?.id, handleSelect, handleRemove, ); - }, [assignee?.id, handleRemove, handleSelect, intl, participantIds]); + }, [assignee?.id, handleRemove, handleSelect, intl, participantIds, runName, theme]); const renderTaskDetails = () => ( diff --git a/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/index.test.tsx b/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/index.test.tsx index d0162262b..206ce8ba0 100644 --- a/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/index.test.tsx +++ b/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/index.test.tsx @@ -84,7 +84,7 @@ describe('ChecklistItemBottomSheet Enhanced Component', () => { describe('with database model item', () => { it('should render enhanced component with database model item', async () => { const rawItem = TestHelper.fakePlaybookChecklistItem('checklist-id', {id: 'item-1', assignee_id: 'user-1'}); - const rawRun = TestHelper.fakePlaybookRun({id: 'run-1', participant_ids: ['user-1', 'user-2']}); + const rawRun = TestHelper.fakePlaybookRun({id: 'run-1', participant_ids: ['user-1', 'user-2'], name: 'Run 1'}); await addRunToDatabase(rawRun); await addItemToDatabase(rawItem); await addUserToDatabase(TestHelper.fakeUser({id: 'user-1'})); @@ -103,6 +103,7 @@ describe('ChecklistItemBottomSheet Enhanced Component', () => { expect(bottomSheet).toHaveProp('assignee', expect.objectContaining({id: 'user-1'})); expect(bottomSheet).toHaveProp('currentUserTimezone', expect.objectContaining({useAutomaticTimezone: false, manualTimezone: 'America/New_York', automaticTimezone: 'America/New_York'})); expect(bottomSheet).toHaveProp('participantIds', ['user-1', 'user-2']); + expect(bottomSheet).toHaveProp('runName', 'Run 1'); }); it('should handle missing run correctly', async () => { @@ -120,6 +121,7 @@ describe('ChecklistItemBottomSheet Enhanced Component', () => { const bottomSheet = getByTestId('checklist_item_bottom_sheet'); expect(bottomSheet).toBeTruthy(); expect(bottomSheet).toHaveProp('participantIds', []); + expect(bottomSheet).toHaveProp('runName', ''); }); it('should handle assigneeId changes through observable', async () => { diff --git a/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/index.ts b/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/index.ts index b226cc39b..c17a552ef 100644 --- a/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/index.ts +++ b/app/products/playbooks/screens/playbook_run/checklist/checklist_item/checklist_item_bottom_sheet/index.ts @@ -40,6 +40,9 @@ const enhanced = withObservables(['item', 'runId'], ({item, runId, database}: Ow participantIds: observePlaybookRunById(database, runId).pipe( switchMap((run) => observeParticipantsIdsFromPlaybookModel(run, true)), ), + runName: observePlaybookRunById(database, runId).pipe( + switchMap((run) => of$(run?.name || '')), + ), }; } @@ -50,6 +53,7 @@ const enhanced = withObservables(['item', 'runId'], ({item, runId, database}: Ow assignee, currentUserTimezone, participantIds: of$([]), + runName: of$(''), }; }); diff --git a/app/products/playbooks/screens/playbook_run/playbook_run.test.tsx b/app/products/playbooks/screens/playbook_run/playbook_run.test.tsx index 9e6c29222..81dc0bcb5 100644 --- a/app/products/playbooks/screens/playbook_run/playbook_run.test.tsx +++ b/app/products/playbooks/screens/playbook_run/playbook_run.test.tsx @@ -217,6 +217,8 @@ describe('PlaybookRun', () => { ownerChip.props.action.onPress(); expect(goToSelectUser).toHaveBeenCalledWith( + expect.anything(), + 'Test Playbook Run', 'Owner', [...props.participants.map((p) => p.id), props.owner!.id], props.owner!.id, @@ -224,7 +226,7 @@ describe('PlaybookRun', () => { ); expect(openUserProfileModal).not.toHaveBeenCalled(); - let handleSelect = jest.mocked(goToSelectUser).mock.calls[0][3]; + let handleSelect = jest.mocked(goToSelectUser).mock.calls[0][5]; handleSelect(TestHelper.fakeUser({id: 'user-2'})); expect(setOwner).toHaveBeenCalledWith( @@ -241,6 +243,8 @@ describe('PlaybookRun', () => { ownerChip.props.onPress(); expect(goToSelectUser).toHaveBeenCalledWith( + expect.anything(), + 'Test Playbook Run', 'Owner', [...props.participants.map((p) => p.id), props.owner!.id], props.owner!.id, @@ -248,7 +252,7 @@ describe('PlaybookRun', () => { ); expect(openUserProfileModal).not.toHaveBeenCalled(); - handleSelect = jest.mocked(goToSelectUser).mock.calls[0][3]; + handleSelect = jest.mocked(goToSelectUser).mock.calls[0][5]; handleSelect(TestHelper.fakeUser({id: 'user-2'})); expect(setOwner).toHaveBeenCalledWith( @@ -268,7 +272,7 @@ describe('PlaybookRun', () => { const ownerChip = getByTestId('user-chip'); ownerChip.props.onPress(); - const handleSelect = jest.mocked(goToSelectUser).mock.calls[0][3]; + const handleSelect = jest.mocked(goToSelectUser).mock.calls[0][5]; handleSelect(TestHelper.fakeUser({id: 'user-2'})); await waitFor(() => { diff --git a/app/products/playbooks/screens/playbook_run/playbook_run.tsx b/app/products/playbooks/screens/playbook_run/playbook_run.tsx index 47f4fe027..4ae731455 100644 --- a/app/products/playbooks/screens/playbook_run/playbook_run.tsx +++ b/app/products/playbooks/screens/playbook_run/playbook_run.tsx @@ -200,12 +200,14 @@ export default function PlaybookRun({ } goToSelectUser( + theme, + playbookRun?.name || '', intl.formatMessage(messages.owner), [...participants.map((p) => p.id), owner?.id || ''], owner?.id, handleSelectOwner, ); - }, [handleSelectOwner, intl, owner, participants]); + }, [handleSelectOwner, intl, owner, participants, playbookRun?.name, theme]); const ownerAction = useMemo(() => { if (readOnly) {