• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

geosolutions-it / MapStore2 / 15422327504

03 Jun 2025 04:08PM UTC coverage: 76.952% (-0.04%) from 76.993%
15422327504

Pull #11024

github

web-flow
Merge 2ddc9a6d7 into 2dbe8dab2
Pull Request #11024: Update User Guide - Upload image on Text Widget

31021 of 48282 branches covered (64.25%)

38629 of 50199 relevant lines covered (76.95%)

36.23 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

68.35
/web/client/components/contextcreator/ConfigurePluginsStep.jsx
1
/*
2
 * Copyright 2019, GeoSolutions Sas.
3
 * All rights reserved.
4
 *
5
 * This source code is licensed under the BSD-style license found in the
6
 * LICENSE file in the root directory of this source tree.
7
*/
8

9
import React from 'react';
10
import {compose, withState, lifecycle, getContext} from 'recompose';
11
import {get} from 'lodash';
12
import {Glyphicon, Tooltip, OverlayTrigger, Alert} from 'react-bootstrap';
13
import CodeMirror from '../../libs/codemirror/react-codemirror-suspense';
14

15
import Button from '../misc/Button';
16
import Transfer from '../misc/transfer/Transfer';
17
import ResizableModal from '../misc/ResizableModal';
18
import ToolbarButton from '../misc/toolbar/ToolbarButton';
19
import Message from '../I18N/Message';
20
import ConfigureMapTemplates from './ConfigureMapTemplates';
21
import tutorialEnhancer from './enhancers/tutorialEnhancer';
22

23
import Dropzone from 'react-dropzone';
24
import Spinner from "react-spinkit";
25

26
import {getMessageById} from '../../utils/LocaleUtils';
27
import PropTypes from 'prop-types';
28
import ConfirmModal from '../resources/modals/ConfirmModal';
29

30
import {ERROR, checkZipBundle} from '../../utils/ExtensionsUtils';
31
import Modal from "../misc/Modal";
32

33
const getEnabledTools = (plugin, isMandatory, editedPlugin, documentationBaseURL, onEditPlugin,
1✔
34
    onShowDialog, changePluginsKey, hideUploadExtension) => {
35
    return [{
1✔
36
        visible: plugin.name === 'MapTemplates',
37
        glyph: '1-map',
38
        tooltipId: 'contextCreator.configurePlugins.tooltips.mapTemplatesConfig',
39
        onClick: () => onShowDialog('mapTemplatesConfig', true)
×
40
    }, {
41
        visible: !isMandatory && !plugin.denyUserSelection,
2✔
42
        glyph: '1-user-mod',
43
        tooltipId: plugin.isUserPlugin ?
1!
44
            'contextCreator.configurePlugins.tooltips.disableUserPlugin' :
45
            'contextCreator.configurePlugins.tooltips.enableUserPlugin',
46
        bsStyle: plugin.isUserPlugin ? 'success' : undefined,
1!
47
        onClick: () => changePluginsKey([plugin.name], 'isUserPlugin', !plugin.isUserPlugin)
×
48
    }, {
49
        visible: plugin.isUserPlugin && !plugin.denyUserSelection,
1!
50
        glyph: plugin.active ? 'check' : 'unchecked',
1!
51
        tooltipId: plugin.active ?
1!
52
            'contextCreator.configurePlugins.tooltips.deactivatePlugin' :
53
            'contextCreator.configurePlugins.tooltips.activatePlugin',
54
        onClick: () => changePluginsKey([plugin.name], 'active', !plugin.active)
×
55
    }, {
56
        glyph: 'wrench',
57
        tooltipId: 'contextCreator.configurePlugins.tooltips.editConfiguration',
58
        active: plugin.name === editedPlugin,
59
        onClick: () => onEditPlugin(plugin.name === editedPlugin ? undefined : plugin.name)
×
60
    }, {
61
        visible: !!(plugin.docUrl || documentationBaseURL),
1!
62
        glyph: 'question-sign',
63
        tooltipId: 'contextCreator.configurePlugins.tooltips.pluginDocumentation',
64
        Element: (props) =>
65
            (<a target="_blank" rel="noopener noreferrer"
1✔
66
                href={plugin.docUrl || documentationBaseURL && documentationBaseURL + '#plugins.' + (plugin.docName || plugin.name)}>
1!
67
                <ToolbarButton {...props}/>
68
            </a>)
69
    }, {
70
        visible: !!(!hideUploadExtension && plugin.isExtension),
2✔
71
        glyph: 'trash',
72
        tooltipId: 'contextCreator.configurePlugins.tooltips.removePlugin',
73
        onClick: () => onShowDialog('confirmRemovePlugin', true, plugin.name)
×
74
    }];
75
};
76

77
const getAvailableTools = (plugin, onShowDialog, hideUploadExtension) => {
1✔
78
    return [{
12✔
79
        visible: !!(!hideUploadExtension && plugin.isExtension),
23✔
80
        glyph: 'trash',
81
        tooltipId: 'contextCreator.configurePlugins.tooltips.removePlugin',
82
        onClick: () => onShowDialog('confirmRemovePlugin', true, plugin.name)
1✔
83
    }];
84
};
85

86
/**
87
 * Converts plugin objects to Transform items
88
 * @param {string} editedPlugin currently edited plugin
89
 * @param {string} editedCfg text of a configuration of currently edited plugin
90
 * @param {object} cfgError object describing current cfg editing error
91
 * @param {function} setEditor editor instance setter
92
 * @param {string} documentationBaseURL base url for plugin documentation
93
 * @param {boolean} showDescriptionTooltip show a tooltip when hovering over plugin's description
94
 * @param {number} descriptionTooltipDelay description tooltip show delay
95
 * @param {function} onEditPlugin edit plugin configuration callback
96
 * @param {function} onEnablePlugins enable plugins callback
97
 * @param {function} onDisablePlugins disable plugins callback
98
 * @param {function} onUpdateCfg update currently edited configuration callback
99
 * @param {function} onShowMapTemplatesConfig
100
 * @param {function} changePluginsKey callback to change properties of plugin objects
101
 * @param {boolean} isRoot true if plugin objects in plugins argument are at the root level of a tree hierarchy
102
 * @param {object[]} plugins plugin objects to convert
103
 * @param {boolean} processChildren if true this function will recursively convert the children
104
 * @param {boolean} parentIsEnabled true if 'enabled' property of parent plugin object is true
105
 */
106
const pluginsToItems = ({
1✔
107
    editedPlugin,
108
    editedCfg,
109
    cfgError,
110
    setEditor,
111
    documentationBaseURL,
112
    showDescriptionTooltip,
113
    descriptionTooltipDelay,
114
    onEditPlugin,
115
    onEnablePlugins,
116
    onDisablePlugins,
117
    onUpdateCfg,
118
    onShowDialog,
119
    changePluginsKey,
120
    isRoot,
121
    hideUploadExtension,
122
    plugins = [],
13✔
123
    processChildren,
124
    parentIsEnabled
125
}) =>
126
    plugins.filter(plugin => !plugin.hidden).map(plugin => {
67✔
127
        const enableTools = (isRoot || parentIsEnabled);
13!
128
        const isMandatory = plugin.forcedMandatory || plugin.mandatory;
13✔
129
        return {
13✔
130
            id: plugin.name,
131
            title: plugin.title || plugin.label || plugin.name,
13!
132
            cardSize: 'sm',
133
            description: plugin.description || 'plugin name: ' + plugin.name,
23✔
134
            showDescriptionTooltip,
135
            descriptionTooltipDelay,
136
            mandatory: isMandatory,
137
            className: !isRoot && parentIsEnabled && !plugin.enabled ? 'plugin-card-disabled' : '',
26!
138
            tools: enableTools ? (plugin.enabled ? getEnabledTools(plugin, isMandatory, editedPlugin, documentationBaseURL, onEditPlugin,
26!
139
                onShowDialog, changePluginsKey, hideUploadExtension) : getAvailableTools(plugin, onShowDialog, hideUploadExtension)) : [],
140
            component: (enableTools && plugin.enabled) && plugin.name === editedPlugin ?
40!
141
                <div className="plugin-configuration-editor">
142
                    <CodeMirror
143
                        value={editedCfg}
144
                        editorDidMount={editor => setEditor(editor)}
×
145
                        onBeforeChange={(editor, data, cfg) => onUpdateCfg(cfg)}
×
146
                        options={{
147
                            theme: 'lesser-dark',
148
                            mode: 'application/json',
149
                            lineNumbers: true,
150
                            styleSelectedText: true,
151
                            indentUnit: 2,
152
                            tabSize: 2
153
                        }}/>
154
                    {cfgError && <div className="plugin-configuration-errorarea">
×
155
                        <div className="plugin-configuration-errorarea-header">
156
                            <Message msgId="contextCreator.configurePlugins.cfgParsingError.title"/>
157
                        </div>
158
                        <div className="plugin-configuration-errorarea-body">
159
                            <Message msgId="contextCreator.configurePlugins.cfgParsingError.body" msgParams={{error: cfgError.message}}>
160
                                {msg => <pre className="plugin-configuration-errormsg">{msg}</pre>}
×
161
                            </Message>
162
                        </div>
163
                    </div>}
164
                </div> : null,
165
            preview:
166
                (<React.Fragment>
167
                    {!isRoot && parentIsEnabled && !isMandatory && <Button
13!
168
                        style={{left: '-4px', position: 'relative'}}
169
                        key="checkbox"
170
                        className="square-button-md no-border"
171
                        onClick={(event) => {
172
                            event.stopPropagation();
×
173
                            if (!isMandatory) {
×
174
                                (plugin.enabled ? onDisablePlugins : onEnablePlugins)([plugin.name]);
×
175
                            }
176
                        }}>
177
                        <Glyphicon glyph={plugin.enabled ? 'check' : 'unchecked'} />
×
178
                    </Button>}
179
                    {plugin.glyph ? <Glyphicon key="icon" glyph={plugin.glyph} /> : <Glyphicon key="icon" glyph="plug" />}
13!
180
                </React.Fragment>),
181
            children: processChildren &&
26!
182
                pluginsToItems({
183
                    editedPlugin,
184
                    editedCfg,
185
                    cfgError,
186
                    setEditor,
187
                    documentationBaseURL,
188
                    showDescriptionTooltip,
189
                    descriptionTooltipDelay,
190
                    onEditPlugin,
191
                    onEnablePlugins,
192
                    onDisablePlugins,
193
                    onUpdateCfg,
194
                    onShowDialog,
195
                    changePluginsKey,
196
                    isRoot: false,
197
                    hideUploadExtension,
198
                    plugins: plugin.children,
199
                    processChildren: true,
200
                    parentIsEnabled: plugin.enabled
201
                }) || []
202
        };
203
    });
