Merge branch 'dev' into attr-string-dict

This commit is contained in:
Alex Kaminskii 2023-02-06 15:47:43 +01:00
commit 9647f1b659
11 changed files with 225 additions and 49 deletions

View file

@ -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
View file

@ -3,5 +3,6 @@ public
node_modules
*DS_Store
*.env
*.log
**/*.envrc
.idea

View file

@ -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) => {

View file

@ -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);

View file

@ -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));

View file

@ -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>
)}

View file

@ -6,3 +6,6 @@ build
.cache
.eslintrc.cjs
*.gen.ts
**/*.test.ts
src/main/plugin.ts

View file

@ -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'],

View 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)
})
})

View 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)
})

View file

@ -10,5 +10,6 @@
"target": "es6",
"module": "es6",
"moduleResolution": "nodenext"
}
},
"exclude": ["**/*.test.ts"]
}