-
Sylvain Fargier authoredSylvain Fargier authored
Code owners
Assign users and groups as approvers for specific file changes. Learn more.
ImportExportDropDownTools.vue.js 11.83 KiB
/* eslint-disable max-lines */
// @ts-check
import Vue from "vue";
import { mapState } from 'vuex';
import { BaseLogger as logger } from '@cern/base-vue';
import { assign, cloneDeep, difference, first, forEach, get, groupBy, has, indexOf,
intersection, isNil, keys, mapValues, merge, omit, set, toNumber,
transform } from 'lodash';
import { ChannelParamsMap, ZSPMode } from '../../interfaces/daq';
import { AreaListDnsMap } from '../../Consts';
import { parseValue } from '../../interfaces';
/**
* @typedef {{
* fileInput: HTMLInputElement,
* importDialog: V.Instance<typeof BaseVue.BaseDialog>
* }} Refs
* @typedef {V.Instance<typeof component, V.ExtVue<any, Refs>> } Instance
*/
const component = /** @type {V.Constructor<any, Refs>} */ (Vue).extend({
name: 'ImportExportDropDownTools',
props: {
sources: { type: Object, default: null }, // DaqConfigSource
isConfiguring: { type: Boolean, default: false }
},
/**
* @returns {{
* confFile?: File,
* configuration?: Object,
* dragOver: boolean,
* warnings: {[daq: string]: string}
* }}
*/
data() {
return {
confFile: undefined,
configuration: undefined,
dragOver: false,
warnings: {}
};
},
computed: {
...mapState([ 'dns' ]),
...mapState('eacs', [ 'daq', 'daqList', 'cardList' ])
},
methods: {
/**
* EXPORT
*/
/** @this {Instance} */
generateJSON() {
const dataObj = {};
forEach(mapValues(this.daqList, (/** @type{any} */ d) => this.daq[d.name]),
(daqConf, daqName) => {
// We remove edit object used in edit mode
set(dataObj, daqName, omit(daqConf, [ 'edit', 'zsp.edit' ]));
});
return dataObj;
},
/** @this {Instance} */
exportJSON() {
const dataObj = this.generateJSON();
this.downloadJson(dataObj);
},
/** @this {Instance} */
exportCSV() {
const jsonConf = this.generateJSON();
/** @type Array<any> */
const flattenObj = [];
forEach(jsonConf, (daqConf, daqName) => {
const channelsConf = get(daqConf, 'channels');
const zspConf = get(daqConf, 'zsp');
const zspInfo = {
zspMode: get(zspConf, 'mode'),
zspMaster: null
};
// If zspMode is 1 (singleMaster), get the master
if (zspInfo.zspMode === ZSPMode.SINGLE_MASTER) {
const masterId = first(keys(get(zspConf, 'master')));
set(zspInfo, 'zspMaster', masterId);
}
transform(channelsConf, (res, cConf) => {
const obj = merge({ daq: daqName }, cConf, cloneDeep(zspInfo));
// Add zspMaster information only if mode is multimaster
if (obj.zspMode === ZSPMode.MASTER) {
if (indexOf(keys(get(zspConf, 'master')), cConf.id) !== -1) {
// Case Master:
set(obj, 'zspMaster', cConf.id);
}
else {
// Case Slave
const masterId = get(zspConf, [ 'channels', cConf.id, 'masterId' ]);
// if is null, it means is independent.
if (!isNil(masterId)) set(obj, 'zspMaster', masterId);
}
}
res.push(obj);
}, flattenObj);
});
const csv = this.jsonToCsv(flattenObj);
this.downloadCsv(csv);
},
/**
* @this {Instance}
* @param {any} jsonArray
*/
jsonToCsv(jsonArray) {
const header = keys(first(jsonArray));
/**
* @param {string} key
* @param {any} value
* @returns {any}
*/
const replacer = (key, value) => {
if (value === true || value === false) {
return toNumber(value);
}
else return isNil(value) ? '' : value;
};
const csv = [
header.join(','),
// @ts-ignore
...jsonArray.map((row) => header.map((field) => JSON.stringify(row[field], replacer)).join(','))
].join('\n');
return csv;
},
/**
* @param {string} csv
* @returns {any}
*/
csvToJson(csv) {
const lines = csv.split('\n');
const result = [];
const headers = first(lines)?.split(',');
if (isNil(headers)) return;
for (let i = 1; i < lines.length; i++) {
if (!lines[i]) continue;
const obj = {};
const currentline = lines[i].split(',');
for (let j = 0; j < headers.length; j++) {
const val = currentline[j].replace(/['"]+/g, '');
const name = headers[j];
const parsedValue = parseValue(val,
get(ChannelParamsMap, [ name, 'type' ]));
set(obj, name, parsedValue);
}
result.push(obj);
}
return result;
},
/**
* @param {any} jsonObj json object to export
*/
downloadJson(jsonObj) {
var dataStr = "data:text/json;charset=utf-8," + encodeURIComponent(JSON.stringify(jsonObj));
this.downloadFile(dataStr, 'json');
},
/**
* @param {string} csv
*/
downloadCsv(csv) {
var dataStr = "data:text/csv;charset=utf-8," + encodeURIComponent(csv);
this.downloadFile(dataStr, 'csv');
},
/**
* @param {string} dataStr
* @param {'json' | 'csv'} extension
*/
downloadFile(dataStr, extension) {
const area = get(AreaListDnsMap, this.dns, 'UNKNOWN');
const fileName = `daq-config-${area}-${new Date().toISOString()}.${extension}`;
var downloadAnchorNode = document.createElement('a');
downloadAnchorNode.setAttribute("href", dataStr);
downloadAnchorNode.setAttribute("download", fileName);
document.body.appendChild(downloadAnchorNode); // required for firefox
downloadAnchorNode.click();
downloadAnchorNode.remove();
},
/**
* IMPORT
*/
/** @this {Instance} */
async openImportDialog() {
try {
this.remove();
await this.$refs.importDialog.request();
}
catch (err) { /* do nothing */}
},
/** @this {Instance} */
async onChange() {
assign(this, this.$options.data()); /* reset state */
this.confFile = first(this.$refs.fileInput.files);
if (isNil(this.confFile)) return;
const confFileContent = await this.confFile.text();
if (isNil(confFileContent)) return;
// Check Extension
// @ts-ignore
const fileType = this.confFile.name.split('.').pop().toLowerCase();
if (fileType === 'json')
this.importJSON(confFileContent);
else if (fileType === 'csv')
this.importCSV(confFileContent);
else
logger.error('File not valid: ' + this.confFile.name);
},
/**
* @param {string} importJsonStr
*/
async importJSON(importJsonStr) {
try {
this.configuration = JSON.parse(importJsonStr);
this.doChecks();
}
catch (e) {
logger.error('Daq configuration file content is invalid: ' + this.confFile?.name);
}
},
/**
* @param {string} importCsvStr
*/
async importCSV(importCsvStr) {
// Translate CSV to JSON configuration
this.configuration = {};
const jsonObj = this.csvToJson(importCsvStr);
// Group by daq name
const gJsonObj = groupBy(jsonObj, 'daq');
transform(gJsonObj, (res, daqConf, daqName) => {
// Recreate zsp configuration
const zspChannels = transform(daqConf, (resZps, channelConf) => {
const zspObj = {
sn: channelConf.serialNumber,
channel: toNumber(channelConf.index),
id: channelConf.id
};
set(resZps, channelConf.id, zspObj);
}, {});
const zspMode = toNumber(first(daqConf).zspMode);
if (zspMode === ZSPMode.SINGLE_MASTER) {
const masterId = first(daqConf).zspMaster;
set(zspChannels, [ masterId, 'slave' ], []);
}
else if (zspMode === ZSPMode.MASTER) {
forEach(daqConf, (cConf) => {
const masterId = get(cConf, 'zspMaster');
if (masterId !== cConf.id) {
set(zspChannels, [ cConf.id, 'masterId' ], masterId);
const masterSlaves = get(zspChannels, [ masterId, 'slave' ], []);
masterSlaves.push(cConf.id);
set(zspChannels, [ masterId, 'slave' ], masterSlaves);
}
});
}
// Reconstruct zsp update object
const zspConf = {
mode: zspMode,
channels: zspChannels
};
// Reconstruct channels object
const channelsConf = transform(daqConf, (res, channelConf) => {
const newCc = omit(channelConf, [ 'daq', 'zspMode', 'zspMaster' ]);
set(res, channelConf.id, newCc);
}, {});
set(res, daqName, {
zsp: zspConf,
channels: channelsConf
});
}, this.configuration);
this.doChecks();
},
doChecks() {
// check daqs are there by matching with daqList
const daqs = keys(this.configuration);
const daqList = keys(this.daqList);
const unknownDaqs = difference(daqs, daqList);
const missingDaqs = difference(daqList, daqs);
forEach(missingDaqs, (daq) => {
Vue.set(this.warnings, daq, "Missing daq configuration. Current configuration will be used.");
});
forEach(unknownDaqs, (daq) => {
Vue.set(this.warnings, daq, "Unknown daq or not installed in current area. It will be ignored.");
});
/** @type {{[card: string]: string}} */
const cardsInfo = {};
const commonDaqs = intersection(daqs, daqList);
forEach(this.configuration, (daq, daqName) => {
if (indexOf(commonDaqs, daqName) === -1) return;
const channels = get(daq, 'channels');
forEach(mapValues(channels), (c) => set(cardsInfo, c.serialNumber, daqName));
});
const cards = keys(cardsInfo);
const cardList = keys(this.cardList);
const unknownCards = difference(cards, cardList);
const missingCards = difference(cardList, cards);
forEach(missingCards, (card) => {
const daqName = get(this.cardList, [ card, 'daq' ]);
Vue.set(this.warnings, `${daqName} > ${card}`,
"Missing card configuration. Current configuration will be used.");
});
forEach(unknownCards, (card) => {
const daqName = get(cardsInfo, card);
Vue.set(this.warnings, `${daqName} > ${card}`,
"Unknown card or not installed. It will be ignored.");
});
},
remove() {
assign(this, this.$options.data()); /* reset state */
},
/**
* @this {Instance}
* @param {DragEvent} event
*/
dragover(event) {
event.preventDefault();
this.dragOver = true;
},
/**
* @this {Instance}
*/
dragleave() {
this.dragOver = false;
},
/**
* @this {Instance}
* @param {DragEvent} event
*/
drop(event) {
event.preventDefault();
if (isNil(event.dataTransfer)) return;
this.$refs.fileInput.files = event.dataTransfer.files;
this.onChange(); // Trigger the onChange event manually
// Clean up
this.dragleave();
},
/** @this {Instance} */
async applyConf() {
// Load json form file
forEach(this.configuration, (conf, daq) => {
if (!has(this.daqList, daq)) return;
const channelsConf = get(conf, 'channels');
const zspConf = get(conf, 'zsp');
if (channelsConf && zspConf) {
// Update the channels
this.$store.commit('eacs/daq/' + daq + '/resetEdit');
forEach(channelsConf, (cc, cardSnId) => {
forEach(cc, (value, name) => {
this.$store.commit('eacs/daq/' + daq + '/editChannel',
{ id: cardSnId, name, value });
});
});
// Update zsp
this.$store.commit('eacs/daq/' + daq + '/zsp/resetEdit');
this.$store.commit('eacs/daq/' + daq + '/zsp/update', zspConf);
}
});
// Now trigger configure
this.$emit('configure');
}
}
});
export default component;