Skip to content

Commit da14059

Browse files
committed
[eas-cli] Add integrations:posthog:dashboard
1 parent 0a4e058 commit da14059

2 files changed

Lines changed: 153 additions & 0 deletions

File tree

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import openBrowserAsync from 'better-opn';
2+
3+
import { getMockOclifConfig } from '../../../../__tests__/commands/utils';
4+
import { ExpoGraphqlClient } from '../../../../commandUtils/context/contextUtils/createGraphqlClient';
5+
import { testProjectId } from '../../../../credentials/__tests__/fixtures-constants';
6+
import { PostHogRegion } from '../../../../graphql/generated';
7+
import { PostHogQuery } from '../../../../graphql/queries/PostHogQuery';
8+
import {
9+
PostHogOrganizationConnectionData,
10+
PostHogProjectData,
11+
} from '../../../../graphql/types/PostHogConnection';
12+
import Log from '../../../../log';
13+
import { ora } from '../../../../ora';
14+
import IntegrationsPostHogDashboard from '../dashboard';
15+
16+
jest.mock('better-opn');
17+
jest.mock('../../../../graphql/queries/PostHogQuery');
18+
jest.mock('../../../../log');
19+
jest.mock('../../../../ora', () => ({
20+
ora: jest.fn(() => ({
21+
start: jest.fn().mockReturnThis(),
22+
succeed: jest.fn().mockReturnThis(),
23+
fail: jest.fn().mockReturnThis(),
24+
})),
25+
}));
26+
27+
describe(IntegrationsPostHogDashboard, () => {
28+
const graphqlClient = {} as ExpoGraphqlClient;
29+
const mockConfig = getMockOclifConfig();
30+
31+
const mockConnection: PostHogOrganizationConnectionData = {
32+
id: 'connection-1',
33+
posthogOrganizationIdentifier: 'org-123',
34+
posthogOrganizationName: 'Test Org',
35+
posthogRegion: PostHogRegion.Us,
36+
createdAt: '2024-01-01T00:00:00.000Z',
37+
updatedAt: '2024-01-01T00:00:00.000Z',
38+
};
39+
40+
const mockProject: PostHogProjectData = {
41+
id: 'project-1',
42+
posthogProjectIdentifier: 'res-123',
43+
posthogProjectName: 'Test Project',
44+
posthogProjectToken: 'phc_public_key',
45+
posthogHost: 'https://us.posthog.com',
46+
createdAt: '2024-01-01T00:00:00.000Z',
47+
updatedAt: '2024-01-01T00:00:00.000Z',
48+
posthogOrganizationConnection: mockConnection,
49+
};
50+
51+
function createCommand(): IntegrationsPostHogDashboard {
52+
const command = new IntegrationsPostHogDashboard([], mockConfig);
53+
jest.spyOn(command as any, 'getContextAsync').mockReturnValue({
54+
privateProjectConfig: {
55+
projectId: testProjectId,
56+
exp: { slug: 'testapp' },
57+
},
58+
loggedIn: { graphqlClient },
59+
} as never);
60+
return command;
61+
}
62+
63+
beforeEach(() => {
64+
jest.resetAllMocks();
65+
jest.spyOn(Log, 'warn').mockImplementation(() => {});
66+
jest.mocked(PostHogQuery.getPostHogProjectByAppIdAsync).mockResolvedValue(mockProject);
67+
jest.mocked(openBrowserAsync).mockResolvedValue({} as never);
68+
jest.mocked(ora).mockReturnValue({
69+
start: jest.fn().mockReturnThis(),
70+
succeed: jest.fn().mockReturnThis(),
71+
fail: jest.fn().mockReturnThis(),
72+
} as any);
73+
});
74+
75+
it('opens the linked PostHog project dashboard', async () => {
76+
await createCommand().runAsync();
77+
78+
expect(openBrowserAsync).toHaveBeenCalledWith('https://us.posthog.com/project/res-123');
79+
});
80+
81+
it('logs an empty state when no project link exists', async () => {
82+
jest.mocked(PostHogQuery.getPostHogProjectByAppIdAsync).mockResolvedValue(null);
83+
84+
await createCommand().runAsync();
85+
86+
expect(openBrowserAsync).not.toHaveBeenCalled();
87+
expect(Log.warn).toHaveBeenCalledWith(
88+
expect.stringContaining('No PostHog project is linked to Expo app')
89+
);
90+
});
91+
92+
it('fails the spinner when the browser cannot be opened', async () => {
93+
jest.mocked(openBrowserAsync).mockResolvedValue(false);
94+
const spinner = {
95+
start: jest.fn().mockReturnThis(),
96+
succeed: jest.fn().mockReturnThis(),
97+
fail: jest.fn().mockReturnThis(),
98+
};
99+
jest.mocked(ora).mockReturnValue(spinner as any);
100+
101+
await createCommand().runAsync();
102+
103+
expect(spinner.fail).toHaveBeenCalledWith(
104+
expect.stringContaining('Unable to open a web browser')
105+
);
106+
});
107+
});
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import openBrowserAsync from 'better-opn';
2+
3+
import EasCommand from '../../../commandUtils/EasCommand';
4+
import { getPostHogProjectDashboardUrl, logNoPostHogProject } from '../../../commandUtils/posthog';
5+
import { PostHogQuery } from '../../../graphql/queries/PostHogQuery';
6+
import { ora } from '../../../ora';
7+
8+
export default class IntegrationsPostHogDashboard extends EasCommand {
9+
static override description = 'open the PostHog dashboard for the linked PostHog project';
10+
11+
static override contextDefinition = {
12+
...this.ContextOptions.ProjectConfig,
13+
};
14+
15+
async runAsync(): Promise<void> {
16+
const {
17+
privateProjectConfig: { projectId, exp },
18+
loggedIn: { graphqlClient },
19+
} = await this.getContextAsync(IntegrationsPostHogDashboard, {
20+
nonInteractive: false,
21+
withServerSideEnvironment: null,
22+
});
23+
24+
const posthogProject = await PostHogQuery.getPostHogProjectByAppIdAsync(graphqlClient, projectId);
25+
26+
if (!posthogProject) {
27+
logNoPostHogProject(exp.slug);
28+
return;
29+
}
30+
31+
const dashboardUrl = getPostHogProjectDashboardUrl(posthogProject);
32+
const failedMessage = `Unable to open a web browser. PostHog dashboard is available at: ${dashboardUrl}`;
33+
const spinner = ora(`Opening ${dashboardUrl}`).start();
34+
try {
35+
const opened = await openBrowserAsync(dashboardUrl);
36+
if (opened) {
37+
spinner.succeed(`Opened ${dashboardUrl}`);
38+
} else {
39+
spinner.fail(failedMessage);
40+
}
41+
} catch (error) {
42+
spinner.fail(failedMessage);
43+
throw error;
44+
}
45+
}
46+
}

0 commit comments

Comments
 (0)