List file sizes in GitHub - github
The GitHub file browser lists a file's name and info about the last commit:
Is there a way to add each file's size to these listings?
If there is no official way to do this, here is a bookmarklet which uses GitHub's API to add size info (won't work in IE):
javascript:(function(){function f(a){var g=document.querySelector('div[role="rowheader"] a[title="'+a.name+'"]').closest('div[role="row"]').lastElementChild,c=document.createElement("div");c.style.width="5em";"file"===a.type&&(c.textContent=(a.size/1024).toLocaleString("en-US",{minimumFractionDigits:1,maximumFractionDigits:1})+" KB",c.style.textAlign="right",c.style.whiteSpace="nowrap");g.insertAdjacentElement("beforebegin",c)}var b=window.location.pathname.split("/"),d=b[1],h=b[2],e=b[4];b=b.slice(5);d=["https://api.github.com/repos",d,h,"contents"].concat(b||[]).join("/")+(e?"?ref="+e:"");console.log(d);fetch(d).then(function(a){return a.json()}).then(function(a){return Array.isArray(a)?a.forEach(f):console.warn(a)})})();
Unminified source:
(function () {
"use strict";
//Parse the current GitHub repo url. Examples:
// Repo root: /Sphinxxxx/vanilla-picker
// Subfolder: /Sphinxxxx/vanilla-picker/tree/master/src/css
// Subfolder at commit: /Sphinxxxx/vanilla-picker/tree/382231756aac75a49f046ccee1b04263196f9a22/src/css
// Subfolder at tag: /Sphinxxxx/vanilla-picker/tree/v2.2.0/src/css
//
//If applicable, the name of the commit/branch/tag is always the 4th element in the url path.
//Here, we put that in the "ref" variable:
const [/* Leading slash */, owner, repo, /* "tree" */, ref, ...path] = window.location.pathname.split('/');
//Create the URL to query GitHub's API: https://developer.github.com/v3/repos/contents/#get-contents
//Example:
// https://api.github.com/repos/Sphinxxxx/vanilla-picker/contents/src/css?ref=382231756aac75a49f046ccee1b04263196f9a22
const query = ['https://api.github.com/repos', owner, repo, 'contents'].concat(path || []),
url = query.join('/') + (ref ? '?ref=' + ref : '');
console.log(url);
//https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
fetch(url).then(r => r.json())
.then(j => Array.isArray(j) ? j.forEach(handleFileInfo) : console.warn(j));
function handleFileInfo(info) {
//console.log(info);
const link = document.querySelector(`div[role="rowheader"] a[title="${info.name}"]`);
const timeCol = link.closest('div[role="row"]').lastElementChild;
const sizeCol = document.createElement('div');
sizeCol.style.width = '5em';
if(info.type === 'file') {
//http://stackoverflow.com/a/17663871/1869660
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString#Parameters
sizeCol.textContent = (info.size/1024).toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' KB';
sizeCol.style.textAlign = 'right';
sizeCol.style.whiteSpace = 'nowrap';
}
timeCol.insertAdjacentElement('beforebegin', sizeCol);
}
})();
Create a bookmarklet from the first piece of code, or just copy and paste it to your browser's console.
For Chrome browsers there exists an extension:
https://chrome.google.com/webstore/detail/github-repository-size/apnjnioapinblneaedefcnopcjepgkci
You can force file sizes to appear with a JavaScript bookmarklet that calls the GitHub API.
GitHub in 2023 now has two kinds of file table (one is a <table>, the other is <div>-based) so I prepared this new bookmarklet that detects which style is in use before modifying it:
javascript:(function e(){let t=[...document.querySelectorAll('div[role="row"]')],n;if(t.length||(t=[...document.querySelectorAll("tbody tr")],n=document.querySelector("thead tr")),(t=t.slice(1)).length&&"github.com"===document.location.host){let[,l,i,r,o,...c]=window.location.pathname.split("/");if(i&&("tree"===r||!r)){let s=["https://api.github.com/repos",l,i,"contents"].concat(c||[]).join("/")+(o?"?ref="+o:"");console.log(s),fetch(s).then(e=>e.json()).then(e=>{let l=new Map;if(e.forEach(e=>l.set(e.name,e)),n&&"Size"!==n.children[1].innerText){let i=document.createElement("th");i.style.width="6em",i.textContent="Size",n.firstElementChild.insertAdjacentElement("afterend",i)}for(let r of t){let o=r.children[n?0:1],c=o.innerText;c.indexOf("\n")>0&&(c=c.slice(0,c.indexOf("\n")));let s=l.get(c),d=document.createElement(n?"td":"div");d.style.width="6em",d.className=o.className,d.innerText=a(s?.size),o.insertAdjacentElement("afterend",d)}})}else console.error("GITHUB PATH NOT UNDERSTOOD")}else console.error("GITHUB TABLE NOT FOUND; can't add file sizes");function a(e){if(!e)return null==e?"–":"0";if(e<0)return"-"+a(-e);let t;return e<1024?e+" B":(e<1048576?(t=" KiB",e/=1024):e<1073741824?(t=" MiB",e/=1048576):(t=" GiB",e/=1073741824),e.toFixed(1)+t)}})();
See instructions here for making a bookmarklet.
Here's the original unminified code:
(function addSizes() {
// Sometimes each row is like `<div role="row" class="Box-row...">` with no column headers
// e.g. https://github.com/qwertie/ecsharp
// Sometimes each row is like `<tr class="react-directory-row">` with header in `<thead...><tr...>`
// e.g. https://github.com/qwertie/ecsharp/tree/master/Main
// Look for the first kind of rows; if not found, look for the other kind
let rows = [...document.querySelectorAll(`div[role="row"]`)], header;
if (!rows.length) {
rows = [...document.querySelectorAll(`tbody tr`)];
header = document.querySelector(`thead tr`);
}
rows = rows.slice(1);
if (rows.length && document.location.host === 'github.com') {
// Parse path after https://github.com, e.g.
// /username/repo
// /username/repo/tree/branch-name
// /username/repo/tree/branch-name/folder-name
let [/* Leading slash */, owner, repo, __tree__, branch, ...path] = window.location.pathname.split('/');
if (repo && (__tree__ === 'tree' || !__tree__)) {
let query = ['https://api.github.com/repos', owner, repo, 'contents']
.concat(path || []).join('/') + (branch ? '?ref=' + branch : '');
console.log(query);
// The GitHub API will returns an array of objects like
// [{ "name": "file.py", "path": "folder/file.py", "size": 5226, ... }, ...]
fetch(query).then(r => r.json()).then(files => {
// Index the results by file name
let fileMap = new Map();
files.forEach(file => fileMap.set(file.name, file));
// If there is a header row, add a header cell for the file size. Note:
// If user browses to another folder, the 'Size' header column still exists
// but no file sizes. In that case, avoid adding another 'Size' column.
if (header && header.children[1].innerText !== 'Size') {
let sizeCol = document.createElement('th');
sizeCol.style.width = '6em';
sizeCol.textContent = 'Size';
header.firstElementChild.insertAdjacentElement('afterend', sizeCol);
}
// For each row of the table: get the file name, look it up in
// fileMap, get the file size, and insert a size column.
for (let row of rows) {
let nameCol = row.children[header ? 0 : 1];
let name = nameCol.innerText;
if (name.indexOf('\n') > 0) name = name.slice(0, name.indexOf('\n'));
let file = fileMap.get(name);
let sizeCol = document.createElement(header ? 'td' : 'div');
sizeCol.style.width = '6em';
sizeCol.className = nameCol.className;
sizeCol.innerText = formatFileSize(file?.size);
nameCol.insertAdjacentElement('afterend', sizeCol);
}
});
} else {
console.error('GITHUB PATH NOT UNDERSTOOD');
}
} else {
console.error("GITHUB TABLE NOT FOUND; can't add file sizes");
}
function formatFileSize(size) {
if (!size) return size == null ? '–' : '0';
if (size < 0) return '-' + formatFileSize(-size);
let suffix;
if (size < 1024) {
return size + ' B';
} else if (size < 1024*1024) {
suffix = ' KiB';
size /= 1024;
} else if (size < 1024*1024*1024) {
suffix = ' MiB';
size /= 1024*1024;
} else {
suffix = ' GiB';
size /= 1024*1024*1024;
}
return size.toFixed(1) + suffix;
};
})();
It's based loosely on #Sphinxxx's code, but the file size will be the second column rather than the third, and the units change automatically as the file size increases.
No, the GitHub file browser is not configurable that way.
Getting back that extra information would mean transferring an enormous extra amount of data (for each pages of each repos) for GitHub, so I am not sure this is a feature you would see anytime soon.
Note that 'size' is an acceptable criteria for GitHub search though (meaning that size information is there and can be used, not just for browsing files).
element language:xml size:100
Matches code with the word "element" that's marked as being XML and has exactly 100 bytes.
Updated script/bookmarklet after #Sphinxxx answer, now with comments:
Bookmarklet:
!function(){"use strict";const[,t,e,,n,...i]=window.location.pathname.split("/"),o=["https://api.github.com/repos",t,e,"contents"].concat(i||[]).join("/")+(n?"?ref="+n:"");function r(t){var e=null,n=null;if("file"===t.type){const i=`div[role="rowheader"] a[title="${t.name}"]`;e=document.querySelector(i).closest('div[role="row"]').lastElementChild,(n=document.createElement("div")).style.width="5em",n.textContent=(t.size/1024).toLocaleString("en-US",{minimumFractionDigits:1,maximumFractionDigits:1})+" KB",n.style.textAlign="right",n.style.whiteSpace="nowrap",e.insertAdjacentElement("beforebegin",n)}}fetch(o).then((t=>t.json())).then((t=>Array.isArray(t)?t.forEach(r):console.warn("Not an array of files: ",t)))}();
Usage:
Right click, copy link, paste into address bar (or into bookmark link), prepend "javascript:" without quotes, press ENTER
Variant for new github layout with folders tree on the left and file contents on the right:
!function(){"use strict";const[,t,e,,n,...i]=window.location.pathname.split("/"),o=["https://api.github.com/repos",t,e,"contents"].concat(i||[]).join("/")+(n?"?ref="+n:"");function r(t){var e=null,n=null;if("file"===t.type){const i=`div[title="${t.name}"]`;e=document.querySelector(i).closest('tr[class="react-directory-row"]').lastElementChild,(n=document.createElement("td")).style.width="5em",n.innerHTML=(t.size/1024).toLocaleString("en-US",{minimumFractionDigits:1,maximumFractionDigits:1})+" KB",e.insertAdjacentElement("beforebegin",n)}}fetch(o).then((t=>t.json())).then((t=>Array.isArray(t)?t.forEach(r):console.warn("Not an array of files: ",t)))}();
Script 1:
(function () {
"use strict";
//Parse the current GitHub repo url. Examples:
// Repo root: /Sphinxxxx/vanilla-picker
// Subfolder: /Sphinxxxx/vanilla-picker/tree/master/src/css
// Subfolder at commit: /Sphinxxxx/vanilla-picker/tree/382231756aac75a49f046ccee1b04263196f9a22/src/css
// Subfolder at tag: /Sphinxxxx/vanilla-picker/tree/v2.2.0/src/css
//
//If applicable, the name of the commit/branch/tag is always the 4th element in the url path.
//Here, we put that in the "ref" variable:
const [/* Leading slash */, owner, repo, /* "tree" */, ref, ...path] = window.location.pathname.split('/'); // split url and store pieces into constants.
//Create the URL to query GitHub's API: https://developer.github.com/v3/repos/contents/#get-contents
//Example:
// https://api.github.com/repos/Sphinxxxx/vanilla-picker/contents/src/css?ref=382231756aac75a49f046ccee1b04263196f9a22;
const query = ['https://api.github.com/repos', owner, repo, 'contents'].concat(path || []),
url = query.join('/') + (ref ? '?ref=' + ref : '');
//https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
fetch(url).then(r => r.json())
.then(j => Array.isArray(j) ? j.forEach(handleFileInfo) : console.warn("Not an array of files: ",j));
function handleFileInfo(info) {
var timeCol = null;
var sizeCol = null;
var link = "";
var QR = "";
if(info.type === 'file') { // skip folders
const QR = `div[role="rowheader"] a[title="${info.name}"]`; // select the cell containing the file name
link = document.querySelector(QR);
///// Climb along the html hierarchy until it finds a DIV named "row",
///// and get last element (last cell of row), i.e. the date column:
timeCol = link.closest('div[role="row"]').lastElementChild;
///// Create label for file size
sizeCol = document.createElement('div');
sizeCol.style.width = '5em';
//http://stackoverflow.com/a/17663871/1869660
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString#Parameters
sizeCol.textContent = (info.size/1024).toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' KB';
sizeCol.style.textAlign = 'right';
sizeCol.style.whiteSpace = 'nowrap';
///// Insert new label before last element of row:
timeCol.insertAdjacentElement('beforebegin', sizeCol);
} else {
// skip folders
}
}
})();
Script 2:
(function () {
"use strict";
//Parse the current GitHub repo url. Examples:
// Repo root: /Sphinxxxx/vanilla-picker
// Subfolder: /Sphinxxxx/vanilla-picker/tree/master/src/css
// Subfolder at commit: /Sphinxxxx/vanilla-picker/tree/382231756aac75a49f046ccee1b04263196f9a22/src/css
// Subfolder at tag: /Sphinxxxx/vanilla-picker/tree/v2.2.0/src/css
//
//If applicable, the name of the commit/branch/tag is always the 4th element in the url path.
//Here, we put that in the "ref" variable:
const [/* Leading slash */, owner, repo, /* "tree" */, ref, ...path] = window.location.pathname.split('/'); // split url and store pieces into constants.
//Create the URL to query GitHub's API: https://developer.github.com/v3/repos/contents/#get-contents
//Example:
// https://api.github.com/repos/Sphinxxxx/vanilla-picker/contents/src/css?ref=382231756aac75a49f046ccee1b04263196f9a22;
const query = ['https://api.github.com/repos', owner, repo, 'contents'].concat(path || []),
url = query.join('/') + (ref ? '?ref=' + ref : '');
//https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch
fetch(url).then(r => r.json())
.then(j => Array.isArray(j) ? j.forEach(handleFileInfo) : console.warn("Not an array of files: ",j));
function handleFileInfo(info) {
var timeCol = null;
var sizeCol = null;
var link = "";
var QR = "";
if(info.type === 'file') { // skip folders
const QR = `div[title="${info.name}"]`; // select the cell containing the file name
link = document.querySelector(QR);
///// Climb along the html hierarchy until it finds a DIV named "row",
///// and get last element (last cell of row), i.e. the date column:
timeCol = link.closest('tr[class="react-directory-row"]').lastElementChild;
///// Create label for file size
sizeCol = document.createElement('td');
sizeCol.style.width = '5em';
//http://stackoverflow.com/a/17663871/1869660
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/toLocaleString#Parameters
sizeCol.innerHTML = (info.size/1024).toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 }) + ' KB';
//sizeCol.style.textAlign = 'right';
//sizeCol.style.whiteSpace = 'nowrap';
///// Insert new label before last element of row:
timeCol.insertAdjacentElement('beforebegin', sizeCol);
} else {
// skip folders
}
}
})();
Related
FileSystemProvider auto-create directory
I'm working on implementing this the File System provider functionality: https://code.visualstudio.com/api/references/vscode-api#FileSystemProvider I'd like the following writeFile function to automatically create any necessary parent directories as needed. Was wondering if any one else has done anything similar, or how the experts would approach it. https://github.com/microsoft/vscode-extension-samples/blob/aa42c2ec7248059ff5903ff11aa6eeabc04329bf/fsprovider-sample/src/fileSystemProvider.ts#L80 Option 1: change the following line to silent=true and if it returns an undefined, create the necessary directory https://github.com/microsoft/vscode-extension-samples/blob/aa42c2ec7248059ff5903ff11aa6eeabc04329bf/fsprovider-sample/src/fileSystemProvider.ts#L199 option 2: Extend the logic of this if statement or replace the following error with some logic to create the folders https://github.com/microsoft/vscode-extension-samples/blob/aa42c2ec7248059ff5903ff11aa6eeabc04329bf/fsprovider-sample/src/fileSystemProvider.ts#L183 thoughts or suggestions?
I was able to get this working with the following function. I tried the follow the existing code style and approach for simplicity. /** * make parent directires if not present */ private _makePath(uri: Uri) { // get the base directory const dirname = uri.with({ path: path.posix.dirname(uri.path) }); // split the base directory to the folder parts const parts = dirname.path.split('/'); // array to hold the parent folders as we create them const fullPath: string[] = []; // loop through folders for ( const part of parts) { if (!part) { continue; // empty part, continue with loop } // track the folder path as we check/create them fullPath.push(part); // see if current folder exists const here = this._lookup(Uri.parse(...fullPath), true); if (!here) { // current folder not found, so create it this.createDirectory(Uri.parse(path.join(...fullPath))); } else { console.log('directory exists already: ', part); } } } Then call the function at each file creation: writeFile(uri: vscode.Uri, content: Uint8Array, options: { create: boolean, overwrite: boolean }, id?: string): void { console.log('writefile: ', uri); const basename = path.posix.basename(uri.path); // *** create dir path if not there *** this._makePath(uri); const parent = this._lookupParentDirectory(uri); let entry = parent.entries.get(basename); if (entry instanceof Directory) { throw vscode.FileSystemError.FileIsADirectory(uri); } if (!entry && !options.create) { throw vscode.FileSystemError.FileNotFound(uri); } if (entry && options.create && !options.overwrite) { throw vscode.FileSystemError.FileExists(uri); } if (!entry) { entry = new File(basename, id); parent.entries.set(basename, entry); this._fireSoon({ type: vscode.FileChangeType.Created, uri }); } entry.mtime = Date.now(); entry.size = content.byteLength; entry.data = content; this._fireSoon({ type: vscode.FileChangeType.Changed, uri }); }
Word web addin load whole document from server header/footer
We are trying to load a word document from server using JavaScript. We send the document using a base64 encoding. With our current approach, only the body is loading using the function: context.document.body.insertFileFromBase64(fileContent, "replace"); Unfortunately, the header and the footer are not loading. Is there another approach to load the whole document including body and footer?
the insertFile operation does not overwrite existing header/footers in the document.
According to my research, I saw this article for using insertFileFromBase64.The article says," if you use insertFileFromBase64 to insert the file it does have this blank page with header and footer." Did you have the same issue for this? However, another article says it's a design issue. Userform will encode data and will create an appointment on Microsoft Outlook Calendar The article provides approach: function getFile(){ Office.context.document.getFileAsync(Office.FileType.Compressed, { sliceSize: 4194304 /*64 KB*/ }, function (result) { if (result.status == "succeeded") { // If the getFileAsync call succeeded, then // result.value will return a valid File Object. var myFile = result.value; var sliceCount = myFile.sliceCount; var slicesReceived = 0, gotAllSlices = true, docdataSlices = []; console.log("File size:" + myFile.size + " #Slices: " + sliceCount); // Get the file slices. getSliceAsync(myFile, 0, sliceCount, gotAllSlices, docdataSlices, slicesReceived); } else { app.showNotification("Error:", result.error.message); } }); } function getSliceAsync(file, nextSlice, sliceCount, gotAllSlices, docdataSlices, slicesReceived) { file.getSliceAsync(nextSlice, function (sliceResult) { if (sliceResult.status == "succeeded") { if (!gotAllSlices) { // Failed to get all slices, no need to continue. return; } // Got one slice, store it in a temporary array. // (Or you can do something else, such as // send it to a third-party server.) docdataSlices[sliceResult.value.index] = sliceResult.value.data; if (++slicesReceived == sliceCount) { // All slices have been received. file.closeAsync(); onGotAllSlices(docdataSlices); } else { getSliceAsync(file, ++nextSlice, sliceCount, gotAllSlices, docdataSlices, slicesReceived); } } else { gotAllSlices = false; file.closeAsync(); console.log("getSliceAsync Error:", sliceResult.error.message); } }); } function onGotAllSlices(docdataSlices) { var docdata = []; for (var i = 0; i < docdataSlices.length; i++) { docdata = docdata.concat(docdataSlices[i]); } var fileContent = new String(); for (var j = 0; j < docdata.length; j++) { fileContent += String.fromCharCode(docdata[j]); } var mybase64 = window.btoa(fileContent); console.log("here is the base 64", mybase64); // Now all the file content is stored in 'fileContent' variable, // you can do something with it, such as print, fax... }
Setting URL in UploadCollectionItem in UI5 1.38.4
What parameters are mandatory for an UploadCollectionItem with the URL parameter set will show the file when the filename is clicked. I am using a factory to handle files coming from different locations. attachmentFactory(sId, context) { const modelObj = context.getModel().getProperty(context.getPath()); const uploadListItem = new SAPUploadCollectionItem(); // If __metadata exists, attachment entry is from odata, if not then it's a FileEntry object. if (modelObj.__metadata) { uploadListItem.setFileName(modelObj.FILE_NAME); uploadListItem.setMimeType(modelObj.MIME_CODE); uploadListItem.setUrl("https://upload.wikimedia.org/wikipedia/commons/4/49/Koala_climbing_tree.jpg"); } else { uploadListItem.setFileName(modelObj.name); uploadListItem.setMimeType(modelObj.type); uploadListItem.setUrl("https://upload.wikimedia.org/wikipedia/commons/4/49/Koala_climbing_tree.jpg"); } return uploadListItem; } I get an exception in UI5 when I press the link in the function UploadCollection.prototype._triggerLink = function(oEvent, oContext) { var iLine = null; var aId; if (oContext.editModeItem) { //In case there is a list item in edit mode, the edit mode has to be finished first. sap.m.UploadCollection.prototype._handleOk(oEvent, oContext, oContext.editModeItem, true); if (oContext.sErrorState === "Error") { //If there is an error, the link of the list item must not be triggered. return this; } oContext.sFocusId = oEvent.getParameter("id"); } aId = oEvent.oSource.getId().split("-"); iLine = aId[aId.length - 2]; sap.m.URLHelper.redirect(oContext.aItems[iLine].getProperty("url"), true); }; oContext.aItems is an array but the source.getId() value is "__item9-ta_filenameHL" so __item9 is not found in oContext.aItems I'm not sure if this is a bug or I'm setting up my UploadCollectionItem incorrectly
I had to set the sId of the UploadCollectionItem to be the sId that was passed into the factory.
Nesting Model Reports + Search Results
Seem like I'm not the first to investigate this. But not despairing yet :-) The target state I'd like to reach is: A recursion through Packages, where some of the nested Sub-Packages are glorified Linked Documents, and some are Grids rendering Search Results. I like that Model Documents provide Search Tag values -- but I can't seem to nest the grids where I want them to show up. Approach 1: Nested Model Documents If I could nest Model Document elements, with their Search/Search Term tags, I would be sorted. I could have Context:Package Introduction:Package <- just renders a Linked Document attached to it Systems Affected:Package <- just renders a Linked Document attached to it Systems Affected:Model Document <- renders the results of a canned Search. But EA appears to be designed to ignore the Model Document element. Approach 2: SQL backed Template Fragment I like the Model Document as it has Search/Search Term + all the Inclusion/Exclusion configuration options. But if I had to give that part up I am thinking of emulating as much as I can via SQL or script. First attempt - using SQL and a nested set of Packages such as: Context:Package Introduction:Package <- just renders a Linked Document attached to it Systems Affected:Package <- just renders a Linked Document attached to it Systems Affected:Package<> <- renders the results of a SQL Search. If the template uses a TemplateSelector it can spot the Package with a wellknown stereotype, and invoke a Template that is backed by SQL. The SQL uses the Package's Keywords as the source of the Element type to search for. SELECT o.ea_guid AS [CLASSGUID], o.Object_Type AS [CLASSTYPE], o.PDATA5 AS [KEYWORDS], o.Object_Type AS [Type], o.Stereotype AS [Stereotype], * FROM t_object as O WHERE O.Object_Type IN (SELECT PDATA5 FROM t_object AS S where S.Object_ID = #OBJECTID#) It works...barely.It's a bit hacky. Searching off of Element Type is not going to be sufficient for production documents. Approach 3: Script backed Template Fragment If I can get a script running I suspect that I could leverage functionality that is already in the system. Context:Package Introduction:Package <- just renders a Linked Document attached to it Systems Affected:Package <- just renders a Linked Document attached to it Systems Affected:Package<> <- renders the results of a canned Search. If the template uses a TemplateSelector it can spot the Package with a wellknown stereotype, and invoke a Template that is backed by a script. The script I'm trying is: var x = Repository.GetElementsByQuery("POC.Req", "Device"); return x; But the report remains blank of those elements I need for the table. Q: Does the returned Collection need transformation before it can be used? Approach 4 I've heard that there is an approach to Render Document sections as PDF, and link to them as Linked Documents... Sounds Convoluted. Sounds like a wrong/magic approach. Approach 5 Any other suggestions? Thanks for the help. Addendum Just got approach 3 to work by converting the script results to xml before handing it back The template's script now looks like -- first pass ...I'll work on passing the ObjectId in a bit in order to get fancier. ExecuteSearch("Simple", "Device") and it invokes a script saved somewhere else as !INC Local Scripts.EAConstants-JScript /* * Script Name: * Author: * Purpose: * Date: */ function main() { return ExecuteSearch("Simple", "Device"); } function ExecuteSearch(searchName, searchParam){ var x = Repository.GetElementsByQuery(searchName, searchParam); //return x; var xmlDOC = CreateReport(x); var s = xmlDOC.xml; Session.Output(s); return s; } function CreateReport(entityCollection){ var xmlDOC = CreateXmlDOC(); var xmlRoot = AppendXmlChild(xmlDOC,"EADATA"); var xmlDataSet = AppendXmlChild(xmlDOC,"Dataset_0",xmlRoot); var xmlData = AppendXmlChild(xmlDOC,"Data",xmlDataSet); for(var i = 0;i<entityCollection.Count();i++){ var entity = entityCollection.GetAt(i); var xmlRow = AppendXmlChild(xmlDOC,"Row",xmlData); //AppendXmlChild(xmlDOC,"CLASSTYPE",xmlRow).Text = entity.Type; AppendXmlChild(xmlDOC,"Guid",xmlRow).text = entity.ElementGUID; AppendXmlChild(xmlDOC,"CLASSTYPE",xmlRow).text = entity.Type; AppendXmlChild(xmlDOC,"CLASSGUID",xmlRow).text = entity.ElementGUID; AppendXmlChild(xmlDOC,"Type",xmlRow).text = entity.Type; AppendXmlChild(xmlDOC,"Stereotype",xmlRow).text = entity.Stereotype; AppendXmlChild(xmlDOC,"Name",xmlRow).text = entity.Name; AppendXmlChild(xmlDOC,"Object",xmlRow).text = entity.Name; AppendXmlChild(xmlDOC,"Id",xmlRow).text = entity.ElementID; AppendXmlChild(xmlDOC,"Scope",xmlRow).text = entity.Scope; AppendXmlChild(xmlDOC,"Phase",xmlRow).text = entity.Phase; AppendXmlChild(xmlDOC,"Status",xmlRow).text = entity.Status; var noteElement = AppendXmlChild(xmlDOC,"Notes",xmlRow);//.text = entity.Notes; noteElement.appendChild(xmlDOC.createCDATASection(entity.Notes)); AppendXmlChild(xmlDOC,"Keywords",xmlRow).text = entity.PDATA5; } return xmlDOC; } function CreateXmlDOC() { var xmlDOM; try { xmlDOM = new ActiveXObject( "MSXML2.DOMDocument.4.0" ); } catch(e) { xmlDOM = new ActiveXObject( "MSXML2.DOMDocument.6.0" ); } xmlDOM.createProcessingInstruction("xml","version=\"1.0\""); xmlDOM.validateOnParse = false; xmlDOM.async = false; return xmlDOM; } function AppendXmlChild(xmlDOM, xmlElementName, xmlParent, isCDATA){ if (!xmlParent){xmlParent = xmlDOM;} var child = xmlDOM.createElement(xmlElementName); xmlParent.appendChild(child); return child; } main();
The script I used in the end is as follows. It now correctly investigates the element's tags for clues as how to proceed. Hope it helps others. !INC Local Scripts.EAConstants-JScript /* * Script Name: * Author: * Purpose: * Date: */ //Only used for development function devTest() { //With Child elements: {2255D8C8-F1BB-4069-BDAF-8B303D108C62} //With SearchName: {919252E0-BDEB-4f26-A39F-C0E74382952A} //With PackageGUID: {8543ED3B-EC39-4bf0-92C2-FD49A00C376B} Session.Output ("DEVTEST"); var package = Repository.GetPackageByGuid("{8543ED3B-EC39-4bf0-92C2-FD49A00C376B}"); Session.Output("Package Name:" + package.Name); Session.Output("Package Guid:" + package.PackageGUID); Session.Output("Package Id:" + package.PackageID); Session.Output("Package ElementId:" + package.Element.ElementID); //Session.Output("Package Element Id:" + package.Element.ElementID); //Use the Element associate to the Package, not the Package ID itself. var packageElementId = package.Element.ElementID; //NOT: package.PackageID; var xmlDoc=ExecuteSearch(packageElementId); try { Session.Output(xmlDoc.xml); }catch (e){ Session.Output(e.message); } } //Extracts from a given Package it's SearchName/SearchValue tags //in order to do a search that mimicks a ModelDocument's way of //generating a report. function ExecuteSearch(elementId){ //Validation if (!elementId){ Session.Output("Exiting: No elementId received."); return; } var packageElement = Repository.GetElementByID(elementId); if (!packageElement){ Session.Output("Exiting: No package with given elementId: " + elementId); return; } try { var xmlDOC = ExecuteSearch2(packageElement); var xml = xmlDOC.xml; return xml; }catch (e){ Session.Output("ERROR: " + e.message); } return null; } function ExecuteSearch2(packageElement){ //Session.Output(packageElement.ElementGUID + ": '" + packageElement.Name + "' invoking ExecuteSearch(" + packageElement.ElementID + ")"); //Session.Output("Attribute 'Test': " + GetElementTaggedValue(packageElement,'Test')); //Precendence is to search //* Direct children, //* by Package, recursively, //* Package, single //* Package //First dibs: does this package have any direct elements? //Get back to the package that is related to the Element before you count Elements: var package = Repository.GetPackageByGuid(packageElement.ElementGUID); var elementCollection = package.Elements; if (elementCollection.Count()){ Session.Output("Package [" + packageElement.ElementGUID + "] has child Elements:"+ elementCollection.Count()); return CreateReportDoc(elementCollection); } //If package had no children, look at Attributes for reference to other package. //At present, can't find an easy way to determine package Id from the EA GUI, so //using the Guid. var searchPackageGuid = GetElementTaggedValue(packageElement,'SearchPackageGUID'); if (!searchPackageGuid){ searchPackageGuid = GetElementTaggedValue(packageElement,'SearchPackageGuid'); } if (searchPackageGuid){ //Session.Output("Package [" + packageElement.ElementGUID + "] has SearchPackageGuid:"+ searchPackageGuid); return ExecuteSearchByPackageGuid(searchPackageGuid); } // //If I ever find a way to get a packageId: var searchPackageId = GetElementTaggedValue(packageElement,'SearchPackageId'); if (searchPackageId){ //Session.Output("Package [" + packageElement.ElementGUID + "] has SearchPackageId:"+ searchPackageId); return ExecuteSearchByPackageId(searchPackageId); } // //If searching by SQL: var searchSQL = GetElementTaggedValue(packageElement,'SearchSQL'); if (searchSQL){ Session.Output("Package [" + packageElement.ElementGUID + "] has SearchSQL:"+ searchSQL); return ExecuteSearchBySQL(searchSQL); } //Not pointing to a package, so maybe pointing to a canned search: var searchName = GetElementTaggedValue(packageElement,'SearchName'); if (!searchName){ //Session.Output("No SearchName"); return; } var searchValue = GetElementTaggedValue(packageElement,'SearchValue'); //Session.Output("Package [" + packageElement.ElementGUID + "] has SearchName/Value:"+ searchName + "/" + searchValue); return ExecuteSearchBySearchName(searchName, searchValue); } //Mimicks functionality of a ModelDocument that searches by canned SearchName/SearchValue. function ExecuteSearchBySearchName(searchName, searchValue){ var elementCollection = Repository.GetElementsByQuery(searchName, searchValue); //return x; return CreateReportDoc(elementCollection); } function ExecuteSearchByPackageGuid(packageGuid){ var package = Repository.GetPackageByGuid(packageGuid); return ExecuteSearch2(package.Element); } function ExecuteSearchBySQL(searchSQL){ var elementCollection = Repository.GetElementSet(searchSQL, 2); } function HOLD_ExecuteSearchBySet(idList){ var elementCollection = Repository.GetElementsSet(idList); //return x; return CreateReportDoc(elementCollection); } //Iterate through the elements and convert to an Xml Document //suitable for use by a Script backed Template: function CreateReportDoc(elementCollection){ var xmlDOC = CreateXmlDOC(); var xmlData = CreateXmlReport(xmlDOC); for(var i = 0;i<elementCollection.Count();i++){ //For each Element, create a new row: var xmlRow = AppendXmlChild(xmlData,"Row"); //And embed the specific element: var element = elementCollection.GetAt(i); CreateReportRow(xmlRow, element); } return xmlDOC; } function CreateReportRow(xmlRow, element){ //And attach child property elements. //For hairy ones, add them as a CDATA. //AppendXmlChild(xmlDOC,"CLASSTYPE",xmlRow).Text = element.Type; AppendXmlChild(xmlRow,"Guid").text = element.ElementGUID; AppendXmlChild(xmlRow,"CLASSTYPE").text = element.Type; AppendXmlChild(xmlRow,"CLASSGUID").text = element.ElementGUID; AppendXmlChild(xmlRow,"Type").text = element.Type; AppendXmlChild(xmlRow,"Stereotype").text = element.Stereotype; AppendXmlChild(xmlRow,"Name").text = element.Name; AppendXmlChild(xmlRow,"Object").text = element.Name; AppendXmlChild(xmlRow,"Id").text = element.ElementID; AppendXmlChild(xmlRow,"Scope").text = element.Scope; AppendXmlChild(xmlRow,"Phase").text = element.Phase; AppendXmlChild(xmlRow,"Status").text = element.Status; AppendXmlChild(xmlRow,"Keywords").text = element.PDATA5; //Notes need wrapping as CDATA var noteElement = AppendXmlChild(xmlRow,"Notes");//.text = entity.Notes; noteElement.appendChild(xmlRow.ownerDocument.createCDATASection(element.Notes)); //Now get tags: AppendXmlChild(xmlRow,"Tags.ID").text = GetElementTaggedValue(element,"ID"); AppendXmlChild(xmlRow,"Tags.Type").text = GetElementTaggedValue(element,"Type"); AppendXmlChild(xmlRow,"Tags.Category").text = GetElementTaggedValue(element,"Category"); AppendXmlChild(xmlRow,"Tags.Traceability").text = GetElementTaggedValue(element,"Traceability"); return xmlRow; } //helper function to create an empty xml document function CreateXmlDOC() { var xmlDOM; try { xmlDOM = new ActiveXObject( "MSXML2.DOMDocument.4.0" ); } catch(e) { xmlDOM = new ActiveXObject( "MSXML2.DOMDocument.6.0" ); } xmlDOM.createProcessingInstruction("xml","version=\"1.0\""); xmlDOM.validateOnParse = false; xmlDOM.async = false; return xmlDOM; } //helper function to create the beginning of an xml document //suitable to render the results of a search: function CreateXmlReport(xmlDOC){ var xmlRoot = AppendXmlChild(xmlDOC,"EADATA"); var xmlDataSet = AppendXmlChild(xmlRoot,"Dataset_0"); var xmlData = AppendXmlChild(xmlDataSet,"Data"); return xmlData; } //helper function to attach a new child xml element to a parent xml element function AppendXmlChild(xmlParent, xmlElementName, isCDATA){ var xmlDocument = xmlParent.ownerDocument; if (!xmlDocument){xmlDocument = xmlParent} var child = xmlDocument.createElement(xmlElementName); xmlParent.appendChild(child); return child; } //Gets an Element's tag. Eats exception if Tag does not exist. function GetElementTaggedValue(element, tagName){ var tag; try { tag = element.TaggedValues.GetByName(tagName); } catch (e) { } if (!tag){return;} var result = tag.Value; return result; } function ConvertPackageIdToBranchId(packageId){ var package = Repository.GetPackageByID(objectId); if (!package){return;} var packages = [package]; packages.concat(ConvertPackageToBranch(package)); var result=[]; for(var i=0;i<packages.length;i++){ result.push(packages[i].PackageID); } return result; } function ConvertPackageToBranch(package){ var result = []; for (var i=0;i<package.Packages.Count();i++){ var childPackage = package.Packages.GetAt(i); result.push(childPackage); result.concat(ConvertPackageToBranch(childPackage)); } return result; } //devTest();
CSV File create in Google Apps
I have tried to Upload .csv file via API using Google Apps (Script). I give the below property in part of API URL. "ZOHO_FILE":file.csv Is there any way to create .csv file in Google Apps Script ? If possible, pls let us know, how can create .csv file in Google Apps ? Sorry for my english :-(
You can use this to convert a data range: function convertRangeToCsvFile(dataRange) { try { var data = dataRange.getValues(); var csvFile = undefined; // Loop through the data in the range and build a string with the CSV data if (data.length > 1) { var csv = ""; for (var row = 0; row < data.length; row++) { for (var col = 0; col < data[row].length; col++) { if (data[row][col].toString().indexOf(",") != -1) { data[row][col] = "\"" + data[row][col] + "\""; } } // Join each row's columns // Add a carriage return to end of each row, except for the last one if (row < data.length-1) { csv += data[row].join(",") + "\r\n"; } else { csv += data[row]; } } csvFile = csv; } return csvFile; } catch(err) { Logger.log(err); Browser.msgBox(err); } } or this to download a whole sheet function downloadSpreadsheet(){ //This function generates a pdf of your current spreadsheet and emails it to yourself as attachment //Make sure your spreadsheet is not too big. The pdf size tends to be 200kb/page and if too large //if the pdf is too large the urlFetch might timeout var AUTH_TOKEN = "xxxxxxxxxxxxxx"; //Enter your AUTH_TOKEN //You can receive it from https://appscripts.appspot.com/getAuthToken var ssID=SpreadsheetApp.getActiveSpreadsheet().getId()+"&gid="+SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getSheetId(); var url = "https://spreadsheets.google.com/feeds/download/spreadsheets/Export?key=" + ssID + "&exportFormat=csv"; //Add &gid=x at the end of above url if you only want a particular sheet //gid of a sheet can be obtained by the undocumented function getSheetId() //ex: SpreadsheetApp.getActiveSpreadsheet().getActiveSheet().getSheetId(); //exportFormat=xls did not work when I tried. I dont know why var auth = "AuthSub token=\"" + AUTH_TOKEN + "\""; var res = UrlFetchApp.fetch(url, {headers: {Authorization: auth}}); var content=res.getContentText(); return content }
You asked for Apps Script, I hope; Section 2 and 3 of the article below tells how to import and export CSV https://developers.google.com/apps-script/articles/docslist_tutorial#section2