[MM-67361] Hide playbook (#9478)
* feat: hide Playbooks button when no running playbook runs in team The PlaybooksButton on the home screen channel list now only appears when there is at least one in-progress playbook run in the current team, in addition to requiring the playbooks feature to be enabled. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * improve team query * test: add unit tests for observeHasRunningPlaybookRunsInTeam query Add tests to verify the new query correctly: - Returns true when running playbook runs exist (end_at = 0) - Returns false when all runs are finished (end_at > 0) - Returns false when no runs exist - Filters by team_id correctly Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
fbaf13c7aa
commit
d014856a3a
5 changed files with 139 additions and 13 deletions
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
/* eslint-disable max-lines */
|
||||
|
||||
import {of as of$} from 'rxjs';
|
||||
import {of as of$, firstValueFrom} from 'rxjs';
|
||||
|
||||
import {MM_TABLES} from '@constants/database';
|
||||
import DatabaseManager from '@database/manager';
|
||||
|
|
@ -18,6 +18,7 @@ import {
|
|||
getLastPlaybookRunsFetchAt,
|
||||
queryParticipantsFromAPIRun,
|
||||
observeParticipantsIdsFromPlaybookModel,
|
||||
observeHasRunningPlaybookRunsInTeam,
|
||||
} from './run';
|
||||
|
||||
import type ServerDataOperator from '@database/operator/server_data_operator';
|
||||
|
|
@ -763,4 +764,111 @@ describe('Playbook Run Queries', () => {
|
|||
expect(runIds).not.toContain(mockRuns[1].id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('observeHasRunningPlaybookRunsInTeam', () => {
|
||||
it('should return true when there are running playbook runs in the team', async () => {
|
||||
const teamId = 'team-id-1';
|
||||
const mockRuns = TestHelper.createPlaybookRuns(1, 0, 0).map((run) => ({
|
||||
...run,
|
||||
team_id: teamId,
|
||||
end_at: 0,
|
||||
}));
|
||||
|
||||
await operator.handlePlaybookRun({
|
||||
runs: mockRuns,
|
||||
prepareRecordsOnly: false,
|
||||
removeAssociatedRecords: false,
|
||||
});
|
||||
|
||||
const result = await firstValueFrom(observeHasRunningPlaybookRunsInTeam(operator.database, teamId));
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
|
||||
it('should return false when all runs in the team are finished', async () => {
|
||||
const teamId = 'team-id-1';
|
||||
const mockRuns = TestHelper.createPlaybookRuns(2, 0, 0).map((run) => ({
|
||||
...run,
|
||||
team_id: teamId,
|
||||
end_at: 1620000000000,
|
||||
}));
|
||||
|
||||
await operator.handlePlaybookRun({
|
||||
runs: mockRuns,
|
||||
prepareRecordsOnly: false,
|
||||
removeAssociatedRecords: false,
|
||||
});
|
||||
|
||||
const result = await firstValueFrom(observeHasRunningPlaybookRunsInTeam(operator.database, teamId));
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return false when no runs exist in the team', async () => {
|
||||
const teamId = 'team-id-1';
|
||||
|
||||
const result = await firstValueFrom(observeHasRunningPlaybookRunsInTeam(operator.database, teamId));
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should only count runs from the specified team', async () => {
|
||||
const teamId = 'team-id-1';
|
||||
const otherTeamId = 'team-id-2';
|
||||
|
||||
const mockRuns = [
|
||||
{
|
||||
...TestHelper.createPlaybookRuns(1, 0, 0)[0],
|
||||
id: 'run-1',
|
||||
team_id: teamId,
|
||||
end_at: 1620000000000, // Finished in target team
|
||||
},
|
||||
{
|
||||
...TestHelper.createPlaybookRuns(1, 0, 0)[0],
|
||||
id: 'run-2',
|
||||
team_id: otherTeamId,
|
||||
end_at: 0, // Running in other team
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handlePlaybookRun({
|
||||
runs: mockRuns,
|
||||
prepareRecordsOnly: false,
|
||||
removeAssociatedRecords: false,
|
||||
});
|
||||
|
||||
const result = await firstValueFrom(observeHasRunningPlaybookRunsInTeam(operator.database, teamId));
|
||||
|
||||
expect(result).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true when at least one run is in progress among mixed runs', async () => {
|
||||
const teamId = 'team-id-1';
|
||||
|
||||
const mockRuns = [
|
||||
{
|
||||
...TestHelper.createPlaybookRuns(1, 0, 0)[0],
|
||||
id: 'run-1',
|
||||
team_id: teamId,
|
||||
end_at: 1620000000000, // Finished
|
||||
},
|
||||
{
|
||||
...TestHelper.createPlaybookRuns(1, 0, 0)[0],
|
||||
id: 'run-2',
|
||||
team_id: teamId,
|
||||
end_at: 0, // Running
|
||||
},
|
||||
];
|
||||
|
||||
await operator.handlePlaybookRun({
|
||||
runs: mockRuns,
|
||||
prepareRecordsOnly: false,
|
||||
removeAssociatedRecords: false,
|
||||
});
|
||||
|
||||
const result = await firstValueFrom(observeHasRunningPlaybookRunsInTeam(operator.database, teamId));
|
||||
|
||||
expect(result).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -97,3 +97,14 @@ export const queryPlaybookRunsByParticipantAndTeam = (database: Database, partic
|
|||
Q.sortBy('create_at', 'desc'),
|
||||
);
|
||||
};
|
||||
|
||||
export const observeHasRunningPlaybookRunsInTeam = (database: Database, teamId: string) => {
|
||||
return database.get<PlaybookRunModel>(PLAYBOOK_RUN).query(
|
||||
Q.and(
|
||||
Q.where('team_id', teamId),
|
||||
Q.where('end_at', Q.eq(0)),
|
||||
),
|
||||
).observeCount().pipe(
|
||||
switchMap((count) => of$(count > 0)),
|
||||
);
|
||||
};
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ type ChannelListProps = {
|
|||
scheduledPostHasError: boolean;
|
||||
lastChannelId?: string;
|
||||
scheduledPostsEnabled?: boolean;
|
||||
playbooksEnabled?: boolean;
|
||||
showPlaybooksButton?: boolean;
|
||||
};
|
||||
|
||||
const getTabletWidth = (moreThanOneTeam: boolean) => {
|
||||
|
|
@ -57,7 +57,7 @@ const CategoriesList = ({
|
|||
scheduledPostHasError,
|
||||
lastChannelId,
|
||||
scheduledPostsEnabled,
|
||||
playbooksEnabled,
|
||||
showPlaybooksButton,
|
||||
}: ChannelListProps) => {
|
||||
const theme = useTheme();
|
||||
const styles = getStyleSheet(theme);
|
||||
|
|
@ -128,14 +128,14 @@ const CategoriesList = ({
|
|||
}, [activeScreen, draftsCount, isTablet, scheduledPostCount, scheduledPostHasError, scheduledPostsEnabled]);
|
||||
|
||||
const playbooksButtonComponent = useMemo(() => {
|
||||
if (!playbooksEnabled) {
|
||||
if (!showPlaybooksButton) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<PlaybooksButton/>
|
||||
);
|
||||
}, [playbooksEnabled]);
|
||||
}, [showPlaybooksButton]);
|
||||
|
||||
const content = useMemo(() => {
|
||||
if (!hasChannels) {
|
||||
|
|
|
|||
|
|
@ -171,7 +171,7 @@ describe('components/categories_list', () => {
|
|||
expect(wrapper.queryByText('Drafts')).not.toBeTruthy();
|
||||
});
|
||||
|
||||
it('should not render channel list with Playbooks menu if playbooks feature is disabled', () => {
|
||||
it('should not render Playbooks menu when showPlaybooksButton is false', () => {
|
||||
const wrapper = renderWithEverything(
|
||||
<CategoriesList
|
||||
moreThanOneTeam={false}
|
||||
|
|
@ -179,14 +179,14 @@ describe('components/categories_list', () => {
|
|||
draftsCount={0}
|
||||
scheduledPostCount={0}
|
||||
scheduledPostHasError={false}
|
||||
playbooksEnabled={false}
|
||||
showPlaybooksButton={false}
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
expect(wrapper.queryByText('Playbook checklists')).not.toBeTruthy();
|
||||
expect(wrapper.queryByText('Playbook checklists')).toBeNull();
|
||||
});
|
||||
|
||||
it('should render channel list with Playbooks menu if playbooks feature is enabled', () => {
|
||||
it('should render Playbooks menu when showPlaybooksButton is true', () => {
|
||||
const wrapper = renderWithEverything(
|
||||
<CategoriesList
|
||||
moreThanOneTeam={false}
|
||||
|
|
@ -194,7 +194,7 @@ describe('components/categories_list', () => {
|
|||
draftsCount={0}
|
||||
scheduledPostCount={0}
|
||||
scheduledPostHasError={false}
|
||||
playbooksEnabled={true}
|
||||
showPlaybooksButton={true}
|
||||
/>,
|
||||
{database},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -2,9 +2,10 @@
|
|||
// See LICENSE.txt for license information.
|
||||
|
||||
import {withDatabase, withObservables} from '@nozbe/watermelondb/react';
|
||||
import {of} from 'rxjs';
|
||||
import {combineLatest, of} from 'rxjs';
|
||||
import {switchMap} from 'rxjs/operators';
|
||||
|
||||
import {observeHasRunningPlaybookRunsInTeam} from '@playbooks/database/queries/run';
|
||||
import {observeIsPlaybooksEnabled} from '@playbooks/database/queries/version';
|
||||
import {observeDraftCount} from '@queries/servers/drafts';
|
||||
import {observeScheduledPostEnabled, observeScheduledPostsForTeam} from '@queries/servers/scheduled_post';
|
||||
|
|
@ -28,7 +29,13 @@ const enchanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
|||
switchMap((scheduledPosts) => of(hasScheduledPostError(scheduledPosts))),
|
||||
);
|
||||
const scheduledPostsEnabled = observeScheduledPostEnabled(database);
|
||||
const playbooksEnabled = observeIsPlaybooksEnabled(database);
|
||||
const showPlaybooksButton = currentTeamId.pipe(
|
||||
switchMap((teamId) => combineLatest([
|
||||
observeIsPlaybooksEnabled(database),
|
||||
observeHasRunningPlaybookRunsInTeam(database, teamId),
|
||||
])),
|
||||
switchMap(([enabled, hasRunningRuns]) => of(enabled && hasRunningRuns)),
|
||||
);
|
||||
|
||||
return {
|
||||
lastChannelId,
|
||||
|
|
@ -36,7 +43,7 @@ const enchanced = withObservables([], ({database}: WithDatabaseArgs) => {
|
|||
scheduledPostCount,
|
||||
scheduledPostHasError,
|
||||
scheduledPostsEnabled,
|
||||
playbooksEnabled,
|
||||
showPlaybooksButton,
|
||||
};
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue