@jetbrains/ring-ui 4.1.0-beta.3 → 4.1.1
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/CHANGELOG.md +13 -0
- package/README.md +17 -15
- package/babel.config.js +3 -2
- package/components/alert/alert.js +9 -3
- package/components/alert/alert.test.js +21 -48
- package/components/alert/container.css +1 -1
- package/components/alert/container.test.js +3 -13
- package/components/alert-service/alert-service.examples.css +18 -0
- package/components/alert-service/alert-service.examples.js +21 -0
- package/components/alert-service/alert-service.js +10 -3
- package/components/analytics/analytics__fus-plugin.js +3 -3
- package/components/analytics/analytics__ga-plugin.js +2 -2
- package/components/auth/auth.test.js +14 -7
- package/components/auth/auth__core.js +64 -33
- package/components/auth-dialog/auth-dialog.css +2 -3
- package/components/auth-dialog/auth-dialog.js +4 -1
- package/components/auth-dialog/auth-dialog.test.js +3 -19
- package/components/avatar/avatar.css +4 -1
- package/components/avatar/avatar.examples.js +3 -2
- package/components/avatar/avatar.js +34 -6
- package/components/avatar/avatar.test.js +20 -17
- package/components/avatar/fallback-avatar.js +136 -0
- package/components/avatar-editor-ng/avatar-editor-ng.css +2 -2
- package/components/avatar-editor-ng/avatar-editor-ng.js +2 -1
- package/components/avatar-editor-ng/{avatar-editor-ng.html → avatar-editor-ng__template.js} +2 -2
- package/components/badge/badge.test.js +13 -20
- package/components/button/button.css +2 -2
- package/components/button/button.js +4 -1
- package/components/button/button.test.js +32 -33
- package/components/button-group/button-group.js +1 -1
- package/components/button-group/caption.js +1 -1
- package/components/button-ng/button-ng.js +1 -1
- package/components/button-set-ng/button-set-ng.js +3 -1
- package/components/checkbox/checkbox.css +1 -1
- package/components/code/code.js +2 -5
- package/components/confirm/confirm.js +1 -0
- package/components/confirm-service/confirm-service.js +5 -5
- package/components/content-layout/content-layout.css +1 -1
- package/components/data-list/data-list.css +1 -1
- package/components/date-picker/date-input.js +5 -4
- package/components/date-picker/date-picker.css +34 -22
- package/components/date-picker/date-picker.js +16 -14
- package/components/date-picker/date-popup.js +22 -7
- package/components/date-picker/month-names.js +8 -5
- package/components/date-picker/month.js +6 -2
- package/components/date-picker/weekdays.js +10 -2
- package/components/dialog/dialog.examples.js +3 -1
- package/components/dialog/dialog.js +10 -5
- package/components/dialog/dialog.test.js +1 -1
- package/components/dialog/dialog__body-scroll-preventer.js +51 -31
- package/components/dialog-ng/dialog-ng.js +10 -10
- package/components/dialog-ng/{dialog-ng.html → dialog-ng__template.js} +2 -2
- package/components/dropdown/dropdown.examples.js +36 -1
- package/components/dropdown/dropdown.test.js +2 -2
- package/components/dropdown-menu/dropdown-menu.examples.js +47 -0
- package/components/dropdown-menu/dropdown-menu.js +117 -0
- package/components/dropdown-menu/dropdown-menu.test.js +76 -0
- package/components/error-bubble/error-bubble-legacy.css +1 -1
- package/components/error-bubble/error-bubble.css +1 -1
- package/components/error-bubble/error-bubble.examples.js +1 -1
- package/components/error-page/error-page.css +2 -2
- package/components/footer-ng/footer-ng.js +13 -3
- package/components/form/form.css +2 -2
- package/components/form-ng/form-ng.js +3 -1
- package/components/global/global.css +1 -1
- package/components/global/theme.js +1 -1
- package/components/global/variables.css +8 -1
- package/components/grid/grid.css +10 -9
- package/components/header/header.css +1 -1
- package/components/header/header.examples.js +7 -8
- package/components/header/profile.js +10 -11
- package/components/http/http.js +1 -1
- package/components/icon/icon.css +5 -4
- package/components/input/input-legacy.css +7 -7
- package/components/island/header.js +2 -2
- package/components/island/island.css +8 -3
- package/components/island-legacy/island-legacy.css +3 -1
- package/components/list/list.js +6 -1
- package/components/list/list__custom.js +9 -3
- package/components/list/list__item.js +8 -2
- package/components/list/list__link.js +2 -1
- package/components/loader-inline/loader-inline.css +1 -1
- package/components/loader-screen/loader-screen.css +1 -1
- package/components/message/message.css +1 -1
- package/components/message/message.examples.js +8 -7
- package/components/pager/pager.js +5 -3
- package/components/permissions/permissions.js +1 -1
- package/components/popup/popup.js +1 -1
- package/components/popup/popup.test.js +15 -13
- package/components/progress-bar/progress-bar.css +1 -1
- package/components/progress-bar/progress-bar.examples.js +3 -3
- package/components/progress-bar/progress-bar.js +5 -2
- package/components/progress-bar/progress-bar.test.js +12 -13
- package/components/progress-bar-ng/progress-bar-ng.examples.js +3 -3
- package/components/query-assist/query-assist.css +13 -3
- package/components/query-assist/query-assist.examples.js +3 -4
- package/components/query-assist/query-assist.js +56 -12
- package/components/query-assist/query-assist.test.js +37 -5
- package/components/save-field-ng/save-field-ng.css +0 -3
- package/components/save-field-ng/save-field-ng.js +3 -1
- package/components/save-field-ng/{save-field-ng.html → save-field-ng__template.js} +2 -2
- package/components/select/select.css +12 -7
- package/components/select/select.examples.js +13 -0
- package/components/select/select.js +30 -43
- package/components/select/select.test.js +4 -5
- package/components/select/select__popup.js +1 -0
- package/components/shortcuts-hint-ng/shortcuts-hint-ng.css +1 -1
- package/components/shortcuts-hint-ng/shortcuts-hint-ng.js +1 -1
- package/components/shortcuts-hint-ng/{shortcuts-hint-ng.html → shortcuts-hint-ng__template.js} +2 -2
- package/components/sidebar/sidebar.css +1 -0
- package/components/sidebar-ng/sidebar-ng.js +6 -2
- package/components/sidebar-ng/{sidebar-ng__button.html → sidebar-ng__button-template.js} +2 -2
- package/components/sidebar-ng/{sidebar-ng.html → sidebar-ng__template.js} +2 -2
- package/components/table/header.js +9 -1
- package/components/table/row.js +2 -1
- package/components/table/table.css +2 -1
- package/components/table-legacy/table-legacy.css +2 -2
- package/components/table-legacy/table-legacy__toolbar.css +2 -2
- package/components/table-legacy-ng/table-legacy-ng.js +38 -5
- package/components/table-legacy-ng/table-legacy-ng__pager.js +7 -1
- package/components/tabs/collapsible-tab.js +2 -2
- package/components/tabs/collapsible-tabs.js +5 -9
- package/components/tabs/tab-link.js +4 -2
- package/components/tabs/tabs.css +32 -5
- package/components/tabs-ng/tabs-ng.js +4 -2
- package/components/tabs-ng/{tabs-ng.html → tabs-ng__template.js} +6 -2
- package/components/tag/tag.css +5 -2
- package/components/tag/tag.examples.js +3 -0
- package/components/tag/tag.js +19 -16
- package/components/tags-input/tag-input.examples.js +1 -1
- package/components/tags-input/tags-input.js +5 -2
- package/components/template-ng/template-ng.js +1 -1
- package/components/tooltip/tooltip.js +7 -2
- package/components/user-agreement/user-agreement.css +1 -5
- package/components/user-agreement/user-agreement.examples.js +7 -6
- package/components/user-agreement/user-agreement.js +11 -3
- package/package.json +85 -83
- package/webpack.config.js +14 -10
- package/components/button-set-ng/button-set-ng.html +0 -1
- package/components/footer-ng/footer-ng.html +0 -13
- package/components/form-ng/form-ng__error-bubble.html +0 -3
- package/components/table-legacy-ng/table-legacy-ng.html +0 -4
- package/components/table-legacy-ng/table-legacy-ng__column.html +0 -12
- package/components/table-legacy-ng/table-legacy-ng__header.html +0 -4
- package/components/table-legacy-ng/table-legacy-ng__pager.html +0 -7
- package/components/table-legacy-ng/table-legacy-ng__row.html +0 -12
- package/components/table-legacy-ng/table-legacy-ng__title.html +0 -9
- package/dist/_helpers/_rollupPluginBabelHelpers.js +0 -123
- package/dist/_helpers/background-flow.js +0 -232
- package/dist/_helpers/badge.js +0 -3
- package/dist/_helpers/button.js +0 -145
- package/dist/_helpers/clickableLink.js +0 -65
- package/dist/_helpers/data-tests.js +0 -15
- package/dist/_helpers/disable-hover-hoc.js +0 -407
- package/dist/_helpers/dom.js +0 -101
- package/dist/_helpers/get-uid.js +0 -15
- package/dist/_helpers/linear-function.js +0 -17
- package/dist/_helpers/list.js +0 -1327
- package/dist/_helpers/logo.js +0 -36
- package/dist/_helpers/memoize.js +0 -17
- package/dist/_helpers/popup.js +0 -691
- package/dist/_helpers/popup.target.js +0 -27
- package/dist/_helpers/rerender-hoc.js +0 -49
- package/dist/_helpers/schedule-raf.js +0 -31
- package/dist/_helpers/sniffer.js +0 -6
- package/dist/_helpers/theme.js +0 -40
- package/dist/_helpers/url.js +0 -125
- package/dist/alert-service.js +0 -149
- package/dist/alert.js +0 -284
- package/dist/analytics.js +0 -116
- package/dist/auth-dialog-service.js +0 -56
- package/dist/auth-dialog.js +0 -122
- package/dist/auth.js +0 -1744
- package/dist/avatar.js +0 -152
- package/dist/badge.js +0 -52
- package/dist/button-group.js +0 -48
- package/dist/button-set.js +0 -27
- package/dist/button-toolbar.js +0 -30
- package/dist/button.js +0 -12
- package/dist/caret.js +0 -262
- package/dist/checkbox.js +0 -108
- package/dist/confirm-service.js +0 -102
- package/dist/confirm.js +0 -113
- package/dist/content-layout.js +0 -184
- package/dist/contenteditable.js +0 -81
- package/dist/data-list.js +0 -466
- package/dist/date-picker.js +0 -1398
- package/dist/dialog.js +0 -223
- package/dist/dropdown.js +0 -250
- package/dist/error-bubble.js +0 -56
- package/dist/error-message.js +0 -53
- package/dist/footer.js +0 -124
- package/dist/grid.js +0 -148
- package/dist/group.js +0 -34
- package/dist/header.js +0 -658
- package/dist/heading.js +0 -76
- package/dist/http.js +0 -207
- package/dist/hub-source.js +0 -130
- package/dist/icon.js +0 -211
- package/dist/input.js +0 -228
- package/dist/island.js +0 -314
- package/dist/link.js +0 -117
- package/dist/list.js +0 -29
- package/dist/loader-inline.js +0 -165
- package/dist/loader-screen.js +0 -45
- package/dist/loader.js +0 -338
- package/dist/login-dialog.js +0 -173
- package/dist/logo.js +0 -8
- package/dist/message.js +0 -226
- package/dist/old-browsers-message.js +0 -129
- package/dist/pager.js +0 -325
- package/dist/panel.js +0 -34
- package/dist/permissions.js +0 -466
- package/dist/popup-menu.js +0 -93
- package/dist/popup.js +0 -16
- package/dist/progress-bar.js +0 -111
- package/dist/proxy-attrs.js +0 -19
- package/dist/query-assist.js +0 -1081
- package/dist/radio.js +0 -112
- package/dist/select.js +0 -1920
- package/dist/selection.js +0 -213
- package/dist/shortcuts.js +0 -307
- package/dist/storage.js +0 -373
- package/dist/style.css +0 -1
- package/dist/tab-trap.js +0 -174
- package/dist/table.js +0 -903
- package/dist/tabs.js +0 -721
- package/dist/tag.js +0 -187
- package/dist/tags-input.js +0 -440
- package/dist/tags-list.js +0 -91
- package/dist/text.js +0 -38
- package/dist/toggle.js +0 -80
- package/dist/tooltip.js +0 -202
- package/dist/user-card.js +0 -218
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
|
+
## [4.1.0]
|
|
2
|
+
|
|
3
|
+
### Pre-built version
|
|
4
|
+
|
|
5
|
+
Ring UI now comes with pre-built version in `@jetbrains/ring-ui/dist` directory.
|
|
6
|
+
This addresses the following issues:
|
|
7
|
+
|
|
8
|
+
* does not require using specific bundler (WebPack) anymore
|
|
9
|
+
* does not require dealing with Ring UI building configuration
|
|
10
|
+
* decreases your project build time
|
|
11
|
+
|
|
12
|
+
See "README.md" for quick start with pre-built version
|
|
13
|
+
|
|
1
14
|
## [4.0.0]
|
|
2
15
|
|
|
3
16
|
### BREAKING CHANGES
|
package/README.md
CHANGED
|
@@ -7,27 +7,28 @@ This collection of UI components aims to provide all the necessary building bloc
|
|
|
7
7
|
|
|
8
8
|
## Installation
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
`npm install @jetbrains/ring-ui`
|
|
11
11
|
|
|
12
12
|
### Quick start (importing components as ES modules)
|
|
13
13
|
|
|
14
14
|
The easiest way is to import necessary components as ES modules:
|
|
15
|
-
```
|
|
16
|
-
import
|
|
17
|
-
import
|
|
18
|
-
import Input from "@jetbrains/ring-ui/dist/input"
|
|
19
|
-
import Select from "@jetbrains/ring-ui/dist/select"
|
|
20
|
-
import Toggle from "@jetbrains/ring-ui/dist/toggle"
|
|
21
|
-
import { Tabs, Tab } from "@jetbrains/ring-ui/dist/tabs"
|
|
15
|
+
```js
|
|
16
|
+
// You need to import RingUI styles once
|
|
17
|
+
import '@jetbrains/ring-ui/dist/style.css';
|
|
22
18
|
|
|
23
|
-
import
|
|
19
|
+
import alertService from '@jetbrains/ring-ui/dist/alert-service/alert-service';
|
|
20
|
+
import Button from '@jetbrains/ring-ui/dist/button/button';
|
|
24
21
|
|
|
25
22
|
...
|
|
26
23
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
24
|
+
export const Demo = () => {
|
|
25
|
+
return (
|
|
26
|
+
<Button onClick={() => alertService.successMessage('Hello world')}>
|
|
27
|
+
Click me
|
|
28
|
+
</Button>
|
|
29
|
+
);
|
|
30
|
+
};
|
|
31
|
+
|
|
31
32
|
```
|
|
32
33
|
|
|
33
34
|
The bundle size will depend on the amount of components you imported.
|
|
@@ -43,9 +44,10 @@ The bundle size will depend on the amount of components you imported.
|
|
|
43
44
|
- `npm run build` to build a production bundle
|
|
44
45
|
- `npm run create-component` to create a new component template with styles and tests
|
|
45
46
|
|
|
46
|
-
### Webpack
|
|
47
|
+
### Building Ring UI from source via Webpack
|
|
47
48
|
|
|
48
|
-
In case
|
|
49
|
+
In case you have complex build, and you want to compile RingUI sources together with your sources
|
|
50
|
+
in a same build process, you can use the following configuration:
|
|
49
51
|
|
|
50
52
|
1. Install Ring UI with `npm install @jetbrains/ring-ui --save-exact`
|
|
51
53
|
|
package/babel.config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
const browserslist = require('browserslist');
|
|
2
|
+
const deprecate = require('util-deprecate');
|
|
2
3
|
|
|
3
4
|
const coreJsVersion = process.env.RING_UI_COREJS_VERSION ||
|
|
4
5
|
require('core-js/package.json').version;
|
|
@@ -9,8 +10,8 @@ module.exports = function config(api) {
|
|
|
9
10
|
api.cache(true);
|
|
10
11
|
|
|
11
12
|
if (isDeprecatedCoreJS) {
|
|
12
|
-
//
|
|
13
|
-
|
|
13
|
+
// TODO remove in 5.0
|
|
14
|
+
deprecate(() => null, `Compiling Ring UI with deprecated Core JS version "${coreJsVersion}". Consider updating to 3rd.`)();
|
|
14
15
|
}
|
|
15
16
|
|
|
16
17
|
return {
|
|
@@ -82,6 +82,7 @@ export default class Alert extends PureComponent {
|
|
|
82
82
|
children: PropTypes.node,
|
|
83
83
|
className: PropTypes.string,
|
|
84
84
|
captionClassName: PropTypes.string,
|
|
85
|
+
closeButtonClassName: PropTypes.string,
|
|
85
86
|
'data-test': PropTypes.string
|
|
86
87
|
};
|
|
87
88
|
|
|
@@ -121,12 +122,17 @@ export default class Alert extends PureComponent {
|
|
|
121
122
|
static Type = Type;
|
|
122
123
|
|
|
123
124
|
closeRequest = (...args) => {
|
|
125
|
+
this.startCloseAnimation();
|
|
126
|
+
return this.props.onCloseRequest(...args);
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
startCloseAnimation = () => {
|
|
124
130
|
const height = getRect(this.node).height;
|
|
125
131
|
this.setState({height});
|
|
126
|
-
return this.props.onCloseRequest(...args);
|
|
127
132
|
};
|
|
128
133
|
|
|
129
134
|
_close() {
|
|
135
|
+
this.startCloseAnimation();
|
|
130
136
|
setTimeout(() => {
|
|
131
137
|
this.props.onClose();
|
|
132
138
|
}, ANIMATION_TIME);
|
|
@@ -189,7 +195,7 @@ export default class Alert extends PureComponent {
|
|
|
189
195
|
};
|
|
190
196
|
|
|
191
197
|
render() {
|
|
192
|
-
const {type, inline, isClosing, isShaking,
|
|
198
|
+
const {type, inline, isClosing, isShaking, closeButtonClassName,
|
|
193
199
|
showWithAnimation, className, 'data-test': dataTest} = this.props;
|
|
194
200
|
|
|
195
201
|
const classes = classNames(className, {
|
|
@@ -218,7 +224,7 @@ export default class Alert extends PureComponent {
|
|
|
218
224
|
? (
|
|
219
225
|
<button
|
|
220
226
|
type="button"
|
|
221
|
-
className={styles.close}
|
|
227
|
+
className={classNames(styles.close, closeButtonClassName)}
|
|
222
228
|
data-test="alert-close"
|
|
223
229
|
aria-label="close alert"
|
|
224
230
|
onClick={this.closeRequest}
|
|
@@ -1,86 +1,59 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {render, screen} from '@testing-library/react';
|
|
3
|
+
import userEvent from '@testing-library/user-event';
|
|
3
4
|
|
|
4
5
|
import Alert from './alert';
|
|
5
6
|
import styles from './alert.css';
|
|
6
7
|
|
|
8
|
+
const TIMEOUT = 100;
|
|
7
9
|
const TICK = 500;
|
|
8
10
|
|
|
9
11
|
describe('Alert', () => {
|
|
10
|
-
const mountAlert = props => mount(<Alert {...props}/>);
|
|
11
|
-
const shallowAlert = props => shallow(<Alert {...props}/>);
|
|
12
|
-
const renderAlert = props => render(<Alert {...props}/>);
|
|
13
|
-
|
|
14
|
-
let clock;
|
|
15
|
-
beforeEach(() => {
|
|
16
|
-
clock = sandbox.useFakeTimers({toFake: ['setTimeout']});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
it('should render', () => {
|
|
20
|
-
mountAlert({}).should.have.type(Alert);
|
|
21
|
-
});
|
|
22
|
-
|
|
23
12
|
it('should render text', () => {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
type: Alert.Type.MESSAGE
|
|
27
|
-
});
|
|
28
|
-
alertComponent.should.have.text('Test message');
|
|
13
|
+
render(<Alert type={Alert.Type.MESSAGE}>{'Test message'}</Alert>);
|
|
14
|
+
screen.getByText('Test message').should.exist;
|
|
29
15
|
});
|
|
30
16
|
|
|
31
17
|
it('should transfer className', () => {
|
|
32
|
-
|
|
18
|
+
render(<Alert className="foo"/>);
|
|
19
|
+
screen.getByTestId('alert').should.have.class('foo');
|
|
33
20
|
});
|
|
34
21
|
|
|
35
22
|
it('should render component', () => {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
type: Alert.Type.MESSAGE
|
|
39
|
-
});
|
|
40
|
-
alertComponent.should.have.text('foo');
|
|
23
|
+
render(<Alert type={Alert.Type.MESSAGE}><div>{'foo'}</div></Alert>);
|
|
24
|
+
screen.getByText('foo').should.exist;
|
|
41
25
|
});
|
|
42
26
|
|
|
43
27
|
it('should render an error', () => {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
type: Alert.Type.ERROR
|
|
47
|
-
});
|
|
48
|
-
alertComponent.should.have.className(styles.error);
|
|
28
|
+
render(<Alert type={Alert.Type.ERROR}>{'Test'}</Alert>);
|
|
29
|
+
screen.getByTestId('alert').should.have.class(styles.error);
|
|
49
30
|
});
|
|
50
31
|
|
|
51
32
|
it('should be closeable if by default', () => {
|
|
52
|
-
|
|
33
|
+
render(<Alert>{'Test element'}</Alert>);
|
|
53
34
|
|
|
54
|
-
|
|
35
|
+
screen.getByRole('button', {name: 'close alert'}).should.exist;
|
|
55
36
|
});
|
|
56
37
|
|
|
57
38
|
it('should be not closeable if defined', () => {
|
|
58
|
-
|
|
59
|
-
children: 'Test element',
|
|
60
|
-
closeable: false
|
|
61
|
-
});
|
|
39
|
+
render(<Alert closeable={false}>{'Test element'}</Alert>);
|
|
62
40
|
|
|
63
|
-
|
|
41
|
+
should.not.exist(screen.queryByRole('button', {name: 'close alert'}));
|
|
64
42
|
});
|
|
65
43
|
|
|
66
44
|
it('should call onCloseRequest on click by close button', () => {
|
|
67
45
|
const closeSpy = sandbox.spy();
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
});
|
|
72
|
-
const closeElement = alertComponent.find('button[data-test="alert-close"]');
|
|
73
|
-
closeElement.simulate('click');
|
|
46
|
+
render(<Alert onCloseRequest={closeSpy}>{'Test element'}</Alert>);
|
|
47
|
+
const closeElement = screen.queryByRole('button', {name: 'close alert'});
|
|
48
|
+
userEvent.click(closeElement);
|
|
74
49
|
closeSpy.should.have.been.called;
|
|
75
50
|
});
|
|
76
51
|
|
|
77
52
|
it('should call onCloseRequest on timeout', () => {
|
|
53
|
+
const clock = sandbox.useFakeTimers({toFake: ['setTimeout']});
|
|
78
54
|
const closeSpy = sandbox.spy();
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
timeout: 100,
|
|
82
|
-
onCloseRequest: closeSpy
|
|
83
|
-
});
|
|
55
|
+
render(<Alert timeout={TIMEOUT} onCloseRequest={closeSpy}>{'Test element'}</Alert>);
|
|
56
|
+
|
|
84
57
|
clock.tick(TICK);
|
|
85
58
|
|
|
86
59
|
closeSpy.should.have.been.called;
|
|
@@ -1,22 +1,12 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import {render, screen} from '@testing-library/react';
|
|
3
3
|
|
|
4
4
|
import Alert from './alert';
|
|
5
5
|
import AlertContainer from './container';
|
|
6
6
|
|
|
7
7
|
describe('Alert Container', () => {
|
|
8
|
-
const children = <Alert>{'Test'}</Alert>;
|
|
9
|
-
|
|
10
|
-
const mountAlertContainer = props => mount(
|
|
11
|
-
<AlertContainer {...props}>{children}</AlertContainer>
|
|
12
|
-
);
|
|
13
|
-
|
|
14
|
-
it('should render alert container component', () => {
|
|
15
|
-
mountAlertContainer().should.have.type(AlertContainer);
|
|
16
|
-
});
|
|
17
|
-
|
|
18
8
|
it('should render alert container to body', () => {
|
|
19
|
-
|
|
20
|
-
|
|
9
|
+
render(<AlertContainer><Alert>{'Test'}</Alert></AlertContainer>);
|
|
10
|
+
screen.getByTestId('alert-container').should.exist;
|
|
21
11
|
});
|
|
22
12
|
});
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
@import "../global/variables.css";
|
|
2
|
+
|
|
3
|
+
@value unit from "../global/global.css";
|
|
4
|
+
@value animation-duration: 300ms;
|
|
5
|
+
@value animation-easing: ease-out;
|
|
6
|
+
|
|
7
|
+
.customAlert {
|
|
8
|
+
background: var(--ring-main-color);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
.closeButton,
|
|
12
|
+
.closeButton:hover {
|
|
13
|
+
color: var(--ring-dark-text-color);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
.closeButton:hover {
|
|
17
|
+
opacity: 0.8;
|
|
18
|
+
}
|
|
@@ -2,6 +2,8 @@ import React from 'react';
|
|
|
2
2
|
|
|
3
3
|
import reactDecorator from '../../.storybook/react-decorator';
|
|
4
4
|
|
|
5
|
+
import styles from './alert-service.examples.css';
|
|
6
|
+
|
|
5
7
|
import Button from '@jetbrains/ring-ui/components/button/button';
|
|
6
8
|
import ButtonToolbar from '@jetbrains/ring-ui/components/button-toolbar/button-toolbar';
|
|
7
9
|
|
|
@@ -26,6 +28,7 @@ export const alertService = () => {
|
|
|
26
28
|
setTimeout(() => {
|
|
27
29
|
alert.message('A initial message', MSG_TIMEOUT);
|
|
28
30
|
alert.error('Error message');
|
|
31
|
+
this.showCustomMessage();
|
|
29
32
|
});
|
|
30
33
|
}
|
|
31
34
|
|
|
@@ -33,6 +36,21 @@ export const alertService = () => {
|
|
|
33
36
|
alert._getShowingAlerts().forEach(item => alert.removeWithoutAnimation(item.key));
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
showCustomMessage = () => {
|
|
40
|
+
this.lastKey = alert.addAlert(
|
|
41
|
+
<div className={styles.customAlert}>
|
|
42
|
+
<h1>Hello!</h1>
|
|
43
|
+
<p>{'This is a custom message'}</p>
|
|
44
|
+
</div>,
|
|
45
|
+
null,
|
|
46
|
+
0,
|
|
47
|
+
{
|
|
48
|
+
className: styles.customAlert,
|
|
49
|
+
closeButtonClassName: styles.closeButton
|
|
50
|
+
}
|
|
51
|
+
);
|
|
52
|
+
};
|
|
53
|
+
|
|
36
54
|
showError = () => {
|
|
37
55
|
this.lastKey = alert.error('Something wrong happened');
|
|
38
56
|
};
|
|
@@ -59,6 +77,9 @@ export const alertService = () => {
|
|
|
59
77
|
<Button onClick={this.showMessage} primary>
|
|
60
78
|
Show message
|
|
61
79
|
</Button>
|
|
80
|
+
<Button onClick={this.showCustomMessage}>
|
|
81
|
+
Show custom message
|
|
82
|
+
</Button>
|
|
62
83
|
<Button onClick={this.showRandomWarning}>Show warning</Button>
|
|
63
84
|
<Button onClick={this.removeLastAlert}>Remove last alert</Button>
|
|
64
85
|
</ButtonToolbar>
|
|
@@ -76,7 +76,8 @@ class AlertService {
|
|
|
76
76
|
}, ANIMATION_TIME);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
-
addAlert(message, type, timeout = this.defaultTimeout,
|
|
79
|
+
addAlert(message, type, timeout = this.defaultTimeout, options = {}) {
|
|
80
|
+
const {onCloseRequest, onClose, ...restOptions} = options;
|
|
80
81
|
const sameAlert = this.findSameAlert(message, type);
|
|
81
82
|
if (sameAlert) {
|
|
82
83
|
sameAlert.isShaking = true;
|
|
@@ -91,8 +92,14 @@ class AlertService {
|
|
|
91
92
|
type,
|
|
92
93
|
timeout,
|
|
93
94
|
isClosing: false,
|
|
94
|
-
onCloseRequest: () =>
|
|
95
|
-
|
|
95
|
+
onCloseRequest: () => {
|
|
96
|
+
onCloseRequest && onCloseRequest();
|
|
97
|
+
this.startAlertClosing(alert);
|
|
98
|
+
},
|
|
99
|
+
onClose: () => {
|
|
100
|
+
onClose && onClose();
|
|
101
|
+
this.removeWithoutAnimation(alert.key);
|
|
102
|
+
},
|
|
96
103
|
...restOptions
|
|
97
104
|
};
|
|
98
105
|
|
|
@@ -24,9 +24,9 @@ export default class AnalyticsFUSPlugin {
|
|
|
24
24
|
return;
|
|
25
25
|
}
|
|
26
26
|
((i, s, o, g, r) => {
|
|
27
|
-
i[r] = i[r] || (
|
|
27
|
+
i[r] = i[r] || function addArgumentsToQueueForWaitingTheScriptLoading() {
|
|
28
28
|
(i[r].query = i[r].query || []).push(arguments);
|
|
29
|
-
}
|
|
29
|
+
};
|
|
30
30
|
const script = document.createElement(o);
|
|
31
31
|
script.async = true;
|
|
32
32
|
script.src = g;
|
|
@@ -39,7 +39,7 @@ export default class AnalyticsFUSPlugin {
|
|
|
39
39
|
recorderVersion,
|
|
40
40
|
productCode: productId,
|
|
41
41
|
productBuild,
|
|
42
|
-
internal:
|
|
42
|
+
internal: isDevelopment,
|
|
43
43
|
groups,
|
|
44
44
|
useForSubdomains: true
|
|
45
45
|
});
|
|
@@ -10,9 +10,9 @@ export default class AnalyticsGAPlugin {
|
|
|
10
10
|
}
|
|
11
11
|
((i, s, o, g, r) => {
|
|
12
12
|
i.GoogleAnalyticsObject = r;
|
|
13
|
-
i[r] = i[r] || (
|
|
13
|
+
i[r] = i[r] || function addArgumentsToQueueForWaitingTheScriptLoading() {
|
|
14
14
|
(i[r].q = i[r].q || []).push(arguments);
|
|
15
|
-
}
|
|
15
|
+
};
|
|
16
16
|
i[r].l = 1 * new Date();
|
|
17
17
|
const a = s.createElement(o);
|
|
18
18
|
const m = s.getElementsByTagName(o)[0];
|
|
@@ -203,7 +203,8 @@ describe('Auth', () => {
|
|
|
203
203
|
redirectUri: 'http://localhost:8080/hub',
|
|
204
204
|
clientId: '1-1-1-1-1',
|
|
205
205
|
scope: ['0-0-0-0-0', 'youtrack'],
|
|
206
|
-
optionalScopes: ['youtrack']
|
|
206
|
+
optionalScopes: ['youtrack'],
|
|
207
|
+
waitForRedirectTimeout: 0
|
|
207
208
|
});
|
|
208
209
|
try {
|
|
209
210
|
await auth.init();
|
|
@@ -226,7 +227,8 @@ describe('Auth', () => {
|
|
|
226
227
|
auth = new Auth({
|
|
227
228
|
serverUri: '',
|
|
228
229
|
redirect: true,
|
|
229
|
-
cleanHash: true
|
|
230
|
+
cleanHash: true,
|
|
231
|
+
waitForRedirectTimeout: 0
|
|
230
232
|
});
|
|
231
233
|
|
|
232
234
|
try {
|
|
@@ -245,7 +247,8 @@ describe('Auth', () => {
|
|
|
245
247
|
auth = new Auth({
|
|
246
248
|
serverUri: '',
|
|
247
249
|
redirect: true,
|
|
248
|
-
cleanHash: true
|
|
250
|
+
cleanHash: true,
|
|
251
|
+
waitForRedirectTimeout: 0
|
|
249
252
|
});
|
|
250
253
|
|
|
251
254
|
try {
|
|
@@ -265,7 +268,8 @@ describe('Auth', () => {
|
|
|
265
268
|
auth = new Auth({
|
|
266
269
|
serverUri: '',
|
|
267
270
|
redirect: true,
|
|
268
|
-
cleanHash: false
|
|
271
|
+
cleanHash: false,
|
|
272
|
+
waitForRedirectTimeout: 0
|
|
269
273
|
});
|
|
270
274
|
|
|
271
275
|
try {
|
|
@@ -284,7 +288,8 @@ describe('Auth', () => {
|
|
|
284
288
|
serverUri: '',
|
|
285
289
|
redirect: true,
|
|
286
290
|
redirectUri: 'http://localhost:8080/hub',
|
|
287
|
-
requestCredentials: 'skip'
|
|
291
|
+
requestCredentials: 'skip',
|
|
292
|
+
waitForRedirectTimeout: 0
|
|
288
293
|
});
|
|
289
294
|
try {
|
|
290
295
|
await auth.init();
|
|
@@ -313,7 +318,8 @@ describe('Auth', () => {
|
|
|
313
318
|
redirectUri: 'http://localhost:8080/hub',
|
|
314
319
|
clientId: '1-1-1-1-1',
|
|
315
320
|
scope: ['0-0-0-0-0', 'youtrack'],
|
|
316
|
-
optionalScopes: ['youtrack']
|
|
321
|
+
optionalScopes: ['youtrack'],
|
|
322
|
+
waitForRedirectTimeout: 0
|
|
317
323
|
});
|
|
318
324
|
|
|
319
325
|
auth._storage._tokenStorage = auth._storage._stateStorage =
|
|
@@ -330,7 +336,8 @@ describe('Auth', () => {
|
|
|
330
336
|
auth._storage.saveToken({
|
|
331
337
|
accessToken: 'token',
|
|
332
338
|
expires: TokenValidator._epoch() + HOUR,
|
|
333
|
-
scopes: ['0-0-0-0-0']
|
|
339
|
+
scopes: ['0-0-0-0-0'],
|
|
340
|
+
waitForRedirectTimeout: 0
|
|
334
341
|
});
|
|
335
342
|
});
|
|
336
343
|
|
|
@@ -14,6 +14,7 @@ export const DEFAULT_EXPIRES_TIMEOUT = 40 * 60;
|
|
|
14
14
|
export const DEFAULT_BACKGROUND_TIMEOUT = 10 * 1000;
|
|
15
15
|
const DEFAULT_BACKEND_CHECK_TIMEOUT = 10 * 1000;
|
|
16
16
|
const BACKGROUND_REDIRECT_TIMEOUT = 20 * 1000;
|
|
17
|
+
const DEFAULT_WAIT_FOR_REDIRECT_TIMEOUT = 5 * 1000;
|
|
17
18
|
/* eslint-enable no-magic-numbers */
|
|
18
19
|
|
|
19
20
|
export const USER_CHANGED_EVENT = 'userChange';
|
|
@@ -46,6 +47,7 @@ const DEFAULT_CONFIG = {
|
|
|
46
47
|
onBackendDown: () => {},
|
|
47
48
|
|
|
48
49
|
defaultExpiresIn: DEFAULT_EXPIRES_TIMEOUT,
|
|
50
|
+
waitForRedirectTimeout: DEFAULT_WAIT_FOR_REDIRECT_TIMEOUT,
|
|
49
51
|
translations: {
|
|
50
52
|
login: 'Log in',
|
|
51
53
|
loginTo: 'Log in to %serviceName%',
|
|
@@ -231,7 +233,7 @@ export default class Auth {
|
|
|
231
233
|
* that should be restored after returning back from auth server.
|
|
232
234
|
*/
|
|
233
235
|
async init() {
|
|
234
|
-
this._storage.onTokenChange(token => {
|
|
236
|
+
this._storage.onTokenChange(async token => {
|
|
235
237
|
const isGuest = this.user ? this.user.guest : false;
|
|
236
238
|
|
|
237
239
|
if (isGuest && !token) {
|
|
@@ -241,7 +243,13 @@ export default class Auth {
|
|
|
241
243
|
if (!token) {
|
|
242
244
|
this.logout();
|
|
243
245
|
} else {
|
|
244
|
-
|
|
246
|
+
try {
|
|
247
|
+
await this._detectUserChange(token.accessToken);
|
|
248
|
+
} catch (error) {
|
|
249
|
+
if (this._canShowDialogs()) {
|
|
250
|
+
this._showAuthDialog({nonInteractive: true, error});
|
|
251
|
+
}
|
|
252
|
+
}
|
|
245
253
|
}
|
|
246
254
|
});
|
|
247
255
|
|
|
@@ -305,6 +313,11 @@ export default class Auth {
|
|
|
305
313
|
const authRequest = await this._requestBuilder.prepareAuthRequest();
|
|
306
314
|
this._redirectCurrentPage(authRequest.url);
|
|
307
315
|
|
|
316
|
+
// HUB-10867 Since we already redirecting the page, there is no actual need to throw an error
|
|
317
|
+
// and scare user with flashing error
|
|
318
|
+
// But let's keep it just in case redirect was not successful
|
|
319
|
+
await new Promise(resolve => setTimeout(resolve, this.config.waitForRedirectTimeout));
|
|
320
|
+
|
|
308
321
|
throw error;
|
|
309
322
|
}
|
|
310
323
|
|
|
@@ -466,35 +479,31 @@ export default class Auth {
|
|
|
466
479
|
async _detectUserChange(accessToken) {
|
|
467
480
|
const windowWasOpen = this._isLoginWindowOpen;
|
|
468
481
|
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
};
|
|
482
|
+
const user = await this.getUser(accessToken);
|
|
483
|
+
const onApply = () => {
|
|
484
|
+
this.user = user;
|
|
485
|
+
this.listeners.trigger(USER_CHANGED_EVENT, user);
|
|
486
|
+
};
|
|
475
487
|
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
488
|
+
if (user && this.user && this.user.id !== user.id) {
|
|
489
|
+
if (!this._canShowDialogs() || this.user.guest || windowWasOpen) {
|
|
490
|
+
onApply();
|
|
491
|
+
return;
|
|
492
|
+
}
|
|
493
|
+
if (user.guest) {
|
|
494
|
+
this._showAuthDialog({nonInteractive: true});
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
485
497
|
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
498
|
+
await this._showUserChangedDialog({
|
|
499
|
+
newUser: user,
|
|
500
|
+
onApply,
|
|
501
|
+
onPostpone: () => {
|
|
502
|
+
this.listeners.trigger(USER_CHANGE_POSTPONED_EVENT);
|
|
503
|
+
this.config.onPostponeChangedUser(this.user, user);
|
|
504
|
+
}
|
|
505
|
+
});
|
|
494
506
|
|
|
495
|
-
}
|
|
496
|
-
} catch (e) {
|
|
497
|
-
// noop
|
|
498
507
|
}
|
|
499
508
|
}
|
|
500
509
|
|
|
@@ -554,7 +563,7 @@ export default class Auth {
|
|
|
554
563
|
loginToCaption: translations.loginTo,
|
|
555
564
|
confirmLabel: translations.login,
|
|
556
565
|
cancelLabel: cancelable ? translations.cancel : translations.postpone,
|
|
557
|
-
errorMessage: error
|
|
566
|
+
errorMessage: this._extractErrorMessage(error, true),
|
|
558
567
|
onConfirm,
|
|
559
568
|
onCancel
|
|
560
569
|
});
|
|
@@ -601,6 +610,29 @@ export default class Auth {
|
|
|
601
610
|
});
|
|
602
611
|
}
|
|
603
612
|
|
|
613
|
+
_extractErrorMessage(error, logError = false) {
|
|
614
|
+
if (!error) {
|
|
615
|
+
return null;
|
|
616
|
+
}
|
|
617
|
+
if (logError) {
|
|
618
|
+
// eslint-disable-next-line no-console
|
|
619
|
+
console.error('RingUI Auth error', error);
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
try {
|
|
623
|
+
// We've got some error from this list
|
|
624
|
+
// https://www.jetbrains.com/help/youtrack/devportal/OAuth-2.0-Errors.html
|
|
625
|
+
if (error.code && typeof error.code.code === 'string') {
|
|
626
|
+
const readableCode = error.code.code.split('_').join(' ');
|
|
627
|
+
return `Authorization error: ${readableCode}`;
|
|
628
|
+
}
|
|
629
|
+
} catch {
|
|
630
|
+
// noop
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
return error.toString ? error.toString() : null;
|
|
634
|
+
}
|
|
635
|
+
|
|
604
636
|
_showBackendDownDialog(backendError) {
|
|
605
637
|
const {onBackendDown, translations} = this.config;
|
|
606
638
|
const REPEAT_TIMEOUT = 5000;
|
|
@@ -711,11 +743,10 @@ export default class Auth {
|
|
|
711
743
|
}
|
|
712
744
|
|
|
713
745
|
async switchUser() {
|
|
714
|
-
if (this.config.embeddedLogin) {
|
|
715
|
-
|
|
746
|
+
if (!this.config.embeddedLogin) {
|
|
747
|
+
throw new Error('Auth: switchUser only supported for "embeddedLogin" mode');
|
|
716
748
|
}
|
|
717
|
-
|
|
718
|
-
throw new Error('Auth: switchUser only supported for "embeddedLogin" mode');
|
|
749
|
+
await this._runEmbeddedLogin();
|
|
719
750
|
}
|
|
720
751
|
|
|
721
752
|
/**
|