Skip to content

Commit

Permalink
UI for combined updates (only own GitHub)
Browse files Browse the repository at this point in the history
  • Loading branch information
javiertuya committed Mar 1, 2024
1 parent 0fd0040 commit 69d2256
Show file tree
Hide file tree
Showing 7 changed files with 216 additions and 5 deletions.
3 changes: 3 additions & 0 deletions dashgit-web/app/Config.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@ const config = {
this.setDefault(data, "statusCacheUpdateTime", 30);
this.setDefault(data, "statusCacheRefreshTime", 3600);
this.setDefault(data, "maxAge", 0);
this.setDefault(data, "enableCombinedUpdates", false)
this.setDefault(data, "updateManagerRepo", "")
this.setDefault(data, "updateManagerToken", "")
this.setDefault(data, "providers", []);
for (const provider of data.providers)
this.setProviderDefaults(provider);
Expand Down
5 changes: 5 additions & 0 deletions dashgit-web/app/ConfigController.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ $(document).on('click', '.config-btn-provider-up', function (e) {
configView.moveProvider($(this), -1);
});

//Events that are particular to some items in the configuration
$(document).on('change', '#config-common-enableCombinedUpdates', function (e) {
configView.setToggleDependencies();
});

// Data update events

$(document).on('click', '.config-btn-provider-submit', function (e) {
Expand Down
39 changes: 35 additions & 4 deletions dashgit-web/app/ConfigView.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const configView = {
},

// Main entry point to show the providers configuration form,
// composed of a subform with common data and addiitonal subforms for the providers
// composed of a subform with common data and additional subforms for the providers
renderData: function (data) {
let html = this.common2html(data);
html += `<div id="config-providers-all">`; //only to group all providers
Expand All @@ -38,6 +38,7 @@ const configView = {
html += "</div>"
$("#config-form").html(html);
this.setMoveStatus();
this.setToggleDependencies(); // eg. visibility of elements that depends on a checkbox
// activate tooltips at the input labels
$(`.info-icon`).tooltip({ delay: 200 });
},
Expand All @@ -55,6 +56,7 @@ const configView = {
${this.anyGitHubWithoutToken(data)
? `<p class="card-text mb-1 text-danger">GitHub unauthenticated providers are subject to lower rate limits and do not allow you to view branches, build statuses and notifications.</p>`
: ""}
<h6 class="card-subtitle mb-1 mt-1 text-body-secondary">Common parameters:</h6>
<div class="row">
${this.input2html("config-common-max-age", "number", "Max age", data.maxAge == 0 ? "" : data.maxAge, 'min="0" max="365"', "100", "100",
Expand All @@ -64,6 +66,17 @@ const configView = {
${this.input2html("config-common-statusCacheRefreshTime", "number", "Status Cache Refresh Time", data.statusCacheRefreshTime, 'min="60" max="7200"', "200", "100",
"Specifies a much longer period (in seconds) than Status Cache Update Time. When this time expires, the cache is fully refreshed")}
</div>
<h6 class="card-subtitle mb-1 mt-1 text-body-secondary">Automatically create and merge combined dependency updates
<a href="https://github.com/javiertuya/dashgit/blob/main/README.md" target="_blank">[more info]</a></h6>
<div class="row">
${this.input2html(`config-common-updateManagerRepo`, "text", "Update Manager Repo", data.updateManagerRepo, 'required', "200", "200",
"The full name (OWNER/REPO) of a dedicated GitHub repository where the combined updates will be generated and merged")}
${this.input2html(`config-common-updateManagerToken`, "password", "Access token", data.updateManagerToken, '', "150", "225",
"An API access token with write permission to the Update Manager Repo that combines and merges the updates")}
${this.check2html(`config-common-enableCombinedUpdates`, "Enable combined dependency updates", data.enableCombinedUpdates,
"Enables the ability to automatically create and merge combined dependency updates for each repository")}
</div>
<div class="row">
${this.button2html("", "submit", "Save configuration", "config-btn-provider-submit btn-primary")}
${this.button2html("", "button", `${this.provider2icon("GitHub")} Add GitHub provider`, "config-btn-add-github btn-success")}
Expand Down Expand Up @@ -149,6 +162,9 @@ const configView = {
data.maxAge = age == "" ? 0 : age;
data.statusCacheUpdateTime = $("#config-common-statusCacheUpdateTime").val();
data.statusCacheRefreshTime = $("#config-common-statusCacheRefreshTime").val();
data.enableCombinedUpdates = $("#config-common-enableCombinedUpdates").is(':checked');
data.updateManagerRepo = $("#config-common-updateManagerRepo").val();
data.updateManagerToken = $("#config-common-updateManagerToken").val();
return data;
},

Expand Down Expand Up @@ -184,6 +200,21 @@ const configView = {

// View mannipulation

//Sets the appropriate view state for toggles that have dependent inputs that must be hidden or shown
setToggleDependencies: function() {
if ($(`#config-common-enableCombinedUpdates`).is(':checked')) {
$(`#config-common-updateManagerRepo-div-container`).show();
$(`#config-common-updateManagerRepo`).attr('required', 'required');
$(`#config-common-updateManagerToken-div-container`).show();
$(`#config-common-updateManagerToken`).attr('required', 'required');
} else {
$(`#config-common-updateManagerRepo-div-container`).hide();
$(`#config-common-updateManagerRepo`).removeAttr('required');
$(`#config-common-updateManagerToken-div-container`).hide();
$(`#config-common-updateManagerToken`).removeAttr('required');
}
},

addProvider: function (provider) {
//get latest id to increase it in the new provider
//can't count as some item keys may have been removed
Expand Down Expand Up @@ -271,7 +302,7 @@ const configView = {
let labelStyle = labelWidth == "" ? "" : `style="width:${labelWidth}px"`;
let valueStyle = valueWidth == "" ? "" : `style="width:${valueWidth}px"`;
return `
<div class="col-auto">
<div class="col-auto" id="${id}-div-container">
<div class="input-group input-group-sm">
<span class="input-group-text" id="${id}-label" ${labelStyle}>${label}${this.infoIcon(info)}</span>
<input id="${id}" type="${type}" value="${value}" ${validation} ${valueStyle}
Expand All @@ -297,12 +328,12 @@ const configView = {
`;
},

check2html: function (id, label, checked) {
check2html: function (id, label, checked, info) {
return `
<div class="col-auto">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="" ${checked ? "checked" : ""} id="${id}">
<label class="form-check-label" for="${id}">${label}</label>
<label class="form-check-label" for="${id}">${label}${this.infoIcon(info)}</label>
</div>
</div>
`;
Expand Down
24 changes: 24 additions & 0 deletions dashgit-web/app/GitHubApi.js
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,30 @@ const gitHubApi = {
}`
},

// Creates the dedicated branch with a json file in the update repository manager to
// trigger the GitHub Actions that perform the required tasks.
createContent: async function (token, owner, repo, branch, path, content, message) {
const octokit = new Octokit({ userAgent: this.userAgent, auth: config.decrypt(token), });

console.log("Get default branch, sha, create branch, create update.json file")
const repoResponse = await octokit.request("GET /repos/{owner}/{repo}", { owner: owner, repo: repo });
console.log(repoResponse);

const masterResponse = await octokit.rest.git.getRef({ owner: owner, repo: repo, ref: "heads/" + repoResponse.data.default_branch });
console.log(masterResponse);

const branchResponse = await octokit.rest.git.createRef({
owner: owner, repo: repo, ref: "refs/heads/" + branch, sha: masterResponse.data.object.sha
});
console.log(branchResponse);

const response = await octokit.rest.repos.createOrUpdateFileContents({
owner: owner, repo: repo, branch: branch, path: path, content: content, sha: branchResponse.data.object.sha, message: message
});
console.log(response);
return response.data.content.download_url;
},

}
export { gitHubApi };

Expand Down
46 changes: 46 additions & 0 deletions dashgit-web/app/WiController.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { gitHubApi } from "./GitHubApi.js"
import { gitLabApi } from "./GitLabApi.js"
import { wiView } from "./WiView.js"
import { wiServices } from "./WiServices.js"
import { cache } from "./Cache.js"
import { config } from "./Config.js"

Expand Down Expand Up @@ -38,6 +39,28 @@ import { config } from "./Config.js"
* - Query transformations: detected with try/catch
* - Testing: bad credential, bad url (gitlab), bad GraphQL query
*/


// Response to events from the UI (dependabot tab)
$(document).on('change', '.wi-update-check', function (e) {
wiView.confirmUpdateClear();
});
$(document).on('click', '#wi-btn-update-select-all', function (e) {
$(`.wi-update-check`).prop("checked", true);
wiView.confirmUpdateClear();
});
$(document).on('click', '#wi-btn-update-unselect-all', function (e) {
$(`.wi-update-check`).prop("checked", false);
wiView.confirmUpdateClear();
});
$(document).on('click', '#wi-btn-update-dispatch', function (e) {
wiView.confirmUpdate();
});
$(document).on('click', '#wi-btn-update-dispatch-confirm', async function (e) {
wiView.confirmUpdateProgress();
wiController.sendCombinedUpdates();
});

const wiController = {
reset: function (hard) {
cache.reset(hard); //used for reload operations
Expand Down Expand Up @@ -184,6 +207,29 @@ const wiController = {
wiView.updateNotifications(providerId, notifCount); //don't pass model as it is alredy in cache
},

// To perform combined dependency updates, a json file with the updates selected is sent
// to the dedicated update manager repository in a new branch for this set of combined updates.
// The GitHub Actions configured in the manager repository will perform all required tasks.
// Note that the workflow file must execute on push when changes are made in the path.dashgit/manage-update/**
// If it would set on push to branches, an additonal execution would be triggered for the branch creation
sendCombinedUpdates: async function() {
const itemsToUpdate = wiView.getUpdateCheckItems();
const model = wiServices.getUpdatesModel(itemsToUpdate);
const content = JSON.stringify(model, null, 2);
const currentDate = new Date();
const branch = "dashgit/manage/update-" + currentDate.toISOString().replaceAll("-", "").replaceAll("T", "-").replaceAll(":", "").replaceAll(".", "-");
const message = `DashGit combined updates for ${itemsToUpdate.length} dependencies at ${currentDate.toString()}`;
const path = ".dashgit/manage-update/update.json";
const ownerRepo = config.data.updateManagerRepo.split("/");
gitHubApi.createContent(config.data.updateManagerToken, ownerRepo[0], ownerRepo[1], branch, path, btoa(content), message)
.then(async function(responseUrl) {
wiView.confirmUpdateEnd(`https://github.com/${config.data.updateManagerRepo}/actions`, responseUrl);
}).catch(async function(error) {
wiView.confirmUpdateClear();
wiView.renderAlert("danger", error);
});
},

}

export { wiController };
23 changes: 23 additions & 0 deletions dashgit-web/app/WiServices.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,29 @@ const wiServices = {
return items;
},

// Creates the model required for combined updates:
// takes the list of selected items and produces a hierarchical structure by providers and repositories
getUpdatesModel: function (items) {
// items [{ provider, repo, iid}]
console.log("Generate update model");
console.log(items);
let updates = {};
for (let item of items) {
if (updates[item.provider] == undefined) {
updates[item.provider] = {
urlValue:config.getProviderByUid(item.provider).url,
userValue:config.getProviderByUid(item.provider).user,
tokenSecret:"GITHUB_TOKEN",
repositories:{}
};
}
if (updates[item.provider]["repositories"][item.repo] == undefined)
updates[item.provider]["repositories"][item.repo] = [];
updates[item.provider]["repositories"][item.repo].push(item.iid);
}
return {updates:{updateManagerRepo:config.data.updateManagerRepo, providers:updates}};
},

// Date conversions and display (relative to now)

intervalToString: function (sdate2, sdate1, today) {
Expand Down
81 changes: 80 additions & 1 deletion dashgit-web/app/WiView.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ const wiView = {
let grouping = $("#checkGroup").is(':checked');
console.log(`Render Work Items, sort order: ${sorting}, grouping: ${grouping}`);
let html = `<div class="accordion" id="wi-providers-panel">`;
if (config.data.enableCombinedUpdates && target == "dependabot")
html = html + this.updateHeader2html();
for (let mod of models) {
let header = mod.header;
let items = mod.items;
Expand Down Expand Up @@ -93,9 +95,12 @@ const wiView = {
<div id="wi-providers-panel-${target}-${provider}"
class="${this.statePanelBody(target, provider)}">
<div class="accordion-body">
<table id="wi-items-${this.getId(target, header.uid, "all")}" class='table table-sm table-borderless m-0'>
`;
// adds the header for combined updates (if applicable)
if (config.data.enableCombinedUpdates && target == "dependabot")
html += this.updateProvider2html(provider);
// adds every row
html += `<table id="wi-items-${this.getId(target, header.uid, "all")}" class='table table-sm table-borderless m-0'>`;
html += this.rowsmodel2html(target, header, items, grouping, sorting);
html += `</table>`;
html += `</div></div></div>`;
Expand Down Expand Up @@ -134,6 +139,7 @@ const wiView = {
${this.notification2html(header.uid, item.uid)}
</td>
<td>
${this.updateCheck2html(target, header.uid, item.repo_name, item.iid)}
${this.actions2html(item.actions)}
${grouping ? "" : this.repourl2html(item.repo_url, item.repo_name)}
${this.branch2html(item.branch_url, item.branch_name)}
Expand All @@ -146,6 +152,9 @@ const wiView = {
</tr>
`;
},

// Other low level content

actions2html: function (actions) {
if (actions == undefined)
return "";
Expand Down Expand Up @@ -289,6 +298,76 @@ const wiView = {
$(selector).hide();
},

// Content of UI related with dependabot updates

updateHeader2html: function () {
return `
<div style="padding-left:8px">
<p class="mb-3 mt-2">
You can select the dependabot updates that you want combine and merge in a single pull request for each repository.
</p>
<div class="col-auto mb-2">
<button type="button" id="wi-btn-update-select-all" class="btn btn-success btn-sm">Select all</button>
<button type="button" id="wi-btn-update-unselect-all" class="btn btn-success btn-sm">Unselect all</button>
<button type="button" id="wi-btn-update-dispatch" class="btn btn-primary btn-sm">Combine and merge the selected dependency updates</button>
</div>
<div class="col-auto m-3" id="wi-update-header-confirm"></div>
</div>
`;
},
updateProvider2html: function (provider) {
return `
<div class="mb-0">
<p>This first version only allows GitHub using the provider username and the GITHUB_TOKEN of the Manager Repository</p>
</div>
`;
},
updateCheck2html: function (target, providerId, repoName, iid) {
if (config.data.enableCombinedUpdates && target == "dependabot") {
console.log(`${providerId} ${repoName} ${iid}`)
return `<input class="form-check-input wi-update-check" type="checkbox" value="" aria-label="..."
provider="${providerId}" repo="${repoName}" iid="${iid}"></input>&nbsp;`;
}
return "";
},

confirmUpdateClear: function () {
$("#wi-update-header-confirm").html("");
},
confirmUpdate: function () {
if (this.getUpdateCheckItems().length == 0)
return;
$("#wi-update-header-confirm").html(`
<p class="text-danger">You are going to update ${this.getUpdateCheckItems().length} dependencies, please, press CONFIRM to start the update process<p>
<button type="button" id="wi-btn-update-dispatch-confirm" class="btn btn-danger btn-sm">
CONFIRM (takes a few seconds)
<span id="wi-update-header-confirm-spinner"></span>
</button>
`);
},
confirmUpdateProgress: function () {
//setTimeout(function() {
$("#wi-update-header-confirm-spinner").html(`<span class="spinner-border spinner-border-sm" role="status" aria-hidden="true"></span>`);
//}, 0);
},
confirmUpdateEnd: function (logUrl, updateUrl) {
$("#wi-update-header-confirm").html(`
<p class="text-success"><strong>${this.getUpdateCheckItems().length} dependencies are being updated. &nbsp;
<a href="${logUrl}" target="_blank">[See the logs at GitHub Actions]</a> &nbsp;
<a href="${updateUrl}" target="_blank">[See the update file]
</strong><p>
`);
$("#tab-content").find(`.wi-update-check:checkbox:checked`).attr("disabled", true);
},
getUpdateCheckItems: function () { // all selected, but not disabled
const items = $("#tab-content").find(`.wi-update-check:checkbox:checked`);
let updates = [];
for (let item of items)
if (!$(item).attr("disabled")) //exclude previous updates (that have been disabled)
updates.push({ provider: $(item).attr("provider"), repo: $(item).attr("repo"), iid: $(item).attr("iid") });
return updates;
},

//Primitive functions related to the display of single elements

labels2html: function (repoName, labels) {
Expand Down

0 comments on commit 69d2256

Please sign in to comment.