@mintlify/cli 4.0.1228 → 4.0.1230
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/__test__/telemetry.test.ts +0 -27
- package/bin/cli.js +0 -2
- package/bin/middlewares/telemetryMiddleware.js +0 -16
- package/bin/tsconfig.build.tsbuildinfo +1 -1
- package/bin/welcome.js +1 -4
- package/package.json +8 -8
- package/src/cli.tsx +0 -2
- package/src/middlewares/telemetryMiddleware.ts +0 -17
- package/src/welcome.ts +1 -4
- package/__test__/analytics/client.test.ts +0 -166
- package/__test__/analytics/format.test.ts +0 -137
- package/bin/analytics/client.js +0 -55
- package/bin/analytics/format.js +0 -13
- package/bin/analytics/index.js +0 -509
- package/bin/analytics/output.js +0 -75
- package/bin/analytics/types.js +0 -1
- package/src/analytics/client.ts +0 -146
- package/src/analytics/format.ts +0 -13
- package/src/analytics/index.tsx +0 -605
- package/src/analytics/output.ts +0 -99
- package/src/analytics/types.ts +0 -132
package/bin/welcome.js
CHANGED
|
@@ -23,10 +23,7 @@ const GROUPS = [
|
|
|
23
23
|
},
|
|
24
24
|
{
|
|
25
25
|
title: 'Insights',
|
|
26
|
-
commands: [
|
|
27
|
-
{ name: 'analytics', description: 'View analytics for your site' },
|
|
28
|
-
{ name: 'workflow', description: 'Create and manage workflows' },
|
|
29
|
-
],
|
|
26
|
+
commands: [{ name: 'workflow', description: 'Create and manage workflows' }],
|
|
30
27
|
},
|
|
31
28
|
{
|
|
32
29
|
title: 'Account',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mintlify/cli",
|
|
3
|
-
"version": "4.0.
|
|
3
|
+
"version": "4.0.1230",
|
|
4
4
|
"description": "The Mintlify CLI",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=18.0.0"
|
|
@@ -45,12 +45,12 @@
|
|
|
45
45
|
},
|
|
46
46
|
"dependencies": {
|
|
47
47
|
"@inquirer/prompts": "7.9.0",
|
|
48
|
-
"@mintlify/common": "1.0.
|
|
49
|
-
"@mintlify/link-rot": "3.0.
|
|
50
|
-
"@mintlify/models": "0.0.
|
|
51
|
-
"@mintlify/prebuild": "1.0.
|
|
52
|
-
"@mintlify/previewing": "4.0.
|
|
53
|
-
"@mintlify/validation": "0.1.
|
|
48
|
+
"@mintlify/common": "1.0.949",
|
|
49
|
+
"@mintlify/link-rot": "3.0.1134",
|
|
50
|
+
"@mintlify/models": "0.0.324",
|
|
51
|
+
"@mintlify/prebuild": "1.0.1095",
|
|
52
|
+
"@mintlify/previewing": "4.0.1159",
|
|
53
|
+
"@mintlify/validation": "0.1.742",
|
|
54
54
|
"adm-zip": "0.5.16",
|
|
55
55
|
"chalk": "5.2.0",
|
|
56
56
|
"color": "4.2.3",
|
|
@@ -95,5 +95,5 @@
|
|
|
95
95
|
"vitest": "2.1.9",
|
|
96
96
|
"vitest-mock-process": "1.0.4"
|
|
97
97
|
},
|
|
98
|
-
"gitHead": "
|
|
98
|
+
"gitHead": "17167bf8f0a141fee9164c38384bf1724e31390c"
|
|
99
99
|
}
|
package/src/cli.tsx
CHANGED
|
@@ -19,7 +19,6 @@ import yargs, { type Argv } from 'yargs';
|
|
|
19
19
|
import { hideBin } from 'yargs/helpers';
|
|
20
20
|
|
|
21
21
|
import { accessibilityCheck } from './accessibilityCheck.js';
|
|
22
|
-
import { analyticsBuilder } from './analytics/index.js';
|
|
23
22
|
import { comingSoon } from './comingSoon.js';
|
|
24
23
|
import { setTelemetryEnabled } from './config.js';
|
|
25
24
|
import { getConfigValue, setConfigValue, clearConfigValue } from './config.js';
|
|
@@ -614,7 +613,6 @@ export const cli = ({ packageName = 'mint' }: { packageName?: string }) => {
|
|
|
614
613
|
}
|
|
615
614
|
}
|
|
616
615
|
)
|
|
617
|
-
.command('analytics', 'View analytics for your documentation', analyticsBuilder)
|
|
618
616
|
.command('workflow', 'Create and manage workflows', workflowsBuilder)
|
|
619
617
|
.command(
|
|
620
618
|
'score [url]',
|
|
@@ -3,17 +3,13 @@ import { getVersions } from '../helpers.js';
|
|
|
3
3
|
import { trackCommand } from '../telemetry/track.js';
|
|
4
4
|
|
|
5
5
|
const SCRAPE_SUBCOMMANDS = new Set(['page', 'site', 'openapi']);
|
|
6
|
-
const ANALYTICS_SUBCOMMANDS = new Set(['stats', 'search', 'feedback', 'conversation']);
|
|
7
6
|
const CONFIG_SUBCOMMANDS = new Set(['set', 'get', 'clear']);
|
|
8
|
-
const CONVERSATION_SUBCOMMANDS = new Set(['list', 'view', 'buckets']);
|
|
9
|
-
const BUCKETS_SUBCOMMANDS = new Set(['list', 'view']);
|
|
10
7
|
|
|
11
8
|
export function getSanitizedCommandForTelemetry(_: (string | number)[]): string {
|
|
12
9
|
const parts = _.filter((p): p is string => typeof p === 'string');
|
|
13
10
|
if (parts.length === 0) return '';
|
|
14
11
|
const first = parts[0]!;
|
|
15
12
|
const second = parts[1];
|
|
16
|
-
const third = parts[2];
|
|
17
13
|
|
|
18
14
|
if (first === 'scrape' && second !== undefined && SCRAPE_SUBCOMMANDS.has(second)) {
|
|
19
15
|
return `scrape ${second}`;
|
|
@@ -23,19 +19,6 @@ export function getSanitizedCommandForTelemetry(_: (string | number)[]): string
|
|
|
23
19
|
return `config ${second}`;
|
|
24
20
|
}
|
|
25
21
|
|
|
26
|
-
if (first === 'analytics' && second !== undefined && ANALYTICS_SUBCOMMANDS.has(second)) {
|
|
27
|
-
if (second === 'conversation' && third !== undefined) {
|
|
28
|
-
if (CONVERSATION_SUBCOMMANDS.has(third)) {
|
|
29
|
-
const fourth = parts[3];
|
|
30
|
-
if (third === 'buckets' && fourth !== undefined && BUCKETS_SUBCOMMANDS.has(fourth)) {
|
|
31
|
-
return `analytics conversation buckets ${fourth}`;
|
|
32
|
-
}
|
|
33
|
-
return `analytics conversation ${third}`;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return `analytics ${second}`;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
22
|
return first;
|
|
40
23
|
}
|
|
41
24
|
|
package/src/welcome.ts
CHANGED
|
@@ -36,10 +36,7 @@ const GROUPS: CommandGroup[] = [
|
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
title: 'Insights',
|
|
39
|
-
commands: [
|
|
40
|
-
{ name: 'analytics', description: 'View analytics for your site' },
|
|
41
|
-
{ name: 'workflow', description: 'Create and manage workflows' },
|
|
42
|
-
],
|
|
39
|
+
commands: [{ name: 'workflow', description: 'Create and manage workflows' }],
|
|
43
40
|
},
|
|
44
41
|
{
|
|
45
42
|
title: 'Account',
|
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getFeedback,
|
|
3
|
-
getFeedbackByPage,
|
|
4
|
-
getBuckets,
|
|
5
|
-
getBucketThreads,
|
|
6
|
-
getConversations,
|
|
7
|
-
getKpi,
|
|
8
|
-
getSearches,
|
|
9
|
-
getViews,
|
|
10
|
-
getVisitors,
|
|
11
|
-
} from '../../src/analytics/client.js';
|
|
12
|
-
|
|
13
|
-
vi.mock('../../src/keyring.js', () => ({
|
|
14
|
-
getAccessToken: vi.fn().mockResolvedValue(null),
|
|
15
|
-
getRefreshToken: vi.fn().mockResolvedValue(null),
|
|
16
|
-
storeCredentials: vi.fn().mockResolvedValue(undefined),
|
|
17
|
-
}));
|
|
18
|
-
|
|
19
|
-
const mockFetch = vi.fn();
|
|
20
|
-
global.fetch = mockFetch;
|
|
21
|
-
|
|
22
|
-
beforeEach(() => {
|
|
23
|
-
vi.stubEnv('MINTLIFY_SESSION_TOKEN', 'test-token');
|
|
24
|
-
vi.stubEnv('MINTLIFY_API_URL', 'http://test-server:5000');
|
|
25
|
-
mockFetch.mockReset();
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
afterEach(() => {
|
|
29
|
-
vi.unstubAllEnvs();
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
function mockOk(data: unknown) {
|
|
33
|
-
mockFetch.mockResolvedValueOnce({
|
|
34
|
-
ok: true,
|
|
35
|
-
status: 200,
|
|
36
|
-
json: () => Promise.resolve(data),
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function mockError(status: number, body: string) {
|
|
41
|
-
mockFetch.mockResolvedValueOnce({
|
|
42
|
-
ok: false,
|
|
43
|
-
status,
|
|
44
|
-
statusText: 'Bad Request',
|
|
45
|
-
text: () => Promise.resolve(body),
|
|
46
|
-
});
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function calledUrl(): string {
|
|
50
|
-
const arg = mockFetch.mock.calls[0]![0];
|
|
51
|
-
return typeof arg === 'string' ? arg : String(arg);
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
function calledUrlObj(): URL {
|
|
55
|
-
return new URL(calledUrl());
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
describe('client auth', () => {
|
|
59
|
-
it('throws when no session token is set', async () => {
|
|
60
|
-
vi.stubEnv('MINTLIFY_SESSION_TOKEN', '');
|
|
61
|
-
await expect(getFeedback({ dateFrom: '2024-01-01', dateTo: '2024-01-31' })).rejects.toThrow(
|
|
62
|
-
'Not authenticated'
|
|
63
|
-
);
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it('sends session cookie header', async () => {
|
|
67
|
-
mockOk({ feedback: [], nextCursor: null, hasMore: false });
|
|
68
|
-
await getFeedback({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'test');
|
|
69
|
-
expect(mockFetch).toHaveBeenCalledWith(
|
|
70
|
-
expect.any(String),
|
|
71
|
-
expect.objectContaining({
|
|
72
|
-
headers: expect.objectContaining({
|
|
73
|
-
Authorization: 'Bearer test-token',
|
|
74
|
-
}),
|
|
75
|
-
})
|
|
76
|
-
);
|
|
77
|
-
});
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
describe('client request handling', () => {
|
|
81
|
-
it('throws on API error', async () => {
|
|
82
|
-
mockError(400, 'Invalid params');
|
|
83
|
-
await expect(getFeedback({ dateFrom: '2024-01-01', dateTo: '2024-01-31' })).rejects.toThrow(
|
|
84
|
-
'API error (400): Invalid params'
|
|
85
|
-
);
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
it('passes subdomain as query param when provided', async () => {
|
|
89
|
-
mockOk({ feedback: [], nextCursor: null, hasMore: false });
|
|
90
|
-
await getFeedback({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'my-docs');
|
|
91
|
-
expect(calledUrlObj().searchParams.get('subdomain')).toBe('my-docs');
|
|
92
|
-
expect(calledUrlObj().pathname).toBe('/api/cli/analytics/feedback');
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('omits subdomain param when not provided', async () => {
|
|
96
|
-
mockOk({ feedback: [], nextCursor: null, hasMore: false });
|
|
97
|
-
await getFeedback({ dateFrom: '2024-01-01', dateTo: '2024-01-31' });
|
|
98
|
-
expect(calledUrlObj().searchParams.has('subdomain')).toBe(false);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('sets query params and omits undefined values', async () => {
|
|
102
|
-
mockOk({ feedback: [], nextCursor: null, hasMore: false });
|
|
103
|
-
await getFeedback({
|
|
104
|
-
dateFrom: '2024-01-01',
|
|
105
|
-
dateTo: '2024-01-31',
|
|
106
|
-
limit: 10,
|
|
107
|
-
cursor: undefined,
|
|
108
|
-
});
|
|
109
|
-
expect(calledUrlObj().searchParams.get('dateFrom')).toBe('2024-01-01');
|
|
110
|
-
expect(calledUrlObj().searchParams.get('limit')).toBe('10');
|
|
111
|
-
expect(calledUrlObj().searchParams.has('cursor')).toBe(false);
|
|
112
|
-
});
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
describe('endpoint paths', () => {
|
|
116
|
-
it('getKpi', async () => {
|
|
117
|
-
mockOk({ humanVisitors: 0 });
|
|
118
|
-
await getKpi({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'docs');
|
|
119
|
-
expect(calledUrlObj().pathname).toBe('/api/cli/analytics/kpi');
|
|
120
|
-
expect(calledUrlObj().searchParams.get('subdomain')).toBe('docs');
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
it('getFeedbackByPage', async () => {
|
|
124
|
-
mockOk({ feedback: [], hasMore: false });
|
|
125
|
-
await getFeedbackByPage({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'docs');
|
|
126
|
-
expect(calledUrlObj().pathname).toBe('/api/cli/analytics/feedback/by-page');
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
it('getConversations', async () => {
|
|
130
|
-
mockOk({ conversations: [], nextCursor: null, hasMore: false });
|
|
131
|
-
await getConversations({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'docs');
|
|
132
|
-
expect(calledUrlObj().pathname).toBe('/api/cli/analytics/assistant');
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
it('getSearches', async () => {
|
|
136
|
-
mockOk({ searches: [], totalSearches: 0, nextCursor: null });
|
|
137
|
-
await getSearches({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'docs');
|
|
138
|
-
expect(calledUrlObj().pathname).toBe('/api/cli/analytics/searches');
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it('getViews', async () => {
|
|
142
|
-
mockOk({ totals: {}, views: [], hasMore: false });
|
|
143
|
-
await getViews({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'docs');
|
|
144
|
-
expect(calledUrlObj().pathname).toBe('/api/cli/analytics/views');
|
|
145
|
-
});
|
|
146
|
-
|
|
147
|
-
it('getVisitors', async () => {
|
|
148
|
-
mockOk({ totals: {}, visitors: [], hasMore: false });
|
|
149
|
-
await getVisitors({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'docs');
|
|
150
|
-
expect(calledUrlObj().pathname).toBe('/api/cli/analytics/visitors');
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
it('getBuckets', async () => {
|
|
154
|
-
mockOk({ data: [], pagination: { total: 0 } });
|
|
155
|
-
await getBuckets({ dateFrom: '2024-01-01', dateTo: '2024-01-31' }, 'docs');
|
|
156
|
-
expect(calledUrlObj().pathname).toBe('/api/cli/analytics/conversations/buckets');
|
|
157
|
-
expect(calledUrlObj().searchParams.get('subdomain')).toBe('docs');
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
it('getBucketThreads', async () => {
|
|
161
|
-
mockOk({ data: [], pagination: { total: 0, hasMore: false, nextCursor: null } });
|
|
162
|
-
await getBucketThreads('bucket-123', { dateFrom: '2024-01-01' }, 'docs');
|
|
163
|
-
expect(calledUrlObj().pathname).toBe('/api/cli/analytics/conversations/buckets/bucket-123');
|
|
164
|
-
expect(calledUrlObj().searchParams.get('subdomain')).toBe('docs');
|
|
165
|
-
});
|
|
166
|
-
});
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import { num, pct, truncate } from '../../src/analytics/format.js';
|
|
2
|
-
import {
|
|
3
|
-
formatBarChart,
|
|
4
|
-
formatPlainTable,
|
|
5
|
-
formatPrettyTable,
|
|
6
|
-
resolveFormat,
|
|
7
|
-
} from '../../src/analytics/output.js';
|
|
8
|
-
import * as helpers from '../../src/helpers.js';
|
|
9
|
-
|
|
10
|
-
afterEach(() => {
|
|
11
|
-
vi.restoreAllMocks();
|
|
12
|
-
});
|
|
13
|
-
|
|
14
|
-
describe('num', () => {
|
|
15
|
-
it('formats numbers with locale separators', () => {
|
|
16
|
-
expect(num(0)).toBe('0');
|
|
17
|
-
expect(num(1234)).toBe('1,234');
|
|
18
|
-
expect(num(1000000)).toBe('1,000,000');
|
|
19
|
-
});
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
describe('pct', () => {
|
|
23
|
-
it('returns percentage string', () => {
|
|
24
|
-
expect(pct(1, 4)).toBe('25.0%');
|
|
25
|
-
expect(pct(1, 3)).toBe('33.3%');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
it('returns dash when total is zero', () => {
|
|
29
|
-
expect(pct(0, 0)).toBe('\u2014');
|
|
30
|
-
});
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
describe('truncate', () => {
|
|
34
|
-
it('returns short strings unchanged', () => {
|
|
35
|
-
expect(truncate('hello', 10)).toBe('hello');
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
it('truncates long strings with ellipsis', () => {
|
|
39
|
-
expect(truncate('hello world', 6)).toBe('hello\u2026');
|
|
40
|
-
});
|
|
41
|
-
|
|
42
|
-
it('returns string unchanged when exactly at max', () => {
|
|
43
|
-
expect(truncate('hello', 5)).toBe('hello');
|
|
44
|
-
});
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
describe('formatPrettyTable', () => {
|
|
48
|
-
it('returns dim message for empty rows', () => {
|
|
49
|
-
const result = formatPrettyTable(['A', 'B'], []);
|
|
50
|
-
expect(result).toContain('No data found');
|
|
51
|
-
});
|
|
52
|
-
|
|
53
|
-
it('formats headers and rows with aligned columns', () => {
|
|
54
|
-
const result = formatPrettyTable(
|
|
55
|
-
['Name', 'Count'],
|
|
56
|
-
[
|
|
57
|
-
['foo', '10'],
|
|
58
|
-
['barbaz', '5'],
|
|
59
|
-
]
|
|
60
|
-
);
|
|
61
|
-
const lines = result.split('\n');
|
|
62
|
-
expect(lines).toHaveLength(4);
|
|
63
|
-
expect(lines[2]).toContain('foo');
|
|
64
|
-
expect(lines[3]).toContain('barbaz');
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
describe('formatPlainTable', () => {
|
|
69
|
-
it('returns empty string for empty rows', () => {
|
|
70
|
-
expect(formatPlainTable(['A', 'B'], [])).toBe('');
|
|
71
|
-
});
|
|
72
|
-
|
|
73
|
-
it('formats with tab separation and uppercase headers', () => {
|
|
74
|
-
const result = formatPlainTable(
|
|
75
|
-
['Name', 'Count'],
|
|
76
|
-
[
|
|
77
|
-
['foo', '10'],
|
|
78
|
-
['bar', '5'],
|
|
79
|
-
]
|
|
80
|
-
);
|
|
81
|
-
const lines = result.split('\n');
|
|
82
|
-
expect(lines[0]).toContain('NAME');
|
|
83
|
-
expect(lines[0]).toContain('\t');
|
|
84
|
-
expect(lines[1]).toContain('foo');
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
describe('resolveFormat', () => {
|
|
89
|
-
it('returns json when AI mode is active', () => {
|
|
90
|
-
vi.spyOn(helpers, 'isAI').mockReturnValue(true);
|
|
91
|
-
expect(resolveFormat({})).toBe('json');
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
it('returns specified format', () => {
|
|
95
|
-
vi.spyOn(helpers, 'isAI').mockReturnValue(true);
|
|
96
|
-
expect(resolveFormat({ format: 'plain' })).toBe('plain');
|
|
97
|
-
expect(resolveFormat({ format: 'json' })).toBe('json');
|
|
98
|
-
expect(resolveFormat({ format: 'graph' })).toBe('graph');
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('defaults to plain', () => {
|
|
102
|
-
vi.spyOn(helpers, 'isAI').mockReturnValue(false);
|
|
103
|
-
expect(resolveFormat({})).toBe('plain');
|
|
104
|
-
});
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
describe('formatBarChart', () => {
|
|
108
|
-
it('returns dim message for empty items', () => {
|
|
109
|
-
expect(formatBarChart([])).toContain('No data found');
|
|
110
|
-
});
|
|
111
|
-
|
|
112
|
-
it('renders bars with labels and values', () => {
|
|
113
|
-
const result = formatBarChart([
|
|
114
|
-
{ label: 'Foo', value: 10 },
|
|
115
|
-
{ label: 'Bar', value: 5 },
|
|
116
|
-
]);
|
|
117
|
-
const lines = result.split('\n');
|
|
118
|
-
expect(lines).toHaveLength(2);
|
|
119
|
-
expect(lines[0]).toContain('Foo');
|
|
120
|
-
expect(lines[0]).toContain('10');
|
|
121
|
-
expect(lines[0]).toContain('\u2588');
|
|
122
|
-
expect(lines[1]).toContain('Bar');
|
|
123
|
-
expect(lines[1]).toContain('5');
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
it('scales bars relative to max value', () => {
|
|
127
|
-
const result = formatBarChart([
|
|
128
|
-
{ label: 'Big', value: 100 },
|
|
129
|
-
{ label: 'Small', value: 10 },
|
|
130
|
-
]);
|
|
131
|
-
const bigBar = result.split('\n')[0]!;
|
|
132
|
-
const smallBar = result.split('\n')[1]!;
|
|
133
|
-
const bigBlocks = (bigBar.match(/\u2588/g) || []).length;
|
|
134
|
-
const smallBlocks = (smallBar.match(/\u2588/g) || []).length;
|
|
135
|
-
expect(bigBlocks).toBeGreaterThan(smallBlocks);
|
|
136
|
-
});
|
|
137
|
-
});
|
package/bin/analytics/client.js
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
-
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
-
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
-
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
-
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
-
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
-
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
import { authenticatedFetch } from '../authenticatedFetch.js';
|
|
11
|
-
import { API_URL } from '../constants.js';
|
|
12
|
-
function request(path_1) {
|
|
13
|
-
return __awaiter(this, arguments, void 0, function* (path, params = {}) {
|
|
14
|
-
const url = new URL(`${API_URL}/api/cli/analytics${path}`);
|
|
15
|
-
for (const [key, value] of Object.entries(params)) {
|
|
16
|
-
if (value !== undefined)
|
|
17
|
-
url.searchParams.set(key, String(value));
|
|
18
|
-
}
|
|
19
|
-
const res = yield authenticatedFetch(url.toString(), {
|
|
20
|
-
headers: { Accept: 'application/json' },
|
|
21
|
-
});
|
|
22
|
-
if (!res.ok) {
|
|
23
|
-
const body = yield res.text().catch(() => '');
|
|
24
|
-
throw new Error(`API error (${res.status}): ${body || res.statusText}`);
|
|
25
|
-
}
|
|
26
|
-
return res.json();
|
|
27
|
-
});
|
|
28
|
-
}
|
|
29
|
-
export function getKpi(opts, subdomain) {
|
|
30
|
-
return request('/kpi', Object.assign(Object.assign({}, opts), { subdomain }));
|
|
31
|
-
}
|
|
32
|
-
export function getFeedback(opts, subdomain) {
|
|
33
|
-
return request('/feedback', Object.assign(Object.assign({}, opts), { subdomain }));
|
|
34
|
-
}
|
|
35
|
-
export function getFeedbackByPage(opts, subdomain) {
|
|
36
|
-
return request('/feedback/by-page', Object.assign(Object.assign({}, opts), { subdomain }));
|
|
37
|
-
}
|
|
38
|
-
export function getConversations(opts, subdomain) {
|
|
39
|
-
return request('/assistant', Object.assign(Object.assign({}, opts), { subdomain }));
|
|
40
|
-
}
|
|
41
|
-
export function getSearches(opts, subdomain) {
|
|
42
|
-
return request('/searches', Object.assign(Object.assign({}, opts), { subdomain }));
|
|
43
|
-
}
|
|
44
|
-
export function getViews(opts, subdomain) {
|
|
45
|
-
return request('/views', Object.assign(Object.assign({}, opts), { subdomain }));
|
|
46
|
-
}
|
|
47
|
-
export function getVisitors(opts, subdomain) {
|
|
48
|
-
return request('/visitors', Object.assign(Object.assign({}, opts), { subdomain }));
|
|
49
|
-
}
|
|
50
|
-
export function getBuckets(opts, subdomain) {
|
|
51
|
-
return request('/conversations/buckets', Object.assign(Object.assign({}, opts), { subdomain }));
|
|
52
|
-
}
|
|
53
|
-
export function getBucketThreads(bucketId, opts, subdomain) {
|
|
54
|
-
return request(`/conversations/buckets/${encodeURIComponent(bucketId)}`, Object.assign(Object.assign({}, opts), { subdomain }));
|
|
55
|
-
}
|
package/bin/analytics/format.js
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export function num(n) {
|
|
2
|
-
return n.toLocaleString('en-US');
|
|
3
|
-
}
|
|
4
|
-
export function pct(n, total) {
|
|
5
|
-
if (total === 0)
|
|
6
|
-
return '\u2014';
|
|
7
|
-
return ((n / total) * 100).toFixed(1) + '%';
|
|
8
|
-
}
|
|
9
|
-
export function truncate(s, max) {
|
|
10
|
-
if (s.length <= max)
|
|
11
|
-
return s;
|
|
12
|
-
return s.slice(0, max - 1) + '\u2026';
|
|
13
|
-
}
|