Commit ac616f68 authored by Matija Obreza's avatar Matija Obreza
Browse files

Fix: API inconsistencies and other small updates

- Service update
- Dataset and Partner editor create/update
- Geonames API virtual path
parent ba59543a
Pipeline #6974 passed with stages
in 5 minutes and 20 seconds
......@@ -94,7 +94,7 @@ function deleteDataset(dataset: Dataset) {
}
// Review step exports
export {rejectDataset, approveDataset, publishDataset, deleteDataset};
export { rejectDataset, approveDataset, publishDataset, deleteDataset };
// Accession identifiers step
......@@ -111,7 +111,7 @@ function updateDatasetAccessionRefs(dataset: Dataset, accessionRefs: AccessionRe
}
// Accession identifiers step exports
export {updateDatasetAccessionRefs};
export { updateDatasetAccessionRefs };
// Descriptors step
......@@ -186,7 +186,7 @@ const deleteRepositoryFileRequest = (datasetUUID: string, uuid: string) => (disp
};
// Repository File step exports
export {uploadRepositoryFileRequest, updateRepositoryFileRequest, deleteRepositoryFileRequest};
export { uploadRepositoryFileRequest, updateRepositoryFileRequest, deleteRepositoryFileRequest };
// Creators step
function addCreatorToDataset(creator: DatasetCreator, uuid: string) {
......@@ -202,7 +202,7 @@ function addCreatorToDataset(creator: DatasetCreator, uuid: string) {
function updateCreator(creator: DatasetCreator) {
return {
type: UPDATE_DATASET_CREATOR,
payload : {
payload: {
creator,
},
};
......@@ -211,7 +211,7 @@ function updateCreator(creator: DatasetCreator) {
function removeCreator(creator: DatasetCreator, uuid: string) {
return {
type: REMOVE_CREATOR_FROM_DATASET,
payload : {
payload: {
creator,
datasetUUID: uuid,
},
......@@ -221,8 +221,11 @@ function removeCreator(creator: DatasetCreator, uuid: string) {
// FIXME No uuid param required, currentDataset is available in state
function createDatasetCreator(uuid: string) {
return (dispatch, getState) => {
const creator: DatasetCreator = new DatasetCreator();
creator.fullName = 'Dataset creator';
log('createCreator');
return DatasetService.createDatasetCreator(uuid, new DatasetCreator())
return DatasetService.createDatasetCreator(uuid, creator)
.then((obj) => {
dispatch(addCreatorToDataset(obj, uuid));
}).catch((error) => {
......@@ -332,7 +335,7 @@ function deleteLocationRequest(datasetUUID: string, location: DatasetLocation) {
}
// Timing and Location step exports
export {createLocationRequest, updateLocationRequest, deleteLocationRequest, receiveLocation};
export { createLocationRequest, updateLocationRequest, deleteLocationRequest, receiveLocation };
// Common actions
......@@ -354,10 +357,12 @@ const saveDataset = (dataset: Dataset) => (dispatch, getState) => {
const needToRedirect: boolean = !(dataset.version && dataset.uuid);
if (_.isEqual({...getState().datasets.dashboard.dataset}, {...dataset})) {
if (_.isEqual({ ...getState().datasets.dashboard.dataset }, { ...dataset })) {
return;
}
const saveOrUpdate = dataset.id && dataset.version ? DatasetService.updateDataset : DatasetService.createDataset;
// remove normalized data here
const data: Dataset = {
repositoryFiles: [],
......@@ -367,7 +372,7 @@ const saveDataset = (dataset: Dataset) => (dispatch, getState) => {
...dataset,
};
return DatasetService.updateDataset(data)
return saveOrUpdate(data)
.then((saved) => {
dispatch(receiveDataset(saved));
if (needToRedirect) {
......@@ -388,7 +393,7 @@ function gotoNextStep(dataset: Dataset) {
}
function refreshStartEndDates(dataset) {
const res = {...dataset};
const res = { ...dataset };
res.startDate = null;
res.endDate = null;
res.locations.forEach((location) => {
......@@ -403,4 +408,4 @@ function refreshStartEndDates(dataset) {
}
// Common actions exports
export {loadDataset, saveDataset};
export { loadDataset, saveDataset };
......@@ -55,7 +55,7 @@ const renderRadioGroup = translate()(({input, meta, t, classes}) => {
className={ classes.RadioGrid }
>
{ Object.keys(CreatorRole).map((role) => (
<FormControlLabel value={ role } label={ t(`dataset.creator.role.${role}`) } control={ <Radio/> } />
<FormControlLabel key={ role } value={ role } label={ t(`dataset.creator.role.${role}`) } control={ <Radio/> } />
)) }
</RadioGroup>
</FormControl>
......
......@@ -95,16 +95,6 @@ class LocationForm extends React.Component<ILocationFormProps, any> {
<DeleteIcon />
</IconButton>
</div>
<Field
required
name={ `${location}.userCountry` }
component={ MaterialAutosuggest }
label="Country"
placeholder="Select country where the characterization/evaluation was performed."
suggestions={ countries }
suggestionLabel="name"
validate={ [ Validators.required ] }
/>
<Fields
names={ [
`${location}.userCountry`,
......@@ -119,6 +109,16 @@ class LocationForm extends React.Component<ILocationFormProps, any> {
checkGeonames={ this.checkGeonames }
component={ FormMap }
/>
<Field
required
name={ `${location}.userCountry` }
component={ MaterialAutosuggest }
label="Country"
placeholder="Select country where the characterization/evaluation was performed."
suggestions={ countries }
suggestionLabel="name"
validate={ [ Validators.required ] }
/>
<Field
name={ `${location}.stateProvince` }
component={ TextField }
......
......@@ -10,9 +10,10 @@ import { fixDate } from 'utilities';
import Authorize from 'ui/common/authorized/Authorize';
import Markdown from 'ui/catalog/markdown';
import DescriptorCard from 'descriptors/ui/c/DescriptorCard';
import { PartnerLink, CropLink, DescriptorLink, ExternalLink } from 'ui/catalog/Links';
import { PartnerLink, DescriptorLink, ExternalLink } from 'ui/catalog/Links';
import { Properties, PropertiesItem } from 'ui/catalog/Properties';
import Permissions from 'ui/common/permission/Permissions';
import CropChips from 'crops/ui/c/CropChips';
import Grid from '@material-ui/core/Grid';
import Card, { CardHeader, CardContent, CardActions } from 'ui/common/Card';
......@@ -199,7 +200,7 @@ class DetailInfo extends React.Component<IDetailInfoProps, any> {
<Properties>
{ descriptorList.crop &&
<PropertiesItem title="Crop:"><CropLink code={ descriptorList.crop } /></PropertiesItem> }
<PropertiesItem title="Crop:"><CropChips crops={ [ descriptorList.crop ] } /></PropertiesItem> }
<PropertiesItem title="Publisher:">{ descriptorList.publisher || <i>Not specified</i> }</PropertiesItem>
......
import RepositoryFile from 'model/repository/RepositoryFile';
import RepositoryFolder from 'model/repository/RepositoryFolder';
import ImageGallery from 'model/repository/ImageGallery';
/*
* Defined in Swagger as '#/definitions/FolderDetails'
*/
class FolderDetails {
public files: RepositoryFile[];
public folder: RepositoryFolder;
public subFolders: RepositoryFolder[];
public gallery: ImageGallery;
}
export default FolderDetails;
import RepositoryImage from 'model/repository/RepositoryImage';
import { CurrentPermissions, IUserPermissions } from 'model/acl/CurrentPermissions';
/*
* Defined in Swagger as '#/definitions/ImageGallery'
*/
class ImageGallery implements IUserPermissions {
public _permissions: CurrentPermissions;
public static clazz: string = 'org.genesys.filerepository.model.ImageGallery';
public active: boolean;
public createdBy: number;
public createdDate: Date;
public description: string;
public folder: string;
public id: number;
public images: RepositoryImage[];
public lastModifiedBy: number;
public lastModifiedDate: Date;
public title: string;
public version: number;
}
export default ImageGallery;
import { CurrentPermissions, IUserPermissions } from "model/acl/CurrentPermissions";
import { CurrentPermissions, IUserPermissions } from 'model/acl/CurrentPermissions';
/*
* Defined in Swagger as '#/definitions/RepositoryFolder'
......
import RepositoryFile from 'model/repository/RepositoryFile';
/*
* Defined in Swagger as '#/definitions/RepositoryImage'
*/
class RepositoryImage extends RepositoryFile {
public format: string;
public height: number;
public orientation: string;
public thumbnailPath: string;
public width: number;
}
export default RepositoryImage;
import { push } from 'react-router-redux';
import {SubmissionError} from 'redux-form';
import { SubmissionError } from 'redux-form';
// Constants
import {DASHBOARD_RECEIVE_PARTNER} from 'partners/constants';
import { DASHBOARD_RECEIVE_PARTNER } from 'partners/constants';
// Model
import Partner from 'model/genesys/Partner';
import {IReducerAction} from 'model/common.model';
import { IReducerAction } from 'model/common.model';
// Service
import PartnerService from 'service/PartnerService';
// Util
import {log} from 'utilities/debug';
import { log } from 'utilities/debug';
const receivePartner = (partner: Partner): IReducerAction => ({
type: DASHBOARD_RECEIVE_PARTNER, payload: partner,
......@@ -24,8 +24,9 @@ const showPartner = (uuid: string) => (dispatch) => {
};
export const savePartner = (partner: Partner) => (dispatch, getState) => {
const saveOrUpdate = partner.id && partner.version ? PartnerService.updatePartner : PartnerService.createPartner;
return PartnerService.updatePartner(partner)
return saveOrUpdate(partner)
.then((saved) => {
dispatch(receivePartner(saved));
dispatch(showPartner(saved.uuid));
......
......@@ -451,11 +451,15 @@ class DatasetService {
const apiUrl = URL_ADD_FILE_TO_DATASET.expand({UUID});
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
const data = new FormData();
data.append('file', file);
// data.append('metadata', new Blob([ JSON.stringify(metadata) ], { type : 'application/json' }));
const content = { data };
return axiosBackend.request({
url: apiUrl,
method: 'POST',
headers: { 'Content-Type': 'multipart/form-data' },
...content,
}).then(({ data }) => data as Dataset);
}
......
import axios from 'axios';
// Geonames API.
export const API_GEONAMES = `/proxy/api/geonames`;
export const API_GEONAMES = `/api/geonames`;
export const GET_GEONAMES_COUNTRY_URL = `${API_GEONAMES}/countryCodeJSON`;
export const GET_GEONAMES_DETAIL_INFO_URL = `${API_GEONAMES}/findNearbyPlaceNameJSON`;
export default class GeoNamesService {
// Call geonames api
public static geonames(lat: number, lng: number): Promise<[any, any]> {
const virtualPath = document.baseURI.replace(/^(https?:\/\/[^\/]+)?(.*)\/$/, '$2');
const params = {
formatted: true,
......@@ -18,11 +19,13 @@ export default class GeoNamesService {
return Promise.all([
axios({
baseURL: virtualPath,
url: GET_GEONAMES_COUNTRY_URL,
method: 'GET',
params,
}),
axios({
baseURL: virtualPath,
url: GET_GEONAMES_DETAIL_INFO_URL,
method: 'GET',
params,
......
......@@ -2,38 +2,30 @@
import * as UrlTemplate from 'url-template';
import { axiosBackend } from 'utilities/requestUtils';
import FolderDetails from 'model/repository/FolderDetails';
import ImageGallery from 'model/repository/ImageGallery';
import RepositoryFile from 'model/repository/RepositoryFile';
import RepositoryFolder from 'model/repository/RepositoryFolder';
const URL_METADATA = `/api/v0/api/v0/fileinfo`;
const URL_GET_FILE = UrlTemplate.parse(`/api/v1/repository/file/{fileUuid}`);
const URL_REMOVE_FILE = UrlTemplate.parse(`/api/v1/repository/file/{fileUuid}`);
const URL_UPDATE_FILE = `/api/v1/repository/file`;
const URL_MOVE_AND_RENAME_FILE = UrlTemplate.parse(`/api/v1/repository/file/{fileUuid}/move`);
const URL_UPLOAD_FILE = UrlTemplate.parse(`/api/v1/repository/upload/{path}`);
const URL_UPDATE_FOLDER = `/api/v1/repository/folder`;
const URL_GET_FOLDER = UrlTemplate.parse(`/api/v1/repository/folder`);
const URL_ENSURE_FOLDER = UrlTemplate.parse(`/api/v1/repository/folder`);
const URL_DELETE_FOLDER = UrlTemplate.parse(`/api/v1/repository/folder`);
const URL_RENAME_FOLDER = UrlTemplate.parse(`/api/v1/repository/folder/{folderUuid}/rename`);
const URL_UPDATE_GALLERY = `/api/v1/repository/gallery`;
const URL_GET_GALLERY = UrlTemplate.parse(`/api/v1/repository/gallery`);
const URL_CREATE_GALLERY = UrlTemplate.parse(`/api/v1/repository/gallery`);
const URL_REMOVE_GALLERY = UrlTemplate.parse(`/api/v1/repository/gallery`);
const URL_UPLOAD_FILE = UrlTemplate.parse(`/api/v1/repository/upload`);
/*
* Defined in Swagger as 'repository'
*/
class RepositoryService {
/**
* metadata at /api/v0/api/v0/fileinfo
*
* @param file file
*/
public static metadata(file: File): Promise<any> {
const apiUrl = URL_METADATA;
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return axiosBackend.request({
url: apiUrl,
method: 'POST',
...content,
}).then(({ data }) => data as any);
}
class FileRepositoryService {
/**
* getFile at /api/v1/repository/file/{fileUuid}
......@@ -109,7 +101,171 @@ class RepositoryService {
}
/**
* uploadFile at /api/v1/repository/upload/{path}
* updateFolder at /api/v1/repository/folder
*
* @param folder folder
*/
public static updateFolder(folder: RepositoryFolder): Promise<FolderDetails> {
const apiUrl = URL_UPDATE_FOLDER;
// console.log(`Fetching from ${apiUrl}`);
const content = { data: folder };
return axiosBackend.request({
url: apiUrl,
method: 'PUT',
...content,
}).then(({ data }) => data as FolderDetails);
}
/**
* getFolder at /api/v1/repository/folder/**
*
* @param path URL path
*/
public static getFolder(path: string): Promise<FolderDetails> {
const apiUrl = URL_GET_FOLDER.expand({}) + path;
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return axiosBackend.request({
url: apiUrl,
method: 'GET',
...content,
}).then(({ data }) => data as FolderDetails);
}
/**
* ensureFolder at /api/v1/repository/folder/**
*
* @param path URL path
*/
public static ensureFolder(path: string): Promise<RepositoryFolder> {
const apiUrl = URL_ENSURE_FOLDER.expand({}) + path;
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return axiosBackend.request({
url: apiUrl,
method: 'PUT',
...content,
}).then(({ data }) => data as RepositoryFolder);
}
/**
* deleteFolder at /api/v1/repository/folder/**
*
* @param path URL path
*/
public static deleteFolder(path: string): Promise<RepositoryFolder> {
const apiUrl = URL_DELETE_FOLDER.expand({}) + path;
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return axiosBackend.request({
url: apiUrl,
method: 'DELETE',
...content,
}).then(({ data }) => data as RepositoryFolder);
}
/**
* renameFolder at /api/v1/repository/folder/{folderUuid}/rename
*
* @param folderUuid folderUuid
* @param fullPath fullPath
*/
public static renameFolder(folderUuid: string, fullPath: undefined): Promise<FolderDetails> {
const apiUrl = URL_RENAME_FOLDER.expand({folderUuid});
// console.log(`Fetching from ${apiUrl}`);
const content = { data: fullPath };
return axiosBackend.request({
url: apiUrl,
method: 'POST',
...content,
}).then(({ data }) => data as FolderDetails);
}
/**
* updateGallery at /api/v1/repository/gallery
*
* @param imageGallery imageGallery
*/
public static updateGallery(imageGallery: ImageGallery): Promise<ImageGallery> {
const apiUrl = URL_UPDATE_GALLERY;
// console.log(`Fetching from ${apiUrl}`);
const content = { data: imageGallery };
return axiosBackend.request({
url: apiUrl,
method: 'PUT',
...content,
}).then(({ data }) => data as ImageGallery);
}
/**
* getGallery at /api/v1/repository/gallery/**
*
* @param path URL path
*/
public static getGallery(path: string): Promise<ImageGallery> {
const apiUrl = URL_GET_GALLERY.expand({}) + path;
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return axiosBackend.request({
url: apiUrl,
method: 'GET',
...content,
}).then(({ data }) => data as ImageGallery);
}
/**
* createGallery at /api/v1/repository/gallery/**
*
* @param path URL path
* @param metadata metadata
*/
public static createGallery(path: string, metadata: ImageGallery): Promise<ImageGallery> {
const apiUrl = URL_CREATE_GALLERY.expand({}) + path;
// console.log(`Fetching from ${apiUrl}`);
const content = { data: metadata };
return axiosBackend.request({
url: apiUrl,
method: 'POST',
...content,
}).then(({ data }) => data as ImageGallery);
}
/**
* removeGallery at /api/v1/repository/gallery/**
*
* @param path URL path
*/
public static removeGallery(path: string): Promise<ImageGallery> {
const apiUrl = URL_REMOVE_GALLERY.expand({}) + path;
// console.log(`Fetching from ${apiUrl}`);
const content = { /* No content in request body */ };
return axiosBackend.request({
url: apiUrl,
method: 'DELETE',
...content,
}).then(({ data }) => data as ImageGallery);
}
/**
* uploadFile at /api/v1/repository/upload/**
*
* @param path URL path
* @param file file
......@@ -117,7 +273,7 @@ class RepositoryService {
*/
public static uploadFile(path: string, file: File, metadata?: RepositoryFile): Promise<RepositoryFile> {
const apiUrl = URL_UPLOAD_FILE.expand({ path });
const apiUrl = URL_UPLOAD_FILE.expand({}) + path;
// console.log(`Fetching from ${apiUrl}`);
const data = new FormData();
data.append('file', file);
......@@ -135,4 +291,4 @@ class RepositoryService {
}
export default RepositoryService;
export default FileRepositoryService;
......@@ -11,6 +11,7 @@ import Markdown from 'ui/common/markdown';
import OriginalMarkdownField from 'ui/common/markdown/MarkdownField';
import FormControl from 'ui/common/forms/FormControl';
import Input from '@material-ui/core/Input';
import FormHelperText from '@material-ui/core/FormHelperText';
class MarkdownField extends OriginalMarkdownField {
......@@ -120,16 +121,16 @@ class MarkdownField extends OriginalMarkdownField {
}
public render() {
const {classes, basicMarkdown, input, label, required, meta, meta: {touched, error}, ...custom} = this.props;
const { basicMarkdown, input, label, required, meta, meta: { touched, error } } = this.props;
const basic: boolean = basicMarkdown === undefined || null ? false : basicMarkdown;
if (basic) {
return (
<FormControl fullWidth required={ required } meta={ meta } label={ label }>
<Input error={ touched && error } { ...input } { ...custom } />
<h6>
<span>Basic markdown supported: * **</span>
</h6>
<Input error={ touched && error } { ...input } />
<FormHelperText>
Basic markdown supported: <code>*</code> <code>**</code>
</FormHelperText>
</FormControl>
);
}
......@@ -138,7 +139,7 @@ class MarkdownField extends OriginalMarkdownField {
<div>
{ ! this.state.previewMode ?
<FormControl fullWidth required={ required } meta={ meta } label={ label }>
<Input error={ touched && error } inputRef={ this.setRef } multiline { ...input } { ...custom } />
<Input error={ touched && error } inputRef={ this.setRef } multiline { ...input } />
<h6><a onClick={ this.onChangePreviewMode }>Preview Markdown</a> <span> Full markdown supported</span></h6>
</FormControl>
:
......