DEV - Optimizing ways and means of Import/Export

As an admin/developer of Innovator I want to pretend I'm working in a repo where I can identify and track changes, or at the very least see all my changes so I can not miss a thing on Export.

I'm still a noob. I'm not unfamiliar with Dbs of various flavors, and while I've a love of JS, I'm well versed in PHP and have used some other programming languages. I'm especially a fan of recognizing needs and making work easier and more streamlined. For me, making life easier might come in the form of a bash one-liner, or a NodeJS server/app, or PostMan collection.

I think Import/Export is lacking and there should be one tool in the end, and that tool should be built-in, not dissimilar to Nash Utility, but a whole lot prettier!

Here's a summary of why I believe things should change.

IMPORT

In my experience there's a mess when working with other developers in that sometimes a packages infers it should be imported using Nash Utility, or sometimes I'm to use the package tools Import tool.

In my most recent battle I was given a package and the developer specified I had to use v12SP6 import tool. I just grabbed the latest, which I think was that associated with the SP9 package. It didn't work, so I methodically tracked every step and took screenshots and all. Well, it ended up that when I got to the step that emphasizes a specific SP Import Tool, this time, I went to the FTP site and grabbed the Import tool of SP6, specifically, and the import worked. One of the greatest difficulties that I thought absurd during this headache was that I couldn't even just simply select the text of errors in the popup of the Import Tool, so I always had to go open the log to get to the error. To me, that's silly.

All of these things have been very frustrating and highlighting a deficiency. And, then there's the challenge of the necessity of doing things this way.

EXPORT

Well, I would bet many people have gone through this, but I just made my first package! I click all around and gather the pieces. The thing is looking robust and I think I'm done. I goto the Export tool and specify my package, and I check the box to export dependencies. I'm thinking there's no way I can export this new ItemType wrong now.

The created package will not import to the same Db, after restoring to pre-work, that my work started on. This is very frustrating. After days I just use Nash as an alternate means and start pasting the XML and importing, slowly, while sometimes importing the new pieces into the Db in the wrong order. I finally get to the new ItemType itself! My first ItemType! ...and I'm missing my Methods and my Lists from my export. I just missed them and they weren't pulled in, and I can't restore the Db I backed up where I did my work, exported, and replaced with the original Db, pre-work. I don't know why... but, ouch. I can't get to pieces of my first ItemType.

----------

SOLUTION DISCUSSION

First of all, I wish my employer would pay me to just fix whatever I run into. Isn't that a dev's dream? I don't mean Aras, I mean like just spend my day on StackOverflow and fix ***. But, that's not the case.

I never want to miss an ItemType -OR- any relationships be it Method, List, Permissions, Identities, and even PolyItems, etc. Miss nothing, always!

I want to be able to see a picture of all my changes from a certain date like I'd checked out a branch.

In the end I think there should be no need for separate Import/Export programs!
There should be something to Export that utilizes authentication of "root" that works in the browser (like a Nash Utility) that can bundle changes a user has selected. That bundle should work as an Import in a browser-based tool that either: Knows the set order things need to import in -or- is order agnostic. I'm using the proper order, I think. Here's a solution in the works. Feel free to contribute!

