Source: store/recordData.js

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'];
            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, 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.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;