Add subtitles for playbook bottom sheet screens to add the run name (#9130)
* Add subtitles for playbook bottom sheet screens to add the run name * Add missing change
This commit is contained in:
parent
167d0c6579
commit
f384ddb65d
8 changed files with 145 additions and 47 deletions
|
|
@ -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),
|
||||
},
|
||||
},
|
||||
},
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<typeof ChecklistItemBottomSheet> {
|
||||
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<typeof EditCommand>).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'}));
|
||||
|
|
|
|||
|
|
@ -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<typeof OptionItem>['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 = () => (
|
||||
<View style={styles.taskDetailsContainer}>
|
||||
|
|
|
|||
|
|
@ -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 () => {
|
||||
|
|
|
|||
|
|
@ -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$(''),
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -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(() => {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue