Merge branch 'dev' into attr-string-dict
This commit is contained in:
commit
9647f1b659
11 changed files with 225 additions and 49 deletions
6
.github/workflows/ui-tests.js.yml
vendored
6
.github/workflows/ui-tests.js.yml
vendored
|
|
@ -47,8 +47,10 @@ jobs:
|
|||
cd tracker/tracker
|
||||
npm i -g yarn
|
||||
yarn
|
||||
# - name: Jest tests
|
||||
# run: yarn test
|
||||
- name: Jest tests
|
||||
run: |
|
||||
cd tracker/tracker
|
||||
yarn test
|
||||
- name: Build tracker inst
|
||||
run: |
|
||||
cd tracker/tracker
|
||||
|
|
|
|||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -3,5 +3,6 @@ public
|
|||
node_modules
|
||||
*DS_Store
|
||||
*.env
|
||||
*.log
|
||||
**/*.envrc
|
||||
.idea
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ function WidgetSessions(props: Props) {
|
|||
const fetchSessions = (metricId: any, filter: any) => {
|
||||
if (!isMounted()) return;
|
||||
setLoading(true);
|
||||
delete filter.eventsOrderSupport;
|
||||
widget
|
||||
.fetchSessions(metricId, filter)
|
||||
.then((res: any) => {
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
STACKEVENTS,
|
||||
STORAGE,
|
||||
toggleBottomBlock,
|
||||
changeSkipInterval,
|
||||
} from 'Duck/components/player';
|
||||
import { PlayerContext } from 'App/components/Session/playerContext';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
|
|
@ -290,6 +291,7 @@ export default connect(
|
|||
fullscreenOff,
|
||||
toggleBottomBlock,
|
||||
fetchSessions,
|
||||
changeSkipInterval,
|
||||
}
|
||||
)(ControlPlayer);
|
||||
|
||||
|
|
|
|||
|
|
@ -1,8 +1,8 @@
|
|||
import React, { useEffect } from 'react';
|
||||
import { connect } from 'react-redux';
|
||||
import { setAutoplayValues } from 'Duck/sessions';
|
||||
import { session as sessionRoute } from 'App/routes';
|
||||
import { Link, Icon, Tooltip } from 'UI';;
|
||||
import { withSiteId, session as sessionRoute } from 'App/routes';
|
||||
import { Link, Icon, Tooltip } from 'UI';
|
||||
import { withRouter, RouteComponentProps } from 'react-router-dom';
|
||||
import cn from 'classnames';
|
||||
import { fetchAutoplaySessions } from 'Duck/search';
|
||||
|
|
@ -10,18 +10,20 @@ import { fetchAutoplaySessions } from 'Duck/search';
|
|||
const PER_PAGE = 10;
|
||||
|
||||
interface Props extends RouteComponentProps {
|
||||
siteId: string;
|
||||
previousId: string;
|
||||
nextId: string;
|
||||
defaultList: any;
|
||||
currentPage: number;
|
||||
total: number;
|
||||
setAutoplayValues?: () => void;
|
||||
setAutoplayValues: () => void;
|
||||
latestRequestTime: any;
|
||||
sessionIds: any;
|
||||
fetchAutoplaySessions?: (page: number) => Promise<void>;
|
||||
fetchAutoplaySessions: (page: number) => Promise<void>;
|
||||
}
|
||||
function QueueControls(props: Props) {
|
||||
const {
|
||||
siteId,
|
||||
previousId,
|
||||
nextId,
|
||||
currentPage,
|
||||
|
|
@ -49,43 +51,46 @@ function QueueControls(props: Props) {
|
|||
}
|
||||
}, []);
|
||||
|
||||
const nextHandler = () => {
|
||||
props.history.push(withSiteId(sessionRoute(nextId), siteId));
|
||||
};
|
||||
|
||||
const prevHandler = () => {
|
||||
props.history.push(withSiteId(sessionRoute(previousId), siteId));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex items-center">
|
||||
<Tooltip
|
||||
placement="bottom"
|
||||
title={<div className="whitespace-nowrap">Play Previous Session</div>}
|
||||
disabled={!previousId}
|
||||
<div
|
||||
onClick={prevHandler}
|
||||
className={cn('p-1 bg-gray-bg group rounded-full color-gray-darkest font-medium', {
|
||||
'pointer-events-none opacity-50': !previousId,
|
||||
'cursor-pointer': !!previousId,
|
||||
})}
|
||||
>
|
||||
<Link to={sessionRoute(previousId)} disabled={!previousId}>
|
||||
<div
|
||||
className={cn(
|
||||
'p-1 bg-gray-bg group rounded-full color-gray-darkest font-medium',
|
||||
previousId && 'cursor-pointer',
|
||||
!disabled && nextId && 'hover:bg-bg-blue'
|
||||
)}
|
||||
>
|
||||
<Icon name="prev1" className="group-hover:fill-main" color="inherit" size="16" />
|
||||
</div>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip
|
||||
placement="bottom"
|
||||
title={<div className="whitespace-nowrap">Play Next Session</div>}
|
||||
disabled={!nextId}
|
||||
<Tooltip
|
||||
placement="bottom"
|
||||
title={<div className="whitespace-nowrap">Play Previous Session</div>}
|
||||
disabled={!previousId}
|
||||
>
|
||||
<Icon name="prev1" className="group-hover:fill-main" color="inherit" size="16" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
<div
|
||||
onClick={nextHandler}
|
||||
className={cn('p-1 bg-gray-bg group ml-1 rounded-full color-gray-darkest font-medium', {
|
||||
'pointer-events-none opacity-50': !nextId,
|
||||
'cursor-pointer': !!nextId,
|
||||
})}
|
||||
>
|
||||
<Link to={sessionRoute(nextId)} disabled={!nextId}>
|
||||
<div
|
||||
className={cn(
|
||||
'p-1 bg-gray-bg group ml-1 rounded-full color-gray-darkest font-medium',
|
||||
nextId && 'cursor-pointer',
|
||||
!disabled && nextId && 'hover:bg-bg-blue'
|
||||
)}
|
||||
>
|
||||
<Icon name="next1" className="group-hover:fill-main" color="inherit" size="16" />
|
||||
</div>
|
||||
</Link>
|
||||
</Tooltip>
|
||||
<Tooltip
|
||||
placement="bottom"
|
||||
title={<div className="whitespace-nowrap">Play Next Session</div>}
|
||||
disabled={!nextId}
|
||||
>
|
||||
<Icon name="next1" className="group-hover:fill-main" color="inherit" size="16" />
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -98,6 +103,7 @@ export default connect(
|
|||
total: state.getIn(['sessions', 'total']) || 0,
|
||||
sessionIds: state.getIn(['sessions', 'sessionIds']) || [],
|
||||
latestRequestTime: state.getIn(['search', 'latestRequestTime']),
|
||||
siteId: state.getIn(['site', 'siteId']),
|
||||
}),
|
||||
{ setAutoplayValues, fetchAutoplaySessions }
|
||||
)(withRouter(QueueControls))
|
||||
)(withRouter(QueueControls));
|
||||
|
|
|
|||
|
|
@ -25,7 +25,8 @@ export default class SharePopup extends React.PureComponent {
|
|||
isOpen: false,
|
||||
channelId: this.props.channels.getIn([0, 'webhookId']),
|
||||
teamsChannel: this.props.msTeamsChannels.getIn([0, 'webhookId']),
|
||||
loading: false,
|
||||
loadingSlack: false,
|
||||
loadingTeams: false,
|
||||
};
|
||||
|
||||
componentDidMount() {
|
||||
|
|
@ -39,7 +40,7 @@ export default class SharePopup extends React.PureComponent {
|
|||
|
||||
editMessage = (e) => this.setState({ comment: e.target.value });
|
||||
shareToSlack = () => {
|
||||
this.setState({ loading: true }, () => {
|
||||
this.setState({ loadingSlack: true }, () => {
|
||||
this.props
|
||||
.sendSlackMsg({
|
||||
integrationId: this.state.channelId,
|
||||
|
|
@ -52,7 +53,7 @@ export default class SharePopup extends React.PureComponent {
|
|||
};
|
||||
|
||||
shareToMSTeams = () => {
|
||||
this.setState({ loading: true }, () => {
|
||||
this.setState({ loadingTeams: true }, () => {
|
||||
this.props
|
||||
.sendMsTeamsMsg({
|
||||
integrationId: this.state.teamsChannel,
|
||||
|
|
@ -75,7 +76,8 @@ export default class SharePopup extends React.PureComponent {
|
|||
};
|
||||
|
||||
handleSuccess = (endpoint) => {
|
||||
this.setState({ isOpen: false, comment: '', loading: false });
|
||||
const obj = endpoint === 'Slack' ? { isOpen: false, comment: '', loadingSlack: false } : { isOpen: false, comment: '', loadingTeams: false }
|
||||
this.setState(obj);
|
||||
toast.success(`Sent to ${endpoint}.`);
|
||||
};
|
||||
|
||||
|
|
@ -89,7 +91,7 @@ export default class SharePopup extends React.PureComponent {
|
|||
|
||||
render() {
|
||||
const { trigger, channels, msTeamsChannels, showCopyLink = false } = this.props;
|
||||
const { comment, channelId, teamsChannel, loading } = this.state;
|
||||
const { comment, channelId, teamsChannel, loadingSlack, loadingTeams } = this.state;
|
||||
|
||||
const slackOptions = channels
|
||||
.map(({ webhookId, name }) => ({ value: webhookId, label: name }))
|
||||
|
|
@ -137,7 +139,7 @@ export default class SharePopup extends React.PureComponent {
|
|||
<Button onClick={this.shareToSlack} variant="primary">
|
||||
<div className="flex items-center">
|
||||
<Icon name="integrations/slack-bw" color="white" size="18" marginRight="10" />
|
||||
{loading ? 'Sending...' : 'Send'}
|
||||
{loadingSlack ? 'Sending...' : 'Send'}
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
|
|
@ -163,7 +165,7 @@ export default class SharePopup extends React.PureComponent {
|
|||
size="18"
|
||||
marginRight="10"
|
||||
/>
|
||||
{loading ? 'Sending...' : 'Send'}
|
||||
{loadingTeams ? 'Sending...' : 'Send'}
|
||||
</div>
|
||||
</Button>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -6,3 +6,6 @@ build
|
|||
.cache
|
||||
.eslintrc.cjs
|
||||
*.gen.ts
|
||||
|
||||
**/*.test.ts
|
||||
src/main/plugin.ts
|
||||
|
|
@ -3,7 +3,7 @@ module.exports = {
|
|||
root: true,
|
||||
parser: '@typescript-eslint/parser',
|
||||
parserOptions: {
|
||||
project: ['./tsconfig-base.json', './src/main/tsconfig-cjs.json'],
|
||||
project: ['./tsconfig-base.json', './src/main/tsconfig-cjs.json'], // ??TODO: use correct project
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
plugins: ['prettier', '@typescript-eslint'],
|
||||
|
|
|
|||
63
tracker/tracker/src/webworker/PrimitiveEncoder.unit.test.ts
Normal file
63
tracker/tracker/src/webworker/PrimitiveEncoder.unit.test.ts
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
import { describe, expect, test, jest, beforeEach, afterEach } from '@jest/globals'
|
||||
import PrimitiveEncoder from './PrimitiveEncoder.js'
|
||||
|
||||
describe('PrimitiveEncoder', () => {
|
||||
test('initial state', () => {
|
||||
const enc = new PrimitiveEncoder(10)
|
||||
|
||||
expect(enc.getCurrentOffset()).toBe(0)
|
||||
expect(enc.isEmpty).toBe(true)
|
||||
expect(enc.flush().length).toBe(0)
|
||||
})
|
||||
|
||||
test('skip()', () => {
|
||||
const enc = new PrimitiveEncoder(10)
|
||||
enc.skip(5)
|
||||
expect(enc.getCurrentOffset()).toBe(5)
|
||||
expect(enc.isEmpty).toBe(false)
|
||||
})
|
||||
|
||||
test('checkpoint()', () => {
|
||||
const enc = new PrimitiveEncoder(10)
|
||||
enc.skip(5)
|
||||
enc.checkpoint()
|
||||
expect(enc.flush().length).toBe(5)
|
||||
})
|
||||
|
||||
test('boolean(true)', () => {
|
||||
const enc = new PrimitiveEncoder(10)
|
||||
enc.boolean(true)
|
||||
enc.checkpoint()
|
||||
const bytes = enc.flush()
|
||||
expect(bytes.length).toBe(1)
|
||||
expect(bytes[0]).toBe(1)
|
||||
})
|
||||
test('boolean(false)', () => {
|
||||
const enc = new PrimitiveEncoder(10)
|
||||
enc.boolean(false)
|
||||
enc.checkpoint()
|
||||
const bytes = enc.flush()
|
||||
expect(bytes.length).toBe(1)
|
||||
expect(bytes[0]).toBe(0)
|
||||
})
|
||||
// TODO: test correct enc/dec on a top level(?) with player(PrimitiveReader.ts)/tracker(PrimitiveEncoder.ts)
|
||||
|
||||
test('buffer oveflow with string()', () => {
|
||||
const N = 10
|
||||
const enc = new PrimitiveEncoder(N)
|
||||
const wasWritten = enc.string('long string'.repeat(N))
|
||||
expect(wasWritten).toBe(false)
|
||||
})
|
||||
test('buffer oveflow with uint()', () => {
|
||||
const enc = new PrimitiveEncoder(1)
|
||||
const wasWritten = enc.uint(Number.MAX_SAFE_INTEGER)
|
||||
expect(wasWritten).toBe(false)
|
||||
})
|
||||
test('buffer oveflow with boolean()', () => {
|
||||
const enc = new PrimitiveEncoder(1)
|
||||
let wasWritten = enc.boolean(true)
|
||||
expect(wasWritten).toBe(true)
|
||||
wasWritten = enc.boolean(true)
|
||||
expect(wasWritten).toBe(false)
|
||||
})
|
||||
})
|
||||
95
tracker/tracker/src/webworker/QueueSender.unit.test.ts
Normal file
95
tracker/tracker/src/webworker/QueueSender.unit.test.ts
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
import { describe, expect, test, jest, beforeEach, afterEach } from '@jest/globals'
|
||||
import QueueSender from './QueueSender.js'
|
||||
|
||||
global.fetch = () => Promise.resolve(new Response()) // jsdom does not have it
|
||||
|
||||
function mockFetch(status: number) {
|
||||
return jest
|
||||
.spyOn(global, 'fetch')
|
||||
.mockImplementation(() => Promise.resolve({ status } as Response))
|
||||
}
|
||||
const baseURL = 'MYBASEURL'
|
||||
const sampleArray = new Uint8Array(1)
|
||||
const randomToken = 'abc'
|
||||
function defaultQueueSender({
|
||||
url = baseURL,
|
||||
onUnauthorised = () => {},
|
||||
onFailed = () => {},
|
||||
} = {}) {
|
||||
return new QueueSender(baseURL, onUnauthorised, onFailed)
|
||||
}
|
||||
|
||||
describe('QueueSender', () => {
|
||||
afterEach(() => {
|
||||
jest.restoreAllMocks()
|
||||
})
|
||||
|
||||
// Test fetch first parameter + authorization header to be present
|
||||
|
||||
// authorise() / push()
|
||||
test('Does not call fetch if not authorised', () => {
|
||||
const queueSender = defaultQueueSender()
|
||||
const fetchMock = mockFetch(200)
|
||||
|
||||
queueSender.push(sampleArray)
|
||||
expect(fetchMock).not.toBeCalled()
|
||||
})
|
||||
test('Calls fetch on push() if authorised', () => {
|
||||
const queueSender = defaultQueueSender()
|
||||
const fetchMock = mockFetch(200)
|
||||
|
||||
queueSender.authorise(randomToken)
|
||||
expect(fetchMock).toBeCalledTimes(0)
|
||||
queueSender.push(sampleArray)
|
||||
expect(fetchMock).toBeCalledTimes(1)
|
||||
})
|
||||
test('Calls fetch on authorisation if there was a push() call before', () => {
|
||||
const queueSender = defaultQueueSender()
|
||||
const fetchMock = mockFetch(200)
|
||||
|
||||
queueSender.push(sampleArray)
|
||||
queueSender.authorise(randomToken)
|
||||
expect(fetchMock).toBeCalledTimes(1)
|
||||
})
|
||||
|
||||
// .clean()
|
||||
test("Doesn't call fetch on push() after clean()", () => {
|
||||
const queueSender = defaultQueueSender()
|
||||
const fetchMock = mockFetch(200)
|
||||
|
||||
queueSender.authorise(randomToken)
|
||||
queueSender.clean()
|
||||
queueSender.push(sampleArray)
|
||||
expect(fetchMock).not.toBeCalled()
|
||||
})
|
||||
test("Doesn't call fetch on authorisation if there was push() & clean() calls before", () => {
|
||||
const queueSender = defaultQueueSender()
|
||||
const fetchMock = mockFetch(200)
|
||||
|
||||
queueSender.push(sampleArray)
|
||||
queueSender.clean()
|
||||
queueSender.authorise(randomToken)
|
||||
expect(fetchMock).not.toBeCalled()
|
||||
})
|
||||
|
||||
//Test N sequential ToBeCalledTimes(N)
|
||||
//Test N sequential pushes with different timeouts to be sequential
|
||||
|
||||
// onUnauthorised
|
||||
test('Calls onUnauthorized callback on 401', (done) => {
|
||||
const onUnauthorised = jest.fn()
|
||||
const queueSender = defaultQueueSender({
|
||||
onUnauthorised,
|
||||
})
|
||||
const fetchMock = mockFetch(401)
|
||||
queueSender.authorise(randomToken)
|
||||
queueSender.push(sampleArray)
|
||||
setTimeout(() => {
|
||||
// how to make test simpler and more explicit?
|
||||
expect(onUnauthorised).toBeCalled()
|
||||
done()
|
||||
}, 100)
|
||||
})
|
||||
//Test onFailure
|
||||
//Test attempts timeout/ attempts count (toBeCalledTimes on one batch)
|
||||
})
|
||||
|
|
@ -10,5 +10,6 @@
|
|||
"target": "es6",
|
||||
"module": "es6",
|
||||
"moduleResolution": "nodenext"
|
||||
}
|
||||
},
|
||||
"exclude": ["**/*.test.ts"]
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue