/* eslint-disable max-classes-per-file */
import { LitElement, html, css } from 'lit-element';
import { tooltip as tooltipStyles } from './styles/tooltip.css.js';
import { global as globalStyles } from './styles/global.css.js';
import { utils as utilsStyles } from './styles/utils.css.js';
import { tableDecoration } from './styles/tableDecoration.css.js';
import { GlobalDecorator } from './utils/GlobalDecorator.js';
import { DecorateMixin } from './utils/DecorateMixin.js';
import { downloadFile } from './utils/downloadFile.js';
import { PTable } from './components/p-table/PTable.js';
// Decorate third party component styles
GlobalDecorator.decorateStyles(globalStyles, { prepend: true });
PTable.decorateStyles(tableDecoration);
customElements.define('p-table', PTable);
function checkedValues(checkboxOrNodeList) {
if (!checkboxOrNodeList.length) {
return checkboxOrNodeList.checked && checkboxOrNodeList.value;
}
return Array.from(checkboxOrNodeList)
.filter(r => r.checked)
.map(r => r.value);
}
class PBoard extends DecorateMixin(LitElement) {
static get properties() {
return {
// Transformed data from fetch
tableData: Object,
__resultFiles: Array,
__menuData: Object,
};
}
static get styles() {
return [
super.styles,
utilsStyles,
tooltipStyles,
css`
p-table {
border: 1px solid gray;
display: block;
margin: 2px;
}
.heading {
font-size: 1.5em;
letter-spacing: 0.1em;
}
.heading__part {
color: var(--primary-color);
}
.menu-group {
display: flex;
flex-wrap: wrap;
flex-direction: column;
}
`,
];
}
/**
* @param {object} referenceCollections references defined in providence.conf.js Includes reference projects
* @param {object} searchTargetCollections programs defined in providence.conf.js. Includes search-target projects
* @param {object[]} projDeps deps retrieved by running providence, read from search-target-deps-file.json
*/
_selectionMenuTemplate(result) {
if (!result) {
return html``;
}
const { referenceCollections, searchTargetDeps } = result;
return html`
`;
}
_activeAnalyzerSelectTemplate() {
return html`
`;
}
get _selectionMenuFormNode() {
return this.shadowRoot.getElementById('selection-menu-form');
}
get _activeAnalyzerNode() {
return this.shadowRoot.getElementById('active-analyzer');
}
get _tableNode() {
return this.shadowRoot.querySelector('p-table');
}
_createCsv(headers = this._tableNode._viewDataHeaders, data = this._tableNode._viewData) {
let result = 'sep=;\n';
result += `${headers.join(';')}\n`;
data.forEach(row => {
result += `${Object.values(row)
.map(v => {
if (Array.isArray(v)) {
const res = [];
v.forEach(vv => {
// TODO: make recursive
if (typeof vv === 'string') {
res.push(vv);
} else {
// typeof v === 'object'
res.push(JSON.stringify(vv));
}
});
return res.join(', ');
}
if (typeof v === 'object') {
// This has knowledge about specifier.
// TODO make more generic and add toString() to this obj in generation pahse
return v.name;
}
return v;
})
.join(';')}\n`;
});
return result;
}
render() {
return html`
providence dashboard (alpha)
${this._activeAnalyzerSelectTemplate()}
${this._selectionMenuTemplate(this.__menuData)}
`;
}
constructor() {
super();
this.__resultFiles = [];
this.__menuData = null;
}
firstUpdated(...args) {
super.firstUpdated(...args);
this._tableNode.renderCellContent = this._renderCellContent.bind(this);
this.__init();
}
async __init() {
await this.__fetchMenuData();
await this.__fetchResults();
// await this.__fetchProvidenceConf();
this._enrichMenuData();
}
updated(changedProperties) {
super.updated(changedProperties);
if (changedProperties.has('__menuData')) {
this._aggregateResults();
}
}
/**
* Gets all selection menu data and creates an aggregated
* '_viewData' result.
*/
async _aggregateResults() {
if (!this.__menuData) {
return;
}
await this.__fetchResults();
const elements = Array.from(this._selectionMenuFormNode.elements);
const repos = elements.filter(n => n.name === 'repos');
const references = elements.filter(n => n.name === 'references');
const activeRefs = [...new Set(checkedValues(references))];
const activeRepos = [...new Set(checkedValues(repos))];
const activeAnalyzer = this._activeAnalyzerNode.value;
const totalQueryOutput = this.__aggregateResultData(activeRefs, activeRepos, activeAnalyzer);
// function addCategories(specifierRes, metaConfig) {
// const resultCats = [];
// if (metaConfig.categoryConfig) {
// const { project, filePath, name } = specifierRes.exportSpecifier;
// // First of all, do we have a matching project?
// // TODO: we should allow different configs for different (major) versions
// const match = metaConfig.categoryConfig.find(cat => cat.project === project);
// console.log('match', match);
// if (match) {
// Object.entries(match.categories, ([categoryName, matchFn]) => {
// if (matchFn(filePath, name)) {
// resultCats.push(categoryName);
// }
// });
// }
// }
// console.log('resultCats', resultCats, metaConfig);
// return resultCats;
// }
// Prepare viewData
const dataResult = [];
// When we support more analyzers than match-imports and match-subclasses, make a switch
// here
totalQueryOutput.forEach((specifierRes, i) => {
dataResult[i] = {};
dataResult[i].specifier = specifierRes.exportSpecifier;
dataResult[i].sourceProject = specifierRes.exportSpecifier.project;
// dataResult[i].categories = undefined; // addCategories(specifierRes, this.__providenceConf);
dataResult[i].type = specifierRes.exportSpecifier.name === '[file]' ? 'file' : 'specifier';
dataResult[i].count = specifierRes.matchesPerProject
.map(mpp => mpp.files)
.flat(Infinity).length;
dataResult[i].matchedProjects = specifierRes.matchesPerProject;
});
this.tableData = dataResult;
}
__aggregateResultData(activeRefs, activeRepos, activeAnalyzer) {
const jsonResultsActiveFilter = [];
activeRefs.forEach(ref => {
const refSearch = `_${ref.replace('#', '_')}_`;
activeRepos.forEach(dep => {
const depSearch = `_${dep.replace('#', '_')}_`;
const found = this.__resultFiles[activeAnalyzer].find(({ fileName }) => {
return (
fileName.includes(encodeURIComponent(refSearch)) &&
fileName.includes(encodeURIComponent(depSearch))
);
});
if (found) {
jsonResultsActiveFilter.push(found.content);
} else {
// eslint-disable-next-line no-console
console.info(`No result output json for ${refSearch} and ${depSearch}`);
}
});
});
let totalQueryOutput = [];
jsonResultsActiveFilter.forEach(json => {
if (!Array.isArray(json.queryOutput)) {
// can be a string like [no-mactched-dependency]
return;
}
// Start by adding the first entry of totalQueryOutput
if (!totalQueryOutput) {
totalQueryOutput = json.queryOutput;
return;
}
json.queryOutput.forEach(currentRec => {
// Json queryOutput
// Now, look if we already have an "exportSpecifier".
const totalRecFound = totalQueryOutput.find(
totalRec => currentRec.exportSpecifier.id === totalRec.exportSpecifier.id,
);
// If so, concatenate the "matchesPerProject" array to the existing one
if (totalRecFound) {
// TODO: merge smth?
totalRecFound.matchesPerProject = totalRecFound.matchesPerProject.concat(
currentRec.matchesPerProject,
);
}
// If not, just add a new one to the array.
else {
totalQueryOutput.push(currentRec);
}
});
});
return totalQueryOutput;
}
_enrichMenuData() {
const menuData = this.__initialMenuData;
// Object.keys(menuData.searchTargetDeps).forEach((groupName) => {
// menuData.searchTargetDeps[groupName] = menuData.searchTargetDeps[groupName].map(project => (
// { project, checked: true } // check whether we have results, also for active references
// ));
// });
this.__menuData = menuData;
}
/**
* @override
* @param {*} content
*/
// eslint-disable-next-line class-methods-use-this
_renderSpecifier(content) {
let display;
if (content.name === '[file]') {
display = content.filePath;
} else {
display = content.name;
}
const tooltip = content.filePath;
return html`
${display}
`;
}
/**
* @override
* @param {*} content
* @param {*} header
*/
// eslint-disable-next-line class-methods-use-this
_renderCellContent(content, header) {
if (header === 'specifier') {
return this._renderSpecifier(content);
}
if (header === 'matchedProjects') {
return html`${content
.sort((a, b) => b.files.length - a.files.length)
.map(
mpp => html`
${mpp.project}
(${mpp.files.length})
${mpp.files.map(
f => html`- ${typeof f === 'object' ? JSON.stringify(f) : f}
`,
)}
`,
)}`;
}
if (content instanceof Array) {
return content.join(', ');
}
return content;
}
async __fetchMenuData() {
// Derived from providence.conf.js
this.__initialMenuData = await fetch('/menu-data').then(response => response.json());
}
async __fetchProvidenceConf() {
// Gets an
this.__providenceConf = await fetch('/providence.conf.js').then(response => response.json());
}
async __fetchResults() {
this.__resultFiles = await fetch('/results').then(response => response.json());
}
}
customElements.define('p-board', PBoard);