Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix K2 for new GitHub UI #226

Merged
merged 17 commits into from
Jan 17, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading