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

geosolutions-it / MapStore2 / 20165223350

12 Dec 2025 11:22AM UTC coverage: 76.67% (+0.01%) from 76.657%
20165223350

Pull #11788

github

web-flow
Merge a8ab8f3c3 into f5bc24085
Pull Request #11788: #11779: FIX Setting the map projection freezes MapStore causing OOM if resolutions configured in new.json file with a different projection

32335 of 50308 branches covered (64.27%)

15 of 15 new or added lines in 1 file covered. (100.0%)

36 existing lines in 2 files now uncovered.

40238 of 52482 relevant lines covered (76.67%)

38.31 hits per line

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

69.77
/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
const formatPluginTitle = (plugin) => {
1✔
87
    var version = '';
13✔
88
    if (plugin.version && plugin.version !== 'undefined') {
13!
UNCOV
89
        version = ' (' + plugin.version + ')';
×
90
    }
91
    return (plugin.title || plugin.label || plugin.name) + version;
13!
92
};
93

94
const formatPluginDescription = (plugin) => {
1✔
95
    return plugin.description || 'plugin name: ' + plugin.name;
13✔
96
};
97

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

217
const pickIds = items => items && items.map(item => item.id);
1!
218
const ignoreMandatory = items => items && items.filter(item => !item.mandatory);
1!
219

220
const renderPluginError = (error) => {
1✔
221
    const tooltip = (<Tooltip>
3✔
222
        {error.message}
223
    </Tooltip>);
224
    return (
3✔
225
        <OverlayTrigger placement="top" overlay={tooltip}>
226
            <Glyphicon glyph="warning-sign"/>
227
        </OverlayTrigger>
228
    );
229
};
230

231
const renderPluginsToUpload = (plugin, onRemove = () => {}) => {
1!
232
    const uploadingStatus = plugin.error ? <Glyphicon glyph="remove" /> : <Glyphicon glyph="ok"/>;
6✔
233
    return (<div key={plugin.name} className="uploading-file">
6✔
234
        {uploadingStatus}<span className="plugin-name">{plugin.name}</span>
235
        <span className="upload-remove" onClick={onRemove}><Glyphicon glyph="trash" /></span>
236
        <span className="upload-error">{plugin.error && renderPluginError(plugin.error)}</span>
9✔
237
    </div>);
238
};
239

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

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

372
    const pluginsToItemsCommonArgs = {
18✔
373
        editedPlugin,
374
        editedCfg,
375
        cfgError,
376
        setEditor,
377
        documentationBaseURL,
378
        showDescriptionTooltip,
379
        descriptionTooltipDelay,
380
        onEditPlugin,
381
        onEnablePlugins,
382
        onDisablePlugins,
383
        onUpdateCfg,
384
        onShowDialog,
385
        changePluginsKey,
386
        isRoot: true,
387
        hideUploadExtension
388
    };
389

390
    const selectedItems = pluginsToItems({ ...pluginsToItemsCommonArgs, plugins: selectedPlugins, processChildren: false });
18✔
391
    const availableItems = pluginsToItems({ ...pluginsToItemsCommonArgs, plugins: availablePlugins, processChildren: true });
18✔
392
    const enabledItems = pluginsToItems({ ...pluginsToItemsCommonArgs, plugins: enabledPlugins, processChildren: true });
18✔
393

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

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

505
export default compose(
506
    withState('errorLineNumber', 'setErrorLineNumber'),
507
    withState('editor', 'setEditor'),
508
    getContext({
509
        messages: PropTypes.object
510
    }),
511
    lifecycle({
512
        componentDidUpdate() {
UNCOV
513
            const {cfgError, editor, errorLineNumber, setErrorLineNumber} = this.props;
×
UNCOV
514
            const cfgErrorLineNumber = get(cfgError, 'lineNumber');
×
UNCOV
515
            if (editor && cfgErrorLineNumber !== errorLineNumber) {
×
UNCOV
516
                if (errorLineNumber) {
×
UNCOV
517
                    editor.removeLineClass(errorLineNumber - 1, 'background', 'plugin-configuration-line-error');
×
518
                }
UNCOV
519
                setErrorLineNumber(cfgErrorLineNumber);
×
UNCOV
520
                if (cfgErrorLineNumber) {
×
UNCOV
521
                    editor.addLineClass(cfgErrorLineNumber - 1, 'background', 'plugin-configuration-line-error');
×
522
                }
523
            }
524
        }
525
    }),
526
    tutorialEnhancer('configureplugins-initial')
527
)(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