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

geosolutions-it / MapStore2 / 18968041869

31 Oct 2025 09:13AM UTC coverage: 76.918% (-0.01%) from 76.932%
18968041869

Pull #11611

github

web-flow
Merge 1b2ac4428 into f5928b825
Pull Request #11611: #11577 Doc build fixed for node 22. Build strategy

31983 of 49693 branches covered (64.36%)

39738 of 51663 relevant lines covered (76.92%)

37.87 hits per line

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

71.7
/web/client/components/widgets/builder/wizard/ChartWizard.jsx
1
/**
2
 * Copyright 2017, 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
import React, { useEffect, useRef, memo, useState } from 'react';
9
import { compose } from 'recompose';
10
import { isEqual, isEmpty, pick } from "lodash";
11
import { withResizeDetector } from 'react-resize-detector';
12

13
import WizardContainer from '../../../misc/wizard/WizardContainer';
14
import { wizardHandlers } from '../../../misc/wizard/enhancers';
15
import loadingEnhancer from '../../../misc/enhancers/loadingState';
16
import noAttribute from './common/noAttributesEmptyView';
17
import wfsChartOptions from './common/wfsChartOptions';
18
import WPSWidgetOptions from './common/WPSWidgetOptions';
19
import ChartWidgetOptions from './common/WidgetOptions';
20
import NullManagement from './chart/NullManagement';
21
import SimpleChart from '../../../charts/SimpleChart';
22
import ChartAxisOptions from './chart/ChartAxisOptions';
23
import ChartValueFormatting from './chart/ChartValueFormatting';
24
import ChartLayoutOptions from './chart/ChartLayoutOptions';
25
import Message from "../../../I18N/Message";
26

27
import sampleData from '../../enhancers/sampleChartData';
28
import multiProtocolChart from '../../enhancers/multiProtocolChart';
29
import { chartWidgetProps } from '../../enhancers/chartWidget';
30
import dependenciesToWidget from '../../enhancers/dependenciesToWidget';
31
import dependenciesToFilter from '../../enhancers/dependenciesToFilter';
32
import dependenciesToOptions from '../../enhancers/dependenciesToOptions';
33
import emptyChartState from '../../enhancers/emptyChartState';
34
import errorChartState from '../../enhancers/errorChartState';
35
import ChartStyleEditor from './chart/ChartStyleEditor';
36
import ChartTraceEditSelector from './chart/ChartTraceEditSelector';
37
import TraceAxesOptions from './chart/TraceAxesOptions';
38
import TraceLegendOptions from './chart/TraceLegendOptions';
39
import { isChartOptionsValid } from '../../../../utils/WidgetsUtils';
40
import dependenciesToShapes from '../../enhancers/dependenciesToShapes';
41

42
const loadingState = loadingEnhancer(({ loading, data }) => loading || !data, { width: 500, height: 200 });
1!
43
const hasNoAttributes = ({ featureTypeProperties = [] }) => featureTypeProperties.filter(({ type = "" } = {}) => type.indexOf("gml:") !== 0).length === 0;
56!
44
const NoAttributeComp = noAttribute(hasNoAttributes)(() => null);
1✔
45
const ChartOptionsComp = wfsChartOptions(WPSWidgetOptions);
1✔
46
const ChartStyleEditorComp = wfsChartOptions(ChartStyleEditor);
1✔
47
const ChartNullManagementComp = wfsChartOptions(NullManagement);
1✔
48

49
const enhancePreview = compose(
1✔
50
    chartWidgetProps,
51
    dependenciesToWidget,
52
    dependenciesToFilter,
53
    dependenciesToOptions,
54
    multiProtocolChart,
55
    loadingState,
56
    errorChartState,
57
    emptyChartState,
58
    dependenciesToShapes
59
);
60
const PreviewChart = enhancePreview(withResizeDetector(SimpleChart));
1✔
61
const SampleChart = sampleData(withResizeDetector(SimpleChart));
1✔
62

63
const Wizard = wizardHandlers(WizardContainer);
1✔
64

65
const renderPreview = ({
1✔
66
    data = {},
×
67
    trace,
68
    dependencies = {},
5✔
69
    setValid = () => { },
×
70
    hasAggregateProcess,
71
    setErrors = () => {},
×
72
    errors,
73
    widgets = [],
×
74
    valid
75
}) => {
76
    return valid
5!
77
        ? (<PreviewChart
78
            {...data}
79
            dependencies={dependencies}
80
            widgets={widgets}
81
            key="preview-chart"
82
            isAnimationActive={false}
83
            onLoad={() => {
84
                setValid(true);
×
85
                setErrors({...errors, [trace.layer.name]: false});
×
86
            }}
87
            onLoadError={() => {
88
                setValid(false);
×
89
                setErrors({...errors, [trace.layer.name]: true});
×
90
            }}
91
        />)
92
        : (<SampleChart
93
            hasAggregateProcess={hasAggregateProcess}
94
            key="sample-chart"
95
            isAnimationActive={false}
96
            type={trace.type}
97
        />);
98
};
99

100
const StepHeader = ({step} = {}) => (
1!
101
    <div className="ms-wizard-form-separator">
5✔
102
        <Message msgId={`widgets.${step === 0 ? 'advanced.traceData' : 'widgetOptionsTitle'}`}/>
5✔
103
    </div>
104
);
105

106
const ChartWizard = ({
1✔
107
    onChange = () => {},
4✔
108
    onFinish = () => {},
4✔
109
    setPage = () => {},
4✔
110
    setValid = () => {},
4✔
111
    data = {},
1✔
112
    setErrors = () => {},
8✔
113
    errors,
114
    step = 0,
2✔
115
    types,
116
    featureTypeProperties,
117
    dependencies,
118
    hasAggregateProcess,
119
    widgets = [],
8✔
120
    openFilterEditor,
121
    toggleLayerSelector,
122
    valid,
123
    dashBoardEditing
124
}) => {
125
    const selectedChart = (data?.charts || []).find((chart) => chart.chartId === data.selectedChartId);
8✔
126
    const traces = selectedChart?.traces || [];
8✔
127
    const selectedTrace = traces.find(trace => trace.id === data?.selectedTraceId) || traces[0];
8✔
128
    const noAttributes = hasNoAttributes({ featureTypeProperties });
8✔
129
    const prevProps = useRef({});
8✔
130
    const [tab, setTab] = useState('traces');
8✔
131
    useEffect(() => {
8✔
132
        // this side effects where removed from the recompose HOC
133
        // probably all these could be moved at higher level
134
        // because the could be computed early
135
        // this are used to enable/disable buttons in builder toolbar
136
        if (valid && data?.charts?.some(chart => chart.traces.some((trace) => !isChartOptionsValid(trace.options, { hasAggregateProcess })))) {
8!
137
            setValid(false);
×
138
        }
139
        if ((isEmpty(prevProps.current.data || {}) && !isEmpty(data)) || !isEqual(prevProps.current.data, data)) {
8!
140
            const layerNames = data?.charts?.reduce((acc, chart) => [ ...acc, ...chart.traces.map(({ layer } = {}) => layer?.name)], []);
8!
141
            setErrors(isEmpty(layerNames) ? {} : pick(errors, [...layerNames]));
8✔
142
        }
143
        prevProps.current = {
8✔
144
            featureTypeProperties,
145
            data
146
        };
147
    });
148

149
    if (!selectedTrace) {
8✔
150
        return null;
3✔
151
    }
152
    const selectedTab = selectedTrace?.type === "pie" && tab === "axis" ? "traces" : (tab);
5!
153

154
    if (noAttributes) {
5!
155
        return <NoAttributeComp featureTypeProperties={featureTypeProperties}/>;
×
156
    }
157
    const sampleChart = (
158
        <div className="ms-wizard-chart-selector">
5✔
159
            <ChartTraceEditSelector
160
                data={data}
161
                editing={step === 0}
162
                error={!!errors?.[selectedTrace?.layer?.name]}
163
                onChange={onChange}
164
                onAddChart={() => toggleLayerSelector(true)}
×
165
                disableMultiChart={!dashBoardEditing}
166
                tab={selectedTab}
167
                setTab={setTab}
168
                hasAggregateProcess={hasAggregateProcess}
169
            >
170
                <div className="ms-wizard-chart-preview" style={{
171
                    position: 'relative',
172
                    height: 250
173
                }}>
174
                    {renderPreview({
175
                        data,
176
                        trace: selectedTrace,
177
                        valid: traces.some((t) => isChartOptionsValid(t?.options, { hasAggregateProcess })),
5✔
178
                        dependencies,
179
                        setValid,
180
                        hasAggregateProcess,
181
                        setErrors,
182
                        errors,
183
                        widgets
184
                    })}
185
                </div>
186
            </ChartTraceEditSelector>
187
        </div>
188
    );
189

190
    const tabContents = {
5✔
191
        traces: (
192
            <>
193
                <StepHeader step={step} />
194
                {!noAttributes && <ChartOptionsComp
10✔
195
                    hasAggregateProcess={hasAggregateProcess}
196
                    dependencies={dependencies}
197
                    key="chart-options"
198
                    featureTypeProperties={featureTypeProperties}
199
                    types={types}
200
                    data={selectedTrace}
201
                    onChange={(key, value) => {
202
                        onChange(`charts[${selectedChart?.chartId}].traces[${selectedTrace.id}].${key}`, value);
×
203
                    }}
204
                    layer={selectedTrace?.layer}
205
                    disableLayerSelection={!dashBoardEditing}
206
                    showTitle={false}
207
                    error={!!errors?.[selectedTrace?.layer?.name]}
208
                    onChangeLayer={dashBoardEditing ? () => toggleLayerSelector({
×
209
                        key: 'chart-layer-replace',
210
                        chartId: selectedChart?.chartId,
211
                        traceId: selectedTrace?.id
212
                    }) : null}
213
                    onFilterLayer={() => openFilterEditor()}
×
214
                />}
215
                <ChartStyleEditorComp
216
                    // force the re-rendering on trace change
217
                    key={selectedTrace?.id}
218
                    data={selectedTrace}
219
                    layer={selectedTrace?.layer}
220
                    featureTypeProperties={featureTypeProperties}
221
                    traces={traces}
222
                    onChange={(key, value) =>
223
                        onChange(`charts[${selectedChart?.chartId}].traces[${selectedTrace.id}].${key}`, value)
×
224
                    }
225
                />
226
                <TraceAxesOptions
227
                    data={data}
228
                    onChange={onChange}
229
                />
230
                <ChartValueFormatting
231
                    title={<Message msgId="widgets.advanced.valueFormatting" />}
232
                    options={selectedTrace}
233
                    onChange={(key, value) =>
234
                        onChange(`charts[${selectedChart?.chartId}].traces[${selectedTrace.id}].${key}`, value)
×
235
                    }
236
                />
237
                <TraceLegendOptions
238
                    data={data}
239
                    onChange={onChange}
240
                />
241
                <ChartNullManagementComp
242
                    data={selectedTrace}
243
                    onChange={(key, value) => {
244
                        onChange(`charts[${selectedChart?.chartId}].traces[${selectedTrace.id}].${key}`, value);
×
245
                    }}
246
                    featureTypeProperties={featureTypeProperties}
247
                />
248
            </>
249
        ),
250
        axis: (
251
            <ChartAxisOptions
252
                data={data}
253
                onChange={onChange}
254
            />
255
        ),
256
        layout: (
257
            <ChartLayoutOptions
258
                data={data}
259
                onChange={onChange}
260
            />
261
        )
262
    };
263

264
    const ChartOptions = (
265
        <>
5✔
266
            {sampleChart}
267
            {tabContents[selectedTab || 'traces']}
5!
268
        </>
269
    );
270
    const WidgetOptions = !noAttributes ? (
5!
271
        <>
272
            {sampleChart}
273
            <StepHeader step={step} />
274
            <ChartWidgetOptions
275
                key="widget-options"
276
                data={data}
277
                onChange={onChange}
278
                layer={selectedChart?.layer}
279
                showTitle={false}
280
            />
281
        </>
282
    ) : null;
283
    return (<Wizard
5✔
284
        step={step}
285
        setPage={setPage}
286
        onFinish={onFinish}
287
        isStepValid={n =>
288
            n === 0
×
289
                ? data.chartType
290
                : n === 1
×
291
                    ? traces.every(trace => isChartOptionsValid(trace.options, { hasAggregateProcess }))
×
292
                    : true
293
        }
294
        hideButtons
295
        className={"chart-options"}>
296
        {[ChartOptions, WidgetOptions].map(component =>
297
            <>
10✔
298
                {component}
299
            </>
300
        )}
301
    </Wizard>);
302
};
303
export default memo(ChartWizard);
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