// Find all Innovator edit since time
const inn = this.getInnovator();
// Create Dialog.]
const param = {
aras,
dialogWidth: 250,
resizable: true,
title: 'Modified Since'
};
const wnd = aras.getMainWindow();
const dialog = wnd.ArasModules.Dialog.show('div', param);
/////////////////////////////////////////////////////////////////////////////////////////////////////////
// TODO: Edit to query for any new ItemTypes and tack onto itemTypes list to look for new instances. //
/////////////////////////////////////////////////////////////////////////////////////////////////////////
const itemTypes = ['LIST', 'SEQUENCE', 'REVISION', 'VARIABLE', 'IDENTITY', 'MEMBER', 'USER', 'PERMISSION', 'METHOD', 'EMAIL MESSAGE', 'ACTION', 'REPORT', 'FORM', 'WORKFLOW MAP', 'LIFE CYCLE MAP', 'GRID', 'ITEMTYPE', 'RELATIONSHIPTYPE', 'FIELD', 'PROPERTY', 'VIEW', 'SQL', 'METRIC', 'CHART', 'DASHBOARD'];
const badgeStyle = 'style="font-weight:200;background:#999;color:#fff;border-radius:5px;font-size:12px;padding:5px;margin-left:.5em;letter-spacing:.1em;padding-bottom:3px;position:absolute;top: 12px;text-align:center;"';
const table = 'style="display:table;width:100%;border-collapse:collapse;"';
const tableCell = 'style="display:table-cell;border:#000 1px solid;padding:.25em;"';
const tableCheckbox = 'style="display:table-cell;border:#000 1px solid;padding:.25em;text-align:center;"';
const tableHead = 'style="display:table-cell;border:#000 1px solid;font-weight:bold;background:#000;color:#fff;padding:.25em;"';
const yesterday = (d => new Date(d.setDate(d.getDate() - 1)).toISOString().split("T")[0])(new Date());
//
let dateInputDiv, dateInputDOM, dialogDOM, contentDOM, changesList, changesListText, titleDOM, formFooterAdded = false;
//
const alternateRowBG = (i) => `style="display:table-row;${i % 2 ? 'background: #e7e7e7;' : ''}"`;
const formatRow = (itemType, id, name, modifiedOn) => `<div ${tableCheckbox}>${getRowCheckbox(id, name, itemType)}</div><div ${tableCell}>${name}</div><div ${tableCell}>${itemType}</div><div ${tableCell}>${modifiedOn}</div>`;
const getBadge = (num) => `<span ${badgeStyle}>${num}</span>`;
const getRowCheckbox = (id, name, itemType) => {
const checkboxName = `${name}---${itemType}`
return `<input type='checkbox' id='${id}' name='${checkboxName}'>`
};
const styleMe = (elem, styles) => {
for (const [key, value] of Object.entries(styles)) elem.style[key] = value;
};
// Build date input text box/style it
const buildDateInput = () => {
dateInputDiv = document.createElement('div');
const dateInput = document.createElement('input');
dateInput.id = 'modified-since-date-input';
dateInput.type = 'date';
dateInput.max = yesterday;
styleMe(dateInputDiv, {paddingLeft: '16px', paddingRight: '16px', maxHeight: '70vh', overflowX: 'hidden', overflowY: 'auto'});
styleMe(dateInput, {width: '222px', marginBottom: '16px', marginTop: '8px'});
dateInputDiv.appendChild(dateInput);
};
// Loop through the returned methods and return the list
const buildChangesTable = (value) => {
changesListText = '';
const changesTable = document.getElementById('changes-table');
if (changesTable) changesTable.remove();
let changesList = `<div id='changes-table' ${table}><div ${alternateRowBG(-1)}><div ${tableHead}>&nbsp;</div><div ${tableHead}>Item</div><div ${tableHead}>ItemType</div><div ${tableHead}>Modified On</div></div>`;
let sinceDate = value + 'T00:00:00';
let rowCount = 0;
for (let i = 0; i < itemTypes.length; i++) {
const itemType = itemTypes[i];
const item = this.newItem(itemType, 'get');
item.setAttribute('select', 'id,name,modified_on');
// FIXME: We want unique results. It seems IT properties are heavily versioned.
// groupBy doesn't error, but doesn't appear to do anything
// item.setAttribute('groupBy', 'id');
item.setProperty('modified_on', sinceDate);
item.setPropertyAttribute('modified_on', 'condition', 'gt');
const results = item.apply();
if (results.isError()) {
const errorDetail = results.getErrorDetail();
// No items of type
if (!/No items of type/.test(errorDetail)) {
aras.AlertError('results.isError(): ' + errorDetail);
return;
}
}

for (let j = 0; j < results.getItemCount(); j++) {
const id = results.getItemByIndex(j).getProperty('id', '-id missing-');
const name = results.getItemByIndex(j).getProperty('name', '-name missing-');
const modifiedOn = results.getItemByIndex(j).getProperty('modified_on', '-modified_on missing-');
const row = formatRow(itemType, id, name, new Date(modifiedOn).format('MM/dd/yyyy HH:MM'));
changesListText += [id,itemType,name,modifiedOn].join() + '\n';
changesList += `<div ${alternateRowBG(rowCount)}>${row}</div>`;
rowCount++;
}
}
changesList += '</div>';
styleMe(dialogDOM, { width: '720px' });
// TODO: Test that this: Why can't we assign this once upon opening and keep the ref?
contentDOM = document.querySelector('.aras-dialog__content');
dateInputDOM = document.querySelector('#modified-since-date-input');
titleDOM = document.querySelector('.aras-dialog__title');
//
titleDOM.innerText = 'Modified Since ' + value;
titleDOM.insertAdjacentHTML('beforeend', getBadge(rowCount));
const viewButton = document.createElement('button');
viewButton.innerText = 'View CSV';
viewButton.classList.add('aras-button');
styleMe(viewButton, { position: 'absolute', top: '10px', right: '40px', paddingLeft: '.5em', paddingRight: '.5em', background: 'transparent', color: 'darkblue', boxShadow: 'none', border: 'none', cursor: 'zoom-in' });
viewButton.onclick = () => {
const resultsWindow = window.open("about:blank", "_blank");
// contentDOM = document.querySelector('.aras-dialog__content');
resultsWindow.document.write(`<pre>${changesListText}</pre>`);
};
titleDOM.appendChild(viewButton);
// contentDOM.innerHTML += changesList;
dateInputDOM.insertAdjacentHTML('afterend', changesList);
// FIXME: Or... understand me? I don't know why the onchange gets dropped between calls to this_function...? But, it does, so:
dateInputDOM.onchange = ({ target }) => {
console.log(target.value, target.valueAsNumber);
buildChangesTable(target.value);
};
if (!formFooterAdded) buildFormFooter();
};
// Add footer with Package Name input and Create Package button
const buildFormFooter = () => {
const button = document.createElement('button');
const packageNameInput = document.createElement('input');
const submitDiv = document.createElement('div');
styleMe(submitDiv, { padding: '16px', display: 'flex' });
styleMe(packageNameInput, { width: '220px', padding: '0.3em 0.3em 0.3em 0.5em', flexGrow: 1, marginRight: '16px', background: '#e7e7e7', color: '#000', marginRight: '16px', letterSpacing: '.1em', fontSize: '1.5em' });
button.innerText = 'Create Package';
button.classList.add('aras-button', 'aras-button_primary');
styleMe(button, { padding: '.6em', borderRadius: '.25em', width: 'auto', background: '#000' });
button.onclick = () => {
// Get checkbox values
const checked = Array.from(document.querySelector('.aras-dialog__content').querySelectorAll('input[type="checkbox"]:checked')).map(x => x.id);
// Get Package (to create) name
const name = packageNameInput.value;
// Create package
alert(`Put these:\n${checked.join('\n')}\nin package\n${name}\n`);
/* FIXME: The following doesn't error, but it also doesn't actually create a PackageDefinition...?
const newPackage = inn.newItem('PackageDefinition');
newPackage.setAttribute('name', name);
const result = newPackage.apply();
*/
// LOGS, "More than one item": console.log(result.getAttribute('id'));
};
// aras-button aras-button_primary
packageNameInput.id = 'package-name';
packageNameInput.name = 'package-name';
submitDiv.id = 'modified-since-package';
//
submitDiv.appendChild(packageNameInput)
submitDiv.appendChild(button);
contentDOM.appendChild(submitDiv);
formFooterAdded = true;
};
// Seems necessary.
setTimeout(() => {
buildDateInput();
dialogDOM = document.querySelector('.aras-dialog');
styleMe(dialogDOM, { height: 'auto'});
contentDOM = document.querySelector('.aras-dialog__content');
contentDOM.appendChild(dateInputDiv);
dateInputDOM = document.querySelector('#modified-since-date-input');
dateInputDOM.onchange = ({ target }) => {
buildChangesTable(target.value);
};
}, 100);


Anwyway, the above results in a list with checkboxes and an input for package name, and all of that is collected and so badly wants to be a package, but I can't seem to even create the package that I want to add all these things to.

Ideas?






...but, you just get an alert that tells me that I want to make a package with this stuff. I don't get an error when trying to create a PackageDefinition, but it apparently isn't created. Next steps:

  1. Actually create PackageDefinition
  2. Add selections to the new package (using id?)