Skip to content

Commit 23bd230

Browse files
committed
Finalizing pipeline submission (making global adjustments to submit button and submit error reporting).
1 parent 4bf684b commit 23bd230

File tree

14 files changed

+367
-131
lines changed

14 files changed

+367
-131
lines changed

src/components/Pipelines/BoxesTable/BoxesTable.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import PropTypes from 'prop-types';
33
import { injectIntl, FormattedMessage } from 'react-intl';
44
import { Table } from 'react-bootstrap';
55
import { defaultMemoize } from 'reselect';
6+
import classnames from 'classnames';
67

78
import BoxesTableRow from './BoxesTableRow';
89
import { arrayToObject } from '../../../helpers/common';
@@ -23,13 +24,19 @@ const BoxesTable = ({
2324
secondarySelections = null,
2425
selectedVariable = null,
2526
removeBox = null,
27+
pending = false,
2628
intl: { locale },
2729
...rowProps
2830
}) => {
2931
const selectionIndex = secondarySelections && prepareSelectionIndex(secondarySelections);
3032
const variable = selectedVariable && variables && variables.find(v => v.name === selectedVariable);
3133
return (
32-
<Table className={boxes.length > 0 ? 'tbody-hover' : ''} size="sm">
34+
<Table
35+
className={classnames({
36+
'half-opaque': pending,
37+
'tbody-hover': boxes.length > 0 && !pending,
38+
})}
39+
size="sm">
3340
<thead>
3441
<tr>
3542
<th>
@@ -62,6 +69,7 @@ const BoxesTable = ({
6269
selectedVariable={variable}
6370
secondarySelections={selectionIndex}
6471
removeBox={removeBox}
72+
pending={pending}
6573
{...rowProps}
6674
/>
6775
))}
@@ -90,6 +98,7 @@ BoxesTable.propTypes = {
9098
removeBox: PropTypes.func,
9199
secondarySelections: PropTypes.array,
92100
selectedVariable: PropTypes.string,
101+
pending: PropTypes.bool,
93102
intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired,
94103
};
95104

src/components/Pipelines/BoxesTable/BoxesTableRow.js

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -219,6 +219,7 @@ const BoxesTableRow = ({
219219
editBox = null,
220220
removeBox = null,
221221
assignVariable = null,
222+
pending = false,
222223
}) => {
223224
const [firstPort, ...ports] = [
224225
...transformPorts(box, 'portsIn', boxTypes),
@@ -229,10 +230,10 @@ const BoxesTableRow = ({
229230

230231
return (
231232
<tbody
232-
onClick={selectBox ? () => selectBox(box.name) : null}
233-
onDoubleClick={editBox ? () => editBox(box.name) : null}
233+
onClick={selectBox && !pending ? () => selectBox(box.name) : null}
234+
onDoubleClick={editBox && !pending ? () => editBox(box.name) : null}
234235
className={classnames({
235-
clickable: editBox || selectBox,
236+
clickable: (editBox || selectBox) && !pending,
236237
[styles.primarySelection]: primarySelection === box.name,
237238
[styles.secondarySelection]: secondarySelections && secondarySelections[box.name],
238239
})}>
@@ -282,7 +283,9 @@ const BoxesTableRow = ({
282283
timid
283284
onClick={ev => {
284285
ev.stopPropagation();
285-
removeBox(box.name);
286+
if (!pending) {
287+
removeBox(box.name);
288+
}
286289
}}
287290
/>
288291
</td>
@@ -314,6 +317,7 @@ BoxesTableRow.propTypes = {
314317
editBox: PropTypes.func,
315318
removeBox: PropTypes.func,
316319
assignVariable: PropTypes.func,
320+
pending: PropTypes.bool,
317321
};
318322

319323
export default BoxesTableRow;

src/components/Pipelines/PipelineGraph/PipelineGraph.js

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ const PipelineGraph = ({
250250
editBox = null,
251251
selectVariable = null,
252252
editVariable = null,
253+
pending = false,
253254
}) => {
254255
if (canUseDOM) {
255256
const [svg, setSvg] = useState(null);
@@ -271,7 +272,7 @@ const PipelineGraph = ({
271272
}, [boxes, variables, utilization, selectedBox, selectedVariable]);
272273

273274
return (
274-
<InsetPanel className="m-0 p-0">
275+
<InsetPanel className={`m-0 p-0 ${pending ? 'half-opaque' : ''}`}>
275276
{canUseDOM && svg ? (
276277
<div
277278
className={styles.pipelineGraph}
@@ -280,14 +281,18 @@ const PipelineGraph = ({
280281
}}
281282
onContextMenu={ev => {
282283
ev.preventDefault();
283-
const { box, variable } = preprocessClickEvent(ev, boxIds, variableIds);
284-
box && selectBox && selectBox(box);
285-
variable && selectVariable && selectVariable(variable);
284+
if (!pending) {
285+
const { box, variable } = preprocessClickEvent(ev, boxIds, variableIds);
286+
box && selectBox && selectBox(box);
287+
variable && selectVariable && selectVariable(variable);
288+
}
286289
}}
287290
onClick={ev => {
288-
const { box, variable } = preprocessClickEvent(ev, boxIds, variableIds);
289-
box && editBox && editBox(box);
290-
variable && editVariable && editVariable(variable);
291+
if (!pending) {
292+
const { box, variable } = preprocessClickEvent(ev, boxIds, variableIds);
293+
box && editBox && editBox(box);
294+
variable && editVariable && editVariable(variable);
295+
}
291296
}}
292297
/>
293298
) : (
@@ -312,6 +317,7 @@ PipelineGraph.propTypes = {
312317
editBox: PropTypes.func,
313318
selectVariable: PropTypes.func,
314319
editVariable: PropTypes.func,
320+
pending: PropTypes.bool,
315321
};
316322

317323
export default PipelineGraph;

src/components/Pipelines/VariablesTable/VariablesTable.js

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,13 @@ const VariablesTable = ({
2828
selectVariable = null,
2929
editVariable = null,
3030
removeVariable = null,
31+
pending = false,
3132
intl: { locale },
3233
}) => {
3334
const secondarySelectionsIndexed = secondarySelections && prepareSelectionIndex(secondarySelections);
3435

3536
return (
36-
<Table hover={variables.length > 0} size="sm">
37+
<Table hover={variables.length > 0 && !pending} className={pending ? 'half-opaque' : ''} size="sm">
3738
<thead>
3839
<tr>
3940
{utilization && <th />}
@@ -58,10 +59,10 @@ const VariablesTable = ({
5859
return (
5960
<tr
6061
key={`${variable.name}-${portsIn}-${portsOut}`}
61-
onClick={selectVariable ? () => selectVariable(variable.name) : null}
62-
onDoubleClick={editVariable ? () => editVariable(variable.name) : null}
62+
onClick={selectVariable && !pending ? () => selectVariable(variable.name) : null}
63+
onDoubleClick={editVariable && !pending ? () => editVariable(variable.name) : null}
6364
className={classnames({
64-
clickable: selectVariable || editVariable,
65+
clickable: (selectVariable || editVariable) && !pending,
6566
[styles.primarySelection]: primarySelection === variable.name,
6667
[styles.secondarySelection]: secondarySelectionsIndexed && secondarySelectionsIndexed[variable.name],
6768
})}>
@@ -158,7 +159,9 @@ const VariablesTable = ({
158159
timid
159160
onClick={ev => {
160161
ev.stopPropagation();
161-
removeVariable(variable.name);
162+
if (!pending) {
163+
removeVariable(variable.name);
164+
}
162165
}}
163166
/>
164167
</td>
@@ -192,6 +195,7 @@ VariablesTable.propTypes = {
192195
selectVariable: PropTypes.func,
193196
editVariable: PropTypes.func,
194197
removeVariable: PropTypes.func,
198+
pending: PropTypes.bool,
195199
intl: PropTypes.shape({ locale: PropTypes.string.isRequired }).isRequired,
196200
};
197201

src/components/forms/EditPipelineForm/EditPipelineForm.js

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { Container, Row, Col } from 'react-bootstrap';
66

77
import { TextField, MarkdownTextAreaField, CheckboxField } from '../Fields';
88
import { SaveIcon } from '../../icons';
9-
import Callout from '../../widgets/Callout';
109
import FormBox from '../../widgets/FormBox';
1110
import SubmitButton from '../SubmitButton';
1211

@@ -45,12 +44,6 @@ class EditPipelineForm extends Component {
4544
/>
4645
</div>
4746
}>
48-
{submitFailed && (
49-
<Callout variant="danger">
50-
<FormattedMessage id="generic.savingFailed" defaultMessage="Saving failed. Please try again later." />
51-
</Callout>
52-
)}
53-
5447
<Container fluid>
5548
<Row>
5649
<Col lg={12}>

src/components/forms/SubmitButton/SubmitButton.js

Lines changed: 73 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
import React, { Component } from 'react';
22
import PropTypes from 'prop-types';
3-
import { FormattedMessage } from 'react-intl';
3+
import { FormattedMessage, injectIntl } from 'react-intl';
4+
import { Popover, OverlayTrigger } from 'react-bootstrap';
45
import { defaultMemoize } from 'reselect';
56

67
import Button from '../../widgets/TheButton';
78
import { SendIcon, LoadingIcon, SuccessIcon, WarningIcon } from '../../icons';
89
import Confirm from '../Confirm';
10+
import { getErrorMessage } from '../../../locales/apiErrorMessages';
911

1012
const getIcons = defaultMemoize(defaultIcon => ({
1113
submit: defaultIcon || <SendIcon gapRight />,
@@ -33,7 +35,7 @@ const getMessages = defaultMemoize(messages => {
3335
});
3436

3537
class SubmitButton extends Component {
36-
state = { saved: false };
38+
state = { saved: false, lastError: null };
3739

3840
componentWillUnmount() {
3941
this.unmounted = true;
@@ -46,22 +48,32 @@ class SubmitButton extends Component {
4648

4749
submit = () => {
4850
const { handleSubmit, onSubmit = null } = this.props;
51+
52+
// reset button internal state
53+
this.setState({ saved: false, lastError: null });
54+
4955
onSubmit && onSubmit();
50-
return handleSubmit().then(res => {
51-
if (!this.unmounted) {
52-
this.setState({ saved: true });
53-
this.resetAfterSomeTime = setTimeout(this.reset, 2000);
54-
} else {
55-
const { reset } = this.props;
56-
reset && reset(); // the redux form must be still reset
56+
57+
return handleSubmit().then(
58+
res => {
59+
if (!this.unmounted) {
60+
this.setState({ saved: true });
61+
this.resetAfterSomeTime = setTimeout(this.reset, 2000);
62+
} else {
63+
const { reset } = this.props;
64+
reset && reset(); // the redux form must be still reset
65+
}
66+
return res;
67+
},
68+
lastError => {
69+
this.setState({ lastError });
5770
}
58-
return res;
59-
});
71+
);
6072
};
6173

6274
reset = () => {
6375
const { reset } = this.props;
64-
this.setState({ saved: false });
76+
this.setState({ saved: false, lastError: null });
6577
reset && reset();
6678
};
6779

@@ -74,32 +86,68 @@ class SubmitButton extends Component {
7486
return 'submit';
7587
};
7688

89+
getButtonVariant() {
90+
const { submitting, hasFailed, invalid } = this.props;
91+
return hasFailed && !submitting
92+
? 'danger'
93+
: this.state.saved || submitting
94+
? 'success'
95+
: invalid
96+
? 'warning'
97+
: 'success';
98+
}
99+
100+
isButtonDisabled() {
101+
const { submitting, invalid, asyncValidating = false, disabled = false } = this.props;
102+
return invalid || asyncValidating !== false || submitting || disabled;
103+
}
104+
77105
render() {
78106
const {
79-
submitting,
107+
id,
80108
hasFailed,
81-
invalid,
82-
asyncValidating = false,
109+
confirmQuestion = '',
83110
noIcons = false,
84111
defaultIcon = null,
85-
disabled = false,
86-
confirmQuestion = '',
87112
noShadow = false,
88113
messages = {},
114+
intl: { formatMessage },
89115
} = this.props;
90-
const { saved: hasSucceeded } = this.state;
91116

92117
const buttonState = this.getButtonState();
93118
const icons = getIcons(defaultIcon);
94119
const formattedMessages = getMessages(messages);
95120

96-
return (
97-
<Confirm id="confirm-submit" onConfirmed={this.submit} question={confirmQuestion} disabled={!confirmQuestion}>
121+
return hasFailed && this.state.lastError ? (
122+
<OverlayTrigger
123+
placement="top"
124+
overlay={
125+
<Popover id={`error-popover-${id}`}>
126+
<Popover.Title className="bg-danger">
127+
<FormattedMessage id="app.submitButton.lastError.title" defaultMessage="An error occured" />
128+
</Popover.Title>
129+
<Popover.Content className="text-center">
130+
{getErrorMessage(formatMessage)(this.state.lastError)}
131+
</Popover.Content>
132+
</Popover>
133+
}>
98134
<Button
99135
type="submit"
100-
variant={hasSucceeded ? 'success' : hasFailed ? 'danger' : invalid ? 'warning' : 'success'}
101-
disabled={invalid || asyncValidating !== false || submitting || disabled}
102-
noShadow={noShadow}>
136+
variant={this.getButtonVariant()}
137+
disabled={this.isButtonDisabled()}
138+
noShadow={noShadow}
139+
onClick={this.submit}>
140+
{!noIcons && icons[buttonState]}
141+
{formattedMessages[buttonState]}
142+
</Button>
143+
</OverlayTrigger>
144+
) : (
145+
<Confirm
146+
id={`confirm-submit-${id}`}
147+
onConfirmed={this.submit}
148+
question={confirmQuestion}
149+
disabled={!confirmQuestion}>
150+
<Button type="submit" variant={this.getButtonVariant()} disabled={this.isButtonDisabled()} noShadow={noShadow}>
103151
{!noIcons && icons[buttonState]}
104152
{formattedMessages[buttonState]}
105153
</Button>
@@ -112,7 +160,6 @@ SubmitButton.propTypes = {
112160
id: PropTypes.string.isRequired,
113161
handleSubmit: PropTypes.func.isRequired,
114162
submitting: PropTypes.bool,
115-
hasSucceeded: PropTypes.bool,
116163
hasFailed: PropTypes.bool,
117164
asyncValidating: PropTypes.oneOfType([PropTypes.bool, PropTypes.string]),
118165
noIcons: PropTypes.bool,
@@ -129,6 +176,7 @@ SubmitButton.propTypes = {
129176
}),
130177
confirmQuestion: PropTypes.oneOfType([PropTypes.string, PropTypes.element]),
131178
noShadow: PropTypes.bool,
179+
intl: PropTypes.object,
132180
};
133181

134-
export default SubmitButton;
182+
export default injectIntl(SubmitButton);

0 commit comments

Comments
 (0)