204

205
const pickIds = items => items && items.map(item => item.id);
1!
206
const ignoreMandatory = items => items && items.filter(item => !item.mandatory);
1!
207

208
const renderPluginError = (error) => {
1✔
209
    const tooltip = (<Tooltip>
3✔
210
        {error.message}
211
    </Tooltip>);
212
    return (
3✔
213
        <OverlayTrigger placement="top" overlay={tooltip}>
214
            <Glyphicon glyph="warning-sign"/>
215
        </OverlayTrigger>
216
    );
217
};
218

219
const renderPluginsToUpload = (plugin, onRemove = () => {}) => {
1!
220
    const uploadingStatus = plugin.error ? <Glyphicon glyph="remove" /> : <Glyphicon glyph="ok"/>;
6✔
221
    return (<div key={plugin.name} className="uploading-file">
6✔
222
        {uploadingStatus}<span className="plugin-name">{plugin.name}</span>
223
        <span className="upload-remove" onClick={onRemove}><Glyphicon glyph="trash" /></span>
224
        <span className="upload-error">{plugin.error && renderPluginError(plugin.error)}</span>
9✔
225
    </div>);
226
};
227

228
const renderUploadModal = ({
1✔
229
    toUpload,
230
    onClose,
231
    onUpload,
232
    onInstall,
233
    onRemove,
234
    isUploading,
235
    uploadStatus
236
}) => {
237
    return (<Modal
9✔
238
        show>
239
        <Modal.Header key="dialogHeader">
240
            <Modal.Title><Message msgId="contextCreator.configurePlugins.uploadTitle" /></Modal.Title>
241
        </Modal.Header>
242
        <Modal.Body>
243
            {isUploading ? <Spinner/> : <div className="configure-plugins-step-upload">
9!
244
                <Dropzone
245
                    key="dropzone"
246
                    rejectClassName="dropzone-danger"
247
                    className="dropzone"
248
                    activeClassName="active"
249
                    onDrop={onUpload}>
250
                    <div style={{
251
                        display: "flex",
252
                        alignItems: "center",
253
                        width: "100%",
254
                        height: "100%",
255
                        justifyContent: "center"
256
                    }}>
257
                        <span style={{
258
                            textAlign: "center"
259
                        }}>
260
                            <Message msgId="contextCreator.configurePlugins.uploadLabel"/>
261
                        </span>
262
                    </div>
263
                </Dropzone>
264
                <div className="uploads-list">{toUpload.map((plugin, idx) => renderPluginsToUpload(plugin, () => onRemove(idx)))}</div>
6✔
265
                {uploadStatus && uploadStatus.result === "error" && <Alert bsStyle="danger"><Message msgId="contextCreator.configurePlugins.uploadError"/>{uploadStatus.error.message}</Alert>}
9!
266
                {uploadStatus && uploadStatus.result === "ok" && <Alert bsStyle="info"><Message msgId="contextCreator.configurePlugins.uploadOk"/></Alert>}
9!
267
            </div>}
268
        </Modal.Body>
269
        <Modal.Footer>
270
            <Button onClick={onClose}><Message msgId="contextCreator.configurePlugins.cancelUpload"/></Button>
271
            <Button bsStyle="primary" onClick={onInstall} disabled={toUpload.filter(f => !f.error).length === 0}><Message msgId="contextCreator.configurePlugins.install"/></Button>
6✔
272
        </Modal.Footer>
273
    </Modal>);
274
};
275

