import {isEmpty, isEqual} from "lodash"
import Vue from "vue"
import RESTClient from "@/lib/Client/RESTClient.js"
import Client from "../lib/GraphClient/GraphClient.js"
import recordDataAccessQuery from "../lib/GraphClient/queries/editor/getRecordDataAccess.json"
import recordRelationsQuery from "../lib/GraphClient/queries/editor/getRecordRelations.json"
import recordQuery from "../lib/GraphClient/queries/getRecord.json"
import recordHistory from '../lib/GraphClient/queries/getRecordHistory.json'
import recordOrganisationsQuery from "../lib/GraphClient/queries/getRecordOrganisations.json"
import { initEditorSections } from "./utils.js"
let client = new Client();
let restClient = new RESTClient();
/**
* The record store handles the requests related to record (fairsharingRecord).
* @type {Object}
*/
let recordStore = {
namespaced: true,
state: {
currentRecord: {
fairsharingRecord: {
metadata: {
citations: []
}
}
},
currentRecordHistory: {},
recordUpdate: {
error: false,
message: null,
id: null
},
sections: {
generalInformation: initEditorSections(false, ["generalInformation"]).generalInformation
},
editOrganisationLink: {
showOverlay: false,
data: {},
id: null
},
newRecord: false,
currentID: null
},
mutations: {
setCurrentRecord(state, data){
state.currentRecord = data;
let tags = ['subjects', 'domains', 'taxonomies', 'userDefinedTags', 'objectTypes'];
tags.forEach(tag => {
if (state.currentRecord['fairsharingRecord'][tag].length && state.currentRecord['fairsharingRecord'][tag] ) {
state.currentRecord['fairsharingRecord'][tag].forEach(item => {
item.type = tag;
})
}
})
},
setRecordHistory(state, data){
state.currentRecordHistory = data;
},
resetCurrentRecordHistory(state){
state.currentRecordHistory = {};
},
setSections(state, data){
state.currentID = data['fairsharingRecord'].id;
let sectionsNames = [
"generalInformation",
"support",
"dataAccess",
"publications",
"organisations",
"additionalInformation",
"relations"
];
state.sections = initEditorSections(data['fairsharingRecord'], sectionsNames);
},
setGeneralInformation(state, data){
state.sections.generalInformation = initEditorSections(
data['fairsharingRecord'],
["generalInformation"]
).generalInformation;
if(data['fairsharingRecord']) state.sections.generalInformation.message = "Record successfully updated!";
},
resetMessage(state, sectionName){
state.sections[sectionName].message = null;
state.sections[sectionName].message = false;
},
setSectionError(state, error){
state.sections[error.section].message = error.value;
state.sections[error.section].error = true;
},
setChanges(state, diff){
state.sections[diff.section].changes = diff.value;
},
setContacts(state, contacts){
state.sections.generalInformation.data.metadata.contacts = contacts;
},
setTags(state, field){
state.sections.generalInformation.data[field.target] = field.value;
},
resetRegistry(state){
state.sections.generalInformation.data.type = "";
},
setPublications(state, publications) {
state.sections.publications.data = publications;
},
setAdditionalInformation(state, additionalInformation) {
if (!additionalInformation.subfieldName){
Vue.set(state.sections.additionalInformation.data,
additionalInformation.fieldName,
additionalInformation.fieldValue
);
}
else {
Vue.set(state.sections.additionalInformation.data[additionalInformation.fieldName],
additionalInformation.subfieldName,
additionalInformation.fieldValue
);
}
},
setAdditionalInformationSubField(state, additionalInformation) {
if (additionalInformation.id !== null) {
state.sections.additionalInformation.data[additionalInformation.fieldName][additionalInformation.id] =
additionalInformation.fieldValue;
}
else {
if (!state.sections.additionalInformation.data[additionalInformation.fieldName]){
Vue.set(state.sections.additionalInformation.data, additionalInformation.fieldName,[]);
}
try {
Vue.set(state.sections.additionalInformation.data[additionalInformation.fieldName],
state.sections.additionalInformation.data[additionalInformation.fieldName].length,
additionalInformation.fieldValue
);
// eslint-disable-next-line no-empty
}
catch(e) {
// TODO: Investigate comments below.
// Github has been failing tests (which are fine locally) here for reasons which
// have not so far been determined.
// TypeError: Cannot read property 'shallow' of undefined
// ...on the Vue.set, above.
// eslint-enable-next-line no-empty
}
}
},
removeAdditionalInformationSubField(state, additionalInformation){
state.sections.additionalInformation.data[additionalInformation.fieldName].splice(additionalInformation.id, 1)
},
updateOrganisationsLinks(state, links){
state.sections.organisations.data = links;
state.sections.organisations.initialData = JSON.parse(JSON.stringify(links));
state.sections.organisations.changes = 0;
state.sections.organisations.message = "Record successfully updated!";
},
setEditOrganisationLink(state, newLink){
state.editOrganisationLink = newLink;
},
setEditOrganisationLinkOrganisation(state, organisation){
state.editOrganisationLink.data.organisation = organisation;
},
setEditOrganisationLinkGrant(state, grant) {
state.editOrganisationLink.data.grant = grant;
},
setDataAccess(state, dataAccess){
let record = {
exhaustiveLicences: dataAccess.exhaustiveLicences,
licences: dataAccess['licenceLinks'],
support_links: dataAccess.metadata.support_links
};
state.sections.generalInformation.data.metadata.support_links = JSON.parse(JSON.stringify(record.support_links));
state.sections.dataAccess.data.exhaustiveLicences = record.exhaustiveLicences;
state.sections.generalInformation.initialData.metadata.support_links = JSON.parse(JSON.stringify(record.support_links));
record.support_links.forEach(supportLink => {
if (supportLink.name) supportLink.url = {title: supportLink.name, url: supportLink.url}
});
state.sections.dataAccess.data = record;
state.sections.dataAccess.initialData = JSON.parse(JSON.stringify(record));
state.sections.dataAccess.changes = 0;
state.sections.dataAccess.message = "Record successfully updated!";
},
updateAdditionalInformation(state, additionalInformation) {
let record = {};
Object.keys(additionalInformation.record).forEach(field => {
if (additionalInformation.fields.includes(field)) {
record[field] = additionalInformation.record[field];
state.sections.generalInformation.data.metadata[field] = JSON.parse(JSON.stringify(record[field]));
state.sections.generalInformation.initialData.metadata[field] = JSON.parse(JSON.stringify(record[field]));
}
});
state.sections.additionalInformation.data = record;
state.sections.additionalInformation.initialData = JSON.parse(JSON.stringify(record));
state.sections.additionalInformation.changes = 0;
state.sections.additionalInformation.message = "Record successfully updated!";
},
setCreatingNewRecord(state){
state.newRecord = true;
},
setEditingRecord(state){
state.newRecord = false;
},
setRelations(state, relations){
state.sections.relations.data.recordAssociations = relations;
state.sections.relations.initialData.recordAssociations = JSON.parse(JSON.stringify(relations));
state.sections.relations.changes = 0;
state.sections.relations.message = "Record successfully updated!";
state.sections.relations.error = false;
},
setMessage(state, message){
state.sections[message.target].message = message.value;
},
setNewRecord(state, id){
state.recordUpdate = {
error: false,
message: "success",
id: id
}
},
setError(state, error){
state.recordUpdate = {
error: true,
message: error,
id: null
}
},
cleanRecordStore(state){
state.sections = null;
state.sections = initEditorSections(false, [
"generalInformation",
"support",
"dataAccess",
"publications",
"organisations",
"additionalInformation"
]);
}
},
actions: {
async fetchRecord(state, options){
state.commit("resetCurrentRecordHistory");
recordQuery.queryParam = {
id: options.id
};
if (options.token) {
client.setHeader(options.token);
}
let data = await client.executeQuery(recordQuery);
client.initalizeHeader()
if (!data["fairsharingRecord"]['metadata']['contacts']) {
data["fairsharingRecord"]['metadata']['contacts'] = [];
}
// Citations should be created if empty.
if (!data["fairsharingRecord"]['metadata']['citations']) {
data["fairsharingRecord"]['metadata']['citations'] = [];
}
state.commit('setCurrentRecord', JSON.parse(JSON.stringify(data)));
state.commit('setSections', JSON.parse(JSON.stringify(data)));
},
async fetchPreviewRecord(state, id){
state.commit("resetCurrentRecordHistory");
recordQuery.queryParam = {
id: id
};
let data = await client.executeQuery(recordQuery);
state.commit('setCurrentRecord', data);
},
async fetchRecordHistory(state, options){
recordHistory.queryParam = {id: options.id};
client.setHeader(options.token);
let data = await client.executeQuery(recordHistory);
state.commit('setRecordHistory', data["fairsharingRecord"]);
},
async updateGeneralInformation({ state, commit}, options) {
commit("resetMessage", "generalInformation");
let {
type, countries, userDefinedTags, objectTypes, domains, subjects, taxonomies, status, curator_notes, isHidden,
logo, maintainers, watchers,
...record
} = JSON.parse(JSON.stringify(state.sections.generalInformation.data)),
newTags = [],
oldTags = [],
tags = [];
userDefinedTags.forEach(tag => {
if (Object.keys(tag).indexOf("id") === -1){
newTags.push(tag.label)
}
else {
oldTags.push(tag.id)
}
});
newTags = await Promise.all(newTags.map(tag =>
restClient.createNewUserDefinedTag(tag, options.token))
);
newTags.forEach((tag) => {
if (!tag.error) {
tags.push(tag.id);
}
else {
commit("setSectionError", {
section: "generalInformation",
value: tag.error
});
return tag.error;
}
});
isEmpty(logo) ? delete record['logo'] : record.logo = logo
record.country_ids = countries.map(obj => obj.id);
if (type.id) record.record_type_id = type.id;
record.metadata.status = status;
record.curator_notes = curator_notes;
record.hidden = isHidden;
record.object_type_ids = objectTypes.map(obj => obj.id);
record.domain_ids = domains.map(obj => obj.id);
record.subject_ids = subjects.map(obj => obj.id);
record.taxonomy_ids = taxonomies.map(obj => obj.id);
record.maintainer_ids = maintainers.map(obj => obj.id);
record.watcher_ids = watchers.map(obj => obj.id);
record.user_defined_tag_ids = tags.concat(oldTags.filter(function (el) {return el != null;}));
if (options.change) {
record.remove_additional_properties = true
}
let response = await restClient.updateRecord({
record: record,
token: options.token,
id: options.id
});
if (response.error){
commit("setSectionError", {
section: "generalInformation",
value: response.error
});
return response.error;
}
else {
let newRecord = JSON.parse(JSON.stringify(state.sections.generalInformation.data));
let userDefinedTags = [];
newRecord.userDefinedTags.forEach(obj => {
if (Object.keys(obj).indexOf("id") === -1) {
obj.id = newTags.filter(tag => {tag.label = obj.label})[0];
userDefinedTags.push(obj);
}
else userDefinedTags.push(obj);
});
newRecord.userDefinedTags = userDefinedTags;
commit('setGeneralInformation', {fairsharingRecord: newRecord});
}
},
async updatePublications({ state, commit }, options) {
commit("resetMessage", "publications");
let publications = JSON.parse(JSON.stringify(state.sections.publications.data));
let record_data = {
publication_ids: [],
citation_ids: []
};
publications.forEach(function (publication) {
record_data.publication_ids.push(publication.id);
if (publication.isCitation) {
record_data.citation_ids.push(publication.id);
}
delete publication.isCitation;
});
const record = {
record: record_data,
token: options.token,
id: options.id
};
let response = await restClient.updateRecord(record);
if (response.error) {
commit("setSectionError", {
section: "publications",
value: response.error
});
return response.error;
}
else {
commit("setMessage", {target: "publications", value: "Record successfully updated!"});
}
},
async updateOrganisations({state, commit}, userToken){
commit("resetMessage", "organisations");
let deleteItems = [],
updateItems = [],
createItems = [];
state.sections.organisations.initialData.forEach((obj) => {
let found = state.sections.organisations.data.filter(org => org.id === obj.id)[0];
if (!found) { deleteItems.push(obj); }
});
state.sections.organisations.data.forEach(function(obj) {
let query = {
fairsharing_record_id: state.currentRecord['fairsharingRecord'].id,
organisation_id: obj.organisation.id,
relation: obj.relation,
grant_id: (obj.grant) ? obj.grant.id : null,
is_lead: obj.isLead
};
if (Object.prototype.hasOwnProperty.call(obj, 'id')) updateItems.push({query: query, id: obj.id});
else createItems.push(query);
});
let queries = await Promise.all([
...deleteItems.map(organisation => restClient.deleteOrganisationLink(organisation.id, userToken)),
...createItems.map(organisation => restClient.createOrganisationLink(organisation, userToken)),
...updateItems.map(organisation => restClient.updateOrganisationLink(organisation.query, organisation.id, userToken))
]);
queries.forEach((org) => {
if (org.error) {
commit("setSectionError", {
section: "organisations",
value: org.error
});
}
});
recordOrganisationsQuery.queryParam = {id: state.currentRecord.fairsharingRecord.id};
client.setHeader(userToken);
let organisations = await client.executeQuery(recordOrganisationsQuery);
commit('updateOrganisationsLinks', organisations.fairsharingRecord.organisationLinks);
},
async updateAdditionalInformation({ state, commit}, options){
commit("resetMessage", "additionalInformation");
let newRecord = {
metadata: state.sections.generalInformation.initialData.metadata,
};
options.fields.forEach(field => {
if (state.sections.additionalInformation.data[field]) {
Object.keys(state.sections.additionalInformation.data[field]).forEach(key => {
if (state.sections.additionalInformation.data[field][key] === "") {
delete state.sections.additionalInformation.data[field][key]
}
})
newRecord.metadata[field] = state.sections.additionalInformation.data[field]
}
else if (state.sections.additionalInformation.data[field] === null) {
// if its the case that there is a single string textInput only
state.sections.additionalInformation.data[field] = "";
newRecord.metadata[field] = state.sections.additionalInformation.data[field]
}
});
let response = await restClient.updateRecord({
record: newRecord,
token: options.token,
id: options.id
});
if (response.error) {
commit("setSectionError", {
section: "additionalInformation",
value: response.error
});
return response.error;
}
else {
commit("setMessage", {target: "additionalInformation", value: "Record successfully updated!"});
commit('updateAdditionalInformation', {record: newRecord.metadata, fields: options.fields});
}
},
async updateDataAccess({state, commit}, options){
commit("resetMessage", "dataAccess");
let newRecord = {
metadata: state.sections.generalInformation.initialData.metadata,
};
newRecord.metadata.support_links = state.sections.dataAccess.data.support_links;
newRecord.metadata.support_links.forEach(supportLink => {
if (typeof supportLink.url !== 'string') {
supportLink.url = supportLink.url.url;
}
});
let initialLicences = state.sections.dataAccess.initialData.licences,
currentLicences = state.sections.dataAccess.data.licences,
toDelete = [],
toUpdate = [],
toCreate = [];
initialLicences.forEach(licence => {
let found = currentLicences.filter(obj => obj.id === licence.id)[0];
if (!found) toDelete.push(licence.id);
});
currentLicences.forEach(licence => {
let found = initialLicences.filter(obj => obj.id === licence.id)[0],
newLicence = prepareLicence(licence);
if (!found){
toCreate.push(newLicence);
}
else if (found && !isEqual(licence, found)) {
toUpdate.push(newLicence);
}
});
newRecord.exhaustive_licences = state.sections.dataAccess.data.exhaustiveLicences;
let responses = await Promise.all([
restClient.updateRecord({
record: newRecord,
token: options.token,
id: options.id
}),
...toCreate.map(licence => restClient.createLicenceLink(licence, options.token)),
...toUpdate.map(licence => restClient.updateLicenceLink(licence, options.token)),
...toDelete.map(licence => restClient.deleteLicenceLink(licence, options.token))
]);
responses.forEach((response) => {
if (response.error) {
commit("setSectionError", {
section: "dataAccess",
value: response.error
});
return response.error;
}
});
recordDataAccessQuery.queryParam = {id: state.currentRecord.fairsharingRecord.id};
client.setHeader(options.token);
let dataAccess = await client.executeQuery(recordDataAccessQuery);
commit('setDataAccess', dataAccess.fairsharingRecord);
},
async updateRelations({state, commit}, options){
commit("resetMessage", "relations");
let newAssociations = [],
deleteAssociations = [],
oldAssociations = [];
state.sections.relations.data.recordAssociations.forEach(association => {
if (association.new) {
const newAssociation = {
fairsharing_record_id: options.source,
linked_record_id: association.linkedRecord.id,
record_assoc_label_id: association.recordAssocLabel.id
};
newAssociations.push(newAssociation);
}
else {
// Using a combination of record_id and label_id as this should be unique.
// Using only record_id produced:
// https://github.com/FAIRsharing/fairsharing.github.io/issues/1620
let id = association.linkedRecord.id + "_" + association.recordAssocLabelId;
oldAssociations.push(id);
}
});
state.sections.relations.initialData.recordAssociations.forEach(oldAssociation => {
// Same unique ID as above.
let id = oldAssociation.linkedRecord.id + "_" + oldAssociation.recordAssocLabelId;
if (id && !oldAssociations.includes(id)) {
deleteAssociations.push({
id: oldAssociation.id,
_destroy: 1
});
}
});
let responses = await Promise.all([
restClient.saveRelations({
token: options.token,
relations: newAssociations,
target: options.source
}),
restClient.deleteRelations({
token: options.token,
relations: deleteAssociations,
target: options.source
})
]);
let error = false;
for (let response of responses) {
if (response.error) {
commit("setSectionError", {
section: "relations",
value: response.error
});
error = true;
}
}
if (!error){
recordRelationsQuery.queryParam = {id: options.source};
client.setHeader(options.token);
let relations = await client.executeQuery(recordRelationsQuery);
commit('setRelations', relations['fairsharingRecord'].recordAssociations);
}
},
resetRecord(state){
state.commit('setGeneralInformation', {fairsharingRecord: false});
},
async updateRecord(state, newRecord){
let response = await restClient.updateRecord(newRecord);
if (response.error){
state.commit("setError", response.error.response)
}
else {
state.commit("setNewRecord", response)
}
}
},
getters: {
getField: (state) => (fieldName) => {
return state.currentRecord['fairsharingRecord'][fieldName];
},
getSection: (state) => (sectionName) => {
return state.sections[sectionName];
},
getChanges: (state) => {
let changes = {};
Object.keys(state.sections).forEach(section => {
changes[section] = state.sections[section].changes
});
return changes;
},
getAllChanges: (state) => {
let changes = 0;
Object.keys(state.sections).forEach(section => {
changes += state.sections[section].changes;
});
return changes;
},
getCreatingNewRecord: (state) => {
return state.newRecord;
},
getRecordType: (state) => {
return state.sections['generalInformation'].initialData.type
}
}
};
function prepareLicence(rawLicence){
let preparedLicence = { relation: rawLicence.relation };
preparedLicence.fairsharing_record_id = (rawLicence.fairsharingRecord)
? rawLicence.fairsharingRecord.id
: rawLicence.fairsharing_record_id;
if (rawLicence.id) preparedLicence.id = rawLicence.id;
if (rawLicence.licence.id){
preparedLicence.licence_id = rawLicence.licence.id
}
else {
preparedLicence.licence_attributes = rawLicence.licence;
}
return preparedLicence;
}
export default recordStore;