Checklist (#9234)
This commit is contained in:
parent
af2c35b03b
commit
be5b6c196c
65 changed files with 1472 additions and 175 deletions
|
|
@ -17,7 +17,7 @@ export type HeaderRightButton = {
|
|||
buttonType?: 'native' | 'opacity' | 'highlight';
|
||||
color?: string;
|
||||
iconName: string;
|
||||
count?: number;
|
||||
count?: number | string;
|
||||
onPress: () => void;
|
||||
rippleRadius?: number;
|
||||
testID?: string;
|
||||
|
|
|
|||
|
|
@ -25,6 +25,17 @@ const {
|
|||
const {PLAYBOOK_RUN, PLAYBOOK_CHECKLIST, PLAYBOOK_CHECKLIST_ITEM, PLAYBOOK_RUN_ATTRIBUTE, PLAYBOOK_RUN_ATTRIBUTE_VALUE} = PLAYBOOK_TABLES;
|
||||
|
||||
export default schemaMigrations({migrations: [
|
||||
{
|
||||
toVersion: 15,
|
||||
steps: [
|
||||
addColumns({
|
||||
table: PLAYBOOK_RUN,
|
||||
columns: [
|
||||
{name: 'type', type: 'string'},
|
||||
],
|
||||
}),
|
||||
],
|
||||
},
|
||||
{
|
||||
toVersion: 14,
|
||||
steps: [
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ import {
|
|||
} from './table_schemas';
|
||||
|
||||
export const serverSchema: AppSchema = appSchema({
|
||||
version: 14,
|
||||
version: 15,
|
||||
tables: [
|
||||
CategorySchema,
|
||||
CategoryChannelSchema,
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ const {PLAYBOOK_RUN, PLAYBOOK_CHECKLIST, PLAYBOOK_CHECKLIST_ITEM, PLAYBOOK_RUN_A
|
|||
describe('*** Test schema for SERVER database ***', () => {
|
||||
it('=> The SERVER SCHEMA should strictly match', () => {
|
||||
expect(serverSchema).toEqual({
|
||||
version: 14,
|
||||
version: 15,
|
||||
unsafeSql: undefined,
|
||||
tables: {
|
||||
[CATEGORY]: {
|
||||
|
|
@ -463,6 +463,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
|||
items_order: {name: 'items_order', type: 'string'},
|
||||
previous_reminder: {name: 'previous_reminder', type: 'number', isOptional: true},
|
||||
update_at: {name: 'update_at', type: 'number'},
|
||||
type: {name: 'type', type: 'string'},
|
||||
},
|
||||
columnArray: [
|
||||
{name: 'playbook_id', type: 'string'},
|
||||
|
|
@ -489,6 +490,7 @@ describe('*** Test schema for SERVER database ***', () => {
|
|||
{name: 'previous_reminder', type: 'number', isOptional: true},
|
||||
{name: 'items_order', type: 'string'},
|
||||
{name: 'update_at', type: 'number'},
|
||||
{name: 'type', type: 'string'},
|
||||
],
|
||||
},
|
||||
[PLAYBOOK_CHECKLIST]: {
|
||||
|
|
|
|||
|
|
@ -2,10 +2,11 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getPlaybookChecklistById} from '@playbooks/database/queries/checklist';
|
||||
import {getPlaybookChecklistItemById} from '@playbooks/database/queries/item';
|
||||
import TestHelper from '@test/test_helper';
|
||||
|
||||
import {updateChecklistItem, setChecklistItemCommand, setAssignee, setDueDate} from './checklist';
|
||||
import {updateChecklistItem, setChecklistItemCommand, setAssignee, setDueDate, renameChecklist} from './checklist';
|
||||
|
||||
import type ServerDataOperator from '@database/operator/server_data_operator';
|
||||
|
||||
|
|
@ -264,3 +265,109 @@ describe('setDueDate', () => {
|
|||
expect(updated!.dueDate).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renameChecklist', () => {
|
||||
it('should handle not found database', async () => {
|
||||
const {error} = await renameChecklist('foo', 'checklistid', 'New Title');
|
||||
expect(error).toBeTruthy();
|
||||
expect((error as Error).message).toContain('foo database not found');
|
||||
});
|
||||
|
||||
it('should handle checklist not found', async () => {
|
||||
const {error} = await renameChecklist(serverUrl, 'nonexistent', 'New Title');
|
||||
expect(error).toBe('Checklist not found: nonexistent');
|
||||
});
|
||||
|
||||
it('should handle database write errors', async () => {
|
||||
const runId = 'runid';
|
||||
const checklist = {
|
||||
...TestHelper.createPlaybookChecklist(runId, 0, 0),
|
||||
run_id: runId,
|
||||
order: 0,
|
||||
};
|
||||
await operator.handlePlaybookChecklist({checklists: [checklist], prepareRecordsOnly: false});
|
||||
|
||||
const originalWrite = operator.database.write;
|
||||
operator.database.write = jest.fn().mockRejectedValue(new Error('Database write failed'));
|
||||
|
||||
const {error} = await renameChecklist(serverUrl, checklist.id, 'New Title');
|
||||
expect(error).toBeTruthy();
|
||||
|
||||
operator.database.write = originalWrite;
|
||||
});
|
||||
|
||||
it('should rename checklist successfully', async () => {
|
||||
const runId = 'runid';
|
||||
const checklist = {
|
||||
...TestHelper.createPlaybookChecklist(runId, 0, 0),
|
||||
run_id: runId,
|
||||
order: 0,
|
||||
};
|
||||
await operator.handlePlaybookChecklist({checklists: [checklist], prepareRecordsOnly: false});
|
||||
|
||||
const newTitle = 'Updated Checklist Title';
|
||||
const {data, error} = await renameChecklist(serverUrl, checklist.id, newTitle);
|
||||
expect(error).toBeUndefined();
|
||||
expect(data).toBe(true);
|
||||
|
||||
const updated = await getPlaybookChecklistById(operator.database, checklist.id);
|
||||
expect(updated).toBeDefined();
|
||||
expect(updated!.title).toBe(newTitle);
|
||||
});
|
||||
|
||||
it('should handle empty title string', async () => {
|
||||
const runId = 'runid';
|
||||
const checklist = {
|
||||
...TestHelper.createPlaybookChecklist(runId, 0, 0),
|
||||
run_id: runId,
|
||||
order: 0,
|
||||
};
|
||||
await operator.handlePlaybookChecklist({checklists: [checklist], prepareRecordsOnly: false});
|
||||
|
||||
const {data, error} = await renameChecklist(serverUrl, checklist.id, '');
|
||||
expect(error).toBeUndefined();
|
||||
expect(data).toBe(true);
|
||||
|
||||
const updated = await getPlaybookChecklistById(operator.database, checklist.id);
|
||||
expect(updated).toBeDefined();
|
||||
expect(updated!.title).toBe('');
|
||||
});
|
||||
|
||||
it('should handle whitespace-only title', async () => {
|
||||
const runId = 'runid';
|
||||
const checklist = {
|
||||
...TestHelper.createPlaybookChecklist(runId, 0, 0),
|
||||
run_id: runId,
|
||||
order: 0,
|
||||
};
|
||||
await operator.handlePlaybookChecklist({checklists: [checklist], prepareRecordsOnly: false});
|
||||
|
||||
const whitespaceTitle = ' ';
|
||||
const {data, error} = await renameChecklist(serverUrl, checklist.id, whitespaceTitle);
|
||||
expect(error).toBeUndefined();
|
||||
expect(data).toBe(true);
|
||||
|
||||
const updated = await getPlaybookChecklistById(operator.database, checklist.id);
|
||||
expect(updated).toBeDefined();
|
||||
expect(updated!.title).toBe(whitespaceTitle);
|
||||
});
|
||||
|
||||
it('should handle very long titles', async () => {
|
||||
const runId = 'runid';
|
||||
const checklist = {
|
||||
...TestHelper.createPlaybookChecklist(runId, 0, 0),
|
||||
run_id: runId,
|
||||
order: 0,
|
||||
};
|
||||
await operator.handlePlaybookChecklist({checklists: [checklist], prepareRecordsOnly: false});
|
||||
|
||||
const longTitle = 'A'.repeat(300); // 300 characters
|
||||
const {data, error} = await renameChecklist(serverUrl, checklist.id, longTitle);
|
||||
expect(error).toBeUndefined();
|
||||
expect(data).toBe(true);
|
||||
|
||||
const updated = await getPlaybookChecklistById(operator.database, checklist.id);
|
||||
expect(updated).toBeDefined();
|
||||
expect(updated!.title).toBe(longTitle);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {getPlaybookChecklistById} from '@playbooks/database/queries/checklist';
|
||||
import {getPlaybookChecklistItemById} from '@playbooks/database/queries/item';
|
||||
import {logError} from '@utils/log';
|
||||
|
||||
|
|
@ -88,3 +89,24 @@ export async function setDueDate(serverUrl: string, itemId: string, date?: numbe
|
|||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function renameChecklist(serverUrl: string, checklistId: string, title: string) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const checklist = await getPlaybookChecklistById(database, checklistId);
|
||||
if (!checklist) {
|
||||
return {error: `Checklist not found: ${checklistId}`};
|
||||
}
|
||||
|
||||
await database.write(async () => {
|
||||
checklist.update((c) => {
|
||||
c.title = title;
|
||||
});
|
||||
});
|
||||
|
||||
return {data: true};
|
||||
} catch (error) {
|
||||
logError('failed to rename checklist', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import DatabaseManager from '@database/manager';
|
|||
import {PLAYBOOK_TABLES} from '@playbooks/constants/database';
|
||||
import TestHelper from '@test/test_helper';
|
||||
|
||||
import {handlePlaybookRuns, setOwner} from './run';
|
||||
import {handlePlaybookRuns, setOwner, renamePlaybookRun} from './run';
|
||||
|
||||
import type {Database} from '@nozbe/watermelondb';
|
||||
import type PlaybookRunModel from '@playbooks/types/database/models/playbook_run';
|
||||
|
|
@ -80,7 +80,7 @@ describe('setOwner', () => {
|
|||
|
||||
const {data, error} = await setOwner(serverUrl, nonExistentRunId, newOwnerId);
|
||||
|
||||
expect(error).toBe('Run not found');
|
||||
expect(error).toBe('Checklist not found');
|
||||
expect(data).toBeUndefined();
|
||||
});
|
||||
|
||||
|
|
@ -153,3 +153,100 @@ describe('setOwner', () => {
|
|||
expect(updatedRun.ownerUserId).toBe(emptyOwnerId);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renamePlaybookRun', () => {
|
||||
let database: Database;
|
||||
beforeEach(() => {
|
||||
database = DatabaseManager.getServerDatabaseAndOperator(serverUrl).database;
|
||||
});
|
||||
|
||||
it('should handle not found database', async () => {
|
||||
const {error} = await renamePlaybookRun('foo', 'runid', 'New Name');
|
||||
expect(error).toBeTruthy();
|
||||
expect((error as Error).message).toContain('foo database not found');
|
||||
});
|
||||
|
||||
it('should handle playbook run not found', async () => {
|
||||
const {error} = await renamePlaybookRun(serverUrl, 'nonexistent', 'New Name');
|
||||
expect(error).toBe('Playbook run not found: nonexistent');
|
||||
});
|
||||
|
||||
it('should handle database write errors', async () => {
|
||||
const runs = TestHelper.createPlaybookRuns(1, 0, 0);
|
||||
await handlePlaybookRuns(serverUrl, runs, false, false);
|
||||
|
||||
const playbookRunId = runs[0].id;
|
||||
|
||||
const originalWrite = database.write;
|
||||
database.write = jest.fn().mockRejectedValue(new Error('Database write failed'));
|
||||
|
||||
const {error} = await renamePlaybookRun(serverUrl, playbookRunId, 'New Name');
|
||||
expect(error).toBeTruthy();
|
||||
expect((error as Error).message).toBe('Database write failed');
|
||||
|
||||
database.write = originalWrite;
|
||||
});
|
||||
|
||||
it('should rename playbook run successfully', async () => {
|
||||
const runs = TestHelper.createPlaybookRuns(1, 0, 0);
|
||||
await handlePlaybookRuns(serverUrl, runs, false, false);
|
||||
|
||||
const playbookRunId = runs[0].id;
|
||||
const newName = 'Updated Run Name';
|
||||
|
||||
const {data, error} = await renamePlaybookRun(serverUrl, playbookRunId, newName);
|
||||
|
||||
expect(error).toBeUndefined();
|
||||
expect(data).toBe(true);
|
||||
|
||||
const updatedRun = await database.get<PlaybookRunModel>(PLAYBOOK_TABLES.PLAYBOOK_RUN).find(playbookRunId);
|
||||
expect(updatedRun.name).toBe(newName);
|
||||
});
|
||||
|
||||
it('should handle empty name string', async () => {
|
||||
const runs = TestHelper.createPlaybookRuns(1, 0, 0);
|
||||
await handlePlaybookRuns(serverUrl, runs, false, false);
|
||||
|
||||
const playbookRunId = runs[0].id;
|
||||
|
||||
const {data, error} = await renamePlaybookRun(serverUrl, playbookRunId, '');
|
||||
|
||||
expect(error).toBeUndefined();
|
||||
expect(data).toBe(true);
|
||||
|
||||
const updatedRun = await database.get<PlaybookRunModel>(PLAYBOOK_TABLES.PLAYBOOK_RUN).find(playbookRunId);
|
||||
expect(updatedRun.name).toBe('');
|
||||
});
|
||||
|
||||
it('should handle whitespace-only name', async () => {
|
||||
const runs = TestHelper.createPlaybookRuns(1, 0, 0);
|
||||
await handlePlaybookRuns(serverUrl, runs, false, false);
|
||||
|
||||
const playbookRunId = runs[0].id;
|
||||
const whitespaceName = ' ';
|
||||
|
||||
const {data, error} = await renamePlaybookRun(serverUrl, playbookRunId, whitespaceName);
|
||||
|
||||
expect(error).toBeUndefined();
|
||||
expect(data).toBe(true);
|
||||
|
||||
const updatedRun = await database.get<PlaybookRunModel>(PLAYBOOK_TABLES.PLAYBOOK_RUN).find(playbookRunId);
|
||||
expect(updatedRun.name).toBe(whitespaceName);
|
||||
});
|
||||
|
||||
it('should handle very long names', async () => {
|
||||
const runs = TestHelper.createPlaybookRuns(1, 0, 0);
|
||||
await handlePlaybookRuns(serverUrl, runs, false, false);
|
||||
|
||||
const playbookRunId = runs[0].id;
|
||||
const longName = 'A'.repeat(300); // 300 characters
|
||||
|
||||
const {data, error} = await renamePlaybookRun(serverUrl, playbookRunId, longName);
|
||||
|
||||
expect(error).toBeUndefined();
|
||||
expect(data).toBe(true);
|
||||
|
||||
const updatedRun = await database.get<PlaybookRunModel>(PLAYBOOK_TABLES.PLAYBOOK_RUN).find(playbookRunId);
|
||||
expect(updatedRun.name).toBe(longName);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export async function handlePlaybookRuns(serverUrl: string, runs: PlaybookRun[],
|
|||
});
|
||||
return {data};
|
||||
} catch (error) {
|
||||
logError('failed to handle playbook runs', error);
|
||||
logError('failed to handle playbook checklist', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ export async function setOwner(serverUrl: string, playbookRunId: string, ownerId
|
|||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const run = await getPlaybookRunById(database, playbookRunId);
|
||||
if (!run) {
|
||||
return {error: 'Run not found'};
|
||||
return {error: 'Checklist not found'};
|
||||
}
|
||||
|
||||
await database.write(async () => {
|
||||
|
|
@ -40,3 +40,24 @@ export async function setOwner(serverUrl: string, playbookRunId: string, ownerId
|
|||
return {error};
|
||||
}
|
||||
}
|
||||
|
||||
export async function renamePlaybookRun(serverUrl: string, playbookRunId: string, name: string) {
|
||||
try {
|
||||
const {database} = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
|
||||
const run = await getPlaybookRunById(database, playbookRunId);
|
||||
if (!run) {
|
||||
return {error: `Playbook run not found: ${playbookRunId}`};
|
||||
}
|
||||
|
||||
await database.write(async () => {
|
||||
run.update((r) => {
|
||||
r.name = name;
|
||||
});
|
||||
});
|
||||
|
||||
return {data: true};
|
||||
} catch (error) {
|
||||
logError('failed to rename playbook run', error);
|
||||
return {error};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
updateChecklistItem as localUpdateChecklistItem,
|
||||
setAssignee as localSetAssignee,
|
||||
setDueDate as localSetDueDate,
|
||||
renameChecklist as localRenameChecklist,
|
||||
} from '@playbooks/actions/local/checklist';
|
||||
|
||||
import {
|
||||
|
|
@ -19,6 +20,8 @@ import {
|
|||
setChecklistItemCommand,
|
||||
setAssignee,
|
||||
setDueDate,
|
||||
renameChecklist,
|
||||
addChecklistItem,
|
||||
} from './checklist';
|
||||
|
||||
const serverUrl = 'baseHandler.test.com';
|
||||
|
|
@ -37,6 +40,8 @@ const mockClient = {
|
|||
setAssignee: jest.fn(),
|
||||
setChecklistItemCommand: jest.fn(),
|
||||
setDueDate: jest.fn(),
|
||||
renameChecklist: jest.fn(),
|
||||
addChecklistItem: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock('@playbooks/actions/local/checklist');
|
||||
|
|
@ -322,4 +327,104 @@ describe('checklist', () => {
|
|||
expect(localSetDueDate).toHaveBeenCalledWith(serverUrl, itemId, 0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renameChecklist', () => {
|
||||
const checklistId = 'checklist-id-1';
|
||||
const newTitle = 'New Checklist Title';
|
||||
|
||||
it('should handle client error', async () => {
|
||||
jest.spyOn(NetworkManager, 'getClient').mockImplementationOnce(throwFunc);
|
||||
|
||||
const result = await renameChecklist(serverUrl, playbookRunId, checklistNumber, checklistId, newTitle);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeDefined();
|
||||
expect(localRenameChecklist).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle API exception', async () => {
|
||||
mockClient.renameChecklist.mockImplementationOnce(throwFunc);
|
||||
|
||||
const result = await renameChecklist(serverUrl, playbookRunId, checklistNumber, checklistId, newTitle);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeDefined();
|
||||
expect(mockClient.renameChecklist).toHaveBeenCalledWith(playbookRunId, checklistNumber, newTitle);
|
||||
expect(localRenameChecklist).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle local DB update failure', async () => {
|
||||
mockClient.renameChecklist.mockResolvedValueOnce({});
|
||||
(localRenameChecklist as jest.Mock).mockRejectedValueOnce(new Error('DB error'));
|
||||
|
||||
const result = await renameChecklist(serverUrl, playbookRunId, checklistNumber, checklistId, newTitle);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeDefined();
|
||||
expect(mockClient.renameChecklist).toHaveBeenCalledWith(playbookRunId, checklistNumber, newTitle);
|
||||
expect(localRenameChecklist).toHaveBeenCalledWith(serverUrl, checklistId, newTitle);
|
||||
});
|
||||
|
||||
it('should rename checklist successfully', async () => {
|
||||
mockClient.renameChecklist.mockResolvedValueOnce({});
|
||||
|
||||
const result = await renameChecklist(serverUrl, playbookRunId, checklistNumber, checklistId, newTitle);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.data).toBe(true);
|
||||
expect(mockClient.renameChecklist).toHaveBeenCalledWith(playbookRunId, checklistNumber, newTitle);
|
||||
expect(localRenameChecklist).toHaveBeenCalledWith(serverUrl, checklistId, newTitle);
|
||||
});
|
||||
|
||||
it('should rename checklist with empty title', async () => {
|
||||
mockClient.renameChecklist.mockResolvedValueOnce({});
|
||||
const emptyTitle = '';
|
||||
|
||||
const result = await renameChecklist(serverUrl, playbookRunId, checklistNumber, checklistId, emptyTitle);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.data).toBe(true);
|
||||
expect(mockClient.renameChecklist).toHaveBeenCalledWith(playbookRunId, checklistNumber, emptyTitle);
|
||||
expect(localRenameChecklist).toHaveBeenCalledWith(serverUrl, checklistId, emptyTitle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addChecklistItem', () => {
|
||||
const title = 'New Item Title';
|
||||
|
||||
it('should handle client error', async () => {
|
||||
jest.spyOn(NetworkManager, 'getClient').mockImplementationOnce(throwFunc);
|
||||
|
||||
const result = await addChecklistItem(serverUrl, playbookRunId, checklistNumber, title);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeDefined();
|
||||
});
|
||||
|
||||
it('should handle API exception', async () => {
|
||||
mockClient.addChecklistItem.mockImplementationOnce(throwFunc);
|
||||
|
||||
const result = await addChecklistItem(serverUrl, playbookRunId, checklistNumber, title);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeDefined();
|
||||
expect(mockClient.addChecklistItem).toHaveBeenCalledWith(playbookRunId, checklistNumber, title);
|
||||
});
|
||||
|
||||
it('should add checklist item successfully', async () => {
|
||||
mockClient.addChecklistItem.mockResolvedValueOnce({});
|
||||
|
||||
const result = await addChecklistItem(serverUrl, playbookRunId, checklistNumber, title);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.data).toBe(true);
|
||||
expect(mockClient.addChecklistItem).toHaveBeenCalledWith(playbookRunId, checklistNumber, title);
|
||||
});
|
||||
|
||||
it('should add checklist item with empty title', async () => {
|
||||
mockClient.addChecklistItem.mockResolvedValueOnce({});
|
||||
const emptyTitle = '';
|
||||
|
||||
const result = await addChecklistItem(serverUrl, playbookRunId, checklistNumber, emptyTitle);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.data).toBe(true);
|
||||
expect(mockClient.addChecklistItem).toHaveBeenCalledWith(playbookRunId, checklistNumber, emptyTitle);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import {
|
|||
updateChecklistItem as localUpdateChecklistItem,
|
||||
setAssignee as localSetAssignee,
|
||||
setDueDate as localSetDueDate,
|
||||
renameChecklist as localRenameChecklist,
|
||||
} from '@playbooks/actions/local/checklist';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {logDebug} from '@utils/log';
|
||||
|
|
@ -156,3 +157,43 @@ export const setDueDate = async (
|
|||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const renameChecklist = async (
|
||||
serverUrl: string,
|
||||
playbookRunId: string,
|
||||
checklistNumber: number,
|
||||
checklistId: string,
|
||||
newTitle: string,
|
||||
) => {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
|
||||
// Patch the playbook run with updated checklists
|
||||
await client.renameChecklist(playbookRunId, checklistNumber, newTitle);
|
||||
|
||||
// Update local database
|
||||
await localRenameChecklist(serverUrl, checklistId, newTitle);
|
||||
return {data: true};
|
||||
} catch (error) {
|
||||
logDebug('error on renameChecklist', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const addChecklistItem = async (
|
||||
serverUrl: string,
|
||||
playbookRunId: string,
|
||||
checklistNumber: number,
|
||||
title: string,
|
||||
) => {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
await client.addChecklistItem(playbookRunId, checklistNumber, title);
|
||||
return {data: true};
|
||||
} catch (error) {
|
||||
logDebug('error on addChecklistItem', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import {PER_PAGE_DEFAULT} from '@client/rest/constants';
|
|||
import DatabaseManager from '@database/manager';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {updateLastPlaybookRunsFetchAt} from '@playbooks/actions/local/channel';
|
||||
import {handlePlaybookRuns, setOwner as localSetOwner} from '@playbooks/actions/local/run';
|
||||
import {handlePlaybookRuns, setOwner as localSetOwner, renamePlaybookRun as localRenamePlaybookRun} from '@playbooks/actions/local/run';
|
||||
import {getLastPlaybookRunsFetchAt} from '@playbooks/database/queries/run';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import TestHelper from '@test/test_helper';
|
||||
|
||||
import {fetchPlaybookRunsForChannel, fetchFinishedRunsForChannel, fetchPlaybookRunsPageForParticipant, setOwner, finishRun, createPlaybookRun, fetchPlaybookRun, fetchPlaybookRunMetadata, postStatusUpdate} from './runs';
|
||||
import {fetchPlaybookRunsForChannel, fetchFinishedRunsForChannel, fetchPlaybookRunsPageForParticipant, setOwner, finishRun, renamePlaybookRun, createPlaybookRun, fetchPlaybookRun, fetchPlaybookRunMetadata, postStatusUpdate} from './runs';
|
||||
|
||||
const serverUrl = 'baseHandler.test.com';
|
||||
const channelId = 'channel-id-1';
|
||||
|
|
@ -28,6 +28,7 @@ const mockClient = {
|
|||
finishRun: jest.fn(),
|
||||
createPlaybookRun: jest.fn(),
|
||||
postStatusUpdate: jest.fn(),
|
||||
patchPlaybookRun: jest.fn(),
|
||||
};
|
||||
|
||||
jest.mock('@playbooks/database/queries/run');
|
||||
|
|
@ -530,7 +531,6 @@ describe('createPlaybookRun', () => {
|
|||
jest.spyOn(NetworkManager, 'getClient').mockImplementationOnce(throwFunc);
|
||||
|
||||
const result = await createPlaybookRun(serverUrl, playbookId, ownerUserId, teamId, name, description);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.data).toBeUndefined();
|
||||
|
|
@ -717,5 +717,53 @@ describe('postStatusUpdate', () => {
|
|||
|
||||
const result = await postStatusUpdate(serverUrl, playbookRunID, payload, ids);
|
||||
expect(result.error).toBeDefined();
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
describe('renamePlaybookRun', () => {
|
||||
const playbookRunId = 'playbook-run-id-1';
|
||||
const newName = 'New Run Name';
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
jest.mocked(localRenamePlaybookRun).mockResolvedValue({data: true});
|
||||
});
|
||||
|
||||
it('should handle client error', async () => {
|
||||
jest.spyOn(NetworkManager, 'getClient').mockImplementationOnce(throwFunc);
|
||||
|
||||
const result = await renamePlaybookRun(serverUrl, playbookRunId, newName);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.data).toBeUndefined();
|
||||
expect(localRenamePlaybookRun).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should handle API exception', async () => {
|
||||
const clientError = new Error('Client error');
|
||||
mockClient.patchPlaybookRun.mockRejectedValueOnce(clientError);
|
||||
|
||||
const result = await renamePlaybookRun(serverUrl, playbookRunId, newName);
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeDefined();
|
||||
expect(result.data).toBeUndefined();
|
||||
expect(mockClient.patchPlaybookRun).toHaveBeenCalledWith(playbookRunId, {name: newName});
|
||||
expect(localRenamePlaybookRun).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should rename playbook run successfully', async () => {
|
||||
mockClient.patchPlaybookRun.mockResolvedValueOnce(undefined);
|
||||
|
||||
const result = await renamePlaybookRun(serverUrl, playbookRunId, newName);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(result.error).toBeUndefined();
|
||||
expect(result.data).toBe(true);
|
||||
expect(mockClient.patchPlaybookRun).toHaveBeenCalledWith(playbookRunId, {name: newName});
|
||||
expect(localRenamePlaybookRun).toHaveBeenCalledWith(serverUrl, playbookRunId, newName);
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import {PER_PAGE_DEFAULT} from '@client/rest/constants';
|
|||
import DatabaseManager from '@database/manager';
|
||||
import NetworkManager from '@managers/network_manager';
|
||||
import {updateLastPlaybookRunsFetchAt} from '@playbooks/actions/local/channel';
|
||||
import {handlePlaybookRuns, setOwner as localSetOwner} from '@playbooks/actions/local/run';
|
||||
import {handlePlaybookRuns, setOwner as localSetOwner, renamePlaybookRun as localRenamePlaybookRun} from '@playbooks/actions/local/run';
|
||||
import {getLastPlaybookRunsFetchAt} from '@playbooks/database/queries/run';
|
||||
import {getMaxRunUpdateAt} from '@playbooks/utils/run';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
|
|
@ -136,6 +136,20 @@ export const setOwner = async (serverUrl: string, playbookRunId: string, ownerId
|
|||
}
|
||||
};
|
||||
|
||||
export const renamePlaybookRun = async (serverUrl: string, playbookRunId: string, newName: string) => {
|
||||
try {
|
||||
const client = NetworkManager.getClient(serverUrl);
|
||||
await client.patchPlaybookRun(playbookRunId, {name: newName});
|
||||
|
||||
await localRenamePlaybookRun(serverUrl, playbookRunId, newName);
|
||||
return {data: true};
|
||||
} catch (error) {
|
||||
logDebug('error on renamePlaybookRun', getFullErrorMessage(error));
|
||||
forceLogoutIfNecessary(serverUrl, error);
|
||||
return {error};
|
||||
}
|
||||
};
|
||||
|
||||
export const createPlaybookRun = async (
|
||||
serverUrl: string,
|
||||
playbook_id: string,
|
||||
|
|
|
|||
|
|
@ -547,7 +547,6 @@ describe('postStatusUpdate', () => {
|
|||
jest.mocked(client.doFetch).mockResolvedValue(undefined);
|
||||
|
||||
await client.postStatusUpdate(playbookRunID, payload, ids);
|
||||
|
||||
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
||||
});
|
||||
|
||||
|
|
@ -965,3 +964,163 @@ describe('createPlaybookRun', () => {
|
|||
).rejects.toThrow('Network error');
|
||||
});
|
||||
});
|
||||
describe('patchPlaybookRun', () => {
|
||||
test('should patch with multiple field updates', async () => {
|
||||
const playbookRunId = 'run123';
|
||||
const updates = {
|
||||
name: 'Updated Name',
|
||||
owner_user_id: 'user456',
|
||||
description: 'Updated description',
|
||||
};
|
||||
const expectedUrl = `/plugins/playbooks/api/v0/runs/${playbookRunId}`;
|
||||
const expectedOptions = {method: 'patch', body: updates};
|
||||
|
||||
jest.mocked(client.doFetch).mockResolvedValue(undefined);
|
||||
|
||||
await client.patchPlaybookRun(playbookRunId, updates);
|
||||
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
||||
});
|
||||
|
||||
test('should patch with single field update', async () => {
|
||||
const playbookRunId = 'run123';
|
||||
const updates = {name: 'New Name'};
|
||||
const expectedUrl = `/plugins/playbooks/api/v0/runs/${playbookRunId}`;
|
||||
const expectedOptions = {method: 'patch', body: updates};
|
||||
|
||||
jest.mocked(client.doFetch).mockResolvedValue(undefined);
|
||||
|
||||
await client.patchPlaybookRun(playbookRunId, updates);
|
||||
|
||||
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
||||
});
|
||||
|
||||
test('should handle network errors', async () => {
|
||||
const playbookRunId = 'run123';
|
||||
const updates = {name: 'New Name'};
|
||||
|
||||
jest.mocked(client.doFetch).mockRejectedValue(new Error('Network error'));
|
||||
|
||||
await expect(client.patchPlaybookRun(playbookRunId, updates)).rejects.toThrow('Network error');
|
||||
});
|
||||
|
||||
test('should handle empty updates object', async () => {
|
||||
const playbookRunId = 'run123';
|
||||
const updates = {};
|
||||
const expectedUrl = `/plugins/playbooks/api/v0/runs/${playbookRunId}`;
|
||||
const expectedOptions = {method: 'patch', body: updates};
|
||||
|
||||
jest.mocked(client.doFetch).mockResolvedValue(undefined);
|
||||
|
||||
await client.patchPlaybookRun(playbookRunId, updates);
|
||||
|
||||
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('renameChecklist', () => {
|
||||
test('should rename successfully', async () => {
|
||||
const playbookRunId = 'run123';
|
||||
const checklistNumber = 1;
|
||||
const newName = 'Updated Checklist Name';
|
||||
const expectedUrl = `/plugins/playbooks/api/v0/runs/${playbookRunId}/checklists/${checklistNumber}/rename`;
|
||||
const expectedOptions = {method: 'put', body: {title: newName}};
|
||||
|
||||
jest.mocked(client.doFetch).mockResolvedValue(undefined);
|
||||
|
||||
await client.renameChecklist(playbookRunId, checklistNumber, newName);
|
||||
|
||||
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
||||
});
|
||||
|
||||
test('should rename with zero index', async () => {
|
||||
const playbookRunId = 'run123';
|
||||
const checklistNumber = 0;
|
||||
const newName = 'First Checklist';
|
||||
const expectedUrl = `/plugins/playbooks/api/v0/runs/${playbookRunId}/checklists/${checklistNumber}/rename`;
|
||||
const expectedOptions = {method: 'put', body: {title: newName}};
|
||||
|
||||
jest.mocked(client.doFetch).mockResolvedValue(undefined);
|
||||
|
||||
await client.renameChecklist(playbookRunId, checklistNumber, newName);
|
||||
|
||||
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
||||
});
|
||||
|
||||
test('should handle network errors', async () => {
|
||||
const playbookRunId = 'run123';
|
||||
const checklistNumber = 1;
|
||||
const newName = 'Updated Name';
|
||||
|
||||
jest.mocked(client.doFetch).mockRejectedValue(new Error('Network error'));
|
||||
|
||||
await expect(client.renameChecklist(playbookRunId, checklistNumber, newName)).rejects.toThrow('Network error');
|
||||
});
|
||||
|
||||
test('should handle empty name', async () => {
|
||||
const playbookRunId = 'run123';
|
||||
const checklistNumber = 1;
|
||||
const newName = '';
|
||||
const expectedUrl = `/plugins/playbooks/api/v0/runs/${playbookRunId}/checklists/${checklistNumber}/rename`;
|
||||
const expectedOptions = {method: 'put', body: {title: newName}};
|
||||
|
||||
jest.mocked(client.doFetch).mockResolvedValue(undefined);
|
||||
|
||||
await client.renameChecklist(playbookRunId, checklistNumber, newName);
|
||||
|
||||
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
||||
});
|
||||
});
|
||||
|
||||
describe('addChecklistItem', () => {
|
||||
test('should add item successfully', async () => {
|
||||
const playbookRunId = 'run123';
|
||||
const checklistNum = 1;
|
||||
const title = 'New Item';
|
||||
const expectedUrl = `/plugins/playbooks/api/v0/runs/${playbookRunId}/checklists/${checklistNum}/add`;
|
||||
const expectedOptions = {method: 'post', body: {title}};
|
||||
|
||||
jest.mocked(client.doFetch).mockResolvedValue(undefined);
|
||||
|
||||
await client.addChecklistItem(playbookRunId, checklistNum, title);
|
||||
|
||||
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
||||
});
|
||||
|
||||
test('should add item with empty title', async () => {
|
||||
const playbookRunId = 'run123';
|
||||
const checklistNum = 1;
|
||||
const title = '';
|
||||
const expectedUrl = `/plugins/playbooks/api/v0/runs/${playbookRunId}/checklists/${checklistNum}/add`;
|
||||
const expectedOptions = {method: 'post', body: {title}};
|
||||
|
||||
jest.mocked(client.doFetch).mockResolvedValue(undefined);
|
||||
|
||||
await client.addChecklistItem(playbookRunId, checklistNum, title);
|
||||
|
||||
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
||||
});
|
||||
|
||||
test('should handle network errors', async () => {
|
||||
const playbookRunId = 'run123';
|
||||
const checklistNum = 1;
|
||||
const title = 'New Item';
|
||||
|
||||
jest.mocked(client.doFetch).mockRejectedValue(new Error('Network error'));
|
||||
|
||||
await expect(client.addChecklistItem(playbookRunId, checklistNum, title)).rejects.toThrow('Network error');
|
||||
});
|
||||
|
||||
test('should handle invalid checklist number', async () => {
|
||||
const playbookRunId = 'run123';
|
||||
const checklistNum = -1;
|
||||
const title = 'New Item';
|
||||
const expectedUrl = `/plugins/playbooks/api/v0/runs/${playbookRunId}/checklists/${checklistNum}/add`;
|
||||
const expectedOptions = {method: 'post', body: {title}};
|
||||
|
||||
jest.mocked(client.doFetch).mockResolvedValue(undefined);
|
||||
|
||||
await client.addChecklistItem(playbookRunId, checklistNum, title);
|
||||
|
||||
expect(client.doFetch).toHaveBeenCalledWith(expectedUrl, expectedOptions);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export interface ClientPlaybooksMix {
|
|||
fetchPlaybookRuns: (params: FetchPlaybookRunsParams, groupLabel?: RequestGroupLabel) => Promise<FetchPlaybookRunsReturn>;
|
||||
fetchPlaybookRun: (id: string, groupLabel?: RequestGroupLabel) => Promise<PlaybookRun>;
|
||||
fetchPlaybookRunMetadata: (id: string) => Promise<PlaybookRunMetadata>;
|
||||
patchPlaybookRun: (playbookRunId: string, updates: Partial<PlaybookRun>) => Promise<void>;
|
||||
setOwner: (playbookRunId: string, ownerId: string) => Promise<void>;
|
||||
|
||||
// Run Management
|
||||
|
|
@ -27,8 +28,9 @@ export interface ClientPlaybooksMix {
|
|||
restoreChecklistItem: (playbookRunID: string, checklistNum: number, itemNum: number) => Promise<void>;
|
||||
setAssignee: (playbookRunId: string, checklistNum: number, itemNum: number, assigneeId?: string) => Promise<void>;
|
||||
setDueDate: (playbookRunId: string, checklistNum: number, itemNum: number, date?: number) => Promise<void>;
|
||||
addChecklistItem: (playbookRunId: string, checklistNum: number, title: string) => Promise<void>;
|
||||
|
||||
// skipChecklist: (playbookRunID: string, checklistNum: number) => Promise<void>;
|
||||
renameChecklist: (playbookRunId: string, checklistNumber: number, newName: string) => Promise<void>;
|
||||
|
||||
// Slash Commands
|
||||
runChecklistItemSlashCommand: (playbookRunId: string, checklistNumber: number, itemNumber: number) => Promise<{trigger_id: string}>;
|
||||
|
|
@ -85,6 +87,20 @@ const ClientPlaybooks = <TBase extends Constructor<ClientBase>>(superclass: TBas
|
|||
);
|
||||
};
|
||||
|
||||
patchPlaybookRun = async (playbookRunId: string, updates: Partial<PlaybookRun>) => {
|
||||
await this.doFetch(
|
||||
`${this.getPlaybookRunRoute(playbookRunId)}`,
|
||||
{method: 'patch', body: updates},
|
||||
);
|
||||
};
|
||||
|
||||
renameChecklist = async (playbookRunId: string, checklistNumber: number, newName: string) => {
|
||||
await this.doFetch(
|
||||
`${this.getPlaybookRunRoute(playbookRunId)}/checklists/${checklistNumber}/rename`,
|
||||
{method: 'put', body: {title: newName}},
|
||||
);
|
||||
};
|
||||
|
||||
setOwner = async (playbookRunId: string, ownerId: string) => {
|
||||
const data = await this.doFetch(
|
||||
`${this.getPlaybookRunRoute(playbookRunId)}/owner`,
|
||||
|
|
@ -188,6 +204,13 @@ const ClientPlaybooks = <TBase extends Constructor<ClientBase>>(superclass: TBas
|
|||
);
|
||||
};
|
||||
|
||||
addChecklistItem = async (playbookRunId: string, checklistNum: number, title: string) => {
|
||||
await this.doFetch(
|
||||
`${this.getPlaybookRunRoute(playbookRunId)}/checklists/${checklistNum}/add`,
|
||||
{method: 'post', body: {title}},
|
||||
);
|
||||
};
|
||||
|
||||
// Slash Commands
|
||||
runChecklistItemSlashCommand = async (playbookRunId: string, checklistNumber: number, itemNumber: number) => {
|
||||
const data = await this.doFetch(
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ describe('PlaybookRunsOption', () => {
|
|||
|
||||
const optionItem = getByTestId('option-item');
|
||||
expect(optionItem).toBeTruthy();
|
||||
expect(optionItem.props.label).toBe('Playbook runs');
|
||||
expect(optionItem.props.label).toBe('Playbook checklists');
|
||||
expect(optionItem.props.info).toBe('3');
|
||||
expect(optionItem.props.icon).toBe('product-playbooks');
|
||||
expect(optionItem.props.type).toBe('arrow');
|
||||
|
|
@ -49,7 +49,7 @@ describe('PlaybookRunsOption', () => {
|
|||
|
||||
const slideUpPanelItem = getByTestId('slide-up-panel-item');
|
||||
expect(slideUpPanelItem).toBeTruthy();
|
||||
expect(slideUpPanelItem.props.text).toBe('Playbook runs');
|
||||
expect(slideUpPanelItem.props.text).toBe('Playbook checklists');
|
||||
expect(slideUpPanelItem.props.leftIcon).toBe('product-playbooks');
|
||||
expect(slideUpPanelItem.props.rightIcon).toBe('chevron-right');
|
||||
expect(slideUpPanelItem.props.onPress).toBeDefined();
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ type Props = {
|
|||
const messages = defineMessages({
|
||||
playbookRuns: {
|
||||
id: 'playbooks.playbooks_runs.title',
|
||||
defaultMessage: 'Playbook runs',
|
||||
defaultMessage: 'Playbook checklists',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ describe('PlaybooksButton', () => {
|
|||
|
||||
const icon = getByTestId('channel_list.playbooks.button-icon');
|
||||
expect(icon).toHaveProp('name', 'product-playbooks');
|
||||
expect(getByText('Playbook runs')).toBeTruthy();
|
||||
expect(getByText('Playbook checklists')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('calls goToParticipantPlaybooks when pressed', () => {
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ const PlaybooksButton = () => {
|
|||
/>
|
||||
<FormattedText
|
||||
id='playbooks.home_button.title'
|
||||
defaultMessage='Playbook runs'
|
||||
defaultMessage='Playbook checklists'
|
||||
style={textStyle}
|
||||
/>
|
||||
</View>
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ describe('EmptyState', () => {
|
|||
);
|
||||
|
||||
// Verify correct title and description are shown
|
||||
expect(getByText('No in progress runs')).toBeTruthy();
|
||||
expect(getByText('When a run starts in this channel, you’ll see it here.')).toBeTruthy();
|
||||
expect(getByText('Nothing in progress')).toBeTruthy();
|
||||
expect(getByText('When a checklist starts in this channel, you’ll see it here.')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('renders finished empty state correctly', () => {
|
||||
|
|
@ -22,7 +22,7 @@ describe('EmptyState', () => {
|
|||
);
|
||||
|
||||
// Verify correct title and description are shown
|
||||
expect(getByText('No finished runs')).toBeTruthy();
|
||||
expect(getByText('When a run in this channel finishes, you’ll see it here.')).toBeTruthy();
|
||||
expect(getByText('Nothing finished')).toBeTruthy();
|
||||
expect(getByText('When a checklist in this channel finishes, you’ll see it here.')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -40,19 +40,19 @@ const getStylesFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
const messages = defineMessages({
|
||||
inProgressTitle: {
|
||||
id: 'playbooks.runs.in_progress.title',
|
||||
defaultMessage: 'No in progress runs',
|
||||
defaultMessage: 'Nothing in progress',
|
||||
},
|
||||
finishedTitle: {
|
||||
id: 'playbooks.runs.finished.title',
|
||||
defaultMessage: 'No finished runs',
|
||||
defaultMessage: 'Nothing finished',
|
||||
},
|
||||
inProgressDescription: {
|
||||
id: 'playbooks.runs.in_progress.description',
|
||||
defaultMessage: 'When a run starts in this channel, you’ll see it here.',
|
||||
defaultMessage: 'When a checklist starts in this channel, you’ll see it here.',
|
||||
},
|
||||
finishedDescription: {
|
||||
id: 'playbooks.runs.finished.description',
|
||||
defaultMessage: 'When a run in this channel finishes, you’ll see it here.',
|
||||
defaultMessage: 'When a checklist in this channel finishes, you’ll see it here.',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ describe('PlaybookCard', () => {
|
|||
expect(userAvatarsStack.props.users).toEqual(props.participants);
|
||||
expect(userAvatarsStack.props.channelId).toBe((props.run as PlaybookRunModel).channelId);
|
||||
expect(userAvatarsStack.props.location).toBe(props.location);
|
||||
expect(userAvatarsStack.props.bottomSheetTitle.defaultMessage).toBe('Run Participants');
|
||||
expect(userAvatarsStack.props.bottomSheetTitle.defaultMessage).toBe('Participants');
|
||||
|
||||
const progressBar = getByTestId('progress-bar');
|
||||
expect(progressBar.props.progress).toBe(50);
|
||||
|
|
@ -93,7 +93,7 @@ describe('PlaybookCard', () => {
|
|||
expect(userAvatarsStack.props.users).toEqual(props.participants);
|
||||
expect(userAvatarsStack.props.channelId).toBe(props.run.channel_id);
|
||||
expect(userAvatarsStack.props.location).toBe(props.location);
|
||||
expect(userAvatarsStack.props.bottomSheetTitle.defaultMessage).toBe('Run Participants');
|
||||
expect(userAvatarsStack.props.bottomSheetTitle.defaultMessage).toBe('Participants');
|
||||
|
||||
const progressBar = getByTestId('progress-bar');
|
||||
expect(progressBar.props.progress).toBe(50);
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ type Props = {
|
|||
owner?: UserModel;
|
||||
};
|
||||
|
||||
const bottomSheetTitleMessage = defineMessage({id: 'playbook.participants', defaultMessage: 'Run Participants'});
|
||||
const bottomSheetTitleMessage = defineMessage({id: 'playbook.participants', defaultMessage: 'Participants'});
|
||||
|
||||
const PlaybookCard = ({
|
||||
run,
|
||||
|
|
|
|||
|
|
@ -226,7 +226,7 @@ describe('RunList', () => {
|
|||
props.inProgressRuns = [inProgressRun];
|
||||
const {getByText} = renderWithIntlAndTheme(<RunList {...props}/>);
|
||||
|
||||
expect(getByText('Start a new run')).toBeTruthy();
|
||||
expect(getByText('New')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('calls goToSelectPlaybook when Start a new run button is pressed', () => {
|
||||
|
|
@ -235,7 +235,7 @@ describe('RunList', () => {
|
|||
const {getByText} = renderWithIntlAndTheme(<RunList {...props}/>);
|
||||
|
||||
act(() => {
|
||||
fireEvent.press(getByText('Start a new run'));
|
||||
fireEvent.press(getByText('New'));
|
||||
});
|
||||
|
||||
expect(goToSelectPlaybook).toHaveBeenCalledTimes(1);
|
||||
|
|
@ -348,7 +348,7 @@ describe('RunList', () => {
|
|||
const {getByText} = renderWithIntlAndTheme(<RunList {...props}/>);
|
||||
|
||||
act(() => {
|
||||
fireEvent.press(getByText('Start a new run'));
|
||||
fireEvent.press(getByText('New'));
|
||||
});
|
||||
|
||||
expect(goToSelectPlaybook).toHaveBeenCalledTimes(1);
|
||||
|
|
|
|||
|
|
@ -108,7 +108,7 @@ describe('Participants', () => {
|
|||
renderContent: expect.any(Function),
|
||||
initialSnapIndex: 1,
|
||||
snapPoints: expect.any(Array),
|
||||
title: 'Run Participants',
|
||||
title: 'Participants',
|
||||
theme: expect.any(Object),
|
||||
}));
|
||||
});
|
||||
|
|
@ -146,7 +146,7 @@ describe('Participants', () => {
|
|||
const args = jest.mocked(bottomSheet).mock.calls[0][0];
|
||||
const Content = args.renderContent;
|
||||
const {getByText} = renderWithEverything(<Content/>, {database, serverUrl});
|
||||
expect(getByText('Run Participants')).toBeTruthy();
|
||||
expect(getByText('Participants')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -163,7 +163,7 @@ describe('Participants', () => {
|
|||
const args = jest.mocked(bottomSheet).mock.calls[0][0];
|
||||
const Content = args.renderContent;
|
||||
const {queryByText} = renderWithEverything(<Content/>, {database, serverUrl});
|
||||
expect(queryByText('Run Participants')).toBeNull();
|
||||
expect(queryByText('Participants')).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
9
app/products/playbooks/constants/playbook_run.ts
Normal file
9
app/products/playbooks/constants/playbook_run.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
export const PLAYBOOK_RUN_TYPES = {
|
||||
PlaybookType: 'playbook' as const,
|
||||
ChannelChecklistType: 'channelChecklist' as const,
|
||||
} as const;
|
||||
|
||||
export type PlaybookRunType = typeof PLAYBOOK_RUN_TYPES[keyof typeof PLAYBOOK_RUN_TYPES];
|
||||
|
|
@ -6,6 +6,9 @@ export const PARTICIPANT_PLAYBOOKS = 'ParticipantPlaybooks';
|
|||
export const PLAYBOOK_RUN = 'PlaybookRun';
|
||||
export const PLAYBOOK_EDIT_COMMAND = 'PlaybookEditCommand';
|
||||
export const PLAYBOOK_POST_UPDATE = 'PlaybookPostUpdate';
|
||||
export const PLAYBOOK_RENAME_CHECKLIST = 'PlaybookRenameChecklist';
|
||||
export const PLAYBOOK_ADD_CHECKLIST_ITEM = 'PlaybookAddChecklistItem';
|
||||
export const PLAYBOOK_RENAME_RUN = 'PlaybookRenameRun';
|
||||
export const PLAYBOOK_SELECT_USER = 'PlaybookSelectUser';
|
||||
export const PLAYBOOKS_SELECT_DATE = 'PlaybooksSelectDate';
|
||||
export const PLAYBOOKS_SELECT_PLAYBOOK = 'PlaybooksSelectPlaybook';
|
||||
|
|
@ -17,6 +20,9 @@ export default {
|
|||
PLAYBOOK_RUN,
|
||||
PLAYBOOK_EDIT_COMMAND,
|
||||
PLAYBOOK_POST_UPDATE,
|
||||
PLAYBOOK_RENAME_CHECKLIST,
|
||||
PLAYBOOK_ADD_CHECKLIST_ITEM,
|
||||
PLAYBOOK_RENAME_RUN,
|
||||
PLAYBOOK_SELECT_USER,
|
||||
PLAYBOOKS_SELECT_DATE,
|
||||
PLAYBOOKS_SELECT_PLAYBOOK,
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import {safeParseJSONStringArray} from '@utils/helpers';
|
|||
|
||||
import type {Query, Relation} from '@nozbe/watermelondb';
|
||||
import type PlaybookRunModelInterface from '@playbooks//types/database/models/playbook_run';
|
||||
import type {PlaybookRunType} from '@playbooks/constants/playbook_run';
|
||||
import type PlaybookChecklistModel from '@playbooks/types/database/models/playbook_checklist';
|
||||
import type {SyncStatus} from '@typings/database/database';
|
||||
import type ChannelModel from '@typings/database/models/servers/channel';
|
||||
|
|
@ -51,9 +52,12 @@ export default class PlaybookRunModel extends Model implements PlaybookRunModelI
|
|||
[PLAYBOOK_CHECKLIST]: {type: 'has_many', foreignKey: 'run_id'},
|
||||
};
|
||||
|
||||
/** playbook_id : The id of the playbook this run belongs to */
|
||||
/** playbook_id : The id of the playbook this run belongs to (empty string if it is a channel checklist) */
|
||||
@field('playbook_id') playbookId!: string;
|
||||
|
||||
/** type : The type of run ('playbook' or 'channelChecklist') */
|
||||
@field('type') type!: PlaybookRunType;
|
||||
|
||||
/** name : Name of the playbook run */
|
||||
@field('name') name!: string;
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,8 @@ const SERVER_URL = `playbookRunAttributeValueModel.test.${Date.now()}.com`;
|
|||
|
||||
const applyRunData = (run: PlaybookRunModel, mockData: PlaybookRun) => {
|
||||
run._raw.id = mockData.id;
|
||||
run.playbookId = mockData.playbook_id;
|
||||
run.playbookId = mockData.playbook_id ?? '';
|
||||
run.type = mockData.type ?? 'playbook';
|
||||
run.postId = mockData.post_id ?? null;
|
||||
run.ownerUserId = mockData.owner_user_id;
|
||||
run.teamId = mockData.team_id;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
import {OperationType} from '@constants/database';
|
||||
import {prepareBaseRecord} from '@database/operator/server_data_operator/transformers/index';
|
||||
import {PLAYBOOK_TABLES} from '@playbooks/constants/database';
|
||||
import {PLAYBOOK_RUN_TYPES} from '@playbooks/constants/playbook_run';
|
||||
|
||||
import type PlaybookChecklistModel from '@playbooks/types/database/models/playbook_checklist';
|
||||
import type PlaybookChecklistItemModel from '@playbooks/types/database/models/playbook_checklist_item';
|
||||
|
|
@ -33,6 +34,7 @@ export const transformPlaybookRunRecord = ({action, database, value}: Transforme
|
|||
const fieldsMapper = (run: PlaybookRunModel) => {
|
||||
run._raw.id = isCreateAction ? (raw?.id ?? run.id) : run.id;
|
||||
run.playbookId = raw.playbook_id ?? record?.playbookId ?? '';
|
||||
run.type = raw.type ?? record?.type ?? (run.playbookId ? PLAYBOOK_RUN_TYPES.PlaybookType : PLAYBOOK_RUN_TYPES.ChannelChecklistType);
|
||||
run.postId = raw.post_id ?? record?.postId ?? null;
|
||||
run.ownerUserId = raw.owner_user_id ?? record?.ownerUserId ?? '';
|
||||
run.teamId = raw.team_id ?? record?.teamId ?? '';
|
||||
|
|
|
|||
|
|
@ -34,5 +34,6 @@ export default tableSchema({
|
|||
{name: 'previous_reminder', type: 'number', isOptional: true},
|
||||
{name: 'items_order', type: 'string'}, // JSON string
|
||||
{name: 'update_at', type: 'number'},
|
||||
{name: 'type', type: 'string'},
|
||||
],
|
||||
});
|
||||
|
|
|
|||
|
|
@ -10,6 +10,9 @@ import {render} from '@test/intl-test-helper';
|
|||
import EditCommand from './edit_command';
|
||||
import ParticipantPlaybooks from './participant_playbooks';
|
||||
import PlaybookRun from './playbook_run';
|
||||
import AddChecklistItemBottomSheet from './playbook_run/checklist/add_checklist_item_bottom_sheet';
|
||||
import RenameChecklistBottomSheet from './playbook_run/checklist/rename_checklist_bottom_sheet';
|
||||
import RenamePlaybookRunBottomSheet from './playbook_run/rename_playbook_run_bottom_sheet';
|
||||
import PlaybookRuns from './playbooks_runs';
|
||||
import PostUpdate from './post_update';
|
||||
import SelectDate from './select_date';
|
||||
|
|
@ -78,6 +81,25 @@ jest.mock('@playbooks/screens/post_update', () => ({
|
|||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
|
||||
jest.mock('@playbooks/screens/playbook_run/checklist/rename_checklist_bottom_sheet', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
jest.mocked(RenameChecklistBottomSheet).mockImplementation((props) => <Text {...props}>{Screens.PLAYBOOK_RENAME_CHECKLIST}</Text>);
|
||||
|
||||
jest.mock('@playbooks/screens/playbook_run/checklist/add_checklist_item_bottom_sheet', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
jest.mocked(AddChecklistItemBottomSheet).mockImplementation((props) => <Text {...props}>{Screens.PLAYBOOK_ADD_CHECKLIST_ITEM}</Text>);
|
||||
|
||||
jest.mock('@playbooks/screens/playbook_run/rename_playbook_run_bottom_sheet', () => ({
|
||||
__esModule: true,
|
||||
default: jest.fn(),
|
||||
}));
|
||||
jest.mocked(RenamePlaybookRunBottomSheet).mockImplementation((props) => <Text {...props}>{Screens.PLAYBOOK_RENAME_RUN}</Text>);
|
||||
|
||||
jest.mocked(PostUpdate).mockImplementation((props) => <Text {...props}>{Screens.PLAYBOOK_POST_UPDATE}</Text>);
|
||||
|
||||
describe('Screen Registration', () => {
|
||||
|
|
|
|||
|
|
@ -16,6 +16,12 @@ export function loadPlaybooksScreen(screenName: string | number) {
|
|||
return withServerDatabase(require('@playbooks/screens/edit_command').default);
|
||||
case Screens.PLAYBOOK_POST_UPDATE:
|
||||
return withServerDatabase(require('@playbooks/screens/post_update').default);
|
||||
case Screens.PLAYBOOK_RENAME_CHECKLIST:
|
||||
return withServerDatabase(require('@playbooks/screens/playbook_run/checklist/rename_checklist_bottom_sheet').default);
|
||||
case Screens.PLAYBOOK_ADD_CHECKLIST_ITEM:
|
||||
return withServerDatabase(require('@playbooks/screens/playbook_run/checklist/add_checklist_item_bottom_sheet').default);
|
||||
case Screens.PLAYBOOK_RENAME_RUN:
|
||||
return withServerDatabase(require('@playbooks/screens/playbook_run/rename_playbook_run_bottom_sheet').default);
|
||||
case Screens.PLAYBOOK_SELECT_USER:
|
||||
return withServerDatabase(require('@playbooks/screens/select_user').default);
|
||||
case Screens.PLAYBOOKS_SELECT_DATE:
|
||||
|
|
|
|||
|
|
@ -33,11 +33,11 @@ describe('Playbooks Navigation', () => {
|
|||
|
||||
expect(mockIntl.formatMessage).toHaveBeenCalledWith({
|
||||
id: 'playbooks.playbooks_runs.title',
|
||||
defaultMessage: 'Playbook runs',
|
||||
defaultMessage: 'Playbook checklists',
|
||||
});
|
||||
expect(goToScreen).toHaveBeenCalledWith(
|
||||
Screens.PLAYBOOKS_RUNS,
|
||||
'Playbook runs',
|
||||
'Playbook checklists',
|
||||
{channelId},
|
||||
expect.objectContaining({
|
||||
topBar: expect.objectContaining({
|
||||
|
|
@ -59,11 +59,11 @@ describe('Playbooks Navigation', () => {
|
|||
|
||||
expect(mockIntl.formatMessage).toHaveBeenCalledWith({
|
||||
id: 'playbooks.playbook_run.title',
|
||||
defaultMessage: 'Playbook run',
|
||||
defaultMessage: 'Playbook checklist',
|
||||
});
|
||||
expect(goToScreen).toHaveBeenCalledWith(
|
||||
Screens.PLAYBOOK_RUN,
|
||||
'Playbook run',
|
||||
'Playbook checklist',
|
||||
{playbookRunId},
|
||||
{},
|
||||
);
|
||||
|
|
@ -79,11 +79,11 @@ describe('Playbooks Navigation', () => {
|
|||
|
||||
expect(mockIntl.formatMessage).toHaveBeenCalledWith({
|
||||
id: 'playbooks.playbook_run.title',
|
||||
defaultMessage: 'Playbook run',
|
||||
defaultMessage: 'Playbook checklist',
|
||||
});
|
||||
expect(goToScreen).toHaveBeenCalledWith(
|
||||
Screens.PLAYBOOK_RUN,
|
||||
'Playbook run',
|
||||
'Playbook checklist',
|
||||
{playbookRunId, playbookRun},
|
||||
{},
|
||||
);
|
||||
|
|
@ -273,11 +273,11 @@ describe('Playbooks Navigation', () => {
|
|||
|
||||
expect(mockIntl.formatMessage).toHaveBeenCalledWith({
|
||||
id: 'playbooks.participant_playbooks.title',
|
||||
defaultMessage: 'Playbook runs',
|
||||
defaultMessage: 'Playbook checklists',
|
||||
});
|
||||
expect(goToScreen).toHaveBeenCalledWith(
|
||||
Screens.PARTICIPANT_PLAYBOOKS,
|
||||
'Playbook runs',
|
||||
'Playbook checklists',
|
||||
{},
|
||||
{},
|
||||
);
|
||||
|
|
@ -307,11 +307,11 @@ describe('Playbooks Navigation', () => {
|
|||
|
||||
expect(mockIntl.formatMessage).toHaveBeenCalledWith({
|
||||
id: 'playbooks.playbook_run.title',
|
||||
defaultMessage: 'Playbook run',
|
||||
defaultMessage: 'Playbook checklist',
|
||||
});
|
||||
expect(goToScreen).toHaveBeenCalledWith(
|
||||
Screens.PLAYBOOK_RUN,
|
||||
'Playbook run',
|
||||
'Playbook checklist',
|
||||
{playbookRunId: mockPlaybookRun.id},
|
||||
{},
|
||||
);
|
||||
|
|
@ -339,11 +339,11 @@ describe('Playbooks Navigation', () => {
|
|||
|
||||
expect(mockIntl.formatMessage).toHaveBeenCalledWith({
|
||||
id: 'playbooks.playbook_run.title',
|
||||
defaultMessage: 'Playbook run',
|
||||
defaultMessage: 'Playbook checklist',
|
||||
});
|
||||
expect(goToScreen).toHaveBeenCalledWith(
|
||||
Screens.PLAYBOOK_RUN,
|
||||
'Playbook run',
|
||||
'Playbook checklist',
|
||||
{playbookRunId: mockPlaybookRunModel.id},
|
||||
{},
|
||||
);
|
||||
|
|
@ -401,7 +401,7 @@ describe('Playbooks Navigation', () => {
|
|||
|
||||
expect(goToScreen).toHaveBeenCalledWith(
|
||||
Screens.PLAYBOOKS_SELECT_PLAYBOOK,
|
||||
'Start a run',
|
||||
'New',
|
||||
{channelId},
|
||||
{
|
||||
topBar: {
|
||||
|
|
@ -419,7 +419,7 @@ describe('Playbooks Navigation', () => {
|
|||
|
||||
expect(goToScreen).toHaveBeenCalledWith(
|
||||
Screens.PLAYBOOKS_SELECT_PLAYBOOK,
|
||||
'Start a run',
|
||||
'New',
|
||||
{channelId: undefined},
|
||||
{
|
||||
topBar: {
|
||||
|
|
@ -446,7 +446,7 @@ describe('Playbooks Navigation', () => {
|
|||
|
||||
expect(goToScreen).toHaveBeenCalledWith(
|
||||
Screens.PLAYBOOKS_START_A_RUN,
|
||||
'Start a run',
|
||||
'New',
|
||||
{playbook, onRunCreated, channelId},
|
||||
{
|
||||
topBar: {
|
||||
|
|
@ -470,7 +470,7 @@ describe('Playbooks Navigation', () => {
|
|||
|
||||
expect(goToScreen).toHaveBeenCalledWith(
|
||||
Screens.PLAYBOOKS_START_A_RUN,
|
||||
'Start a run',
|
||||
'New',
|
||||
{playbook, onRunCreated, channelId: undefined},
|
||||
{
|
||||
topBar: {
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ import type {Options as RNNOptions} from 'react-native-navigation';
|
|||
|
||||
export function goToPlaybookRuns(intl: IntlShape, channelId: string, channelName: string) {
|
||||
const theme = getThemeFromState();
|
||||
const title = intl.formatMessage({id: 'playbooks.playbooks_runs.title', defaultMessage: 'Playbook runs'});
|
||||
const title = intl.formatMessage({id: 'playbooks.playbooks_runs.title', defaultMessage: 'Playbook checklists'});
|
||||
goToScreen(Screens.PLAYBOOKS_RUNS, title, {channelId}, {
|
||||
topBar: {
|
||||
subtitle: {
|
||||
|
|
@ -26,12 +26,12 @@ export function goToPlaybookRuns(intl: IntlShape, channelId: string, channelName
|
|||
}
|
||||
|
||||
export function goToParticipantPlaybooks(intl: IntlShape) {
|
||||
const title = intl.formatMessage({id: 'playbooks.participant_playbooks.title', defaultMessage: 'Playbook runs'});
|
||||
const title = intl.formatMessage({id: 'playbooks.participant_playbooks.title', defaultMessage: 'Playbook checklists'});
|
||||
goToScreen(Screens.PARTICIPANT_PLAYBOOKS, title, {}, {});
|
||||
}
|
||||
|
||||
export async function goToPlaybookRun(intl: IntlShape, playbookRunId: string, playbookRun?: PlaybookRun) {
|
||||
const title = intl.formatMessage({id: 'playbooks.playbook_run.title', defaultMessage: 'Playbook run'});
|
||||
const title = intl.formatMessage({id: 'playbooks.playbook_run.title', defaultMessage: 'Playbook checklist'});
|
||||
goToScreen(Screens.PLAYBOOK_RUN, title, {playbookRunId, playbookRun}, {});
|
||||
}
|
||||
|
||||
|
|
@ -47,7 +47,7 @@ export async function goToPlaybookRunWithChannelSwitch(intl: IntlShape, serverUr
|
|||
}
|
||||
|
||||
// Then navigate to the playbook run
|
||||
const title = intl.formatMessage({id: 'playbooks.playbook_run.title', defaultMessage: 'Playbook run'});
|
||||
const title = intl.formatMessage({id: 'playbooks.playbook_run.title', defaultMessage: 'Playbook checklist'});
|
||||
goToScreen(Screens.PLAYBOOK_RUN, title, {playbookRunId: playbookRun.id}, {});
|
||||
}
|
||||
|
||||
|
|
@ -84,6 +84,47 @@ export async function goToEditCommand(
|
|||
}, options);
|
||||
}
|
||||
|
||||
export async function goToRenameChecklist(
|
||||
intl: IntlShape,
|
||||
theme: Theme,
|
||||
runName: string,
|
||||
currentTitle: string,
|
||||
onSave: (newTitle: string) => void,
|
||||
) {
|
||||
const title = intl.formatMessage({id: 'playbooks.checklist.rename.title', defaultMessage: 'Rename checklist'});
|
||||
const options = getSubtitleOptions(theme, runName);
|
||||
goToScreen(Screens.PLAYBOOK_RENAME_CHECKLIST, title, {
|
||||
currentTitle,
|
||||
onSave,
|
||||
}, options);
|
||||
}
|
||||
|
||||
export async function goToAddChecklistItem(
|
||||
intl: IntlShape,
|
||||
theme: Theme,
|
||||
runName: string,
|
||||
onSave: (title: string) => void,
|
||||
) {
|
||||
const title = intl.formatMessage({id: 'playbooks.checklist_item.add.title', defaultMessage: 'New Task'});
|
||||
const options = getSubtitleOptions(theme, runName);
|
||||
goToScreen(Screens.PLAYBOOK_ADD_CHECKLIST_ITEM, title, {
|
||||
onSave,
|
||||
}, options);
|
||||
}
|
||||
|
||||
export async function goToRenamePlaybookRun(
|
||||
intl: IntlShape,
|
||||
theme: Theme,
|
||||
currentTitle: string,
|
||||
onSave: (newTitle: string) => void,
|
||||
) {
|
||||
const title = intl.formatMessage({id: 'playbooks.playbook_run.rename.title', defaultMessage: 'Rename playbook run'});
|
||||
goToScreen(Screens.PLAYBOOK_RENAME_RUN, title, {
|
||||
currentTitle,
|
||||
onSave,
|
||||
});
|
||||
}
|
||||
|
||||
export async function goToSelectUser(
|
||||
theme: Theme,
|
||||
runName: string,
|
||||
|
|
@ -122,7 +163,8 @@ export async function goToSelectPlaybook(
|
|||
theme: Theme,
|
||||
channelId?: string,
|
||||
) {
|
||||
const title = intl.formatMessage({id: 'playbooks.select_playbook.title', defaultMessage: 'Start a run'});
|
||||
|
||||
const title = intl.formatMessage({id: 'playbooks.select_playbook.title', defaultMessage: 'New'});
|
||||
goToScreen(Screens.PLAYBOOKS_SELECT_PLAYBOOK, title, {channelId}, {
|
||||
topBar: {
|
||||
subtitle: {
|
||||
|
|
@ -134,7 +176,7 @@ export async function goToSelectPlaybook(
|
|||
}
|
||||
|
||||
export async function goToStartARun(intl: IntlShape, theme: Theme, playbook: Playbook, onRunCreated: (run: PlaybookRun) => void, channelId?: string) {
|
||||
const title = intl.formatMessage({id: 'playbooks.start_a_run.title', defaultMessage: 'Start a run'});
|
||||
const title = intl.formatMessage({id: 'playbooks.start_a_run.title', defaultMessage: 'New'});
|
||||
const subtitle = playbook.title;
|
||||
goToScreen(Screens.PLAYBOOKS_START_A_RUN, title, {playbook, onRunCreated, channelId}, {
|
||||
topBar: {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, StyleSheet, View} from 'react-native';
|
||||
|
||||
import FloatingTextInput from '@components/floating_input/floating_text_input_label';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import SecurityManager from '@managers/security_manager';
|
||||
import {buildNavigationButton, popTopScreen, setButtons} from '@screens/navigation';
|
||||
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
type Props = {
|
||||
componentId: AvailableScreens;
|
||||
onSave: (title: string) => void;
|
||||
}
|
||||
|
||||
const SAVE_BUTTON_ID = 'add-checklist-item';
|
||||
|
||||
const close = (componentId: AvailableScreens): void => {
|
||||
Keyboard.dismiss();
|
||||
popTopScreen(componentId);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingVertical: 32,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
});
|
||||
|
||||
const AddChecklistItemBottomSheet = ({
|
||||
componentId,
|
||||
onSave,
|
||||
}: Props) => {
|
||||
const intl = useIntl();
|
||||
const {formatMessage} = intl;
|
||||
const theme = useTheme();
|
||||
|
||||
const [title, setTitle] = useState<string>('');
|
||||
const [canSave, setCanSave] = useState(false);
|
||||
|
||||
const rightButton = React.useMemo(() => {
|
||||
const base = buildNavigationButton(
|
||||
SAVE_BUTTON_ID,
|
||||
'playbooks.checklist_item.add.save',
|
||||
undefined,
|
||||
formatMessage({id: 'playbooks.checklist_item.add.save', defaultMessage: 'Add'}),
|
||||
);
|
||||
base.enabled = canSave;
|
||||
base.color = theme.sidebarHeaderTextColor;
|
||||
return base;
|
||||
}, [formatMessage, canSave, theme.sidebarHeaderTextColor]);
|
||||
|
||||
useEffect(() => {
|
||||
setButtons(componentId, {
|
||||
rightButtons: [rightButton],
|
||||
});
|
||||
}, [rightButton, componentId]);
|
||||
|
||||
useEffect(() => {
|
||||
setCanSave(title.trim().length > 0);
|
||||
}, [title]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
close(componentId);
|
||||
}, [componentId]);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
if (title.trim().length > 0) {
|
||||
onSave(title.trim());
|
||||
close(componentId);
|
||||
}
|
||||
}, [title, componentId, onSave]);
|
||||
|
||||
useNavButtonPressed(SAVE_BUTTON_ID, componentId, handleSave, [handleSave]);
|
||||
useAndroidHardwareBackHandler(componentId, handleClose);
|
||||
|
||||
const label = formatMessage({id: 'playbooks.checklist_item.add.label', defaultMessage: 'Task name'});
|
||||
|
||||
return (
|
||||
<View
|
||||
nativeID={SecurityManager.getShieldScreenId(componentId)}
|
||||
style={styles.container}
|
||||
>
|
||||
<FloatingTextInput
|
||||
label={label}
|
||||
onChangeText={setTitle}
|
||||
testID='playbooks.checklist_item.add.input'
|
||||
value={title}
|
||||
theme={theme}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default AddChecklistItemBottomSheet;
|
||||
|
|
@ -47,6 +47,7 @@ describe('Checklist', () => {
|
|||
items: mockItems,
|
||||
channelId: 'channel-id-1',
|
||||
playbookRunId: 'run-id-1',
|
||||
playbookRunName: 'Test Run',
|
||||
isFinished: false,
|
||||
isParticipant: true,
|
||||
checklistProgress: {
|
||||
|
|
|
|||
|
|
@ -2,12 +2,16 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useMemo, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {View, Text, TouchableOpacity, type LayoutChangeEvent, useWindowDimensions, StyleSheet} from 'react-native';
|
||||
import Animated, {useAnimatedStyle, useSharedValue, withTiming} from 'react-native-reanimated';
|
||||
|
||||
import CompassIcon from '@components/compass_icon';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {useTheme} from '@context/theme';
|
||||
import {renameChecklist, addChecklistItem} from '@playbooks/actions/remote/checklist';
|
||||
import ProgressBar from '@playbooks/components/progress_bar';
|
||||
import {goToRenameChecklist, goToAddChecklistItem} from '@playbooks/screens/navigation';
|
||||
import {getChecklistProgress} from '@playbooks/utils/progress';
|
||||
import {makeStyleSheetFromTheme, changeOpacity} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
|
@ -37,6 +41,14 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => ({
|
|||
fontSize: 18,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
},
|
||||
editIconContainer: {
|
||||
alignItems: 'flex-end',
|
||||
},
|
||||
editIcon: {
|
||||
fontSize: 18,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
progressText: {
|
||||
...typography('Body', 100, 'Regular'),
|
||||
color: changeOpacity(theme.centerChannelColor, 0.48),
|
||||
|
|
@ -62,7 +74,29 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => ({
|
|||
textDecorationLine: 'line-through',
|
||||
},
|
||||
titleContainer: {
|
||||
flexShrink: 1,
|
||||
flex: 1,
|
||||
},
|
||||
progressAndEditContainer: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
marginLeft: 'auto',
|
||||
},
|
||||
addButton: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
paddingVertical: 8,
|
||||
paddingHorizontal: 12,
|
||||
gap: 6,
|
||||
},
|
||||
addButtonText: {
|
||||
...typography('Body', 100, 'SemiBold'),
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
},
|
||||
addButtonIcon: {
|
||||
fontSize: 18,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.64),
|
||||
},
|
||||
}));
|
||||
|
||||
|
|
@ -72,6 +106,7 @@ type Props = {
|
|||
items: Array<PlaybookChecklistItemModel | PlaybookChecklistItem>;
|
||||
channelId: string;
|
||||
playbookRunId: string;
|
||||
playbookRunName: string;
|
||||
isFinished: boolean;
|
||||
isParticipant: boolean;
|
||||
checklistProgress: ReturnType<typeof getChecklistProgress>;
|
||||
|
|
@ -83,6 +118,7 @@ const Checklist = ({
|
|||
items,
|
||||
channelId,
|
||||
playbookRunId,
|
||||
playbookRunName,
|
||||
isFinished,
|
||||
isParticipant,
|
||||
checklistProgress: {
|
||||
|
|
@ -93,6 +129,8 @@ const Checklist = ({
|
|||
},
|
||||
}: Props) => {
|
||||
const [expanded, setExpanded] = useState(true);
|
||||
const intl = useIntl();
|
||||
const serverUrl = useServerUrl();
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
const height = useSharedValue(0);
|
||||
|
|
@ -102,6 +140,23 @@ const Checklist = ({
|
|||
setExpanded((prev) => !prev);
|
||||
}, []);
|
||||
|
||||
const handleRename = useCallback((newTitle: string) => {
|
||||
renameChecklist(serverUrl, playbookRunId, checklistNumber, checklist.id, newTitle);
|
||||
}, [serverUrl, playbookRunId, checklist.id, checklistNumber]);
|
||||
|
||||
const handleEditPress = useCallback((e: any) => {
|
||||
e.stopPropagation();
|
||||
goToRenameChecklist(intl, theme, playbookRunName, checklist.title, handleRename);
|
||||
}, [intl, theme, playbookRunName, checklist.title, handleRename]);
|
||||
|
||||
const handleAddItem = useCallback((title: string) => {
|
||||
addChecklistItem(serverUrl, playbookRunId, checklistNumber, title);
|
||||
}, [serverUrl, playbookRunId, checklistNumber]);
|
||||
|
||||
const handleAddPress = useCallback(() => {
|
||||
goToAddChecklistItem(intl, theme, playbookRunName, handleAddItem);
|
||||
}, [intl, theme, playbookRunName, handleAddItem]);
|
||||
|
||||
const animatedStyle = useAnimatedStyle(() => {
|
||||
return {
|
||||
height: withTiming(expanded ? height.value : 0, {duration: 300}),
|
||||
|
|
@ -145,7 +200,15 @@ const Checklist = ({
|
|||
{checklist.title}
|
||||
</Text>
|
||||
</View>
|
||||
<Text style={styles.progressText}>{`${completed} / ${totalNumber} done`}</Text>
|
||||
<View style={styles.progressAndEditContainer}>
|
||||
<Text style={styles.progressText}>{`${completed} / ${totalNumber} done`}</Text>
|
||||
<TouchableOpacity onPress={handleEditPress}>
|
||||
<CompassIcon
|
||||
name='pencil-outline'
|
||||
style={styles.editIcon}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
</View>
|
||||
<ProgressBar
|
||||
progress={progress}
|
||||
|
|
@ -167,6 +230,21 @@ const Checklist = ({
|
|||
isDisabled={isFinished || !isParticipant}
|
||||
/>
|
||||
))}
|
||||
{!isFinished && isParticipant && (
|
||||
<TouchableOpacity
|
||||
style={styles.addButton}
|
||||
onPress={handleAddPress}
|
||||
testID='add-checklist-item-button'
|
||||
>
|
||||
<CompassIcon
|
||||
name='plus'
|
||||
style={styles.addButtonIcon}
|
||||
/>
|
||||
<Text style={styles.addButtonText}>
|
||||
{intl.formatMessage({id: 'playbooks.checklist_item.add.button', defaultMessage: 'New'})}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</Animated.View>
|
||||
{/* This is a hack to get the height of the checklist items */}
|
||||
<View
|
||||
|
|
@ -184,6 +262,17 @@ const Checklist = ({
|
|||
isDisabled={isFinished || !isParticipant}
|
||||
/>
|
||||
))}
|
||||
{!isFinished && isParticipant && (
|
||||
<View style={styles.addButton}>
|
||||
<CompassIcon
|
||||
name='plus'
|
||||
style={styles.addButtonIcon}
|
||||
/>
|
||||
<Text style={styles.addButtonText}>
|
||||
{intl.formatMessage({id: 'playbooks.checklist_item.add.button', defaultMessage: 'New'})}
|
||||
</Text>
|
||||
</View>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -63,6 +63,7 @@ describe('Checklist', () => {
|
|||
checklistNumber: 0,
|
||||
channelId: 'channel-id',
|
||||
playbookRunId: 'run-id',
|
||||
playbookRunName: 'Test Run',
|
||||
isFinished: false,
|
||||
isParticipant: true,
|
||||
};
|
||||
|
|
@ -181,6 +182,7 @@ describe('Checklist', () => {
|
|||
checklistNumber: 0,
|
||||
channelId: 'channel-id',
|
||||
playbookRunId: 'run-id',
|
||||
playbookRunName: 'Test Run',
|
||||
isFinished: false,
|
||||
isParticipant: true,
|
||||
};
|
||||
|
|
@ -240,6 +242,7 @@ describe('Checklist', () => {
|
|||
checklistNumber: 0,
|
||||
channelId: 'channel-id',
|
||||
playbookRunId: 'run-id',
|
||||
playbookRunName: 'Test Run',
|
||||
isFinished: false,
|
||||
isParticipant: true,
|
||||
};
|
||||
|
|
@ -271,6 +274,7 @@ describe('Checklist', () => {
|
|||
checklistNumber: 0,
|
||||
channelId: 'channel-id',
|
||||
playbookRunId: 'run-id',
|
||||
playbookRunName: 'Test Run',
|
||||
isFinished: false,
|
||||
isParticipant: true,
|
||||
};
|
||||
|
|
@ -323,6 +327,7 @@ describe('Checklist', () => {
|
|||
checklistNumber: 0,
|
||||
channelId: 'channel-id',
|
||||
playbookRunId: 'run-id',
|
||||
playbookRunName: 'Test Run',
|
||||
isFinished: false,
|
||||
isParticipant: true,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, StyleSheet, View} from 'react-native';
|
||||
|
||||
import FloatingTextInput from '@components/floating_input/floating_text_input_label';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import SecurityManager from '@managers/security_manager';
|
||||
import {buildNavigationButton, popTopScreen, setButtons} from '@screens/navigation';
|
||||
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
type Props = {
|
||||
componentId: AvailableScreens;
|
||||
currentTitle: string;
|
||||
onSave: (newTitle: string) => void;
|
||||
}
|
||||
|
||||
const SAVE_BUTTON_ID = 'save-checklist-name';
|
||||
|
||||
const close = (componentId: AvailableScreens): void => {
|
||||
Keyboard.dismiss();
|
||||
popTopScreen(componentId);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingVertical: 32,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
});
|
||||
|
||||
const RenameChecklistBottomSheet = ({
|
||||
componentId,
|
||||
currentTitle,
|
||||
onSave,
|
||||
}: Props) => {
|
||||
const intl = useIntl();
|
||||
const {formatMessage} = intl;
|
||||
const theme = useTheme();
|
||||
|
||||
const [title, setTitle] = useState<string>(currentTitle);
|
||||
const [canSave, setCanSave] = useState(false);
|
||||
|
||||
const rightButton = React.useMemo(() => {
|
||||
const base = buildNavigationButton(
|
||||
SAVE_BUTTON_ID,
|
||||
'playbooks.checklist.rename.button',
|
||||
undefined,
|
||||
formatMessage({id: 'playbooks.checklist.rename.button', defaultMessage: 'Save'}),
|
||||
);
|
||||
base.enabled = canSave;
|
||||
base.color = theme.sidebarHeaderTextColor;
|
||||
return base;
|
||||
}, [formatMessage, canSave, theme.sidebarHeaderTextColor]);
|
||||
|
||||
useEffect(() => {
|
||||
setButtons(componentId, {
|
||||
rightButtons: [rightButton],
|
||||
});
|
||||
}, [rightButton, componentId]);
|
||||
|
||||
useEffect(() => {
|
||||
setCanSave(title.trim().length > 0 && title !== currentTitle);
|
||||
}, [title, currentTitle]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
close(componentId);
|
||||
}, [componentId]);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
if (title.trim().length > 0) {
|
||||
onSave(title.trim());
|
||||
close(componentId);
|
||||
}
|
||||
}, [title, componentId, onSave]);
|
||||
|
||||
useNavButtonPressed(SAVE_BUTTON_ID, componentId, handleSave, [handleSave]);
|
||||
useAndroidHardwareBackHandler(componentId, handleClose);
|
||||
|
||||
const label = formatMessage({id: 'playbooks.checklist.rename.label', defaultMessage: 'Section name'});
|
||||
|
||||
return (
|
||||
<View
|
||||
nativeID={SecurityManager.getShieldScreenId(componentId)}
|
||||
style={styles.container}
|
||||
>
|
||||
<FloatingTextInput
|
||||
label={label}
|
||||
onChangeText={setTitle}
|
||||
testID='playbooks.checklist.rename.input'
|
||||
value={title}
|
||||
theme={theme}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default RenameChecklistBottomSheet;
|
||||
|
|
@ -31,6 +31,7 @@ describe('ChecklistList', () => {
|
|||
checklists: mockChecklists,
|
||||
channelId: 'channel-id-1',
|
||||
playbookRunId: 'run-id-1',
|
||||
playbookRunName: 'Test Run',
|
||||
isFinished: false,
|
||||
isParticipant: true,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@ type Props = {
|
|||
checklists: Array<PlaybookChecklistModel | PlaybookChecklist>;
|
||||
channelId: string;
|
||||
playbookRunId: string;
|
||||
playbookRunName: string;
|
||||
isFinished: boolean;
|
||||
isParticipant: boolean;
|
||||
}
|
||||
|
|
@ -26,6 +27,7 @@ const ChecklistList = ({
|
|||
checklists,
|
||||
channelId,
|
||||
playbookRunId,
|
||||
playbookRunName,
|
||||
isFinished,
|
||||
isParticipant,
|
||||
}: Props) => {
|
||||
|
|
@ -37,6 +39,7 @@ const ChecklistList = ({
|
|||
checklist={checklist}
|
||||
channelId={channelId}
|
||||
playbookRunId={playbookRunId}
|
||||
playbookRunName={playbookRunName}
|
||||
checklistNumber={index}
|
||||
isFinished={isFinished}
|
||||
isParticipant={isParticipant}
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ describe('ErrorState', () => {
|
|||
it('renders error state correctly', () => {
|
||||
const {getByText} = renderWithIntl(<ErrorState/>);
|
||||
|
||||
expect(getByText('Unable to fetch run details')).toBeTruthy();
|
||||
expect(getByText('Unable to fetch details')).toBeTruthy();
|
||||
expect(getByText('Please check your network connection or try again later.')).toBeTruthy();
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ const getStylesFromTheme = makeStyleSheetFromTheme((theme: Theme) => {
|
|||
const messages = defineMessages({
|
||||
title: {
|
||||
id: 'playbooks.playbook_run.error.title',
|
||||
defaultMessage: 'Unable to fetch run details',
|
||||
defaultMessage: 'Unable to fetch details',
|
||||
},
|
||||
description: {
|
||||
id: 'playbooks.playbook_run.error.description',
|
||||
|
|
|
|||
|
|
@ -193,7 +193,7 @@ describe('PlaybookRun', () => {
|
|||
const userAvatarsStack = getByTestId('user-avatars-stack');
|
||||
expect(userAvatarsStack.props.users).toBe(props.participants);
|
||||
expect(userAvatarsStack.props.location).toBe('PlaybookRun');
|
||||
expect(userAvatarsStack.props.bottomSheetTitle.defaultMessage).toBe('Run Participants');
|
||||
expect(userAvatarsStack.props.bottomSheetTitle.defaultMessage).toBe('Participants');
|
||||
|
||||
props.owner = undefined;
|
||||
rerender(<PlaybookRun {...props}/>);
|
||||
|
|
@ -330,7 +330,7 @@ describe('PlaybookRun', () => {
|
|||
|
||||
const {getByText} = renderWithEverything(<PlaybookRun {...props}/>, {database});
|
||||
|
||||
expect(getByText('Finish Run')).toBeTruthy();
|
||||
expect(getByText('Finish')).toBeTruthy();
|
||||
});
|
||||
|
||||
it('handles finish run button press', () => {
|
||||
|
|
@ -338,12 +338,12 @@ describe('PlaybookRun', () => {
|
|||
props.participants.push(TestHelper.fakeUserModel({id: props.currentUserId}));
|
||||
const {getByText} = renderWithEverything(<PlaybookRun {...props}/>, {database});
|
||||
|
||||
const finishRunButton = getByText('Finish Run');
|
||||
const finishRunButton = getByText('Finish');
|
||||
fireEvent.press(finishRunButton);
|
||||
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
'Finish Run',
|
||||
'There are 3 tasks pending.\n\nAre you sure you want to finish the run for all participants?',
|
||||
'Finish',
|
||||
'There are 3 tasks pending.\n\nAre you sure you want to finish the checklist for all participants?',
|
||||
[
|
||||
{text: 'Cancel', style: 'cancel'},
|
||||
{text: 'Finish', style: 'destructive', onPress: expect.any(Function)},
|
||||
|
|
@ -358,7 +358,7 @@ describe('PlaybookRun', () => {
|
|||
const props = getBaseProps();
|
||||
const {queryByText} = renderWithEverything(<PlaybookRun {...props}/>, {database});
|
||||
|
||||
expect(queryByText('Finish Run')).toBeNull();
|
||||
expect(queryByText('Finish')).toBeNull();
|
||||
});
|
||||
|
||||
it('shows the error snackbar when finishing run fails', async () => {
|
||||
|
|
@ -367,7 +367,7 @@ describe('PlaybookRun', () => {
|
|||
jest.mocked(finishRun).mockResolvedValue({error: 'error'});
|
||||
const {getByText} = renderWithEverything(<PlaybookRun {...props}/>, {database});
|
||||
|
||||
const finishRunButton = getByText('Finish Run');
|
||||
const finishRunButton = getByText('Finish');
|
||||
fireEvent.press(finishRunButton);
|
||||
|
||||
const finishAction = jest.mocked(Alert.alert).mock.calls[0][2]![1];
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import {useServerUrl} from '@context/server';
|
|||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import {finishRun, setOwner} from '@playbooks/actions/remote/runs';
|
||||
import {PLAYBOOK_RUN_TYPES} from '@playbooks/constants/playbook_run';
|
||||
import {getRunScheduledTimestamp, isRunFinished} from '@playbooks/utils/run';
|
||||
import {openUserProfileModal, popTopScreen} from '@screens/navigation';
|
||||
import {showPlaybookErrorSnackbar} from '@utils/snack_bar';
|
||||
|
|
@ -52,11 +53,11 @@ const messages = defineMessages({
|
|||
},
|
||||
participantsTitle: {
|
||||
id: 'playbooks.playbook_run.participants_title',
|
||||
defaultMessage: 'Run Participants',
|
||||
defaultMessage: 'Participants',
|
||||
},
|
||||
runDetails: {
|
||||
id: 'playbooks.playbook_run.run_details',
|
||||
defaultMessage: 'Run details',
|
||||
defaultMessage: 'Checklist details',
|
||||
},
|
||||
overdue: {
|
||||
id: 'playbooks.playbook_run.overdue',
|
||||
|
|
@ -68,11 +69,11 @@ const messages = defineMessages({
|
|||
},
|
||||
finishRunDialogTitle: {
|
||||
id: 'playbooks.playbook_run.finish_run_dialog_title',
|
||||
defaultMessage: 'Finish Run',
|
||||
defaultMessage: 'Finish',
|
||||
},
|
||||
finishRunDialogDescription: {
|
||||
id: 'playbooks.playbook_run.finish_run_dialog_description',
|
||||
defaultMessage: 'There are {pendingCount} {pendingCount, plural, =1 {task} other {tasks}} pending.\n\nAre you sure you want to finish the run for all participants?',
|
||||
defaultMessage: 'There are {pendingCount} {pendingCount, plural, =1 {task} other {tasks}} pending.\n\nAre you sure you want to finish the checklist for all participants?',
|
||||
},
|
||||
finishRunDialogCancel: {
|
||||
id: 'playbooks.playbook_run.finish_run_dialog_cancel',
|
||||
|
|
@ -84,7 +85,7 @@ const messages = defineMessages({
|
|||
},
|
||||
finishRunButton: {
|
||||
id: 'playbooks.playbook_run.finish_run_button',
|
||||
defaultMessage: 'Finish Run',
|
||||
defaultMessage: 'Finish',
|
||||
},
|
||||
});
|
||||
|
||||
|
|
@ -100,9 +101,20 @@ const getStyleSheet = makeStyleSheetFromTheme((theme) => ({
|
|||
gap: 10,
|
||||
alignItems: 'flex-start',
|
||||
},
|
||||
titleRow: {
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
gap: 8,
|
||||
},
|
||||
title: {
|
||||
...typography('Heading', 400, 'SemiBold'),
|
||||
color: theme.centerChannelColor,
|
||||
flex: 1,
|
||||
},
|
||||
editIcon: {
|
||||
fontSize: 18,
|
||||
color: changeOpacity(theme.centerChannelColor, 0.56),
|
||||
paddingHorizontal: 4,
|
||||
},
|
||||
infoText: {
|
||||
...typography('Body', 100, 'Regular'),
|
||||
|
|
@ -186,6 +198,8 @@ export default function PlaybookRun({
|
|||
const isFinished = isRunFinished(playbookRun);
|
||||
const readOnly = isFinished || !isParticipant;
|
||||
|
||||
const playbookRunType = useMemo(() => playbookRun?.type || 'playbook', [playbookRun]);
|
||||
|
||||
const containerStyle = useMemo(() => {
|
||||
return [
|
||||
styles.container,
|
||||
|
|
@ -231,6 +245,26 @@ export default function PlaybookRun({
|
|||
);
|
||||
}, [handleSelectOwner, intl, owner, participants, playbookRun?.name, theme]);
|
||||
|
||||
// this will be back once there is a rename function on the server side
|
||||
// const handleRename = useCallback(async (newName: string) => {
|
||||
// if (!playbookRun) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// const res = await renamePlaybookRun(serverUrl, playbookRun.id, newName);
|
||||
// if (res.error) {
|
||||
// showPlaybookErrorSnackbar();
|
||||
// }
|
||||
// }, [playbookRun, serverUrl]);
|
||||
|
||||
// const handleEditPress = useCallback(() => {
|
||||
// if (!playbookRun) {
|
||||
// return;
|
||||
// }
|
||||
|
||||
// goToRenamePlaybookRun(intl, theme, playbookRun.name, handleRename);
|
||||
// }, [intl, theme, playbookRun, handleRename]);
|
||||
|
||||
const handleFinishRun = useCallback(() => {
|
||||
if (!playbookRun) {
|
||||
return;
|
||||
|
|
@ -279,7 +313,16 @@ export default function PlaybookRun({
|
|||
<ScrollView contentContainerStyle={styles.scrollView}>
|
||||
<View style={styles.intro}>
|
||||
<View style={styles.titleAndDescription}>
|
||||
<Text style={styles.title}>{playbookRun.name}</Text>
|
||||
<View style={styles.titleRow}>
|
||||
<Text style={styles.title}>{playbookRun.name}</Text>
|
||||
{/* This will be back once there is a rename function on the server side
|
||||
<TouchableOpacity onPress={handleEditPress}>
|
||||
<CompassIcon
|
||||
name='pencil-outline'
|
||||
style={styles.editIcon}
|
||||
/>
|
||||
</TouchableOpacity> */}
|
||||
</View>
|
||||
{isFinished && (
|
||||
<Tag
|
||||
message={messages.finished}
|
||||
|
|
@ -330,12 +373,14 @@ export default function PlaybookRun({
|
|||
)}
|
||||
</View>
|
||||
)}
|
||||
<StatusUpdateIndicator
|
||||
isFinished={isFinished}
|
||||
timestamp={getRunScheduledTimestamp(playbookRun)}
|
||||
isParticipant={isParticipant}
|
||||
playbookRunId={playbookRun.id}
|
||||
/>
|
||||
{playbookRunType !== PLAYBOOK_RUN_TYPES.ChannelChecklistType && (
|
||||
<StatusUpdateIndicator
|
||||
isFinished={isFinished}
|
||||
timestamp={getRunScheduledTimestamp(playbookRun)}
|
||||
isParticipant={isParticipant}
|
||||
playbookRunId={playbookRun.id}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
<View style={styles.tasksContainer}>
|
||||
<View style={styles.tasksHeaderContainer}>
|
||||
|
|
@ -353,6 +398,7 @@ export default function PlaybookRun({
|
|||
checklists={checklists}
|
||||
channelId={channelId}
|
||||
playbookRunId={playbookRun.id}
|
||||
playbookRunName={playbookRun.name}
|
||||
isFinished={isRunFinished(playbookRun)}
|
||||
isParticipant={isParticipant}
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,105 @@
|
|||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import {useIntl} from 'react-intl';
|
||||
import {Keyboard, StyleSheet, View} from 'react-native';
|
||||
|
||||
import FloatingTextInput from '@components/floating_input/floating_text_input_label';
|
||||
import {useTheme} from '@context/theme';
|
||||
import useAndroidHardwareBackHandler from '@hooks/android_back_handler';
|
||||
import useNavButtonPressed from '@hooks/navigation_button_pressed';
|
||||
import SecurityManager from '@managers/security_manager';
|
||||
import {buildNavigationButton, popTopScreen, setButtons} from '@screens/navigation';
|
||||
|
||||
import type {AvailableScreens} from '@typings/screens/navigation';
|
||||
|
||||
type Props = {
|
||||
componentId: AvailableScreens;
|
||||
currentTitle: string;
|
||||
onSave: (newTitle: string) => void;
|
||||
}
|
||||
|
||||
const SAVE_BUTTON_ID = 'save-playbook-run-name';
|
||||
|
||||
const close = (componentId: AvailableScreens): void => {
|
||||
Keyboard.dismiss();
|
||||
popTopScreen(componentId);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingVertical: 32,
|
||||
paddingHorizontal: 20,
|
||||
},
|
||||
});
|
||||
|
||||
const RenamePlaybookRunBottomSheet = ({
|
||||
componentId,
|
||||
currentTitle,
|
||||
onSave,
|
||||
}: Props) => {
|
||||
const intl = useIntl();
|
||||
const {formatMessage} = intl;
|
||||
const theme = useTheme();
|
||||
|
||||
const [title, setTitle] = useState<string>(currentTitle);
|
||||
const [canSave, setCanSave] = useState(false);
|
||||
|
||||
const rightButton = React.useMemo(() => {
|
||||
const base = buildNavigationButton(
|
||||
SAVE_BUTTON_ID,
|
||||
'playbooks.playbook_run.rename.button',
|
||||
undefined,
|
||||
formatMessage({id: 'playbooks.playbook_run.rename.button', defaultMessage: 'Save'}),
|
||||
);
|
||||
base.enabled = canSave;
|
||||
base.color = theme.sidebarHeaderTextColor;
|
||||
return base;
|
||||
}, [formatMessage, canSave, theme.sidebarHeaderTextColor]);
|
||||
|
||||
useEffect(() => {
|
||||
setButtons(componentId, {
|
||||
rightButtons: [rightButton],
|
||||
});
|
||||
}, [rightButton, componentId]);
|
||||
|
||||
useEffect(() => {
|
||||
setCanSave(title.trim().length > 0 && title !== currentTitle);
|
||||
}, [title, currentTitle]);
|
||||
|
||||
const handleClose = useCallback(() => {
|
||||
close(componentId);
|
||||
}, [componentId]);
|
||||
|
||||
const handleSave = useCallback(() => {
|
||||
if (title.trim().length > 0) {
|
||||
onSave(title.trim());
|
||||
close(componentId);
|
||||
}
|
||||
}, [title, componentId, onSave]);
|
||||
|
||||
useNavButtonPressed(SAVE_BUTTON_ID, componentId, handleSave, [handleSave]);
|
||||
useAndroidHardwareBackHandler(componentId, handleClose);
|
||||
|
||||
const label = formatMessage({id: 'playbooks.playbook_run.rename.label', defaultMessage: 'Checklist name'});
|
||||
|
||||
return (
|
||||
<View
|
||||
nativeID={SecurityManager.getShieldScreenId(componentId)}
|
||||
style={styles.container}
|
||||
>
|
||||
<FloatingTextInput
|
||||
label={label}
|
||||
onChangeText={setTitle}
|
||||
testID='playbooks.playbook_run.rename.input'
|
||||
value={title}
|
||||
theme={theme}
|
||||
autoFocus={true}
|
||||
/>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default RenamePlaybookRunBottomSheet;
|
||||
|
|
@ -61,7 +61,7 @@ describe('StatusUpdateIndicator', () => {
|
|||
/>,
|
||||
);
|
||||
|
||||
const text = getByText(/Update overdue/);
|
||||
const text = getByText(/pdate overdue/);
|
||||
expect(text).toHaveStyle({color: Preferences.THEMES.denim.dndIndicator});
|
||||
|
||||
const icon = getByTestId('compass-icon');
|
||||
|
|
@ -79,7 +79,7 @@ describe('StatusUpdateIndicator', () => {
|
|||
/>,
|
||||
);
|
||||
|
||||
const text = getByText(/Run finished/);
|
||||
const text = getByText(/Finished/);
|
||||
expect(text).toHaveStyle({color: changeOpacity(Preferences.THEMES.denim.centerChannelColor, 0.72)});
|
||||
|
||||
const icon = getByTestId('compass-icon');
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@ const messages = defineMessages({
|
|||
},
|
||||
finished: {
|
||||
id: 'playbooks.playbook_run.status_update_finished',
|
||||
defaultMessage: 'Run finished\n{time}',
|
||||
defaultMessage: 'Finished at\n{time}',
|
||||
},
|
||||
update: {
|
||||
id: 'playbooks.playbook_run.status_update',
|
||||
|
|
|
|||
|
|
@ -140,7 +140,7 @@ describe('PostUpdate', () => {
|
|||
const {getByText, getByTestId} = renderWithIntlAndTheme(<PostUpdate {...props}/>);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText(/This update for the run/)).toBeTruthy();
|
||||
expect(getByText(/This update for the checklist/)).toBeTruthy();
|
||||
const input = getByTestId('FloatingTextInput');
|
||||
expect(input).toHaveProp('value', 'Default message template');
|
||||
expect(input).toHaveProp('label', 'Update message');
|
||||
|
|
@ -161,7 +161,7 @@ describe('PostUpdate', () => {
|
|||
expect(selector.props.options).toHaveLength(6);
|
||||
|
||||
const toggle = getByTestId('playbooks.post_update.selector.also_mark_run_as_finished');
|
||||
expect(toggle).toHaveProp('label', 'Also mark the run as finished');
|
||||
expect(toggle).toHaveProp('label', 'Also mark the checklist as finished');
|
||||
expect(toggle).toHaveProp('selected', false);
|
||||
expect(toggle).toHaveProp('type', 'toggle');
|
||||
});
|
||||
|
|
@ -221,7 +221,7 @@ describe('PostUpdate', () => {
|
|||
const {getByText} = renderWithIntlAndTheme(<PostUpdate {...props}/>);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(getByText('This update for the run Test Run will be broadcasted to 2 channels and 2 direct messages.')).toBeTruthy();
|
||||
expect(getByText('This update for the checklist Test Run will be broadcasted to 2 channels and 2 direct messages.')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
||||
|
|
@ -430,15 +430,15 @@ describe('PostUpdate', () => {
|
|||
|
||||
await waitFor(() => {
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
'Confirm finish run',
|
||||
'Are you sure you want to finish the run Test Run for all participants?',
|
||||
'Confirm finish checklist',
|
||||
'Are you sure you want to finish the checklist Test Run for all participants?',
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
text: 'Cancel',
|
||||
style: 'cancel',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
text: 'Finish run',
|
||||
text: 'Finish',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
|
@ -470,15 +470,15 @@ describe('PostUpdate', () => {
|
|||
|
||||
await waitFor(() => {
|
||||
expect(Alert.alert).toHaveBeenCalledWith(
|
||||
'Confirm finish run',
|
||||
'There are 3 outstanding tasks. Are you sure you want to finish the run Test Run for all participants?',
|
||||
'Confirm finish checklist',
|
||||
'There are 3 outstanding tasks. Are you sure you want to finish the checklist Test Run for all participants?',
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
text: 'Cancel',
|
||||
style: 'cancel',
|
||||
}),
|
||||
expect.objectContaining({
|
||||
text: 'Finish run',
|
||||
text: 'Finish',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
|
@ -507,7 +507,7 @@ describe('PostUpdate', () => {
|
|||
|
||||
// Simulate alert confirm button press
|
||||
const alertCall = jest.mocked(Alert.alert).mock.calls[0];
|
||||
const confirmButton = alertCall[2]?.find((button) => button.text === 'Finish run');
|
||||
const confirmButton = alertCall[2]?.find((button) => button.text === 'Finish');
|
||||
|
||||
await act(async () => {
|
||||
confirmButton?.onPress?.();
|
||||
|
|
|
|||
|
|
@ -226,12 +226,12 @@ const PostUpdate = ({
|
|||
|
||||
const onPostUpdate = useCallback(() => {
|
||||
if (alsoMarkRunAsFinished) {
|
||||
let message = intl.formatMessage({id: 'playbooks.post_update.confirm.message', defaultMessage: 'Are you sure you want to finish the run {runName} for all participants?'}, {runName});
|
||||
let message = intl.formatMessage({id: 'playbooks.post_update.confirm.message', defaultMessage: 'Are you sure you want to finish the checklist {runName} for all participants?'}, {runName});
|
||||
if (outstanding > 0) {
|
||||
message = intl.formatMessage({id: 'playbooks.post_update.confirm.message.with_tasks', defaultMessage: 'There {outstanding, plural, =1 {is # outstanding task} other {are # outstanding tasks}}. Are you sure you want to finish the run {runName} for all participants?'}, {runName, outstanding});
|
||||
message = intl.formatMessage({id: 'playbooks.post_update.confirm.message.with_tasks', defaultMessage: 'There {outstanding, plural, =1 {is # outstanding task} other {are # outstanding tasks}}. Are you sure you want to finish the checklist {runName} for all participants?'}, {runName, outstanding});
|
||||
}
|
||||
Alert.alert(
|
||||
intl.formatMessage({id: 'playbooks.post_update.confirm.title', defaultMessage: 'Confirm finish run'}),
|
||||
intl.formatMessage({id: 'playbooks.post_update.confirm.title', defaultMessage: 'Confirm finish checklist'}),
|
||||
message,
|
||||
[
|
||||
{
|
||||
|
|
@ -239,7 +239,7 @@ const PostUpdate = ({
|
|||
style: 'cancel',
|
||||
},
|
||||
{
|
||||
text: intl.formatMessage({id: 'playbooks.post_update.confirm.confirm', defaultMessage: 'Finish run'}),
|
||||
text: intl.formatMessage({id: 'playbooks.post_update.confirm.confirm', defaultMessage: 'Finish'}),
|
||||
onPress: onConfirm,
|
||||
},
|
||||
],
|
||||
|
|
@ -279,7 +279,7 @@ const PostUpdate = ({
|
|||
introMessage = (
|
||||
<FormattedMessage
|
||||
id='playbooks.post_update.intro'
|
||||
defaultMessage='This update for the run <Bold>{runName}</Bold> will be broadcasted to {hasChannels, select, true {<Bold>{broadcastChannelCount, plural, =1 {one channel} other {{broadcastChannelCount, number} channels}}</Bold>} other {}}{hasFollowersAndChannels, select, true { and } other {}}{hasFollowers, select, true {<Bold>{followersChannelCount, plural, =1 {one direct message} other {{followersChannelCount, number} direct messages}}</Bold>} other {}}.'
|
||||
defaultMessage='This update for the checklist <Bold>{runName}</Bold> will be broadcasted to {hasChannels, select, true {<Bold>{broadcastChannelCount, plural, =1 {one channel} other {{broadcastChannelCount, number} channels}}</Bold>} other {}}{hasFollowersAndChannels, select, true { and } other {}}{hasFollowers, select, true {<Bold>{followersChannelCount, plural, =1 {one direct message} other {{followersChannelCount, number} direct messages}}</Bold>} other {}}.'
|
||||
values={introMessageValues}
|
||||
/>
|
||||
);
|
||||
|
|
@ -310,7 +310,7 @@ const PostUpdate = ({
|
|||
label={intl.formatMessage({id: 'playbooks.post_update.label.next_update', defaultMessage: 'Timer for next update'})}
|
||||
/>
|
||||
<OptionItem
|
||||
label={intl.formatMessage({id: 'playbooks.post_update.label.also_mark_run_as_finished', defaultMessage: 'Also mark the run as finished'})}
|
||||
label={intl.formatMessage({id: 'playbooks.post_update.label.also_mark_run_as_finished', defaultMessage: 'Also mark the checklist as finished'})}
|
||||
action={setAlsoMarkRunAsFinished}
|
||||
testID='playbooks.post_update.selector.also_mark_run_as_finished'
|
||||
selected={alsoMarkRunAsFinished}
|
||||
|
|
|
|||
|
|
@ -96,7 +96,7 @@ describe('PlaybookRow', () => {
|
|||
<PlaybookRow {...props}/>,
|
||||
);
|
||||
|
||||
expect(getByText(/No runs in progress/)).toBeTruthy();
|
||||
expect(getByText(/No checklists in progress/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('displays single run in progress correctly', () => {
|
||||
|
|
@ -107,7 +107,7 @@ describe('PlaybookRow', () => {
|
|||
<PlaybookRow {...props}/>,
|
||||
);
|
||||
|
||||
expect(getByText(/1 run in progress/)).toBeTruthy();
|
||||
expect(getByText(/1 checklist in progress/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('displays multiple runs in progress correctly', () => {
|
||||
|
|
@ -118,7 +118,7 @@ describe('PlaybookRow', () => {
|
|||
<PlaybookRow {...props}/>,
|
||||
);
|
||||
|
||||
expect(getByText(/5 runs in progress/)).toBeTruthy();
|
||||
expect(getByText(/5 checklists in progress/)).toBeTruthy();
|
||||
});
|
||||
|
||||
it('calls onPress when pressed', async () => {
|
||||
|
|
|
|||
|
|
@ -72,12 +72,12 @@ const PlaybookRow = ({
|
|||
if (activeRuns === 0) {
|
||||
return intl.formatMessage({
|
||||
id: 'playbooks.row.no_runs',
|
||||
defaultMessage: 'No runs in progress',
|
||||
defaultMessage: 'No checklists in progress',
|
||||
});
|
||||
}
|
||||
return intl.formatMessage({
|
||||
id: 'playbooks.row.runs',
|
||||
defaultMessage: '{count} {count, plural, one {run} other {runs}} in progress',
|
||||
defaultMessage: '{count} {count, plural, one {checklist} other {checklists}} in progress',
|
||||
}, {count: activeRuns});
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -288,7 +288,7 @@ describe('SelectUser', () => {
|
|||
const sections = customSection(profiles);
|
||||
|
||||
expect(sections).toHaveLength(2);
|
||||
expect(sections[0].id).toBe('RUN PARTICIPANTS');
|
||||
expect(sections[0].id).toBe('PARTICIPANTS');
|
||||
expect(sections[0].data).toContain(participantUser);
|
||||
expect(sections[1].id).toBe('NOT PARTICIPATING');
|
||||
expect(sections[1].data).toContain(nonParticipantUser);
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ export type Props = {
|
|||
const messages = defineMessages({
|
||||
participants: {
|
||||
id: 'playbooks.select_user.participants',
|
||||
defaultMessage: 'RUN PARTICIPANTS',
|
||||
defaultMessage: 'PARTICIPANTS',
|
||||
},
|
||||
notParticipants: {
|
||||
id: 'playbooks.select_user.not_participants',
|
||||
|
|
|
|||
|
|
@ -184,11 +184,11 @@ function StartARun({
|
|||
<FloatingTextInput
|
||||
label={intl.formatMessage({
|
||||
id: 'playbooks.start_run.run_name_label',
|
||||
defaultMessage: 'Run name',
|
||||
defaultMessage: 'Name',
|
||||
})}
|
||||
placeholder={intl.formatMessage({
|
||||
id: 'playbooks.start_run.run_name_placeholder',
|
||||
defaultMessage: 'Add a name for your run',
|
||||
defaultMessage: 'Add a name',
|
||||
})}
|
||||
value={runName}
|
||||
onChangeText={setRunName}
|
||||
|
|
@ -196,13 +196,13 @@ function StartARun({
|
|||
testID='start_run.run_name_input'
|
||||
error={runName.trim() ? undefined : intl.formatMessage({
|
||||
id: 'playbooks.start_run.run_name_error',
|
||||
defaultMessage: 'Please add a name for this run',
|
||||
defaultMessage: 'Please add a name',
|
||||
})}
|
||||
/>
|
||||
<FloatingTextInput
|
||||
label={intl.formatMessage({
|
||||
id: 'playbooks.start_run.run_description_label',
|
||||
defaultMessage: 'Run description',
|
||||
defaultMessage: 'Description',
|
||||
})}
|
||||
value={runDescription}
|
||||
onChangeText={setRunDescription}
|
||||
|
|
|
|||
5
app/products/playbooks/types/api.d.ts
vendored
5
app/products/playbooks/types/api.d.ts
vendored
|
|
@ -92,6 +92,8 @@ type PlaybookRunAttributeValue = {
|
|||
value: string;
|
||||
}
|
||||
|
||||
type PlaybookRunType = typeof PLAYBOOK_RUN_TYPES[keyof typeof PLAYBOOK_RUN_TYPES];
|
||||
|
||||
type PlaybookRun = {
|
||||
id: string;
|
||||
name: string;
|
||||
|
|
@ -108,7 +110,8 @@ type PlaybookRun = {
|
|||
create_at: number;
|
||||
end_at: number;
|
||||
post_id?: string;
|
||||
playbook_id: string;
|
||||
playbook_id?: string;
|
||||
type?: PlaybookRunType;
|
||||
current_status: PlaybookRunStatusType;
|
||||
last_status_update_at: number;
|
||||
reminder_post_id?: string;
|
||||
|
|
|
|||
|
|
@ -23,6 +23,9 @@ declare class PlaybookRunModel extends Model {
|
|||
// Foreign key to the playbook that generated this run
|
||||
playbookId: string;
|
||||
|
||||
// The type of run ('playbook' or 'channelChecklist')
|
||||
type: PlaybookRunType;
|
||||
|
||||
// ID of the post that created the run (nullable)
|
||||
postId: string | null;
|
||||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import React, {type ComponentProps} from 'react';
|
|||
import NavigationHeader from '@components/navigation_header';
|
||||
import {General} from '@constants';
|
||||
import {useServerUrl} from '@context/server';
|
||||
import {fetchPlaybookRunsForChannel} from '@playbooks/actions/remote/runs';
|
||||
import {createPlaybookRun, fetchPlaybookRunsForChannel} from '@playbooks/actions/remote/runs';
|
||||
import {goToPlaybookRun, goToPlaybookRuns} from '@playbooks/screens/navigation';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {renderWithIntl, waitFor} from '@test/intl-test-helper';
|
||||
|
|
@ -38,6 +38,7 @@ describe('ChannelHeader', () => {
|
|||
return {
|
||||
channelId: 'channel-id',
|
||||
channelType: 'O',
|
||||
currentUserId: 'current-user-id',
|
||||
displayName: 'Test Channel',
|
||||
teamId: 'team-id',
|
||||
hasPlaybookRuns: false,
|
||||
|
|
@ -60,17 +61,20 @@ describe('ChannelHeader', () => {
|
|||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it('does not show playbook button when there are no active runs', () => {
|
||||
it('shows playbook button with "+" when there are no active runs', () => {
|
||||
const props = getBaseProps();
|
||||
props.hasPlaybookRuns = false;
|
||||
props.playbooksActiveRuns = 0;
|
||||
renderWithIntl(<ChannelHeader {...props}/>);
|
||||
props.isPlaybooksEnabled = true;
|
||||
|
||||
const navHeader = jest.mocked(NavigationHeader).mock.calls[0][0];
|
||||
expect(navHeader.rightButtons).toEqual(
|
||||
const {getByTestId} = renderWithIntl(<ChannelHeader {...props}/>);
|
||||
|
||||
const navHeader = getByTestId('navigation-header');
|
||||
expect(navHeader.props.rightButtons).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.not.objectContaining({
|
||||
expect.objectContaining({
|
||||
iconName: 'product-playbooks',
|
||||
count: '+',
|
||||
}),
|
||||
]),
|
||||
);
|
||||
|
|
@ -168,6 +172,44 @@ describe('ChannelHeader', () => {
|
|||
expect(goToPlaybookRun).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('creates a new playbook run when clicking + button with no active runs', async () => {
|
||||
const mockCreatedRun = {
|
||||
id: 'new-run-id',
|
||||
channel_id: 'channel-id',
|
||||
name: 'Test Channel Checklist',
|
||||
} as any;
|
||||
jest.mocked(createPlaybookRun).mockResolvedValue({data: mockCreatedRun});
|
||||
jest.mocked(fetchPlaybookRunsForChannel).mockResolvedValue({runs: [mockCreatedRun]});
|
||||
|
||||
const props = getBaseProps();
|
||||
props.playbooksActiveRuns = 0;
|
||||
props.hasPlaybookRuns = false;
|
||||
props.displayName = 'Test Channel';
|
||||
|
||||
const {getByTestId} = renderWithIntl(<ChannelHeader {...props}/>);
|
||||
|
||||
const navHeader = getByTestId('navigation-header');
|
||||
const playbookButton = (navHeader.props as ComponentProps<typeof NavigationHeader>).rightButtons?.find((button) => button.iconName === 'product-playbooks');
|
||||
expect(playbookButton).toBeTruthy();
|
||||
expect(playbookButton?.count).toBe('+');
|
||||
|
||||
playbookButton?.onPress();
|
||||
|
||||
await waitFor(() => {
|
||||
expect(createPlaybookRun).toHaveBeenCalledWith(
|
||||
serverUrl,
|
||||
'', // empty playbook_id
|
||||
'current-user-id',
|
||||
'team-id',
|
||||
'Test Channel Checklist',
|
||||
'', // empty description
|
||||
'channel-id',
|
||||
);
|
||||
expect(fetchPlaybookRunsForChannel).toHaveBeenCalledWith(serverUrl, 'channel-id');
|
||||
expect(goToPlaybookRun).toHaveBeenCalledWith(expect.anything(), 'new-run-id');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not fetch runs when playbooks are disabled', async () => {
|
||||
const ephemeralGetSpy = jest.spyOn(EphemeralStore, 'getChannelPlaybooksSynced');
|
||||
|
||||
|
|
|
|||
|
|
@ -19,14 +19,17 @@ import {useTheme} from '@context/theme';
|
|||
import {useIsTablet} from '@hooks/device';
|
||||
import {useDefaultHeaderHeight} from '@hooks/header';
|
||||
import {usePreventDoubleTap} from '@hooks/utils';
|
||||
import {fetchPlaybookRunsForChannel} from '@playbooks/actions/remote/runs';
|
||||
import {createPlaybookRun, fetchPlaybookRunsForChannel} from '@playbooks/actions/remote/runs';
|
||||
import {goToPlaybookRun, goToPlaybookRuns} from '@playbooks/screens/navigation';
|
||||
import {BOTTOM_SHEET_ANDROID_OFFSET} from '@screens/bottom_sheet';
|
||||
import ChannelBanner from '@screens/channel/header/channel_banner';
|
||||
import {bottomSheet, popTopScreen, showModal} from '@screens/navigation';
|
||||
import EphemeralStore from '@store/ephemeral_store';
|
||||
import {isTypeDMorGM} from '@utils/channel';
|
||||
import {getFullErrorMessage} from '@utils/errors';
|
||||
import {bottomSheetSnapPoint} from '@utils/helpers';
|
||||
import {logDebug} from '@utils/log';
|
||||
import {showPlaybookErrorSnackbar} from '@utils/snack_bar';
|
||||
import {changeOpacity, makeStyleSheetFromTheme} from '@utils/theme';
|
||||
import {typography} from '@utils/typography';
|
||||
|
||||
|
|
@ -40,6 +43,7 @@ type ChannelProps = {
|
|||
canAddBookmarks: boolean;
|
||||
channelId: string;
|
||||
channelType: ChannelType;
|
||||
currentUserId: string;
|
||||
customStatus?: UserCustomStatus;
|
||||
isBookmarksEnabled: boolean;
|
||||
isCustomStatusEnabled: boolean;
|
||||
|
|
@ -93,6 +97,7 @@ const ChannelHeader = ({
|
|||
channelId,
|
||||
channelType,
|
||||
componentId,
|
||||
currentUserId,
|
||||
customStatus,
|
||||
displayName,
|
||||
hasBookmarks,
|
||||
|
|
@ -214,22 +219,51 @@ const ChannelHeader = ({
|
|||
});
|
||||
}, [isTablet, callsAvailable, isDMorGM, hasPlaybookRuns, theme, onTitlePress, channelId]);
|
||||
|
||||
const handleCreateQuickRun = useCallback(async () => {
|
||||
const runName = `${displayName} Checklist`;
|
||||
const res = await createPlaybookRun(
|
||||
serverUrl,
|
||||
'', // empty playbook_id
|
||||
currentUserId,
|
||||
teamId,
|
||||
runName,
|
||||
'', // empty description
|
||||
channelId,
|
||||
);
|
||||
|
||||
if (res.error || !res.data) {
|
||||
logDebug('error on createPlaybookRun', getFullErrorMessage(res.error));
|
||||
showPlaybookErrorSnackbar();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch updated runs and navigate to the new run
|
||||
await fetchPlaybookRunsForChannel(serverUrl, channelId);
|
||||
await goToPlaybookRun(intl, res.data.id);
|
||||
}, [serverUrl, currentUserId, teamId, displayName, channelId, intl]);
|
||||
|
||||
const openPlaybooksRuns = useCallback(() => {
|
||||
// If no active runs, create a new one instead
|
||||
if (playbooksActiveRuns === 0) {
|
||||
handleCreateQuickRun();
|
||||
return;
|
||||
}
|
||||
|
||||
if (activeRunId) {
|
||||
goToPlaybookRun(intl, activeRunId);
|
||||
return;
|
||||
}
|
||||
goToPlaybookRuns(intl, channelId, displayName);
|
||||
}, [activeRunId, channelId, displayName, intl]);
|
||||
}, [playbooksActiveRuns, handleCreateQuickRun, activeRunId, channelId, displayName, intl]);
|
||||
|
||||
const rightButtons = useMemo(() => {
|
||||
const buttons: HeaderRightButton[] = [];
|
||||
if (playbooksActiveRuns && !isDMorGM) {
|
||||
if (isPlaybooksEnabled && !isDMorGM) {
|
||||
buttons.push({
|
||||
iconName: 'product-playbooks',
|
||||
onPress: openPlaybooksRuns,
|
||||
buttonType: 'opacity',
|
||||
count: playbooksActiveRuns,
|
||||
count: playbooksActiveRuns || '+',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -250,7 +284,7 @@ const ChannelHeader = ({
|
|||
});
|
||||
|
||||
return buttons;
|
||||
}, [playbooksActiveRuns, isDMorGM, onChannelQuickAction, openPlaybooksRuns]);
|
||||
}, [isPlaybooksEnabled, playbooksActiveRuns, isDMorGM, onChannelQuickAction, openPlaybooksRuns]);
|
||||
|
||||
let title = displayName;
|
||||
if (isOwnDirectMessage) {
|
||||
|
|
|
|||
|
|
@ -111,6 +111,7 @@ const enhanced = withObservables(['channelId'], ({channelId, database}: OwnProps
|
|||
return {
|
||||
canAddBookmarks,
|
||||
channelType,
|
||||
currentUserId,
|
||||
customStatus,
|
||||
displayName,
|
||||
hasBookmarks,
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ describe('components/categories_list', () => {
|
|||
/>,
|
||||
{database},
|
||||
);
|
||||
expect(wrapper.queryByText('Playbook runs')).not.toBeTruthy();
|
||||
expect(wrapper.queryByText('Playbook checklists')).not.toBeTruthy();
|
||||
});
|
||||
|
||||
it('should render channel list with Playbooks menu if playbooks feature is enabled', () => {
|
||||
|
|
@ -198,6 +198,6 @@ describe('components/categories_list', () => {
|
|||
/>,
|
||||
{database},
|
||||
);
|
||||
expect(wrapper.getByText('Playbook runs')).toBeTruthy();
|
||||
expect(wrapper.getByText('Playbook checklists')).toBeTruthy();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -233,7 +233,7 @@ describe('parseAndHandleDeepLink', () => {
|
|||
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.'}),
|
||||
intl.formatMessage({id: 'playbooks.only_runs_available.description', defaultMessage: 'Only Playbook Checklists 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'})}],
|
||||
);
|
||||
});
|
||||
|
|
@ -244,8 +244,8 @@ describe('parseAndHandleDeepLink', () => {
|
|||
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.'}),
|
||||
intl.formatMessage({id: 'playbooks.retrospective_not_available.title', defaultMessage: 'Playbooks Retrospective not available'}),
|
||||
intl.formatMessage({id: 'playbooks.retrospective_not_available.description', defaultMessage: 'Only Playbook Checklists 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'})}],
|
||||
);
|
||||
});
|
||||
|
|
@ -273,8 +273,8 @@ describe('parseAndHandleDeepLink', () => {
|
|||
// 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."}),
|
||||
intl.formatMessage({id: 'playbooks.fetch_error.title', defaultMessage: 'Unable to open Checklist'}),
|
||||
intl.formatMessage({id: 'playbooks.fetch_error.description', defaultMessage: "You don't have permission to view this, or it may no longer exist."}),
|
||||
[{text: intl.formatMessage({id: 'playbooks.fetch_error.OK', defaultMessage: 'Okay'})}],
|
||||
);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -118,7 +118,7 @@ export async function handleDeepLink(deepLink: DeepLinkWithData, intlShape?: Int
|
|||
// Alert that playbooks should be access from the webapp or desktop app
|
||||
Alert.alert(
|
||||
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.'}),
|
||||
intl.formatMessage({id: 'playbooks.only_runs_available.description', defaultMessage: 'Only Playbook Checklists 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'}),
|
||||
}],
|
||||
|
|
@ -127,8 +127,8 @@ export async function handleDeepLink(deepLink: DeepLinkWithData, intlShape?: Int
|
|||
}
|
||||
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.'}),
|
||||
intl.formatMessage({id: 'playbooks.retrospective_not_available.title', defaultMessage: 'Playbooks Retrospective not available'}),
|
||||
intl.formatMessage({id: 'playbooks.retrospective_not_available.description', defaultMessage: 'Only Playbook Checklists 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'}),
|
||||
}],
|
||||
|
|
@ -145,8 +145,8 @@ export async function handleDeepLink(deepLink: DeepLinkWithData, intlShape?: Int
|
|||
const {error} = await fetchPlaybookRun(existingServerUrl, deepLinkData.playbookRunId);
|
||||
if (error) {
|
||||
Alert.alert(
|
||||
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."}),
|
||||
intl.formatMessage({id: 'playbooks.fetch_error.title', defaultMessage: 'Unable to open Checklist'}),
|
||||
intl.formatMessage({id: 'playbooks.fetch_error.description', defaultMessage: "You don't have permission to view this, or it may no longer exist."}),
|
||||
[{
|
||||
text: intl.formatMessage({id: 'playbooks.fetch_error.OK', defaultMessage: 'Okay'}),
|
||||
}],
|
||||
|
|
|
|||
|
|
@ -988,7 +988,11 @@
|
|||
"playbook_run.checklist.taskDetails": "Task Details",
|
||||
"playbook_run.out_of_date_header.message": "Unable to connect to server. Content may be out of date. Last updated {lastUpdated}.",
|
||||
"playbook.last_updated": "Last update {date}",
|
||||
"playbook.participants": "Run Participants",
|
||||
"playbook.participants": "Participants",
|
||||
"playbooks.checklist_item.add.button": "New",
|
||||
"playbooks.checklist_item.add.label": "Task name",
|
||||
"playbooks.checklist_item.add.save": "Add",
|
||||
"playbooks.checklist_item.add.title": "New Task",
|
||||
"playbooks.checklist_item.assignee": "Assignee",
|
||||
"playbooks.checklist_item.check": "Check",
|
||||
"playbooks.checklist_item.checked": "Checked",
|
||||
|
|
@ -1001,6 +1005,9 @@
|
|||
"playbooks.checklist_item.skipped": "Skipped",
|
||||
"playbooks.checklist_item.task_rendered_conditionally": "Task rendered conditionally",
|
||||
"playbooks.checklist_item.task_rendered_conditionally_explanation": "This task was rendered conditionally based on",
|
||||
"playbooks.checklist.rename.button": "Save",
|
||||
"playbooks.checklist.rename.label": "Checklist name",
|
||||
"playbooks.checklist.rename.title": "Rename checklist",
|
||||
"playbooks.create_run.select_playbook.no_results": "No Results",
|
||||
"playbooks.due_date.date_at_time": "{date} at {time}",
|
||||
"playbooks.due_date.none": "None",
|
||||
|
|
@ -1010,46 +1017,49 @@
|
|||
"playbooks.edit_command.title": "Slash command",
|
||||
"playbooks.edit_due_date.clear.button": "Clear",
|
||||
"playbooks.edit_due_date.save.button": "Save",
|
||||
"playbooks.fetch_error.description": "You don't have permission to view this run, or it may no longer exist.",
|
||||
"playbooks.fetch_error.description": "You don't have permission to view this, or it may no longer exist.",
|
||||
"playbooks.fetch_error.OK": "Okay",
|
||||
"playbooks.fetch_error.title": "Unable to open Run",
|
||||
"playbooks.home_button.title": "Playbook runs",
|
||||
"playbooks.fetch_error.title": "Unable to open Checklist",
|
||||
"playbooks.home_button.title": "Playbook checklists",
|
||||
"playbooks.not_enabled_or_unsupported.description": "Playbooks are either not enabled on this server or the Playbooks version is not supported. Please contact your system administrator.",
|
||||
"playbooks.not_enabled_or_unsupported.OK": "OK",
|
||||
"playbooks.not_enabled_or_unsupported.title": "Playbooks not available",
|
||||
"playbooks.only_runs_available.description": "Only Playbook Runs are available on mobile. To access the Playbook, please use the desktop or web app.",
|
||||
"playbooks.only_runs_available.description": "Only Playbook Checklists are available on mobile. To access the Playbook, please use the desktop or web app.",
|
||||
"playbooks.only_runs_available.ok": "OK",
|
||||
"playbooks.only_runs_available.title": "Playbooks not available",
|
||||
"playbooks.participant_playbooks.title": "Playbook runs",
|
||||
"playbooks.participant_playbooks.title": "Playbook checklists",
|
||||
"playbooks.playbook_run.error.description": "Please check your network connection or try again later.",
|
||||
"playbooks.playbook_run.error.title": "Unable to fetch run details",
|
||||
"playbooks.playbook_run.finish_run_button": "Finish Run",
|
||||
"playbooks.playbook_run.error.title": "Unable to fetch details",
|
||||
"playbooks.playbook_run.finish_run_button": "Finish",
|
||||
"playbooks.playbook_run.finish_run_dialog_cancel": "Cancel",
|
||||
"playbooks.playbook_run.finish_run_dialog_description": "There are {pendingCount} {pendingCount, plural, =1 {task} other {tasks}} pending.\n\nAre you sure you want to finish the run for all participants?",
|
||||
"playbooks.playbook_run.finish_run_dialog_description": "There are {pendingCount} {pendingCount, plural, =1 {task} other {tasks}} pending.\n\nAre you sure you want to finish the checklist for all participants?",
|
||||
"playbooks.playbook_run.finish_run_dialog_finish": "Finish",
|
||||
"playbooks.playbook_run.finish_run_dialog_title": "Finish Run",
|
||||
"playbooks.playbook_run.finish_run_dialog_title": "Finish",
|
||||
"playbooks.playbook_run.finished": "Finished",
|
||||
"playbooks.playbook_run.overdue": "{num} {num, plural, =1 {task} other {tasks}} overdue",
|
||||
"playbooks.playbook_run.owner": "Owner",
|
||||
"playbooks.playbook_run.participants": "Participants",
|
||||
"playbooks.playbook_run.participants_title": "Run Participants",
|
||||
"playbooks.playbook_run.run_details": "Run details",
|
||||
"playbooks.playbook_run.participants_title": "Participants",
|
||||
"playbooks.playbook_run.rename.button": "Save",
|
||||
"playbooks.playbook_run.rename.label": "Checklist name",
|
||||
"playbooks.playbook_run.rename.title": "Rename playbook run",
|
||||
"playbooks.playbook_run.run_details": "Checklist details",
|
||||
"playbooks.playbook_run.status_update": "Post update",
|
||||
"playbooks.playbook_run.status_update_due": "Update due\n{time}",
|
||||
"playbooks.playbook_run.status_update_finished": "Run finished\n{time}",
|
||||
"playbooks.playbook_run.status_update_finished": "Finished at\n{time}",
|
||||
"playbooks.playbook_run.status_update_overdue": "Update overdue\n{time}",
|
||||
"playbooks.playbook_run.tasks": "Tasks",
|
||||
"playbooks.playbook_run.title": "Playbook run",
|
||||
"playbooks.playbooks_runs.title": "Playbook runs",
|
||||
"playbooks.playbook_run.title": "Playbook checklist",
|
||||
"playbooks.playbooks_runs.title": "Playbook checklists",
|
||||
"playbooks.post_update.confirm.cancel": "Cancel",
|
||||
"playbooks.post_update.confirm.confirm": "Finish run",
|
||||
"playbooks.post_update.confirm.message": "Are you sure you want to finish the run {runName} for all participants?",
|
||||
"playbooks.post_update.confirm.message.with_tasks": "There {outstanding, plural, =1 {is # outstanding task} other {are # outstanding tasks}}. Are you sure you want to finish the run {runName} for all participants?",
|
||||
"playbooks.post_update.confirm.title": "Confirm finish run",
|
||||
"playbooks.post_update.intro": "This update for the run <Bold>{runName}</Bold> will be broadcasted to {hasChannels, select, true {<Bold>{broadcastChannelCount, plural, =1 {one channel} other {{broadcastChannelCount, number} channels}}</Bold>} other {}}{hasFollowersAndChannels, select, true { and } other {}}{hasFollowers, select, true {<Bold>{followersChannelCount, plural, =1 {one direct message} other {{followersChannelCount, number} direct messages}}</Bold>} other {}}.",
|
||||
"playbooks.post_update.confirm.confirm": "Finish",
|
||||
"playbooks.post_update.confirm.message": "Are you sure you want to finish the checklist {runName} for all participants?",
|
||||
"playbooks.post_update.confirm.message.with_tasks": "There {outstanding, plural, =1 {is # outstanding task} other {are # outstanding tasks}}. Are you sure you want to finish the checklist {runName} for all participants?",
|
||||
"playbooks.post_update.confirm.title": "Confirm finish checklist",
|
||||
"playbooks.post_update.intro": "This update for the checklist <Bold>{runName}</Bold> will be broadcasted to {hasChannels, select, true {<Bold>{broadcastChannelCount, plural, =1 {one channel} other {{broadcastChannelCount, number} channels}}</Bold>} other {}}{hasFollowersAndChannels, select, true { and } other {}}{hasFollowers, select, true {<Bold>{followersChannelCount, plural, =1 {one direct message} other {{followersChannelCount, number} direct messages}}</Bold>} other {}}.",
|
||||
"playbooks.post_update.intro.no_followers_or_broadcast_channels": "This update will be saved to the overview page.",
|
||||
"playbooks.post_update.label": "Update message",
|
||||
"playbooks.post_update.label.also_mark_run_as_finished": "Also mark the run as finished",
|
||||
"playbooks.post_update.label.also_mark_run_as_finished": "Also mark the checklist as finished",
|
||||
"playbooks.post_update.label.next_update": "Timer for next update",
|
||||
"playbooks.post_update.option.1_day": "1 day",
|
||||
"playbooks.post_update.option.1_hour": "1 hour",
|
||||
|
|
@ -1060,42 +1070,42 @@
|
|||
"playbooks.post_update.placeholder": "Enter your update message",
|
||||
"playbooks.post_update.post.button": "Post",
|
||||
"playbooks.post_update.title": "Post update",
|
||||
"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.description": "Only Playbook Checklists 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.retrospective_not_available.title": "Playbooks Retrospective not available",
|
||||
"playbooks.row.last_used": "Last used {time}",
|
||||
"playbooks.row.never_used": "Never used",
|
||||
"playbooks.row.no_runs": "No runs in progress",
|
||||
"playbooks.row.runs": "{count} {count, plural, one {run} other {runs}} in progress",
|
||||
"playbooks.run_list.cached_warning_message": "Showing cached data only. Some playbook runs or updates may be missing from this list.",
|
||||
"playbooks.row.no_runs": "No checklists in progress",
|
||||
"playbooks.row.runs": "{count} {count, plural, one {checklist} other {checklists}} in progress",
|
||||
"playbooks.run_list.cached_warning_message": "Showing cached data only. Some updates may be missing from this list.",
|
||||
"playbooks.run_list.cached_warning_title": "Cannot reach the server",
|
||||
"playbooks.run_list.tab_finished": "Finished",
|
||||
"playbooks.run_list.tab_in_progress": "In Progress",
|
||||
"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.",
|
||||
"playbooks.runs.in_progress.title": "No in progress runs",
|
||||
"playbooks.runs.finished.description": "When a checklist in this channel finishes, you’ll see it here.",
|
||||
"playbooks.runs.finished.title": "Nothing finished",
|
||||
"playbooks.runs.in_progress.description": "When a checklist starts in this channel, you’ll see it here.",
|
||||
"playbooks.runs.in_progress.title": "Nothing in progress",
|
||||
"playbooks.runs.show_more": "Show More",
|
||||
"playbooks.runs.start_a_new_run": "Start a new run",
|
||||
"playbooks.runs.start_a_new_run": "New",
|
||||
"playbooks.select_date.title": "Due date",
|
||||
"playbooks.select_playbook.in_this_channel": "In This Channel",
|
||||
"playbooks.select_playbook.other_playbooks": "Other Playbooks",
|
||||
"playbooks.select_playbook.subtitle": "Select a playbook",
|
||||
"playbooks.select_playbook.title": "Start a run",
|
||||
"playbooks.select_playbook.title": "New",
|
||||
"playbooks.select_playbook.your_playbooks": "Your Playbooks",
|
||||
"playbooks.select_user.no_assignee": "No Assignee",
|
||||
"playbooks.select_user.not_participants": "NOT PARTICIPATING",
|
||||
"playbooks.select_user.participants": "RUN PARTICIPANTS",
|
||||
"playbooks.start_a_run.title": "Start a run",
|
||||
"playbooks.select_user.participants": "PARTICIPANTS",
|
||||
"playbooks.start_a_run.title": "New",
|
||||
"playbooks.start_run.channel_label": "Channel",
|
||||
"playbooks.start_run.create_new_channel": "Create a new channel",
|
||||
"playbooks.start_run.create_new_channel.private_channel": "Private channel",
|
||||
"playbooks.start_run.create_new_channel.public_channel": "Public channel",
|
||||
"playbooks.start_run.link_existing_channel": "Link to an existing channel",
|
||||
"playbooks.start_run.run_description_label": "Run description",
|
||||
"playbooks.start_run.run_name_error": "Please add a name for this run",
|
||||
"playbooks.start_run.run_name_label": "Run name",
|
||||
"playbooks.start_run.run_name_placeholder": "Add a name for your run",
|
||||
"playbooks.start_run.run_description_label": "Description",
|
||||
"playbooks.start_run.run_name_error": "Please add a name",
|
||||
"playbooks.start_run.run_name_label": "Name",
|
||||
"playbooks.start_run.run_name_placeholder": "Add a name",
|
||||
"playbooks.status_update_post.invalid_status_update_props": "Playbooks status update post with invalid properties",
|
||||
"playbooks.status_update_post.num_tasks": "**{numTasksChecked, number}** of **{numTasks, number}** {numTasks, plural, =1 {task} other {tasks}} checked",
|
||||
"playbooks.status_update_post.participants": "{numParticipants, number} {numParticipants, plural, =1 {participant} other {participants}}",
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import {Ringtone} from '@constants/calls';
|
|||
import {SYSTEM_IDENTIFIERS} from '@constants/database';
|
||||
import {PUSH_PROXY_STATUS_VERIFIED} from '@constants/push_proxy';
|
||||
import DatabaseManager from '@database/manager';
|
||||
import {PLAYBOOK_RUN_TYPES} from '@playbooks/constants/playbook_run';
|
||||
import {prepareCommonSystemValues} from '@queries/servers/system';
|
||||
|
||||
import type {APIClientInterface} from '@mattermost/react-native-network-client';
|
||||
|
|
@ -1175,6 +1176,7 @@ class TestHelperSingleton {
|
|||
update_at: Date.now() + i,
|
||||
items_order: checklists.map((checklist) => checklist.id),
|
||||
status_update_broadcast_channels_enabled: false,
|
||||
type: PLAYBOOK_RUN_TYPES.PlaybookType,
|
||||
});
|
||||
}
|
||||
return playbookRuns;
|
||||
|
|
@ -1250,6 +1252,7 @@ class TestHelperSingleton {
|
|||
return {
|
||||
...this.fakeModel(),
|
||||
playbookId: this.generateId(),
|
||||
type: 'playbook',
|
||||
postId: null,
|
||||
ownerUserId: this.basicUser?.id || '',
|
||||
teamId: this.basicTeam?.id || '',
|
||||
|
|
|
|||
Loading…
Reference in a new issue