Skip to content

Commit 419786b

Browse files
#2157: Refactor of the create dataset page and attribute table validation (#2164)
--------- Co-authored-by: stefano bovio <[email protected]>
1 parent 9f7d8bc commit 419786b

File tree

31 files changed

+3000
-14
lines changed

31 files changed

+3000
-14
lines changed

geonode_mapstore_client/client/js/api/geonode/v2/index.js

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,8 @@ import {
3636
EXECUTION_REQUEST,
3737
getEndpoints as cGetEndpoints,
3838
getEndpointUrl,
39-
getQueryParams
39+
getQueryParams,
40+
UPLOADS
4041
} from './constants';
4142

4243

@@ -723,6 +724,11 @@ export const deleteAsset = (pk, assetId) => {
723724
});
724725
};
725726

727+
export const createDataset = (body) => {
728+
return axios.post(getEndpointUrl(UPLOADS) + '/upload', body)
729+
.then(({ data }) => data);
730+
};
731+
726732
export const getMetadataDownloadLinkByPk = (pk) => {
727733
return getEndpointUrl(RESOURCES, `/${pk}/iso_metadata_xml`);
728734
}
@@ -764,5 +770,6 @@ export default {
764770
getDatasets,
765771
deleteExecutionRequest,
766772
getResourceByTypeAndByPk,
773+
createDataset,
767774
getMetadataDownloadLinkByPk
768775
};

geonode_mapstore_client/client/js/apps/gn-catalogue.js

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -104,8 +104,7 @@ const getViewer = (component) => {
104104
const viewers = {
105105
[appRouteComponentTypes.VIEWER]: ViewerRoute,
106106
[appRouteComponentTypes.CATALOGUE]: useRedirect ? RedirectRoute : ComponentsRoute,
107-
[appRouteComponentTypes.DATASET_UPLOAD]: ComponentsRoute,
108-
[appRouteComponentTypes.DOCUMENT_UPLOAD]: ComponentsRoute,
107+
[appRouteComponentTypes.COMPONENTS]: ComponentsRoute,
109108
[appRouteComponentTypes.MAP_VIEWER]: MapViewerRoute
110109
};
111110
return viewers[component];
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
/*
2+
* Copyright 2025, 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 from 'react';
9+
import PropTypes from 'prop-types';
10+
import { FormGroup, FormControl, Checkbox, HelpBlock } from 'react-bootstrap';
11+
12+
import FlexBox from '@mapstore/framework/components/layout/FlexBox';
13+
import Message from '@mapstore/framework/components/I18N/Message';
14+
import { getMessageById } from '@mapstore/framework/utils/LocaleUtils';
15+
16+
import { AttributeTypes, getAttributeControlId, RestrictionsTypes } from '../utils/CreateDatasetUtils';
17+
import RangeRestriction from './RangeRestriction';
18+
import EnumRestriction from './EnumRestriction';
19+
20+
const CreateDatasetAttributeRow = ({
21+
data,
22+
geometryAttribute,
23+
disabled,
24+
tools,
25+
onChange,
26+
getErrorByPath = () => undefined,
27+
index
28+
}, context) => {
29+
30+
const errors = {
31+
name: getErrorByPath(`/attributes/${index}/name`),
32+
restrictionsType: getErrorByPath(`/attributes/${index}/restrictionsType`),
33+
restrictionsRangeMin: getErrorByPath(`/attributes/${index}/restrictionsRangeMin`),
34+
restrictionsRangeMax: getErrorByPath(`/attributes/${index}/restrictionsRangeMax`)
35+
};
36+
37+
const typesOptions = geometryAttribute
38+
? [
39+
{ value: AttributeTypes.Point, labelId: 'gnviewer.points' },
40+
{ value: AttributeTypes.LineString, labelId: 'gnviewer.lines' },
41+
{ value: AttributeTypes.Polygon, labelId: 'gnviewer.polygons' }]
42+
: [
43+
{ value: AttributeTypes.String, labelId: 'gnviewer.string' },
44+
{ value: AttributeTypes.Integer, labelId: 'gnviewer.integer' },
45+
{ value: AttributeTypes.Float, labelId: 'gnviewer.float' },
46+
{ value: AttributeTypes.Date, labelId: 'gnviewer.date' }
47+
];
48+
49+
const restrictionsOptions = [
50+
AttributeTypes.Integer,
51+
AttributeTypes.Float,
52+
AttributeTypes.String
53+
].includes(data?.type)
54+
? [
55+
{ value: RestrictionsTypes.None, labelId: 'gnviewer.none' },
56+
...(data?.type !== AttributeTypes.String
57+
? [{ value: RestrictionsTypes.Range, labelId: 'gnviewer.range' }]
58+
: []
59+
),
60+
{ value: RestrictionsTypes.Options, labelId: 'gnviewer.options' }
61+
]
62+
: [{ value: RestrictionsTypes.None, labelId: 'gnviewer.none' }];
63+
64+
function handleOnChange(properties) {
65+
onChange({
66+
...data,
67+
...properties
68+
});
69+
}
70+
71+
function handleTypeChange(event) {
72+
const newType = event.target.value;
73+
const currentType = data?.type;
74+
75+
// If type is changing, clear restrictions and set to default
76+
if (newType !== currentType) {
77+
onChange({
78+
...data,
79+
type: newType,
80+
restrictionsType: RestrictionsTypes.None,
81+
restrictionsRangeMin: null,
82+
restrictionsRangeMax: null,
83+
restrictionsOptions: []
84+
});
85+
} else {
86+
handleOnChange({ type: newType });
87+
}
88+
}
89+
90+
return (
91+
<tr className="gn-dataset-attribute">
92+
<td className="gn-attribute-name">
93+
<FormGroup
94+
controlId={getAttributeControlId(data, 'name')}
95+
validationState={errors?.name ? 'error' : undefined}
96+
>
97+
<FormControl
98+
type="text"
99+
value={data?.name || ''}
100+
disabled={!!geometryAttribute || disabled}
101+
onChange={(event) => handleOnChange({ name: event.target.value })}
102+
/>
103+
{errors?.name ? <HelpBlock><Message msgId={errors.name} /></HelpBlock> : null}
104+
</FormGroup>
105+
</td>
106+
<td className="gn-attribute-type">
107+
<FormGroup controlId={getAttributeControlId(data, 'type')}>
108+
<FormControl
109+
value={data?.type || ''}
110+
componentClass="select"
111+
placeholder="select"
112+
onChange={handleTypeChange}
113+
disabled={disabled}
114+
>
115+
{typesOptions.map(({ labelId, value }) =>
116+
<option key={value} value={value}>
117+
{getMessageById(context.messages, labelId)}
118+
</option>
119+
)}
120+
</FormControl>
121+
</FormGroup>
122+
</td>
123+
<td className="gn-attribute-nillable">
124+
<FormGroup controlId={getAttributeControlId(data, 'nillable')}>
125+
<Checkbox
126+
style={{ paddingTop: 6 }}
127+
checked={!!data?.nillable}
128+
disabled={!!geometryAttribute || disabled}
129+
onChange={(event) =>
130+
handleOnChange({ nillable: event.target.checked })
131+
}
132+
/>
133+
</FormGroup>
134+
</td>
135+
<td>
136+
<FlexBox column gap="sm">
137+
<FormGroup
138+
controlId={getAttributeControlId(data, 'restrictions')}
139+
validationState={errors?.restrictionsType ? 'error' : undefined}>
140+
<FormControl
141+
value={data?.restrictionsType}
142+
componentClass="select"
143+
placeholder="select"
144+
disabled={!!geometryAttribute || disabled}
145+
onChange={(event) =>
146+
handleOnChange({ restrictionsType: event.target.value })
147+
}
148+
>
149+
{restrictionsOptions.map(({ labelId, value }) =>
150+
<option key={value} value={value}>
151+
{getMessageById(context.messages, labelId)}
152+
</option>
153+
)}
154+
</FormControl>
155+
{errors?.restrictionsType ? <HelpBlock>{errors.restrictionsType}</HelpBlock> : null}
156+
</FormGroup>
157+
{data?.restrictionsType === RestrictionsTypes.Range ?
158+
<RangeRestriction
159+
errors={errors}
160+
data={data}
161+
handleOnChange={handleOnChange}
162+
disabled={disabled}
163+
/> : null}
164+
{data?.restrictionsType === RestrictionsTypes.Options
165+
? <EnumRestriction
166+
index={index}
167+
data={data}
168+
handleOnChange={handleOnChange}
169+
getErrorByPath={getErrorByPath}
170+
disabled={disabled}
171+
/> : null}
172+
</FlexBox>
173+
</td>
174+
<td className="gn-attribute-tools">
175+
{tools}
176+
</td>
177+
</tr>
178+
);
179+
};
180+
181+
CreateDatasetAttributeRow.contextTypes = {
182+
messages: PropTypes.object
183+
};
184+
185+
export default CreateDatasetAttributeRow;
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* Copyright 2025, 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 from "react";
9+
import { v4 as uuid } from 'uuid';
10+
import { FormGroup, FormControl, HelpBlock, Glyphicon } from "react-bootstrap";
11+
12+
import FlexBox from "@mapstore/framework/components/layout/FlexBox";
13+
import Message from "@mapstore/framework/components/I18N/Message";
14+
import Button from "@mapstore/framework/components/layout/Button";
15+
import Text from "@mapstore/framework/components/layout/Text";
16+
17+
import { AttributeTypes, getAttributeControlId, parseNumber } from "../utils/CreateDatasetUtils";
18+
19+
const EnumRestriction = ({
20+
index,
21+
data = {},
22+
handleOnChange = () => {},
23+
getErrorByPath = () => {},
24+
disabled
25+
}) => {
26+
return (
27+
<FlexBox column wrap gap="sm">
28+
<FlexBox gap="sm" column component="ul">
29+
{(data?.restrictionsOptions || []).map((option, idx) => {
30+
const optionsError = {
31+
value: getErrorByPath(`/attributes/${index}/restrictionsOptions/${idx}/value`)
32+
};
33+
return (
34+
<FlexBox component="li" gap="sm" key={option.id}>
35+
<Text style={{ paddingTop: 6 }}></Text>
36+
<FlexBox.Fill
37+
component={FormGroup}
38+
key={option.id}
39+
validationState={optionsError?.value ? 'error' : undefined}
40+
controlId={getAttributeControlId(data, `option-${option.id}`)}
41+
>
42+
<FormControl
43+
type={data?.type === AttributeTypes.String ? "text" : "number"}
44+
value={option.value}
45+
disabled={disabled}
46+
onChange={(event) => handleOnChange({
47+
restrictionsOptions: (data?.restrictionsOptions || [])
48+
.map((opt) => {
49+
return opt.id !== option.id
50+
? opt : {
51+
...option,
52+
value: data?.type === AttributeTypes.String
53+
? event.target.value
54+
: parseNumber(event.target.value)
55+
};
56+
})
57+
})}
58+
/>
59+
{optionsError?.value ? <HelpBlock>
60+
<Message msgId={optionsError.value} />
61+
</HelpBlock> : null}
62+
</FlexBox.Fill>
63+
<Button square
64+
onClick={() => handleOnChange({
65+
restrictionsOptions: (data?.restrictionsOptions || [])
66+
.filter(opt => opt.id !== option.id)
67+
})}
68+
disabled={disabled}>
69+
<Glyphicon glyph="trash" />
70+
</Button>
71+
</FlexBox>
72+
);
73+
})}
74+
<div>
75+
<Button size="sm" onClick={() => handleOnChange({
76+
restrictionsOptions: [
77+
...(data?.restrictionsOptions || []),
78+
{
79+
id: uuid(),
80+
value: ''
81+
}
82+
]
83+
})}
84+
disabled={disabled}>
85+
<Glyphicon glyph="plus" />
86+
{' '}<Message msgId="gnviewer.addOption" />
87+
</Button>
88+
</div>
89+
</FlexBox>
90+
</FlexBox>
91+
);
92+
};
93+
94+
export default EnumRestriction;
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
/*
2+
* Copyright 2025, 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 from "react";
9+
import { FormGroup, FormControl, ControlLabel, HelpBlock } from "react-bootstrap";
10+
11+
import FlexBox from "@mapstore/framework/components/layout/FlexBox";
12+
import Message from "@mapstore/framework/components/I18N/Message";
13+
14+
import { getAttributeControlId, parseNumber } from "../utils/CreateDatasetUtils";
15+
16+
const RangeRestriction = ({
17+
errors = {},
18+
data = {},
19+
handleOnChange = () => {},
20+
disabled
21+
}) => {
22+
return (
23+
<FlexBox centerChildrenVertically wrap gap="sm">
24+
<FlexBox.Fill
25+
flexBox
26+
component={FormGroup}
27+
validationState={errors?.restrictionsRangeMin ? 'error' : undefined}
28+
controlId={getAttributeControlId(data, 'restrictions-min')}
29+
centerChildrenVertically
30+
gap="sm"
31+
>
32+
<ControlLabel><Message msgId="gnviewer.min" /></ControlLabel>
33+
<FormControl
34+
type="number"
35+
value={data?.restrictionsRangeMin}
36+
disabled={disabled}
37+
onChange={(event) => handleOnChange({
38+
restrictionsRangeMin: parseNumber(event.target.value)
39+
})}
40+
/>
41+
{errors?.restrictionsRangeMin ?
42+
<HelpBlock>
43+
<Message msgId={errors.restrictionsRangeMin} />
44+
</HelpBlock> : null}
45+
</FlexBox.Fill>
46+
<FlexBox.Fill
47+
flexBox
48+
component={FormGroup}
49+
validationState={errors?.restrictionsRangeMax ? 'error' : undefined}
50+
controlId={getAttributeControlId(data, 'restrictions-max')}
51+
gap="sm"
52+
centerChildrenVertically
53+
>
54+
<ControlLabel><Message msgId="gnviewer.max" /></ControlLabel>
55+
<FormControl
56+
type="number"
57+
value={data?.restrictionsRangeMax}
58+
disabled={disabled}
59+
onChange={(event) => handleOnChange({
60+
restrictionsRangeMax: parseNumber(event.target.value)
61+
})}
62+
/>
63+
{errors?.restrictionsRangeMax
64+
? <HelpBlock>
65+
<Message msgId={errors.restrictionsRangeMax} />
66+
</HelpBlock> : null}
67+
</FlexBox.Fill>
68+
</FlexBox>
69+
);
70+
};
71+
72+
export default RangeRestriction;

0 commit comments

Comments
 (0)