Skip to content

Commit

Permalink
Merge pull request #226 from Expensify/github-new-ui-fix
Browse files Browse the repository at this point in the history
Fix K2 for new GitHub UI
  • Loading branch information
neil-marcellini authored Jan 17, 2025
2 parents 1f12688 + 91f5531 commit d433312
Show file tree
Hide file tree
Showing 14 changed files with 182 additions and 195 deletions.
2 changes: 2 additions & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ module.exports = {
'comma-dangle': ['error', 'always-multiline'],
'rulesdir/no-api-in-views': 'off',
'rulesdir/no-multiple-api-calls': 'off',
'@lwc/lwc/no-async-await': 'off',
'es/no-nullish-coalescing-operators' : 'off'
},
settings: {
'import/resolver': {
Expand Down
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
#1.4.0
- Updated the extension to work with new GitHub UI
- Updated the extension to work with GitHub's new PR merge experience

#1.3.74
- Moved the previous query string params to Onyx

Expand Down
2 changes: 1 addition & 1 deletion assets/manifest-firefox.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifest_version": 3,

"name": "K2 for GitHub",
"version": "1.3.74",
"version": "1.4.0",
"description": "Manage your Kernel Scheduling from directly inside GitHub",

"browser_specific_settings": {
Expand Down
2 changes: 1 addition & 1 deletion assets/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"manifest_version": 3,

"name": "K2 for GitHub",
"version": "1.3.74",
"version": "1.4.0",
"description": "Manage your Kernel Scheduling from directly inside GitHub",

"icons": {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "k2-extension",
"version": "1.3.74",
"version": "1.4.0",
"description": "A Chrome Extension for Kernel Schedule",
"private": true,
"scripts": {
Expand Down
25 changes: 25 additions & 0 deletions src/css/content.scss
Original file line number Diff line number Diff line change
Expand Up @@ -570,3 +570,28 @@ $color-dark-yellow: #DAA520;
color: rgb(230, 237, 243)
}
}

.loader {
width: 14px;
aspect-ratio: 1;
border-radius: 50%;
border: 2px solid #514b82;
animation:
l20-1 0.8s infinite linear alternate,
l20-2 1.6s infinite linear;
}
@keyframes l20-1{
0% {clip-path: polygon(50% 50%,0 0, 50% 0%, 50% 0%, 50% 0%, 50% 0%, 50% 0% )}
12.5% {clip-path: polygon(50% 50%,0 0, 50% 0%, 100% 0%, 100% 0%, 100% 0%, 100% 0% )}
25% {clip-path: polygon(50% 50%,0 0, 50% 0%, 100% 0%, 100% 100%, 100% 100%, 100% 100% )}
50% {clip-path: polygon(50% 50%,0 0, 50% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100% )}
62.5% {clip-path: polygon(50% 50%,100% 0, 100% 0%, 100% 0%, 100% 100%, 50% 100%, 0% 100% )}
75% {clip-path: polygon(50% 50%,100% 100%, 100% 100%, 100% 100%, 100% 100%, 50% 100%, 0% 100% )}
100% {clip-path: polygon(50% 50%,50% 100%, 50% 100%, 50% 100%, 50% 100%, 50% 100%, 0% 100% )}
}
@keyframes l20-2{
0% {transform:scaleY(1) rotate(0deg)}
49.99%{transform:scaleY(1) rotate(135deg)}
50% {transform:scaleY(-1) rotate(0deg)}
100% {transform:scaleY(-1) rotate(-135deg)}
}
65 changes: 63 additions & 2 deletions src/js/lib/pages/github/_base.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import $ from 'jquery';
import * as API from '../../api';

/**
* This class is to be extended by each of the distinct types of webpages that the extension works on
Expand All @@ -7,6 +8,43 @@ import $ from 'jquery';
export default function () {
const Page = {};

const REVIEWER_CHECKLIST_URL = 'https://raw.githubusercontent.com/Expensify/App/main/contributingGuides/REVIEWER_CHECKLIST.md';
const BUGZERO_CHECKLIST_URL = 'https://raw.githubusercontent.com/Expensify/App/main/contributingGuides/BUGZERO_CHECKLIST.md';

/**
* Gets the contents of the reviewer checklist from GitHub and then posts it as a comment to the current PR
* @param {Event} e
* @param {'bugzero' | 'reviewer'} checklistType Type of target checklist
*/
const copyReviewerChecklist = async (e, checklistType) => {
const checklistUrl = checklistType === 'bugzero' ? BUGZERO_CHECKLIST_URL : REVIEWER_CHECKLIST_URL;

e.preventDefault();

// Get the button element
const button = e.target;

// Save the original content of the button
const originalContent = button.innerHTML;

// Replace the button content with a loader
button.innerHTML = '<div class="loader" />';

try {
// Fetch the checklist contents
const response = await fetch(checklistUrl);
const fileContents = await response.text();

// Call the API to add the comment
await API.addComment(fileContents);
} catch (error) {
console.error('Error fetching the checklist:', error);
} finally {
// Restore the original button content
button.innerHTML = originalContent;
}
};

/**
* A unique identifier for each page
*/
Expand Down Expand Up @@ -47,11 +85,34 @@ export default function () {
Page.setup = function () {};

Page.getRepoOwner = function () {
return $('.author a span').text();
return document.querySelectorAll('.AppHeader-context-item-label.Truncate-text')[0] // Org name next to GitHub logo
.textContent.trim();
};

Page.getRepo = function () {
return $('.js-current-repository').text();
return document.querySelectorAll('.AppHeader-context-item-label.Truncate-text')[1] // Repo name next to GitHub logo
.textContent.trim();
};

/**
* Renders buttons for copying checklists in issue/PR bodies
* @param {'bugzero' | 'reviewer'} checklistType Type of target checklist
*/
Page.renderCopyChecklistButtons = function (checklistType) {
// Look through all the comments on the page to find one that has the template for the copy/paste checklist button
// eslint-disable-next-line rulesdir/prefer-underscore-method
$('.markdown-body > p').each((i, el) => {
const commentHtml = $(el).html();

// When the button template is found, replace it with an HTML button and then put that back into the DOM so someone can click on it
if (commentHtml && commentHtml.indexOf('you can simply click: [this button]') > -1) {
const newHtml = commentHtml.replace('[this button]', '<button type="button" class="btn btn-sm k2-copy-checklist">HERE</button>');
$(el).html(newHtml);

// Now that the button is on the page, add a click handler to it (always remove all handlers first so that we know there will always be one handler attached)
$('.k2-copy-checklist').off().on('click', e => copyReviewerChecklist(e, checklistType));
}
});
};

return Page;
Expand Down
30 changes: 0 additions & 30 deletions src/js/lib/pages/github/createpr.js

This file was deleted.

94 changes: 34 additions & 60 deletions src/js/lib/pages/github/issue.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,50 +13,16 @@ import * as API from '../../api';

let clearErrorTimeoutID;
function catchError(e) {
$('.gh-header-actions .k2-element').remove();
$('.gh-header-actions').append('<span class="alert k2-element">OOPS!</span>');
$('div[data-component="PH_Actions"] .k2-element').remove(); // K2 elements in action buttons
$('div[data-component="PH_Actions"]') // Action buttons next to issue title
.append('<span class="alert k2-element">OOPS!</span>');
console.error(e);
clearTimeout(clearErrorTimeoutID);
clearErrorTimeoutID = setTimeout(() => {
$('.gh-header-actions .k2-element').remove();
$('div[data-component="PH_Actions"] .k2-element').remove();
}, 30000);
}

/**
* Gets the contents of the reviewer checklist from GitHub and then posts it as a comment to the current PR
* @param {Event} e
*/
const copyReviewerChecklist = (e) => {
e.preventDefault();
const pathToChecklist = 'https://raw.githubusercontent.com/Expensify/App/main/contributingGuides/BUGZERO_CHECKLIST.md';
$.get(pathToChecklist)
.done((fileContents) => {
if (!fileContents) {
console.error(`could not load contents of ${pathToChecklist} for some reason`);
return;
}

API.addComment(fileContents);
});
};

const renderCopyChecklistButton = () => {
// Look through all the comments on the page to find one that has the template for the copy/paste checklist button
// eslint-disable-next-line rulesdir/prefer-underscore-method
$('.js-comment-body').each((i, el) => {
const commentHtml = $(el).html();

// When the button template is found, replace it with an HTML button and then put that back into the DOM so someone can click on it
if (commentHtml && commentHtml.indexOf('you can simply click: [this button]') > -1) {
const newHtml = commentHtml.replace('[this button]', '<button type="button" class="btn btn-sm k2-copy-checklist">HERE</button>');
$(el).html(newHtml);

// Now that the button is on the page, add a click handler to it (always remove all handlers first so that we know there will always be one handler attached)
$('.k2-copy-checklist').off().on('click', copyReviewerChecklist);
}
});
};

/**
* Sets the owner of an issue when it doesn't have an owner yet
* @param {String} owner to set
Expand Down Expand Up @@ -104,27 +70,32 @@ function replaceOwner(oldOwner, newOwner) {

/**
* This method is all about adding the "issue owner" functionality which melvin will use to see who should be providing ksv2 updates to an issue.
* @param {String | null} [issueOwner] GitHub username of the issue owner. Null means no owner, undefined means get the owner from issue body
*/
const refreshAssignees = () => {
const renderAssignees = (issueOwner) => {
// Always start by erasing whatever was drawn before (so it always starts from a clean slate)
$('.js-issue-assignees .k2-element').remove();
$('div[data-testid="sidebar-section"] .k2-element').remove();

// Check if there is an owner for the issue
const ghDescription = $('.comment-body').text();
const regexResult = ghDescription.match(/Current Issue Owner:\s@(?<owner>\S+)/i);
const currentOwner = regexResult && regexResult.groups && regexResult.groups.owner;
let currentOwner = issueOwner;

// if issue owner is not provided, then try to get it from the issue body
if (currentOwner === undefined) {
const ghDescription = $('.markdown-body').first().text();
const regexResult = ghDescription.match(/Current Issue Owner:\s@(?<owner>\S+)/i);
currentOwner = regexResult && regexResult.groups && regexResult.groups.owner;
}

// Add buttons to each assignee
$('.js-issue-assignees > p > span').each((i, el) => {
const assignee = $(el).find('.assignee span').text();
$('div[data-testid="issue-assignees"]').each((i, el) => {
const assignee = $(el).text();
if (assignee === currentOwner) {
$(el).append(`
$(el).closest('li').append(`
<button type="button" class="Button flex-md-order-2 m-0 owner k2-element k2-button k2-button-remove-owner" data-owner="${currentOwner}">
</button>
`);
} else {
$(el).append(`
$(el).closest('li').append(`
<button type="button" class="Button flex-md-order-2 m-0 k2-element k2-button k2-button-make-owner" data-owner="${assignee}">
</button>
Expand All @@ -133,30 +104,31 @@ const refreshAssignees = () => {
});

// Remove the owner with this button is clicked
$('.k2-button-remove-owner').off('click').on('click', (e) => {
$('.k2-button-remove-owner').off('click').on('click', async (e) => {
e.preventDefault();
const owner = $(e.target).data('owner');
removeOwner(owner);
return false;
renderAssignees(null);
});

// Make a new owner when this button is clicked
$('.k2-button-make-owner').off('click').on('click', (e) => {
$('.k2-button-make-owner').off('click').on('click', async (e) => {
e.preventDefault();
const newOwner = $(e.target).data('owner');
if (currentOwner) {
replaceOwner(currentOwner, newOwner);
} else {
setOwner(newOwner);
}
return false;
renderAssignees(newOwner);
});
};

const refreshPicker = function () {
// Add our wrappers to the DOM which all the React components will be rendered into
if (!$('.k2picker-wrapper').length) {
$('.js-issue-labels').after(sidebarWrapperHTML);
$('div[data-testid="issue-viewer-metadata-pane"] > :nth-child(3)') // Labels section in right side panel
.after(sidebarWrapperHTML);
}

new K2picker().draw();
Expand All @@ -172,7 +144,7 @@ const refreshPicker = function () {
* @returns {Object}
*/
export default function () {
let allreadySetup = false;
let alreadySetup = false;
ReactNativeOnyx.init({
keys: ONYXKEYS,
});
Expand All @@ -183,27 +155,29 @@ export default function () {

IssuePage.setup = function () {
// Prevent this function from running twice (it sometimes does that because of how chrome triggers the extension)
if (allreadySetup) {
if (alreadySetup) {
return;
}
allreadySetup = true;
alreadySetup = true;

// Draw them once when the page is loaded
setTimeout(refreshPicker, 500);
setTimeout(refreshAssignees, 500);
setTimeout(renderAssignees, 500);

// Every second, check to see if the pickers are still there, and if not, redraw them
setInterval(() => {
if (!$('.k2picker-wrapper').length) {
refreshPicker();
}
if (!$('.js-issue-assignees .k2-element').length) {
refreshAssignees();

if (!$('div[data-testid="issue-viewer-metadata-pane"] > :nth-child(2) .k2-element') // Assignee section in right side panel
.length) {
renderAssignees();
}
}, 1000);

// Waiting 2 seconds to call this gives the page enough time to load so that there is a better chance that all the comments will be rendered
setInterval(renderCopyChecklistButton, 2000);
setInterval(() => IssuePage.renderCopyChecklistButtons('bugzero'), 2000);
};

return IssuePage;
Expand Down
Loading

0 comments on commit d433312

Please sign in to comment.