276
const configurePluginsStep = ({
1✔
277
    user,
278
    loading,
279
    loadFlags,
280
    allPlugins = [],
10✔
281
    editedPlugin,
282
    editedCfg,
283
    cfgError,
284
    parsedTemplate,
285
    editedTemplate,
286
    fileDropStatus,
287
    availablePluginsFilterText = "",
18✔
288
    enabledPluginsFilterText = "",
18✔
289
    availablePluginsFilterPlaceholder = "contextCreator.configurePlugins.pluginsFilterPlaceholder",
18✔
290
    enabledPluginsFilterPlaceholder = "contextCreator.configurePlugins.pluginsFilterPlaceholder",
18✔
291
    documentationBaseURL,
292
    showDescriptionTooltip = true,
18✔
293
    descriptionTooltipDelay = 600,
17✔
294
    uploadEnabled = false,
9✔
295
    pluginsToUpload = [],
14✔
296
    uploading = [],
18✔
297
    uploadResult,
298
    showDialog = {},
16✔
299
    mapTemplates,
300
    availableTemplatesFilterText,
301
    enabledTemplatesFilterText,
302
    availableTemplatesFilterPlaceholder,
303
    enabledTemplatesFilterPlaceholder,
304
    disablePluginSort = false,
18✔
305
    onFilterAvailablePlugins = () => {},
18✔
306
    onFilterEnabledPlugins = () => {},
18✔
307
    onEditPlugin = () => {},
18✔
308
    onEnablePlugins = () => {},
18✔
309
    onDisablePlugins = () => {},
18✔
310
    onUpdateCfg = () => {},
18✔
311
    setSelectedPlugins = () => {},
18✔
312
    changePluginsKey = () => {},
18✔
313
    setEditor = () => {},
×
314
    onEnableUpload = () => {},
17✔
315
    onUpload = () => {},
15✔
316
    onAddUpload = () => {},
16✔
317
    onRemoveUpload = () => {},
17✔
318
    onShowDialog = () => {},
16✔
319
    onRemovePlugin = () => {},
17✔
320
    onSaveTemplate,
321
    onDeleteTemplate,
322
    onEditTemplate,
323
    onFilterAvailableTemplates,
324
    onFilterEnabledTemplates,
325
    changeTemplatesKey,
326
    setSelectedTemplates,
327
    setParsedTemplate,
328
    setFileDropStatus,
329
    messages = {},
18✔
330
    hideUploadExtension
331
}) => {
332
    const uploadErrors = {
18✔
333
        [ERROR.WRONG_FORMAT]: "contextCreator.configurePlugins.uploadWrongFileFormatError",
334
        [ERROR.MISSING_INDEX]: "contextCreator.configurePlugins.uploadMissingIndexError",
335
        [ERROR.MALFORMED_INDEX]: "contextCreator.configurePlugins.uploadParseError",
336
        [ERROR.MISSING_PLUGIN]: "contextCreator.configurePlugins.uploadMissingPluginError",
337
        [ERROR.MISSING_BUNDLE]: "contextCreator.configurePlugins.uploadMissingBundleError",
338
        [ERROR.TOO_MANY_BUNDLES]: "contextCreator.configurePlugins.uploadTooManyBundlesError",
339
        [ERROR.ALREADY_INSTALLED]: "contextCreator.configurePlugins.uploadAlreadyInstalledError"
340
    };
341
    const checkUpload = (files) => {
18✔
342
        Promise.all(files.map(file => {
2✔
343
            return checkZipBundle(file, allPlugins.map(p => p.name)).catch(e => {
2✔
344
                throw new Error(getMessageById(messages, uploadErrors[e]));
1✔
345
            });
346
        })).then((namedFiles) => {
347
            onAddUpload(namedFiles);
1✔
348
        }).catch(e => {
349
            onAddUpload(files.map(f => ({name: f.name, file: f, error: e})));
1✔
350
        });
351
    };
352
    const installUploads = () => {
18✔
353
        const uploads = pluginsToUpload.filter(f => !f.error);
1✔
354
        onUpload(uploads);
1✔
355
    };
356
    const selectedPlugins = allPlugins.filter(plugin => plugin.selected);
18✔
357
    const availablePlugins = allPlugins.filter(plugin => !plugin.enabled);
18✔
358
    const enabledPlugins = allPlugins.filter(plugin => plugin.enabled);
18✔
359

360
    const pluginsToItemsCommonArgs = {
18✔
361
        editedPlugin,
362
        editedCfg,
363
        cfgError,
364
        setEditor,
365
        documentationBaseURL,
366
        showDescriptionTooltip,
367
        descriptionTooltipDelay,
368
        onEditPlugin,
369
        onEnablePlugins,
370
        onDisablePlugins,
371
        onUpdateCfg,
372
        onShowDialog,
373
        changePluginsKey,
374
        isRoot: true,
375
        hideUploadExtension
376
    };
377

378
    const selectedItems = pluginsToItems({ ...pluginsToItemsCommonArgs, plugins: selectedPlugins, processChildren: false });
18✔
379
    const availableItems = pluginsToItems({ ...pluginsToItemsCommonArgs, plugins: availablePlugins, processChildren: true });
18✔
380
    const enabledItems = pluginsToItems({ ...pluginsToItemsCommonArgs, plugins: enabledPlugins, processChildren: true });
18✔
381

382
    return (
18✔
383
        <div className="configure-plugins-step">
384
            <Transfer
385
                localizeItems
386
                leftColumn={{
387
                    items: availableItems,
388
                    title: 'contextCreator.configurePlugins.availablePlugins',
389
                    filterText: availablePluginsFilterText,
390
                    filterPlaceholder: availablePluginsFilterPlaceholder,
391
                    emptyStateProps: {
392
                        glyph: 'wrench',
393
                        title: 'contextCreator.configurePlugins.availablePluginsEmpty'
394
                    },
395
                    emptyStateSearchProps: {
396
                        glyph: 'info-sign',
397
                        title: 'contextCreator.configurePlugins.searchResultsEmpty'
398
                    },
399
                    onFilter: onFilterAvailablePlugins,
400
                    tools: hideUploadExtension ? [] : [{
18✔
401
                        id: "upload", glyph: "upload", onClick: () => onEnableUpload(true), tooltipId: 'contextCreator.configurePlugins.tooltips.uploadPlugin'
×
402
                    }]
403
                }}
404
                rightColumn={{
405
                    items: enabledItems,
406
                    title: 'contextCreator.configurePlugins.enabledPlugins',
407
                    filterText: enabledPluginsFilterText,
408
                    filterPlaceholder: enabledPluginsFilterPlaceholder,
409
                    emptyStateProps: {
410
                        glyph: 'wrench',
411
                        title: 'contextCreator.configurePlugins.enabledPluginsEmpty'
412
                    },
413
                    emptyStateSearchProps: {
414
                        glyph: 'info-sign',
415
                        title: 'contextCreator.configurePlugins.searchResultsEmpty'
416
                    },
417
                    emptyTest: items => !items.filter(item => !item.mandatory).length,
18✔
418
                    onFilter: onFilterEnabledPlugins
419
                }}
420
                allowCtrlMultiSelect
421
                selectedItems={selectedItems}
422
                selectedSide={allPlugins.reduce((result, plugin) => plugin.selected && plugin.enabled || result, false) ?
13!
423
                    'right' :
424
                    'left'
425
                }
426
                sortStrategy={items => {
427
                    if (disablePluginSort) {
36!
428
                        return items;
×
429
                    }
430

431
                    const recursiveSort = curItems => curItems && curItems.map(item => ({...item, children: recursiveSort(item.children)}))
49✔
432
                        .sort((x, y) => x.title < y.title ? -1 : 1);
4!
433
                    return recursiveSort(items);
36✔
434
                }}
435
                filter={(text, items) => {
436
                    const loweredText = text.toLowerCase();
36✔
437
                    const recursiveFilter = (curItems = []) =>
36!
438
                        curItems.map(item => ({...item, children: recursiveFilter(item.children)}))
49✔
439
                            .filter(item => item.children.length > 0 || item.title.toLowerCase().indexOf(loweredText) > -1);
13✔
440
                    return recursiveFilter(items);
36✔
441
                }}
442
                onSelect={items => setSelectedPlugins(pickIds(ignoreMandatory(items)))}
×
443
                onTransfer={(items, direction) => (direction === 'right' ? onEnablePlugins : onDisablePlugins)(pickIds(ignoreMandatory(items)))}/>
×
444
            <ResizableModal
445
                loading={loading && loadFlags.templateDataLoading}
18!
446
                showFullscreen
447
                title={<Message msgId="contextCreator.configureTemplates.title"/>}
448
                show={showDialog.mapTemplatesConfig}
449
                fullscreenType="vertical"
450
                clickOutEnabled={false}
451
                size="lg"
452
                onClose={() => onShowDialog('mapTemplatesConfig', false)}>
×
453
                <ConfigureMapTemplates
454
                    user={user}
455
                    loading={loading}
456
                    loadFlags={loadFlags}
457
                    mapTemplates={mapTemplates}
458
                    parsedTemplate={parsedTemplate}
459
                    editedTemplate={editedTemplate}
460
                    fileDropStatus={fileDropStatus}
461
                    availableTemplatesFilterText={availableTemplatesFilterText}
462
                    enabledTemplatesFilterText={enabledTemplatesFilterText}
463
                    availableTemplatesFilterPlaceholder={availableTemplatesFilterPlaceholder}
464
                    enabledTemplatesFilterPlaceholder={enabledTemplatesFilterPlaceholder}
465
                    showUploadDialog={showDialog.uploadTemplate}
466
                    changeTemplatesKey={changeTemplatesKey}
467
                    setSelectedTemplates={setSelectedTemplates}
468
                    setParsedTemplate={setParsedTemplate}
469
                    setFileDropStatus={setFileDropStatus}
470
                    onSave={onSaveTemplate}
471
                    onDelete={onDeleteTemplate}
472
                    onEditTemplate={onEditTemplate}
473
                    onFilterAvailableTemplates={onFilterAvailableTemplates}
474
                    onFilterEnabledTemplates={onFilterEnabledTemplates}
475
                    onShowUploadDialog={onShowDialog.bind(null, 'uploadTemplate')}/>
476
            </ResizableModal>
477
            <ConfirmModal onClose={onShowDialog.bind(null, 'confirmRemovePlugin', false)} onConfirm={onRemovePlugin.bind(null, showDialog.confirmRemovePluginPayload)} show={showDialog.confirmRemovePlugin} buttonSize="large">
478
                <Message msgId="contextCreator.configurePlugins.confirmRemovePlugin"/>
479
            </ConfirmModal>
480
            {uploadEnabled && renderUploadModal({
27✔
481
                toUpload: pluginsToUpload,
482
                onClose: () => onEnableUpload(false),
1✔
483
                onUpload: checkUpload,
484
                onInstall: installUploads,
485
                onRemove: onRemoveUpload,
486
                isUploading: uploading.filter(u => u.uploading).length > 0,
×
487
                uploadStatus: uploadResult
488
            })}
489
        </div>
490
    );
491
};
492

493
export default compose(
494
    withState('errorLineNumber', 'setErrorLineNumber'),
495
    withState('editor', 'setEditor'),
496
    getContext({
497
        messages: PropTypes.object
498
    }),
499
    lifecycle({
500
        componentDidUpdate() {
501
            const {cfgError, editor, errorLineNumber, setErrorLineNumber} = this.props;
×
502
            const cfgErrorLineNumber = get(cfgError, 'lineNumber');
×
503
            if (editor && cfgErrorLineNumber !== errorLineNumber) {
×
504
                if (errorLineNumber) {
×
505
                    editor.removeLineClass(errorLineNumber - 1, 'background', 'plugin-configuration-line-error');
×
506
                }
507
                setErrorLineNumber(cfgErrorLineNumber);
×
508
                if (cfgErrorLineNumber) {
×
509
                    editor.addLineClass(cfgErrorLineNumber - 1, 'background', 'plugin-configuration-line-error');
×
510
                }
511
            }
512
        }
513
    }),
514
    tutorialEnhancer('configureplugins-initial')
515
)(configurePluginsStep);
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc