diff --git a/geonode_mapstore_client/client/js/apps/gn-catalogue.js b/geonode_mapstore_client/client/js/apps/gn-catalogue.js index 249e81a3ca..ecdd03ba7a 100644 --- a/geonode_mapstore_client/client/js/apps/gn-catalogue.js +++ b/geonode_mapstore_client/client/js/apps/gn-catalogue.js @@ -104,8 +104,7 @@ const getViewer = (component) => { const viewers = { [appRouteComponentTypes.VIEWER]: ViewerRoute, [appRouteComponentTypes.CATALOGUE]: useRedirect ? RedirectRoute : ComponentsRoute, - [appRouteComponentTypes.DATASET_UPLOAD]: ComponentsRoute, - [appRouteComponentTypes.DOCUMENT_UPLOAD]: ComponentsRoute, + [appRouteComponentTypes.COMPONENTS]: ComponentsRoute, [appRouteComponentTypes.MAP_VIEWER]: MapViewerRoute }; return viewers[component]; diff --git a/geonode_mapstore_client/client/js/plugins/CreateDataset/components/CreateDatasetAttributeRow.jsx b/geonode_mapstore_client/client/js/plugins/CreateDataset/components/CreateDatasetAttributeRow.jsx new file mode 100644 index 0000000000..d25fb0d24b --- /dev/null +++ b/geonode_mapstore_client/client/js/plugins/CreateDataset/components/CreateDatasetAttributeRow.jsx @@ -0,0 +1,283 @@ +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +import React from 'react'; +import PropTypes from 'prop-types'; +import FlexBox from '@mapstore/framework/components/layout/FlexBox'; +import Text from '@mapstore/framework/components/layout/Text'; +import { FormGroup, FormControl, ControlLabel, Checkbox, Glyphicon, HelpBlock } from 'react-bootstrap'; +import Button from '@mapstore/framework/components/layout/Button'; +import Message from '@mapstore/framework/components/I18N/Message'; +import uuid from 'uuid/v1'; +import { getMessageById } from '@mapstore/framework/utils/LocaleUtils'; +import { AttributeTypes, RestrictionsTypes } from '../utils/CreateDatasetUtils'; + +const parseNumber = (value) => { + if (value === '') { + return null; + } + return parseFloat(value); +}; + +const CreateDatasetAttributeRow = ({ + data, + geometryAttribute, + tools, + onChange, + getErrorByPath = () => undefined, + index +}, context) => { + + const errors = { + name: getErrorByPath(`/attributes/${index}/name`), + restrictionsType: getErrorByPath(`/attributes/${index}/restrictionsType`), + restrictionsRangeMin: getErrorByPath(`/attributes/${index}/restrictionsRangeMin`), + restrictionsRangeMax: getErrorByPath(`/attributes/${index}/restrictionsRangeMax`) + }; + + const typesOptions = geometryAttribute + ? [ + { value: AttributeTypes.Point, labelId: 'gnviewer.points' }, + { value: AttributeTypes.LineString, labelId: 'gnviewer.lines' }, + { value: AttributeTypes.Polygon, labelId: 'gnviewer.polygons' }] + : [ + { value: AttributeTypes.String, labelId: 'gnviewer.string' }, + { value: AttributeTypes.Integer, labelId: 'gnviewer.integer' }, + { value: AttributeTypes.Float, labelId: 'gnviewer.float' }, + { value: AttributeTypes.Date, labelId: 'gnviewer.date' } + ]; + + const restrictionsOptions = [ + AttributeTypes.Integer, + AttributeTypes.Float, + AttributeTypes.String + ].includes(data?.type) + ? [ + { value: RestrictionsTypes.None, labelId: 'gnviewer.none' }, + ...(data?.type !== AttributeTypes.String + ? [{ value: RestrictionsTypes.Range, labelId: 'gnviewer.range' }] + : [] + ), + { value: RestrictionsTypes.Options, labelId: 'gnviewer.options' } + ] + : [{ value: RestrictionsTypes.None, labelId: 'gnviewer.none' }]; + + const getControlId = (suffix) => `attribute-${data?.id}-${suffix}`; + + function handleOnChange(properties) { + onChange({ + ...data, + ...properties + }); + } + + function handleTypeChange(event) { + const newType = event.target.value; + const currentType = data?.type; + + // If type is changing, clear restrictions and set to default + if (newType !== currentType) { + onChange({ + ...data, + type: newType, + restrictionsType: RestrictionsTypes.None, + restrictionsRangeMin: null, + restrictionsRangeMax: null, + restrictionsOptions: [] + }); + } else { + handleOnChange({ type: newType }); + } + } + + return ( + + + + handleOnChange({ name: event.target.value })} + /> + {errors?.name ? : null} + + + + + + {typesOptions.map(({ labelId, value }) => + + )} + + + + + + + handleOnChange({ nillable: event.target.checked }) + } + /> + + + + + + + handleOnChange({ restrictionsType: event.target.value }) + } + > + {restrictionsOptions.map(({ labelId, value }) => + + )} + + {errors?.restrictionsType ? {errors.restrictionsType} : null} + + {data?.restrictionsType === RestrictionsTypes.Range ? + + + + handleOnChange({ + restrictionsRangeMin: parseNumber(event.target.value) + })} + /> + {errors?.restrictionsRangeMin ? + + + : null} + + + + handleOnChange({ + restrictionsRangeMax: parseNumber(event.target.value) + })} + /> + {errors?.restrictionsRangeMax + ? + + : null} + + : null} + {data?.restrictionsType === RestrictionsTypes.Options + ? + + {(data?.restrictionsOptions || []).map((option, idx) => { + const optionsError = { + value: getErrorByPath(`/attributes/${index}/restrictionsOptions/${idx}/value`) + }; + return ( + + + + handleOnChange({ + restrictionsOptions: (data?.restrictionsOptions || []) + .map((opt) => { + return opt.id !== option.id + ? opt : { + ...option, + value: data?.type === AttributeTypes.String + ? event.target.value + : parseNumber(event.target.value) + }; + }) + })} + /> + {optionsError?.value ? + + : null} + + + + ); + })} +
+ +
+
+
: null} +
+ + + {tools} + + + ); +}; + +CreateDatasetAttributeRow.contextTypes = { + messages: PropTypes.object +}; + +export default CreateDatasetAttributeRow; diff --git a/geonode_mapstore_client/client/js/plugins/CreateDataset/containers/CreateDataset.jsx b/geonode_mapstore_client/client/js/plugins/CreateDataset/containers/CreateDataset.jsx new file mode 100644 index 0000000000..387a1cbf46 --- /dev/null +++ b/geonode_mapstore_client/client/js/plugins/CreateDataset/containers/CreateDataset.jsx @@ -0,0 +1,234 @@ +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +import React, { useState } from 'react'; +import { isNil } from 'lodash'; +import FlexBox from '@mapstore/framework/components/layout/FlexBox'; +import Text from '@mapstore/framework/components/layout/Text'; +import { FormGroup, FormControl, ControlLabel, Glyphicon, HelpBlock } from 'react-bootstrap'; +import Button from '@mapstore/framework/components/layout/Button'; +import Message from '@mapstore/framework/components/I18N/Message'; +import uuid from 'uuid/v1'; +import validator from '@rjsf/validator-ajv8'; +import axios from '@mapstore/framework/libs/ajax'; +import { + AttributeTypes, + RestrictionsTypes, + validateSchema, + validateAttributes, + getErrorByPath, + DEFAULT_ATTRIBUTE +} from '../utils/CreateDatasetUtils'; +import CreateDatasetAttributeRow from '../components/CreateDatasetAttributeRow'; +import Spinner from '@mapstore/framework/components/layout/Spinner'; + +const CreateDataset = () => { + + const [dataset, setDataset] = useState({ + name: '', + title: '', + geometry_type: AttributeTypes.Point, + attributes: [] + }); + + const [loading, setLoading] = useState(false); + + const { errors = [] } = validator.rawValidation(validateSchema, dataset) || {}; + const attributeValidationErrors = validateAttributes(dataset); + const allErrors = [...errors, ...attributeValidationErrors]; + + const tileError = getErrorByPath('/title', allErrors); + + function handleAddAttribute() { + setDataset(prevDataset => ({ + ...prevDataset, + attributes: [ + ...prevDataset.attributes, + { + id: uuid(), + name: '', + type: AttributeTypes.String, + restrictionsType: 'none', + nillable: true + } + ] + })); + } + + function handleUpdateAttribute(newAttribute) { + setDataset(prevDataset => ({ + ...prevDataset, + attributes: prevDataset.attributes + .map(attribute => attribute.id !== newAttribute.id ? attribute : newAttribute) + })); + } + + function handleRemoveAttribute(removeId) { + setDataset(prevDataset => ({ + ...prevDataset, + attributes: prevDataset.attributes + .filter(attribute => attribute.id !== removeId) + })); + } + + function handleCreate() { + const formData = new FormData(); + formData.append('title', dataset.title); + formData.append('geometry_type', dataset.geometry_type); + const attributes = dataset.attributes.reduce((acc, attribute) => { + const restrictionsType = attribute.restrictionsType || RestrictionsTypes.None; + acc[attribute.name] = { + type: attribute.type, + nillable: !!attribute.nillable, + ...(restrictionsType === RestrictionsTypes.Range && { + range: { + ...(!isNil(attribute.restrictionsRangeMin) && { + min: attribute.restrictionsRangeMin + }), + ...(!isNil(attribute.restrictionsRangeMax) && { + max: attribute.restrictionsRangeMax + }) + } + }), + ...(restrictionsType === RestrictionsTypes.Options && { + options: (attribute.restrictionsOptions || []).map(option => option.value) + }) + }; + return acc; + }, {}); + + setLoading(true); + formData.append('attributes', JSON.stringify(attributes)); + axios.post('/createlayer/?f=json', formData) + .then((response) => { + if (response?.data?.detail_url) { + window.location.href = response.data.detail_url; + } + }) + .finally(() => setLoading(false)); + } + + return ( + + + + + + + + + setDataset({ + ...dataset, + title: event.target.value + }) + } + /> + {tileError ? : null} + + + + + + + + + + + + + + + + + {/* action buttons */} + + + + { + setDataset({ + ...dataset, + geometry_type: geometry.type + }); + }} + geometryAttribute + /> + {dataset.attributes.map((attribute, idx) => { + return ( + getErrorByPath(path, allErrors)} + tools={ + + } + onChange={handleUpdateAttribute} + /> + ); + })} + + + + +
+ +
+
+ +
+
+
+ ); +}; + +export default CreateDataset; diff --git a/geonode_mapstore_client/client/js/plugins/CreateDataset/index.jsx b/geonode_mapstore_client/client/js/plugins/CreateDataset/index.jsx new file mode 100644 index 0000000000..9f5dcde2f3 --- /dev/null +++ b/geonode_mapstore_client/client/js/plugins/CreateDataset/index.jsx @@ -0,0 +1,32 @@ + + +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ +import React, { Suspense, lazy } from 'react'; +import { createPlugin } from "@mapstore/framework/utils/PluginsUtils"; +const CreateDataset = lazy(() => import('./containers/CreateDataset')); + +/** + * Create a new dataset + * @name CreateDataset + * @memberof plugins + */ +const CreateDatasetPlugin = ({ props }) => { + return ( + + + + ); +}; + +export default createPlugin('CreateDataset', { + component: CreateDatasetPlugin, + containers: {}, + epics: {}, + reducers: {} +}); diff --git a/geonode_mapstore_client/client/js/plugins/CreateDataset/utils/CreateDatasetUtils.js b/geonode_mapstore_client/client/js/plugins/CreateDataset/utils/CreateDatasetUtils.js new file mode 100644 index 0000000000..14d1a1a1f4 --- /dev/null +++ b/geonode_mapstore_client/client/js/plugins/CreateDataset/utils/CreateDatasetUtils.js @@ -0,0 +1,287 @@ +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +export const AttributeTypes = { + Point: "Point", + LineString: "LineString", + Polygon: "Polygon", + String: "string", + Integer: "integer", + Float: "float", + Date: "date" +}; + +export const RestrictionsTypes = { + None: "none", + Range: "range", + Options: "options" +}; + +export const DEFAULT_ATTRIBUTE = { + id: 'geom', + name: 'geom', + restrictionsType: RestrictionsTypes.None, + nillable: false +}; + +export const validateSchema = { + "type": "object", + "properties": { + "title": { + "type": "string", + "minLength": 1 + }, + "geometry_type": { + "type": "string", + "enum": [AttributeTypes.Point, AttributeTypes.LineString, AttributeTypes.Polygon] + }, + "attributes": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { + "type": "string", + "minLength": 1 + }, + "type": { + "type": "string", + "enum": [AttributeTypes.String, AttributeTypes.Integer, AttributeTypes.Float, AttributeTypes.Date] + }, + "nillable": { + "type": "boolean" + } + }, + "required": ["name", "type"], + "allOf": [ + { + "if": { + "properties": { + "type": { "const": AttributeTypes.Integer } + } + }, + "then": { + "properties": { + "restrictionsType": { + "type": "string", + "enum": [RestrictionsTypes.None, RestrictionsTypes.Range, RestrictionsTypes.Options] + }, + "restrictionsRangeMin": { + "type": "integer" + }, + "restrictionsRangeMax": { + "type": "integer" + }, + "restrictionsOptions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "value": { + "type": "integer", + "minLength": 1 + } + } + } + } + }, + "if": { + "properties": { + "restrictionsType": { "const": RestrictionsTypes.Range } + } + }, + "then": { + "properties": { + "restrictionsRangeMin": { + "type": "integer" + }, + "restrictionsRangeMax": { + "type": "integer" + } + } + } + } + }, + { + "if": { + "properties": { + "type": { "const": AttributeTypes.Float } + } + }, + "then": { + "properties": { + "restrictionsType": { + "type": "string", + "enum": [RestrictionsTypes.None, RestrictionsTypes.Range, RestrictionsTypes.Options] + }, + "restrictionsRangeMin": { + "type": "number" + }, + "restrictionsRangeMax": { + "type": "number" + }, + "restrictionsOptions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "value": { + "type": "number", + "minLength": 1 + } + } + } + } + }, + "if": { + "properties": { + "restrictionsType": { "const": RestrictionsTypes.Range } + } + }, + "then": { + "properties": { + "restrictionsRangeMin": { + "type": "number" + }, + "restrictionsRangeMax": { + "type": "number" + } + } + } + } + }, + { + "if": { + "properties": { + "type": { "const": AttributeTypes.String } + } + }, + "then": { + "properties": { + "restrictionsType": { + "type": "string", + "enum": [RestrictionsTypes.None, RestrictionsTypes.Options] + }, + "restrictionsOptions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "value": { + "type": "string", + "minLength": 1 + } + } + } + } + } + } + }, + { + "if": { + "properties": { + "type": { "const": AttributeTypes.Date } + } + }, + "then": { + "properties": { + "restrictionsType": { + "type": "string", + "enum": [RestrictionsTypes.None] + } + } + } + } + ] + } + } + }, + "required": ["title", "geometry_type"] +}; + +/** + * Validate attribute data including range values and unique names + * @param {Object} data - The data to validate + * @returns {Array} The array of errors + */ +export const validateAttributes = (data = {}) => { + const errors = []; + + if (!Array.isArray(data.attributes)) return errors; + + // Count names occurrences + const nameCounts = data.attributes.reduce((counts, attr) => { + const name = attr?.name?.trim(); + if (name) counts[name] = (counts[name] || 0) + 1; + return counts; + }, {}); + + data.attributes.forEach((attr, index) => { + const name = attr?.name?.trim(); + + // Check if name is unique + if (name && nameCounts[name] > 1) { + errors.push({ + instancePath: `/attributes/${index}/name`, + message: 'gnviewer.duplicateAttributeNameError' + }); + } + + // Check if range values are valid + if (attr?.restrictionsType === RestrictionsTypes.Range) { + const { restrictionsRangeMin: min, restrictionsRangeMax: max } = attr; + if (min !== null && max !== null && min > max) { + errors.push({ + instancePath: `/attributes/${index}/restrictionsRangeMin`, + message: 'gnviewer.minError' + }); + errors.push({ + instancePath: `/attributes/${index}/restrictionsRangeMax`, + message: 'gnviewer.maxError' + }); + } + } + }); + + return errors; +}; + +/** + * Get the error message by path + * @param {string} path - The path to the error + * @param {Array} allErrors - The array of errors + * @returns {string} The error message + */ +export const getErrorByPath = (path, allErrors) => { + const error = allErrors?.find(err => err.instancePath === path); + if (error?.message) { + // Override specific error messages + if (error.message.includes('must NOT have fewer than 1 characters')) { + return 'gnviewer.minValueRequired'; + } + if (error.message.includes('must be string')) { + return 'gnviewer.stringValueRequired'; + } + if (error.message.includes('must be integer')) { + return 'gnviewer.integerValueRequired'; + } + if (error.message.includes('must be number')) { + return 'gnviewer.numberValueRequired'; + } + } + return error?.message; +}; diff --git a/geonode_mapstore_client/client/js/plugins/CreateDataset/utils/__tests__/CreateDatasetUtils-test.js b/geonode_mapstore_client/client/js/plugins/CreateDataset/utils/__tests__/CreateDatasetUtils-test.js new file mode 100644 index 0000000000..ffc441df25 --- /dev/null +++ b/geonode_mapstore_client/client/js/plugins/CreateDataset/utils/__tests__/CreateDatasetUtils-test.js @@ -0,0 +1,353 @@ +/* + * Copyright 2025, GeoSolutions Sas. + * All rights reserved. + * + * This source code is licensed under the BSD-style license found in the + * LICENSE file in the root directory of this source tree. + */ + +import expect from 'expect'; +import { + validateAttributes, + getErrorByPath, + RestrictionsTypes +} from '../CreateDatasetUtils'; + +describe('Test CreateDatasetUtils', () => { + describe('validateAttributes', () => { + it('should return empty array for invalid data', () => { + expect(validateAttributes()).toEqual([]); + expect(validateAttributes({})).toEqual([]); + expect(validateAttributes({ attributes: null })).toEqual([]); + expect(validateAttributes({ attributes: undefined })).toEqual([]); + expect(validateAttributes({ attributes: 'not-array' })).toEqual([]); + }); + + it('should return empty array for empty attributes array', () => { + expect(validateAttributes({ attributes: [] })).toEqual([]); + }); + + it('should validate unique attribute names', () => { + const data = { + attributes: [ + { name: 'attr1', type: 'string' }, + { name: 'attr2', type: 'string' }, + { name: 'attr1', type: 'integer' } // duplicate name + ] + }; + + const errors = validateAttributes(data); + expect(errors.length).toBe(2); + expect(errors[0]).toEqual({ + instancePath: '/attributes/0/name', + message: 'gnviewer.duplicateAttributeNameError' + }); + expect(errors[1]).toEqual({ + instancePath: '/attributes/2/name', + message: 'gnviewer.duplicateAttributeNameError' + }); + }); + + it('should handle multiple duplicate names', () => { + const data = { + attributes: [ + { name: 'attr1', type: 'string' }, + { name: 'attr1', type: 'integer' }, + { name: 'attr1', type: 'float' }, + { name: 'attr2', type: 'string' } + ] + }; + + const errors = validateAttributes(data); + expect(errors.length).toBe(3); + expect(errors.every(error => error.message === 'gnviewer.duplicateAttributeNameError')).toBe(true); + expect(errors[0].instancePath).toBe('/attributes/0/name'); + expect(errors[1].instancePath).toBe('/attributes/1/name'); + expect(errors[2].instancePath).toBe('/attributes/2/name'); + }); + + it('should handle empty and whitespace-only names', () => { + const data = { + attributes: [ + { name: '', type: 'string' }, + { name: ' ', type: 'string' }, + { name: 'valid', type: 'string' }, + { name: null, type: 'string' }, + { name: undefined, type: 'string' } + ] + }; + + const errors = validateAttributes(data); + expect(errors.length).toBe(0); + }); + + it('should trim whitespace when checking for duplicates', () => { + const data = { + attributes: [ + { name: 'attr1', type: 'string' }, + { name: ' attr1 ', type: 'integer' }, // same name with whitespace + { name: 'attr2', type: 'string' } + ] + }; + + const errors = validateAttributes(data); + expect(errors.length).toBe(2); + expect(errors[0].instancePath).toBe('/attributes/0/name'); + expect(errors[1].instancePath).toBe('/attributes/1/name'); + }); + + it('should validate range restrictions for integer attributes', () => { + const data = { + attributes: [ + { + name: 'validRange', + type: 'integer', + restrictionsType: RestrictionsTypes.Range, + restrictionsRangeMin: 1, + restrictionsRangeMax: 10 + }, + { + name: 'invalidRange', + type: 'integer', + restrictionsType: RestrictionsTypes.Range, + restrictionsRangeMin: 10, + restrictionsRangeMax: 5 // min > max + } + ] + }; + + const errors = validateAttributes(data); + expect(errors.length).toBe(2); + expect(errors[0]).toEqual({ + instancePath: '/attributes/1/restrictionsRangeMin', + message: 'gnviewer.minError' + }); + expect(errors[1]).toEqual({ + instancePath: '/attributes/1/restrictionsRangeMax', + message: 'gnviewer.maxError' + }); + }); + + it('should validate range restrictions for float attributes', () => { + const data = { + attributes: [ + { + name: 'validFloatRange', + type: 'float', + restrictionsType: RestrictionsTypes.Range, + restrictionsRangeMin: 1.5, + restrictionsRangeMax: 10.5 + }, + { + name: 'invalidFloatRange', + type: 'float', + restrictionsType: RestrictionsTypes.Range, + restrictionsRangeMin: 10.5, + restrictionsRangeMax: 5.5 // min > max + } + ] + }; + + const errors = validateAttributes(data); + expect(errors.length).toBe(2); + expect(errors[0]).toEqual({ + instancePath: '/attributes/1/restrictionsRangeMin', + message: 'gnviewer.minError' + }); + expect(errors[1]).toEqual({ + instancePath: '/attributes/1/restrictionsRangeMax', + message: 'gnviewer.maxError' + }); + }); + + it('should not validate range for non-range restrictions', () => { + const data = { + attributes: [ + { + name: 'noneRestriction', + type: 'integer', + restrictionsType: RestrictionsTypes.None, + restrictionsRangeMin: 10, + restrictionsRangeMax: 5 + }, + { + name: 'optionsRestriction', + type: 'integer', + restrictionsType: RestrictionsTypes.Options, + restrictionsRangeMin: 10, + restrictionsRangeMax: 5 + } + ] + }; + + const errors = validateAttributes(data); + expect(errors.length).toBe(0); + }); + + it('should handle null range values', () => { + const data = { + attributes: [ + { + name: 'nullMin', + type: 'integer', + restrictionsType: RestrictionsTypes.Range, + restrictionsRangeMin: null, + restrictionsRangeMax: 10 + }, + { + name: 'nullMax', + type: 'integer', + restrictionsType: RestrictionsTypes.Range, + restrictionsRangeMin: 5, + restrictionsRangeMax: null + }, + { + name: 'bothNull', + type: 'integer', + restrictionsType: RestrictionsTypes.Range, + restrictionsRangeMin: null, + restrictionsRangeMax: null + } + ] + }; + + const errors = validateAttributes(data); + expect(errors.length).toBe(0); + }); + + it('should handle equal min and max values', () => { + const data = { + attributes: [ + { + name: 'equalValues', + type: 'integer', + restrictionsType: RestrictionsTypes.Range, + restrictionsRangeMin: 5, + restrictionsRangeMax: 5 + } + ] + }; + + const errors = validateAttributes(data); + expect(errors.length).toBe(0); + }); + + it('should validate both unique names and range restrictions', () => { + const data = { + attributes: [ + { + name: 'duplicate', + type: 'integer', + restrictionsType: RestrictionsTypes.Range, + restrictionsRangeMin: 1, + restrictionsRangeMax: 10 + }, + { + name: 'duplicate', + type: 'integer', + restrictionsType: RestrictionsTypes.Range, + restrictionsRangeMin: 10, + restrictionsRangeMax: 5 // invalid range + } + ] + }; + + const errors = validateAttributes(data); + expect(errors.length).toBe(4); + + // Check duplicate name errors + const duplicateErrors = errors.filter(error => error.message === 'gnviewer.duplicateAttributeNameError'); + expect(duplicateErrors.length).toBe(2); + + // Check range errors + const rangeErrors = errors.filter(error => error.message === 'gnviewer.minError' || error.message === 'gnviewer.maxError'); + expect(rangeErrors.length).toBe(2); + }); + + it('should handle missing properties gracefully', () => { + const data = { + attributes: [ + { name: 'attr1' }, // missing type + { type: 'string' }, // missing name + { name: 'attr2', type: 'integer', restrictionsType: RestrictionsTypes.Range }, // missing range values + { name: 'attr3', type: 'string', restrictionsType: 'invalid' } // invalid restriction type + ] + }; + + const errors = validateAttributes(data); + expect(errors.length).toBe(0); // Should not throw errors for missing properties + }); + }); + + describe('getErrorByPath', () => { + const mockErrors = [ + { instancePath: '/title', message: 'must NOT have fewer than 1 characters' }, + { instancePath: '/attributes/0/name', message: 'must be string' }, + { instancePath: '/attributes/1/type', message: 'must be integer' }, + { instancePath: '/attributes/2/restrictionsRangeMin', message: 'must be number' }, + { instancePath: '/attributes/3/name', message: 'gnviewer.duplicateAttributeNameError' }, + { instancePath: '/attributes/4/name', message: 'some other error' } + ]; + + it('should return undefined for non-existent path', () => { + expect(getErrorByPath('/non/existent/path', mockErrors)).toBe(undefined); + expect(getErrorByPath('/title', [])).toBe(undefined); + }); + + it('should return original message for non-override cases', () => { + expect(getErrorByPath('/attributes/3/name', mockErrors)).toBe('gnviewer.duplicateAttributeNameError'); + expect(getErrorByPath('/attributes/4/name', mockErrors)).toBe('some other error'); + }); + + it('should override minLength error message', () => { + expect(getErrorByPath('/title', mockErrors)).toBe('gnviewer.minValueRequired'); + }); + + it('should override string validation error message', () => { + expect(getErrorByPath('/attributes/0/name', mockErrors)).toBe('gnviewer.stringValueRequired'); + }); + + it('should override integer validation error message', () => { + expect(getErrorByPath('/attributes/1/type', mockErrors)).toBe('gnviewer.integerValueRequired'); + }); + + it('should override number validation error message', () => { + expect(getErrorByPath('/attributes/2/restrictionsRangeMin', mockErrors)).toBe('gnviewer.numberValueRequired'); + }); + + it('should handle empty errors array', () => { + expect(getErrorByPath('/any/path', [])).toBe(undefined); + }); + + it('should handle null/undefined errors', () => { + expect(getErrorByPath('/any/path', null)).toBe(undefined); + expect(getErrorByPath('/any/path', undefined)).toBe(undefined); + }); + + it('should handle errors without message property', () => { + const errorsWithoutMessage = [ + { instancePath: '/title' }, + { instancePath: '/attributes/0/name', message: null }, + { instancePath: '/attributes/1/name', message: undefined } + ]; + + expect(getErrorByPath('/title', errorsWithoutMessage)).toBe(undefined); + expect(getErrorByPath('/attributes/0/name', errorsWithoutMessage)).toBe(null); + expect(getErrorByPath('/attributes/1/name', errorsWithoutMessage)).toBe(undefined); + }); + + it('should handle partial message matches', () => { + const partialMatchErrors = [ + { instancePath: '/test1', message: 'must NOT have fewer than 1 characters and more text' }, + { instancePath: '/test2', message: 'prefix must be string suffix' }, + { instancePath: '/test3', message: 'before must be integer after' }, + { instancePath: '/test4', message: 'start must be number end' } + ]; + + expect(getErrorByPath('/test1', partialMatchErrors)).toBe('gnviewer.minValueRequired'); + expect(getErrorByPath('/test2', partialMatchErrors)).toBe('gnviewer.stringValueRequired'); + expect(getErrorByPath('/test3', partialMatchErrors)).toBe('gnviewer.integerValueRequired'); + expect(getErrorByPath('/test4', partialMatchErrors)).toBe('gnviewer.numberValueRequired'); + }); + }); +}); diff --git a/geonode_mapstore_client/client/js/plugins/index.js b/geonode_mapstore_client/client/js/plugins/index.js index e14c3efaee..099480d97b 100644 --- a/geonode_mapstore_client/client/js/plugins/index.js +++ b/geonode_mapstore_client/client/js/plugins/index.js @@ -22,6 +22,7 @@ import OperationPlugin from '@js/plugins/Operation'; import MetadataEditorPlugin from '@js/plugins/MetadataEditor'; import MetadataViewerPlugin from '@js/plugins/MetadataEditor/MetadataViewer'; import FavoritesPlugin from '@js/plugins/Favorites'; +import CreateDatasetPlugin from '@js/plugins/CreateDataset'; import { ResourcesGridPlugin, ResourcesFiltersFormPlugin @@ -78,6 +79,7 @@ export const plugins = { ResourcesGridPlugin, FavoritesPlugin, ResourcesFiltersFormPlugin, + CreateDatasetPlugin, LayerDownloadPlugin: toModulePlugin( 'LayerDownload', () => import(/* webpackChunkName: 'plugins/layer-download' */ '@mapstore/framework/plugins/LayerDownload'), diff --git a/geonode_mapstore_client/client/js/utils/AppRoutesUtils.js b/geonode_mapstore_client/client/js/utils/AppRoutesUtils.js index aa4ac0006c..0e626a77dc 100644 --- a/geonode_mapstore_client/client/js/utils/AppRoutesUtils.js +++ b/geonode_mapstore_client/client/js/utils/AppRoutesUtils.js @@ -11,8 +11,6 @@ import { ResourceTypes } from '@js/utils/ResourceUtils'; export const appRouteComponentTypes = { VIEWER: 'ViewerRoute', CATALOGUE: 'CatalogueRoute', - DATASET_UPLOAD: 'UploadDatasetRoute', - DOCUMENT_UPLOAD: 'UploadDocumentRoute', COMPONENTS: 'ComponentsRoute', MAP_VIEWER: 'MapViewerRoute' }; @@ -183,15 +181,22 @@ export const CATALOGUE_ROUTES = [ { name: 'upload_dataset', path: ['/upload/dataset'], - component: appRouteComponentTypes.DATASET_UPLOAD, + component: appRouteComponentTypes.COMPONENTS, protectedRoute: true, hash: "#/upload/dataset" }, { name: 'upload_document', path: ['/upload/document'], - component: appRouteComponentTypes.DOCUMENT_UPLOAD, + component: appRouteComponentTypes.COMPONENTS, protectedRoute: true, hash: "#/upload/document" + }, + { + name: 'create_dataset', + path: ['/create/dataset'], + component: appRouteComponentTypes.COMPONENTS, + protectedRoute: true, + hash: "#/create/dataset" } ]; diff --git a/geonode_mapstore_client/client/js/utils/__tests__/AppRoutesUtils-test.js b/geonode_mapstore_client/client/js/utils/__tests__/AppRoutesUtils-test.js index d343e170e3..0c1b26a45e 100644 --- a/geonode_mapstore_client/client/js/utils/__tests__/AppRoutesUtils-test.js +++ b/geonode_mapstore_client/client/js/utils/__tests__/AppRoutesUtils-test.js @@ -18,8 +18,6 @@ describe('Test App Routes Utils', () => { expect(componentTypes).toEqual({ VIEWER: 'ViewerRoute', CATALOGUE: 'CatalogueRoute', - DATASET_UPLOAD: 'UploadDatasetRoute', - DOCUMENT_UPLOAD: 'UploadDocumentRoute', COMPONENTS: 'ComponentsRoute', MAP_VIEWER: 'MapViewerRoute' }); @@ -70,7 +68,8 @@ describe('Test App Routes Utils', () => { mapViewerRoute, catalogueRoute, uploadDatasetRoute, - uploadDocumentRoute + uploadDocumentRoute, + createDatasetRoute ] = routeUtils.CATALOGUE_ROUTES; expect(metadataRoute.path).toEqual(['/metadata/:pk']); expect(metadataRoute.name).toEqual('metadata'); @@ -100,5 +99,7 @@ describe('Test App Routes Utils', () => { expect(uploadDatasetRoute.name).toEqual('upload_dataset'); expect(uploadDocumentRoute.path).toEqual(['/upload/document']); expect(uploadDocumentRoute.name).toEqual('upload_document'); + expect(createDatasetRoute.path).toEqual(['/create/dataset']); + expect(createDatasetRoute.name).toEqual('create_dataset'); }); }); diff --git a/geonode_mapstore_client/client/themes/geonode/less/_brand-navbar.less b/geonode_mapstore_client/client/themes/geonode/less/_brand-navbar.less index 0d7a874249..9e06ceab82 100644 --- a/geonode_mapstore_client/client/themes/geonode/less/_brand-navbar.less +++ b/geonode_mapstore_client/client/themes/geonode/less/_brand-navbar.less @@ -137,7 +137,9 @@ } } -.gn-catalogue:has(.page-viewer) { +.gn-catalogue:has(.page-viewer), +.gn-catalogue:has(.gn-upload-container), +.gn-catalogue:has(.gn-create-dataset) { display: flex; position: absolute; overflow: auto; @@ -164,4 +166,12 @@ max-width: 100%; } } +} + +.gn-catalogue:has(.gn-create-dataset) { + #gn-brand-navbar { + > .ms-flex-fill { + max-width: 1440px; + } + } } \ No newline at end of file diff --git a/geonode_mapstore_client/client/themes/geonode/less/_dataset.less b/geonode_mapstore_client/client/themes/geonode/less/_dataset.less new file mode 100644 index 0000000000..63b9f57c5f --- /dev/null +++ b/geonode_mapstore_client/client/themes/geonode/less/_dataset.less @@ -0,0 +1,27 @@ +.gn-create-dataset { + overflow: auto; + + .gn-create-dataset-container { + max-width: 1440px; + margin: auto; + width: 100%; + } + .gn-dataset-attribute { + .gn-attribute-name { + width: 300px; + } + .gn-attribute-type { + width: 120px; + } + .gn-attribute-nillable { + width: 80px; + text-align: center; + } + .gn-attribute-restrictions { + width: 120px; + } + .gn-attribute-tools { + width: 40px; + } + } +} diff --git a/geonode_mapstore_client/client/themes/geonode/less/_footer.less b/geonode_mapstore_client/client/themes/geonode/less/_footer.less index fbea034a4d..3536fbcaf6 100644 --- a/geonode_mapstore_client/client/themes/geonode/less/_footer.less +++ b/geonode_mapstore_client/client/themes/geonode/less/_footer.less @@ -62,7 +62,9 @@ .gn-footer-placeholder { display: block; } -.gn-catalogue:has(.page-viewer) { +.gn-catalogue:has(.page-viewer), +.gn-catalogue:has(.gn-upload-container), +.gn-catalogue:has(.gn-create-dataset) { .gn-footer-placeholder { display: none; } diff --git a/geonode_mapstore_client/client/themes/geonode/less/geonode.less b/geonode_mapstore_client/client/themes/geonode/less/geonode.less index 34e2ab6be9..b37eced28d 100644 --- a/geonode_mapstore_client/client/themes/geonode/less/geonode.less +++ b/geonode_mapstore_client/client/themes/geonode/less/geonode.less @@ -18,6 +18,7 @@ @import '_search-bar.less'; @import '_share.less'; @import '_upload.less'; +@import '_dataset.less'; @import '_mixins.less'; @import '_bootstrap-variables.less'; diff --git a/geonode_mapstore_client/static/mapstore/configs/localConfig.json b/geonode_mapstore_client/static/mapstore/configs/localConfig.json index eef24b9058..9717099b86 100644 --- a/geonode_mapstore_client/static/mapstore/configs/localConfig.json +++ b/geonode_mapstore_client/static/mapstore/configs/localConfig.json @@ -3261,7 +3261,7 @@ "labelId": "gnhome.createDataset", "value": "layer", "type": "link", - "href": "/createlayer/", + "href": "{context.getCataloguePath('/catalogue/#/create/dataset')}", "disableIf": "{(state('settings') && state('settings').createLayer) ? false : true}" }, { @@ -3664,6 +3664,11 @@ "resourceType": "document" } } + ], + "create_dataset": [ + { + "name": "CreateDataset" + } ] } } \ No newline at end of file diff --git a/geonode_mapstore_client/static/mapstore/gn-translations/data.de-DE.json b/geonode_mapstore_client/static/mapstore/gn-translations/data.de-DE.json index e42c4a6926..05d407dea1 100644 --- a/geonode_mapstore_client/static/mapstore/gn-translations/data.de-DE.json +++ b/geonode_mapstore_client/static/mapstore/gn-translations/data.de-DE.json @@ -489,7 +489,35 @@ "geostory": "geostory", "document": "dokument", "dashboard": "dashboard", - "mapviewer": "kartenviewer" + "mapviewer": "kartenviewer", + "none": "Keine", + "options": "Optionen", + "range": "Bereich", + "points": "Punkte", + "lines": "Linien", + "polygons": "Polygone", + "string": "Zeichenkette", + "integer": "Ganzzahl", + "float": "Gleitkommazahl", + "createAnEmptyDataset": "Leeren Datensatz erstellen", + "datasetTitle": "Datensatztitel", + "name": "Name", + "type": "Typ", + "nillable": "Nullbar", + "restrictions": "Einschränkungen", + "loadAttributesFromJSONSchema": "Attribute aus JSON-Schema laden", + "addAttribute": "Attribut hinzufügen", + "createNewDataset": "Neuen Datensatz erstellen", + "addOption": "Option hinzufügen", + "min": "Min", + "max": "Max", + "minError": "Muss kleiner oder gleich dem Maximalwert sein", + "maxError": "Muss größer oder gleich dem Minimalwert sein", + "minValueRequired": "Eingabewert darf nicht leer sein", + "strValueRequired": "Muss eine gültige Zeichenkette sein", + "integerValueRequired": "Muss eine gültige Ganzzahl sein", + "numberValueRequired": "Muss eine gültige Zahl sein", + "duplicateAttributeNameError": "Attributname muss eindeutig sein" }, "resourcesCatalog": { "anonymous": "Jeder", diff --git a/geonode_mapstore_client/static/mapstore/gn-translations/data.en-US.json b/geonode_mapstore_client/static/mapstore/gn-translations/data.en-US.json index 053d72b2cb..03492ba1af 100644 --- a/geonode_mapstore_client/static/mapstore/gn-translations/data.en-US.json +++ b/geonode_mapstore_client/static/mapstore/gn-translations/data.en-US.json @@ -489,7 +489,35 @@ "geostory": "geostory", "document": "document", "dashboard": "dashboard", - "mapviewer": "mapviewer" + "mapviewer": "mapviewer", + "none": "None", + "options": "Options", + "range": "Range", + "points": "Points", + "lines": "Lines", + "polygons": "Polygons", + "string": "String", + "integer": "Integer", + "float": "Float", + "createAnEmptyDataset": "Create an empty dataset", + "datasetTitle": "Dataset title", + "name": "Name", + "type": "Type", + "nillable": "Nillable", + "restrictions": "Restrictions", + "loadAttributesFromJSONSchema": "Load attributes from JSON Schema", + "addAttribute": "Add attribute", + "createNewDataset": "Create new dataset", + "addOption": "Add option", + "min": "Min", + "max": "Max", + "minError": "Must be less than or equal to max value", + "maxError": "Must be greater than or equal to min value", + "minValueRequired": "Input value cannot be empty", + "strValueRequired": "Must be a valid string", + "integerValueRequired": "Must be a valid integer", + "numberValueRequired": "Must be a valid number", + "duplicateAttributeNameError": "Attribute name must be unique" }, "resourcesCatalog": { "anonymous": "Anyone", diff --git a/geonode_mapstore_client/static/mapstore/gn-translations/data.es-ES.json b/geonode_mapstore_client/static/mapstore/gn-translations/data.es-ES.json index b3ecb94096..d2408a80a9 100644 --- a/geonode_mapstore_client/static/mapstore/gn-translations/data.es-ES.json +++ b/geonode_mapstore_client/static/mapstore/gn-translations/data.es-ES.json @@ -488,7 +488,35 @@ "geostory": "geohistoria", "document": "documento", "dashboard": "tablero", - "mapviewer": "visor de mapa" + "mapviewer": "visor de mapa", + "none": "Ninguno", + "options": "Opciones", + "range": "Rango", + "points": "Puntos", + "lines": "Líneas", + "polygons": "Polígonos", + "string": "Cadena", + "integer": "Entero", + "float": "Flotante", + "createAnEmptyDataset": "Crear un conjunto de datos vacío", + "datasetTitle": "Título del conjunto de datos", + "name": "Nombre", + "type": "Tipo", + "nillable": "Nulable", + "restrictions": "Restricciones", + "loadAttributesFromJSONSchema": "Cargar atributos desde esquema JSON", + "addAttribute": "Agregar atributo", + "createNewDataset": "Crear nuevo conjunto de datos", + "addOption": "Agregar opción", + "min": "Mín", + "max": "Máx", + "minError": "Debe ser menor o igual al valor máximo", + "maxError": "Debe ser mayor o igual al valor mínimo", + "minValueRequired": "El valor de entrada no puede estar vacío", + "strValueRequired": "Debe ser una cadena válida", + "integerValueRequired": "Debe ser un entero válido", + "numberValueRequired": "Debe ser un número válido", + "duplicateAttributeNameError": "El nombre del atributo debe ser único" }, "resourcesCatalog": { "anonymous": "Cualquiera", diff --git a/geonode_mapstore_client/static/mapstore/gn-translations/data.fi-FI.json b/geonode_mapstore_client/static/mapstore/gn-translations/data.fi-FI.json index abd6e6aedc..3b7f428844 100644 --- a/geonode_mapstore_client/static/mapstore/gn-translations/data.fi-FI.json +++ b/geonode_mapstore_client/static/mapstore/gn-translations/data.fi-FI.json @@ -459,7 +459,35 @@ "geostory": "geostory", "document": "document", "dashboard": "dashboard", - "mapviewer": "mapviewer" + "mapviewer": "mapviewer", + "none": "None", + "options": "Options", + "range": "Range", + "points": "Points", + "lines": "Lines", + "polygons": "Polygons", + "string": "String", + "integer": "Integer", + "float": "Float", + "createAnEmptyDataset": "Create an empty dataset", + "datasetTitle": "Dataset title", + "name": "Name", + "type": "Type", + "nillable": "Nillable", + "restrictions": "Restrictions", + "loadAttributesFromJSONSchema": "Load attributes from JSON Schema", + "addAttribute": "Add attribute", + "createNewDataset": "Create new dataset", + "addOption": "Add option", + "min": "Min", + "max": "Max", + "minError": "Must be less than or equal to max value", + "maxError": "Must be greater than or equal to min value", + "minValueRequired": "Input value cannot be empty", + "strValueRequired": "Must be a valid string", + "integerValueRequired": "Must be a valid integer", + "numberValueRequired": "Must be a valid number", + "duplicateAttributeNameError": "Attribute name must be unique" }, "resourcesCatalog": { "anonymous": "Anyone", diff --git a/geonode_mapstore_client/static/mapstore/gn-translations/data.fr-FR.json b/geonode_mapstore_client/static/mapstore/gn-translations/data.fr-FR.json index bb374d449b..a3d6def3a9 100644 --- a/geonode_mapstore_client/static/mapstore/gn-translations/data.fr-FR.json +++ b/geonode_mapstore_client/static/mapstore/gn-translations/data.fr-FR.json @@ -489,7 +489,35 @@ "geostory": "géohistoire", "document": "document", "dashboard": "tableau de bord", - "mapviewer": "visionneuse de carte" + "mapviewer": "visionneuse de carte", + "none": "Aucun", + "options": "Options", + "range": "Plage", + "points": "Points", + "lines": "Lignes", + "polygons": "Polygones", + "string": "Chaîne", + "integer": "Entier", + "float": "Flottant", + "createAnEmptyDataset": "Créer un jeu de données vide", + "datasetTitle": "Titre du jeu de données", + "name": "Nom", + "type": "Type", + "nillable": "Nullable", + "restrictions": "Restrictions", + "loadAttributesFromJSONSchema": "Charger les attributs depuis le schéma JSON", + "addAttribute": "Ajouter un attribut", + "createNewDataset": "Créer un nouveau jeu de données", + "addOption": "Ajouter une option", + "min": "Min", + "max": "Max", + "minError": "Doit être inférieur ou égal à la valeur maximale", + "maxError": "Doit être supérieur ou égal à la valeur minimale", + "minValueRequired": "La valeur d'entrée ne peut pas être vide", + "strValueRequired": "Doit être une chaîne valide", + "integerValueRequired": "Doit être un entier valide", + "numberValueRequired": "Doit être un nombre valide", + "duplicateAttributeNameError": "Le nom de l'attribut doit être unique" }, "resourcesCatalog": { "anonymous": "N'importe qui", diff --git a/geonode_mapstore_client/static/mapstore/gn-translations/data.hr-HR.json b/geonode_mapstore_client/static/mapstore/gn-translations/data.hr-HR.json index cf98d9c339..79f507dc41 100644 --- a/geonode_mapstore_client/static/mapstore/gn-translations/data.hr-HR.json +++ b/geonode_mapstore_client/static/mapstore/gn-translations/data.hr-HR.json @@ -459,7 +459,35 @@ "geostory": "geostory", "document": "document", "dashboard": "dashboard", - "mapviewer": "mapviewer" + "mapviewer": "mapviewer", + "none": "None", + "options": "Options", + "range": "Range", + "points": "Points", + "lines": "Lines", + "polygons": "Polygons", + "string": "String", + "integer": "Integer", + "float": "Float", + "createAnEmptyDataset": "Create an empty dataset", + "datasetTitle": "Dataset title", + "name": "Name", + "type": "Type", + "nillable": "Nillable", + "restrictions": "Restrictions", + "loadAttributesFromJSONSchema": "Load attributes from JSON Schema", + "addAttribute": "Add attribute", + "createNewDataset": "Create new dataset", + "addOption": "Add option", + "min": "Min", + "max": "Max", + "minError": "Must be less than or equal to max value", + "maxError": "Must be greater than or equal to min value", + "minValueRequired": "Input value cannot be empty", + "strValueRequired": "Must be a valid string", + "integerValueRequired": "Must be a valid integer", + "numberValueRequired": "Must be a valid number", + "duplicateAttributeNameError": "Attribute name must be unique" }, "resourcesCatalog": { "anonymous": "Anyone", diff --git a/geonode_mapstore_client/static/mapstore/gn-translations/data.it-IT.json b/geonode_mapstore_client/static/mapstore/gn-translations/data.it-IT.json index f96a1d4f74..d9f61457b7 100644 --- a/geonode_mapstore_client/static/mapstore/gn-translations/data.it-IT.json +++ b/geonode_mapstore_client/static/mapstore/gn-translations/data.it-IT.json @@ -491,7 +491,35 @@ "geostory": "geostory", "document": "documento", "dashboard": "dashboard", - "mapviewer": "visualizzatore di mappa" + "mapviewer": "visualizzatore di mappa", + "none": "Nessuno", + "options": "Opzioni", + "range": "Intervallo", + "points": "Punti", + "lines": "Linee", + "polygons": "Poligoni", + "string": "Stringa", + "integer": "Intero", + "float": "Decimale", + "createAnEmptyDataset": "Crea un dataset vuoto", + "datasetTitle": "Titolo del dataset", + "name": "Nome", + "type": "Tipo", + "nillable": "Annullabile", + "restrictions": "Restrizioni", + "loadAttributesFromJSONSchema": "Carica attributi da schema JSON", + "addAttribute": "Aggiungi attributo", + "createNewDataset": "Crea nuovo dataset", + "addOption": "Aggiungi opzione", + "min": "Min", + "max": "Max", + "minError": "Deve essere minore o uguale al valore massimo", + "maxError": "Deve essere maggiore o uguale al valore minimo", + "minValueRequired": "Il valore di input non può essere vuoto", + "strValueRequired": "Deve essere una stringa valida", + "integerValueRequired": "Deve essere un intero valido", + "numberValueRequired": "Deve essere un numero valido", + "duplicateAttributeNameError": "Il nome dell'attributo deve essere univoco" }, "resourcesCatalog": { "anonymous": "Chiunque", diff --git a/geonode_mapstore_client/static/mapstore/gn-translations/data.nl-NL.json b/geonode_mapstore_client/static/mapstore/gn-translations/data.nl-NL.json index 2e4da840c1..da04fb2d45 100644 --- a/geonode_mapstore_client/static/mapstore/gn-translations/data.nl-NL.json +++ b/geonode_mapstore_client/static/mapstore/gn-translations/data.nl-NL.json @@ -459,7 +459,35 @@ "geostory": "geostory", "document": "document", "dashboard": "dashboard", - "mapviewer": "mapviewer" + "mapviewer": "mapviewer", + "none": "None", + "options": "Options", + "range": "Range", + "points": "Points", + "lines": "Lines", + "polygons": "Polygons", + "string": "String", + "integer": "Integer", + "float": "Float", + "createAnEmptyDataset": "Create an empty dataset", + "datasetTitle": "Dataset title", + "name": "Name", + "type": "Type", + "nillable": "Nillable", + "restrictions": "Restrictions", + "loadAttributesFromJSONSchema": "Load attributes from JSON Schema", + "addAttribute": "Add attribute", + "createNewDataset": "Create new dataset", + "addOption": "Add option", + "min": "Min", + "max": "Max", + "minError": "Must be less than or equal to max value", + "maxError": "Must be greater than or equal to min value", + "minValueRequired": "Input value cannot be empty", + "strValueRequired": "Must be a valid string", + "integerValueRequired": "Must be a valid integer", + "numberValueRequired": "Must be a valid number", + "duplicateAttributeNameError": "Attribute name must be unique" }, "resourcesCatalog": { "anonymous": "Anyone", diff --git a/geonode_mapstore_client/static/mapstore/gn-translations/data.pt-PT.json b/geonode_mapstore_client/static/mapstore/gn-translations/data.pt-PT.json index 2a84fa04ec..9632c99bb5 100644 --- a/geonode_mapstore_client/static/mapstore/gn-translations/data.pt-PT.json +++ b/geonode_mapstore_client/static/mapstore/gn-translations/data.pt-PT.json @@ -459,7 +459,35 @@ "geostory": "geostory", "document": "document", "dashboard": "dashboard", - "mapviewer": "mapviewer" + "mapviewer": "mapviewer", + "none": "None", + "options": "Options", + "range": "Range", + "points": "Points", + "lines": "Lines", + "polygons": "Polygons", + "string": "String", + "integer": "Integer", + "float": "Float", + "createAnEmptyDataset": "Create an empty dataset", + "datasetTitle": "Dataset title", + "name": "Name", + "type": "Type", + "nillable": "Nillable", + "restrictions": "Restrictions", + "loadAttributesFromJSONSchema": "Load attributes from JSON Schema", + "addAttribute": "Add attribute", + "createNewDataset": "Create new dataset", + "addOption": "Add option", + "min": "Min", + "max": "Max", + "minError": "Must be less than or equal to max value", + "maxError": "Must be greater than or equal to min value", + "minValueRequired": "Input value cannot be empty", + "strValueRequired": "Must be a valid string", + "integerValueRequired": "Must be a valid integer", + "numberValueRequired": "Must be a valid number", + "duplicateAttributeNameError": "Attribute name must be unique" }, "resourcesCatalog": { "anonymous": "Anyone", diff --git a/geonode_mapstore_client/static/mapstore/gn-translations/data.sk-SK.json b/geonode_mapstore_client/static/mapstore/gn-translations/data.sk-SK.json index bad0040f5a..a206a41694 100644 --- a/geonode_mapstore_client/static/mapstore/gn-translations/data.sk-SK.json +++ b/geonode_mapstore_client/static/mapstore/gn-translations/data.sk-SK.json @@ -459,7 +459,35 @@ "geostory": "geostory", "document": "document", "dashboard": "dashboard", - "mapviewer": "mapviewer" + "mapviewer": "mapviewer", + "none": "None", + "options": "Options", + "range": "Range", + "points": "Points", + "lines": "Lines", + "polygons": "Polygons", + "string": "String", + "integer": "Integer", + "float": "Float", + "createAnEmptyDataset": "Create an empty dataset", + "datasetTitle": "Dataset title", + "name": "Name", + "type": "Type", + "nillable": "Nillable", + "restrictions": "Restrictions", + "loadAttributesFromJSONSchema": "Load attributes from JSON Schema", + "addAttribute": "Add attribute", + "createNewDataset": "Create new dataset", + "addOption": "Add option", + "min": "Min", + "max": "Max", + "minError": "Must be less than or equal to max value", + "maxError": "Must be greater than or equal to min value", + "minValueRequired": "Input value cannot be empty", + "strValueRequired": "Must be a valid string", + "integerValueRequired": "Must be a valid integer", + "numberValueRequired": "Must be a valid number", + "duplicateAttributeNameError": "Attribute name must be unique" }, "resourcesCatalog": { "anonymous": "Anyone", diff --git a/geonode_mapstore_client/static/mapstore/gn-translations/data.sv-SE.json b/geonode_mapstore_client/static/mapstore/gn-translations/data.sv-SE.json index 65eccafe41..f39ea1f013 100644 --- a/geonode_mapstore_client/static/mapstore/gn-translations/data.sv-SE.json +++ b/geonode_mapstore_client/static/mapstore/gn-translations/data.sv-SE.json @@ -460,7 +460,35 @@ "geostory": "geostory", "document": "document", "dashboard": "dashboard", - "mapviewer": "mapviewer" + "mapviewer": "mapviewer", + "none": "None", + "options": "Options", + "range": "Range", + "points": "Points", + "lines": "Lines", + "polygons": "Polygons", + "string": "String", + "integer": "Integer", + "float": "Float", + "createAnEmptyDataset": "Create an empty dataset", + "datasetTitle": "Dataset title", + "name": "Name", + "type": "Type", + "nillable": "Nillable", + "restrictions": "Restrictions", + "loadAttributesFromJSONSchema": "Load attributes from JSON Schema", + "addAttribute": "Add attribute", + "createNewDataset": "Create new dataset", + "addOption": "Add option", + "min": "Min", + "max": "Max", + "minError": "Must be less than or equal to max value", + "maxError": "Must be greater than or equal to min value", + "minValueRequired": "Input value cannot be empty", + "strValueRequired": "Must be a valid string", + "integerValueRequired": "Must be a valid integer", + "numberValueRequired": "Must be a valid number", + "duplicateAttributeNameError": "Attribute name must be unique" }, "resourcesCatalog": { "anonymous": "Anyone", diff --git a/geonode_mapstore_client/static/mapstore/gn-translations/data.vi-VN.json b/geonode_mapstore_client/static/mapstore/gn-translations/data.vi-VN.json index 87fc0e0f01..e36b9e63b9 100644 --- a/geonode_mapstore_client/static/mapstore/gn-translations/data.vi-VN.json +++ b/geonode_mapstore_client/static/mapstore/gn-translations/data.vi-VN.json @@ -459,7 +459,35 @@ "geostory": "geostory", "document": "document", "dashboard": "dashboard", - "mapviewer": "mapviewer" + "mapviewer": "mapviewer", + "none": "None", + "options": "Options", + "range": "Range", + "points": "Points", + "lines": "Lines", + "polygons": "Polygons", + "string": "String", + "integer": "Integer", + "float": "Float", + "createAnEmptyDataset": "Create an empty dataset", + "datasetTitle": "Dataset title", + "name": "Name", + "type": "Type", + "nillable": "Nillable", + "restrictions": "Restrictions", + "loadAttributesFromJSONSchema": "Load attributes from JSON Schema", + "addAttribute": "Add attribute", + "createNewDataset": "Create new dataset", + "addOption": "Add option", + "min": "Min", + "max": "Max", + "minError": "Must be less than or equal to max value", + "maxError": "Must be greater than or equal to min value", + "minValueRequired": "Input value cannot be empty", + "strValueRequired": "Must be a valid string", + "integerValueRequired": "Must be a valid integer", + "numberValueRequired": "Must be a valid number", + "duplicateAttributeNameError": "Attribute name must be unique" }, "resourcesCatalog": { "anonymous": "Anyone", diff --git a/geonode_mapstore_client/static/mapstore/gn-translations/data.zh-ZH.json b/geonode_mapstore_client/static/mapstore/gn-translations/data.zh-ZH.json index cf7ca28758..cf87ff3f88 100644 --- a/geonode_mapstore_client/static/mapstore/gn-translations/data.zh-ZH.json +++ b/geonode_mapstore_client/static/mapstore/gn-translations/data.zh-ZH.json @@ -459,7 +459,35 @@ "geostory": "geostory", "document": "document", "dashboard": "dashboard", - "mapviewer": "mapviewer" + "mapviewer": "mapviewer", + "none": "None", + "options": "Options", + "range": "Range", + "points": "Points", + "lines": "Lines", + "polygons": "Polygons", + "string": "String", + "integer": "Integer", + "float": "Float", + "createAnEmptyDataset": "Create an empty dataset", + "datasetTitle": "Dataset title", + "name": "Name", + "type": "Type", + "nillable": "Nillable", + "restrictions": "Restrictions", + "loadAttributesFromJSONSchema": "Load attributes from JSON Schema", + "addAttribute": "Add attribute", + "createNewDataset": "Create new dataset", + "addOption": "Add option", + "min": "Min", + "max": "Max", + "minError": "Must be less than or equal to max value", + "maxError": "Must be greater than or equal to min value", + "minValueRequired": "Input value cannot be empty", + "strValueRequired": "Must be a valid string", + "integerValueRequired": "Must be a valid integer", + "numberValueRequired": "Must be a valid number", + "duplicateAttributeNameError": "Attribute name must be unique" }, "resourcesCatalog": { "anonymous": "Anyone", diff --git a/geonode_mapstore_client/templates/geonode-mapstore-client/pages/datasets.html b/geonode_mapstore_client/templates/geonode-mapstore-client/pages/datasets.html index 09680eedda..21c988d3f9 100644 --- a/geonode_mapstore_client/templates/geonode-mapstore-client/pages/datasets.html +++ b/geonode_mapstore_client/templates/geonode-mapstore-client/pages/datasets.html @@ -34,7 +34,7 @@

{% trans "Datasets" %}

"labelId": "gnhome.createDataset", "value": "layer", "type": "link", - "href": "/createlayer/", + "href": "{context.getCataloguePath('/catalogue/#/create/dataset')}", "disableIf": "{(state('settings') && state('settings').createLayer) ? false : true}" }, {