(cherry picked from commit 1103ddb8f4)
Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
parent
835322768e
commit
01caefe0bc
4 changed files with 132 additions and 31 deletions
|
|
@ -9,6 +9,7 @@ const DeepLinkType = {
|
|||
Permalink: 'permalink',
|
||||
Playbooks: 'playbooks',
|
||||
PlaybookRuns: 'playbook_runs',
|
||||
PlaybookRunsRetrospective: 'playbook_runs_retrospective',
|
||||
Redirect: '_redirect',
|
||||
Server: 'server',
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {createIntl} from 'react-intl';
|
||||
import {Alert} from 'react-native';
|
||||
import {Navigation} from 'react-native-navigation';
|
||||
|
||||
import {makeDirectChannel, switchToChannelByName} from '@actions/remote/channel';
|
||||
|
|
@ -11,10 +12,15 @@ import {DeepLink, Launch, Preferences, Screens} from '@constants';
|
|||
import DatabaseManager from '@database/manager';
|
||||
import {t} from '@i18n';
|
||||
import WebsocketManager from '@managers/websocket_manager';
|
||||
import {fetchPlaybookRun} from '@playbooks/actions/remote/runs';
|
||||
import {getPlaybookRunById} from '@playbooks/database/queries/run';
|
||||
import {fetchIsPlaybooksEnabled} from '@playbooks/database/queries/version';
|
||||
import {goToPlaybookRun} from '@playbooks/screens/navigation';
|
||||
import {getActiveServerUrl} from '@queries/app/servers';
|
||||
import {queryUsersByUsername} from '@queries/servers/user';
|
||||
import {dismissAllModalsAndPopToRoot} from '@screens/navigation';
|
||||
import NavigationStore from '@store/navigation_store';
|
||||
import TestHelper from '@test/test_helper';
|
||||
import {logError} from '@utils/log';
|
||||
import {addNewServer} from '@utils/server';
|
||||
|
||||
|
|
@ -81,6 +87,11 @@ jest.mock('@i18n', () => ({
|
|||
t: jest.fn((id) => id),
|
||||
}));
|
||||
|
||||
jest.mock('@playbooks/database/queries/version');
|
||||
jest.mock('@playbooks/database/queries/run');
|
||||
jest.mock('@playbooks/actions/remote/runs');
|
||||
jest.mock('@playbooks/screens/navigation');
|
||||
|
||||
describe('extractServerUrl', () => {
|
||||
it('should extract the sanitized server url', () => {
|
||||
expect(extractServerUrl('example.com:8080//path/to///login')).toEqual('example.com:8080/path/to');
|
||||
|
|
@ -107,8 +118,8 @@ describe('parseAndHandleDeepLink', () => {
|
|||
});
|
||||
|
||||
it('should add new server if not existing', async () => {
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://currentserver.com');
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('');
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://currentserver.com');
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce('');
|
||||
const result = await parseAndHandleDeepLink('https://newserver.com/team/channels/town-square');
|
||||
expect(addNewServer).toHaveBeenCalledWith(Preferences.THEMES.denim, 'newserver.com', undefined, {type: DeepLink.Channel,
|
||||
data: {
|
||||
|
|
@ -122,8 +133,8 @@ describe('parseAndHandleDeepLink', () => {
|
|||
});
|
||||
|
||||
it('should handle existing server and switch to home screen', async () => {
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://currentserver.com');
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('https://existingserver.com');
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://currentserver.com');
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce('https://existingserver.com');
|
||||
const result = await parseAndHandleDeepLink('https://existingserver.com/team/channels/town-square');
|
||||
expect(dismissAllModalsAndPopToRoot).toHaveBeenCalled();
|
||||
expect(DatabaseManager.setActiveServerDatabase).toHaveBeenCalledWith('https://existingserver.com');
|
||||
|
|
@ -132,76 +143,75 @@ describe('parseAndHandleDeepLink', () => {
|
|||
});
|
||||
|
||||
it('should update the server url in the server url screen', async () => {
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://currentserver.com');
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce(null);
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://currentserver.com');
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce(undefined);
|
||||
|
||||
(NavigationStore.getVisibleScreen as jest.Mock).mockReturnValueOnce(Screens.SERVER);
|
||||
jest.mocked(NavigationStore.getVisibleScreen).mockReturnValueOnce(Screens.SERVER);
|
||||
const result = await parseAndHandleDeepLink('https://currentserver.com/team/channels/town-square', undefined, undefined, true);
|
||||
console.log('result', JSON.stringify(result, null, 2));
|
||||
const spyOnUpdateProps = jest.spyOn(Navigation, 'updateProps');
|
||||
expect(spyOnUpdateProps).toHaveBeenCalledWith(Screens.SERVER, {serverUrl: 'currentserver.com'});
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should not display the new server modal if the server screen is on the stack but not as the visible screen', async () => {
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://currentserver.com');
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce(null);
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://currentserver.com');
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce(undefined);
|
||||
|
||||
(NavigationStore.getVisibleScreen as jest.Mock).mockReturnValueOnce(Screens.LOGIN);
|
||||
(NavigationStore.getScreensInStack as jest.Mock).mockReturnValueOnce([Screens.SERVER, Screens.LOGIN]);
|
||||
jest.mocked(NavigationStore.getVisibleScreen).mockReturnValueOnce(Screens.LOGIN);
|
||||
jest.mocked(NavigationStore.getScreensInStack).mockReturnValueOnce([Screens.SERVER, Screens.LOGIN]);
|
||||
const result = await parseAndHandleDeepLink('https://currentserver.com/team/channels/town-square', undefined, undefined, true);
|
||||
expect(addNewServer).not.toHaveBeenCalled();
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should switch to channel by name for Channel deep link', async () => {
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('https://existingserver.com');
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://existingserver.com');
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce('https://existingserver.com');
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://existingserver.com');
|
||||
const result = await parseAndHandleDeepLink('https://existingserver.com/team/channels/town-square', intl);
|
||||
expect(switchToChannelByName).toHaveBeenCalledWith('https://existingserver.com', 'town-square', 'team', errorBadChannel, intl);
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should create direct message for DirectMessage deep link', async () => {
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('https://existingserver.com');
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://existingserver.com');
|
||||
(queryUsersByUsername as jest.Mock).mockReturnValueOnce({fetchIds: jest.fn(() => ['user-id'])});
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce('https://existingserver.com');
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://existingserver.com');
|
||||
(queryUsersByUsername as jest.Mock).mockReturnValueOnce(TestHelper.fakeQuery([TestHelper.fakeUserModel({id: 'user-id'})]));
|
||||
const result = await parseAndHandleDeepLink('https://existingserver.com/team/messages/@user-id', intl);
|
||||
expect(makeDirectChannel).toHaveBeenCalledWith('https://existingserver.com', 'user-id', '', true);
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should fetch user and create direct message if user not found locally', async () => {
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('https://existingserver.com');
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://existingserver.com');
|
||||
(queryUsersByUsername as jest.Mock).mockReturnValueOnce({fetchIds: jest.fn(() => [])});
|
||||
(fetchUsersByUsernames as jest.Mock).mockResolvedValueOnce({users: [{id: 'user-id'}]});
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce('https://existingserver.com');
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://existingserver.com');
|
||||
jest.mocked(fetchUsersByUsernames).mockResolvedValueOnce({users: [TestHelper.fakeUser({id: 'user-id'})]});
|
||||
jest.mocked(queryUsersByUsername).mockReturnValueOnce(TestHelper.fakeQuery([]));
|
||||
const result = await parseAndHandleDeepLink('https://existingserver.com/team/messages/@user-id', intl);
|
||||
expect(makeDirectChannel).toHaveBeenCalledWith('https://existingserver.com', 'user-id', '', true);
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should show unknown user error if user not found', async () => {
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('https://existingserver.com');
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://existingserver.com');
|
||||
(queryUsersByUsername as jest.Mock).mockReturnValueOnce({fetchIds: jest.fn(() => [])});
|
||||
(fetchUsersByUsernames as jest.Mock).mockResolvedValueOnce({users: []});
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce('https://existingserver.com');
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://existingserver.com');
|
||||
jest.mocked(queryUsersByUsername).mockReturnValueOnce(TestHelper.fakeQuery([]));
|
||||
jest.mocked(fetchUsersByUsernames).mockResolvedValueOnce({users: []});
|
||||
const result = await parseAndHandleDeepLink('https://existingserver.com/team/messages/@user-id', intl);
|
||||
expect(errorUnkownUser).toHaveBeenCalledWith(intl);
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should switch to group message channel for GroupMessage deep link', async () => {
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('https://existingserver.com');
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://existingserver.com');
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce('https://existingserver.com');
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://existingserver.com');
|
||||
const result = await parseAndHandleDeepLink('https://existingserver.com/team/messages/7b35c77a645e1906e03a2c330f89203385db102f', intl);
|
||||
expect(switchToChannelByName).toHaveBeenCalledWith('https://existingserver.com', '7b35c77a645e1906e03a2c330f89203385db102f', 'team', errorBadChannel, intl);
|
||||
expect(result).toEqual({error: false});
|
||||
});
|
||||
|
||||
it('should show permalink for Permalink deep link', async () => {
|
||||
(DatabaseManager.searchUrl as jest.Mock).mockReturnValueOnce('https://existingserver.com');
|
||||
(getActiveServerUrl as jest.Mock).mockResolvedValueOnce('https://existingserver.com');
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce('https://existingserver.com');
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://existingserver.com');
|
||||
const postid = '7b35c77a645e1906e03a2c330f';
|
||||
const result = await parseAndHandleDeepLink(`https://existingserver.com/team/pl/${postid}`, intl);
|
||||
expect(showPermalink).toHaveBeenCalledWith('https://existingserver.com', 'team', postid);
|
||||
|
|
@ -209,13 +219,81 @@ describe('parseAndHandleDeepLink', () => {
|
|||
});
|
||||
|
||||
it('should log error and return error true on failure', async () => {
|
||||
(getActiveServerUrl as jest.Mock).mockImplementationOnce(() => {
|
||||
jest.mocked(getActiveServerUrl).mockImplementationOnce(() => {
|
||||
throw new Error('DB does not exist error');
|
||||
});
|
||||
const result = await parseAndHandleDeepLink('https://existingserver.com/team/messages/7b35c77a645e1906e03a2c330f89203385db102f');
|
||||
expect(logError).toHaveBeenCalledWith('Failed to open channel from deeplink', expect.any(Error), undefined);
|
||||
expect(result).toEqual({error: true});
|
||||
});
|
||||
|
||||
it('should alert when Playbooks deep link is used', async () => {
|
||||
const alertSpy = jest.spyOn(Alert, 'alert');
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce('https://existingserver.com');
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://existingserver.com');
|
||||
await parseAndHandleDeepLink('https://existingserver.com/playbooks/playbooks/7b35c77a645e1906e03a2c330f', intl);
|
||||
expect(alertSpy).toHaveBeenCalledWith(
|
||||
intl.formatMessage({id: 'playbooks.only_runs_available.title', defaultMessage: 'Playbooks not available'}),
|
||||
intl.formatMessage({id: 'playbooks.only_runs_available.description', defaultMessage: 'Only Playbook Runs are available on mobile. To access the Playbook, please use the desktop or web app.'}),
|
||||
[{text: intl.formatMessage({id: 'playbooks.only_runs_available.ok', defaultMessage: 'OK'})}],
|
||||
);
|
||||
});
|
||||
|
||||
it('should alert when PlaybookRunsRetrospective deep link is used', async () => {
|
||||
const alertSpy = jest.spyOn(Alert, 'alert');
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce('https://existingserver.com');
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://existingserver.com');
|
||||
await parseAndHandleDeepLink('https://existingserver.com/playbooks/runs/7b35c77a645e1906e03a2c330f/retrospective', intl);
|
||||
expect(alertSpy).toHaveBeenCalledWith(
|
||||
intl.formatMessage({id: 'playbooks.retrospective_not_available.title', defaultMessage: 'Playbooks Run Retrospective not available'}),
|
||||
intl.formatMessage({id: 'playbooks.retrospective_not_available.description', defaultMessage: 'Only Playbook Runs are available on mobile. To fill the Run Retrospective, please use the desktop or web app.'}),
|
||||
[{text: intl.formatMessage({id: 'playbooks.retrospective_not_available.ok', defaultMessage: 'OK'})}],
|
||||
);
|
||||
});
|
||||
|
||||
it('should go to playbook run if enabled and playbook exists', async () => {
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce('https://existingserver.com');
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://existingserver.com');
|
||||
jest.mocked(fetchIsPlaybooksEnabled).mockResolvedValue(true);
|
||||
jest.mocked(getPlaybookRunById).mockResolvedValue(TestHelper.fakePlaybookRunModel({id: '7b35c77a645e1906e03a2c330f'}));
|
||||
jest.mocked(goToPlaybookRun).mockImplementation(jest.fn());
|
||||
|
||||
// Re-import to apply mocks
|
||||
await parseAndHandleDeepLink('https://existingserver.com/playbooks/runs/7b35c77a645e1906e03a2c330f', intl);
|
||||
expect(goToPlaybookRun).toHaveBeenCalledWith(intl, '7b35c77a645e1906e03a2c330f');
|
||||
});
|
||||
|
||||
it('should fetch playbook run if not found locally and show error if fetch fails', async () => {
|
||||
const alertSpy = jest.spyOn(Alert, 'alert');
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce('https://existingserver.com');
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://existingserver.com');
|
||||
jest.mocked(fetchIsPlaybooksEnabled).mockResolvedValue(true);
|
||||
jest.mocked(getPlaybookRunById).mockResolvedValue(undefined);
|
||||
jest.mocked(fetchPlaybookRun).mockResolvedValue({error: true});
|
||||
|
||||
// Re-import to apply mocks
|
||||
await parseAndHandleDeepLink('https://existingserver.com/playbooks/runs/7b35c77a645e1906e03a2c330f', intl);
|
||||
expect(alertSpy).toHaveBeenCalledWith(
|
||||
intl.formatMessage({id: 'playbooks.fetch_error.title', defaultMessage: 'Unable to open Run'}),
|
||||
intl.formatMessage({id: 'playbooks.fetch_error.description', defaultMessage: "You don't have permission to view this run, or it may no longer exist."}),
|
||||
[{text: intl.formatMessage({id: 'playbooks.fetch_error.OK', defaultMessage: 'Okay'})}],
|
||||
);
|
||||
});
|
||||
|
||||
it('should alert if playbooks are not enabled or version not supported', async () => {
|
||||
const alertSpy = jest.spyOn(Alert, 'alert');
|
||||
jest.mocked(DatabaseManager.searchUrl).mockReturnValueOnce('https://existingserver.com');
|
||||
jest.mocked(getActiveServerUrl).mockResolvedValueOnce('https://existingserver.com');
|
||||
jest.mocked(fetchIsPlaybooksEnabled).mockResolvedValue(false);
|
||||
|
||||
// Re-import to apply mocks
|
||||
await parseAndHandleDeepLink('https://existingserver.com/playbooks/runs/7b35c77a645e1906e03a2c330f', intl);
|
||||
expect(alertSpy).toHaveBeenCalledWith(
|
||||
intl.formatMessage({id: 'playbooks.not_enabled_or_unsupported.title', defaultMessage: 'Playbooks not available'}),
|
||||
intl.formatMessage({id: 'playbooks.not_enabled_or_unsupported.description', defaultMessage: 'Playbooks are either not enabled on this server or the Playbooks version is not supported. Please contact your system administrator.'}),
|
||||
[{text: intl.formatMessage({id: 'playbooks.not_enabled_or_unsupported.OK', defaultMessage: 'OK'})}],
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getLaunchPropsFromDeepLink', () => {
|
||||
|
|
@ -278,7 +356,7 @@ describe('alertInvalidDeepLink', () => {
|
|||
defaultMessage: 'This link you are trying to open is invalid.',
|
||||
};
|
||||
|
||||
(t as jest.Mock).mockReturnValue(message.id);
|
||||
jest.mocked(t).mockReturnValue(message.id);
|
||||
|
||||
alertInvalidDeepLink(intl);
|
||||
|
||||
|
|
|
|||
|
|
@ -125,6 +125,16 @@ export async function handleDeepLink(deepLink: DeepLinkWithData, intlShape?: Int
|
|||
);
|
||||
break;
|
||||
}
|
||||
case DeepLink.PlaybookRunsRetrospective: {
|
||||
Alert.alert(
|
||||
intl.formatMessage({id: 'playbooks.retrospective_not_available.title', defaultMessage: 'Playbooks Run Retrospective not available'}),
|
||||
intl.formatMessage({id: 'playbooks.retrospective_not_available.description', defaultMessage: 'Only Playbook Runs are available on mobile. To fill the Run Retrospective, please use the desktop or web app.'}),
|
||||
[{
|
||||
text: intl.formatMessage({id: 'playbooks.retrospective_not_available.ok', defaultMessage: 'OK'}),
|
||||
}],
|
||||
);
|
||||
break;
|
||||
}
|
||||
case DeepLink.PlaybookRuns: {
|
||||
const deepLinkData = deepLink.data as DeepLinkPlaybookRuns;
|
||||
const playbookEnabled = await fetchIsPlaybooksEnabled(database);
|
||||
|
|
@ -197,6 +207,9 @@ type PlaybookRunsPathParams = {
|
|||
const PLAYBOOK_RUNS_PATH = '*serverUrl/playbooks/runs/:playbookRunId';
|
||||
export const matchPlaybookRunsDeeplink = match<PlaybookRunsPathParams>(PLAYBOOK_RUNS_PATH);
|
||||
|
||||
const PLAYBOOK_RUNS_RETROSPECTIVE = '*serverUrl/playbooks/runs/:playbookRunId/retrospective';
|
||||
export const matchPlaybookRunsRetrospectiveDeeplink = match<PlaybookRunsPathParams>(PLAYBOOK_RUNS_RETROSPECTIVE);
|
||||
|
||||
type PermalinkPathParams = {
|
||||
serverUrl: string[];
|
||||
teamName: string;
|
||||
|
|
@ -312,6 +325,12 @@ export function parseDeepLink(deepLinkUrl: string, asServer = false): DeepLinkWi
|
|||
return {type: DeepLink.Playbooks, url: deepLinkUrl, data: {serverUrl: serverUrl.join('/'), playbookId}};
|
||||
}
|
||||
|
||||
const playbooksRunsRetrospectiveMatch = matchPlaybookRunsRetrospectiveDeeplink(url);
|
||||
if (playbooksRunsRetrospectiveMatch && isValidId(playbooksRunsRetrospectiveMatch.params.playbookRunId)) {
|
||||
const {params: {serverUrl, playbookRunId}} = playbooksRunsRetrospectiveMatch;
|
||||
return {type: DeepLink.PlaybookRunsRetrospective, url: deepLinkUrl, data: {serverUrl: serverUrl.join('/'), playbookRunId}};
|
||||
}
|
||||
|
||||
const playbooksRunsMatch = matchPlaybookRunsDeeplink(url);
|
||||
if (playbooksRunsMatch && isValidId(playbooksRunsMatch.params.playbookRunId)) {
|
||||
const {params: {serverUrl, playbookRunId}} = playbooksRunsMatch;
|
||||
|
|
|
|||
|
|
@ -1019,6 +1019,9 @@
|
|||
"playbooks.playbook_run.tasks": "Tasks",
|
||||
"playbooks.playbook_run.title": "Playbook run",
|
||||
"playbooks.playbooks_runs.title": "Playbook runs",
|
||||
"playbooks.retrospective_not_available.description": "Only Playbook Runs are available on mobile. To fill the Run Retrospective, please use the desktop or web app.",
|
||||
"playbooks.retrospective_not_available.ok": "OK",
|
||||
"playbooks.retrospective_not_available.title": "Playbooks Run Retrospective not available",
|
||||
"playbooks.runs.finished.description": "When a run in this channel finishes, you’ll see it here.",
|
||||
"playbooks.runs.finished.title": "No finished runs",
|
||||
"playbooks.runs.in_progress.description": "When a run starts in this channel, you’ll see it here.",
|
||||
|
|
|
|||
Loading…
Reference in a new issue