Skip to content

Commit

Permalink
feat: add/remove repos to/from insights. (#77)
Browse files Browse the repository at this point in the history
  • Loading branch information
diivi authored May 16, 2023
1 parent c142fb8 commit 8e4a6ed
Show file tree
Hide file tree
Showing 7 changed files with 226 additions and 1 deletion.
1 change: 1 addition & 0 deletions src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const OPEN_SAUCED_INSIGHTS_DOMAIN = "insights.opensauced.pizza";
export const OPEN_SAUCED_USERS_ENDPOINT = "https://api.opensauced.pizza/v1/users";
export const OPEN_SAUCED_REPOS_ENDPOINT = "https://api.opensauced.pizza/v1/repos";
export const OPEN_SAUCED_SESSION_ENDPOINT = "https://api.opensauced.pizza/v1/auth/session";
export const OPEN_SAUCED_USER_INSIGHTS_ENDPOINT = "https://api.opensauced.pizza/v1/user/insights";

// GitHub constants/selectors
export const GITHUB_PROFILE_MENU_SELECTOR = ".p-nickname.vcard-username.d-block";
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import "../../content-scripts.css";
import { createHtmlElement } from "../../../utils/createHtmlElement";
import { getAuthToken } from "../../../utils/checkAuthentication";
import { getUserInsightsData, getRepoData, updateInsight } from "../../../utils/fetchOpenSaucedApiData";
import { IInsight } from "../../../ts/InsightDto";

export const InsightsSelectDropdown = async (ownerName: string, repoName: string) => {
const insightsDropdown = createHtmlElement("div", {
className: "SelectMenu cursor-default right-0 mt-1 hidden text-inherit",
innerHTML: `
<div class="SelectMenu-modal">
<header class="SelectMenu-header px-3 py-2">
<h5 class="SelectMenu-title f5 text-left">Add Repo To Insights</h5>
<button class="SelectMenu-closeButton" type="button" aria-label="Close menu">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-x">
<path d="M3.72 3.72a.75.75 0 0 1 1.06 0L8 6.94l3.22-3.22a.749.749 0 0 1 1.275.326.749.749 0 0 1-.215.734L9.06 8l3.22 3.22a.749.749 0 0 1-.326 1.275.749.749 0 0 1-.734-.215L8 9.06l-3.22 3.22a.751.751 0 0 1-1.042-.018.751.751 0 0 1-.018-1.042L6.94 8 3.72 4.78a.75.75 0 0 1 0-1.06Z"></path>
</svg>
</button>
</header>
<div class="SelectMenu-list flex-1 overflow-y-auto px-3 pb-2 mb-0">
</div>
<footer class="SelectMenu-footer px-2">
<button type="button" data-view-component="true" class="user-lists-menu-action btn-invisible btn btn-block text-left text-normal rounded-1 px-2">
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-plus mr-1">
<path d="M7.75 2a.75.75 0 0 1 .75.75V7h4.25a.75.75 0 0 1 0 1.5H8.5v4.25a.75.75 0 0 1-1.5 0V8.5H2.75a.75.75 0 0 1 0-1.5H7V2.75A.75.75 0 0 1 7.75 2Z"></path>
</svg>
Add Insight Page
</button>
</footer>
`,
});

const userToken = await getAuthToken();
const userInsightsData = await getUserInsightsData(userToken);
const currentRepoData = await getRepoData(ownerName, repoName);

const userInsights = userInsightsData.data as any[];

const closeButton = insightsDropdown.querySelector(
".SelectMenu-closeButton",
)!;

closeButton.addEventListener("click", () => {
insightsDropdown.classList.add("hidden");
});

const insightsList = insightsDropdown.querySelector(
".SelectMenu-list",
)!;

userInsights.forEach((insight: IInsight) => {
const insightLabel = createHtmlElement("div", {
className: "form-checkbox mt-1 mb-0 p-1",
innerHTML: `
<label class="d-flex">
<input type="checkbox" class="mx-0 js-user-list-menu-item">
<span data-view-component="true" class="Truncate ml-2 text-normal f5">
<span data-view-component="true" class="Truncate-text">${insight.name}</span>
</span>
</label>
`,
});

const insightCheckbox = insightLabel.querySelector(
"input",
)!;

if (insight.repos.some((repo: any) => repo.repo_id === currentRepoData.id)) {
insightCheckbox.setAttribute("checked", "true");
}

insightCheckbox.addEventListener("click", async () => {
const currentRepoId = currentRepoData.id;
const { checked } = insightCheckbox;

const updatedInsight = await updateInsight(
userToken,
currentRepoId,
checked,
`${ownerName}/${repoName}`,
insight,
);

if (!updatedInsight) {
console.log("Something went wrong");
}
});

insightsList.appendChild(insightLabel);
});


document.addEventListener("click", () => {
insightsDropdown.classList.add("hidden");
});

insightsDropdown.addEventListener("click", e => {
e.stopPropagation();
});

const addInsightBtn = insightsDropdown.querySelector(
".user-lists-menu-action",
)!;

addInsightBtn.addEventListener("click", () => {
window.open(
"https://insights.opensauced.pizza/hub/insights/new",
"_blank",
);
});

return insightsDropdown;
};

18 changes: 18 additions & 0 deletions src/content-scripts/components/RepoVoting/RepoUnvoteButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createHtmlElement } from "../../../utils/createHtmlElement";
import { getAuthToken } from "../../../utils/checkAuthentication";
import { VoteRepoButton } from "./RepoVoteButton";
import { voteOrUnvoteRepo } from "../../../utils/fetchOpenSaucedApiData";
import { InsightsSelectDropdown } from "../InsightsSelectDropdown/InsightsSelectDropdown";

export const RepoUnvoteButton = (ownerName: string, repoName: string) => {
const repoUnvoteButton = createHtmlElement("li", {
Expand All @@ -11,6 +12,9 @@ export const RepoUnvoteButton = (ownerName: string, repoName: string) => {
innerHTML: `
<span>Unvote</span>
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" class="align-middle"><g><path fill="none" d="M0 0h24v24H0z"></path><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-.997-6l7.07-7.071-1.414-1.414-5.656 5.657-2.829-2.829-1.414 1.414L11.003 16z"></path></g></svg>
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-triangle-down text-gray-200 align-middle" id="insights-dropdown-btn">
<path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"></path>
</svg>
`,
});

Expand All @@ -27,9 +31,23 @@ export const RepoUnvoteButton = (ownerName: string, repoName: string) => {
repoUnvoteButton.replaceWith(VoteRepoButton(ownerName, repoName));
} else {
console.log("Something went wrong");
repoUnvoteButton.innerHTML = `
<span>Unvote</span>
`;
}
});

const insightsDropdownBtn = repoUnvoteButton.querySelector(
"#insights-dropdown-btn",
)!;

insightsDropdownBtn.addEventListener("click", async e => {
e.stopPropagation();
const insightsDropdown = await InsightsSelectDropdown(ownerName, repoName) as HTMLDivElement;

repoUnvoteButton.appendChild(insightsDropdown);
insightsDropdown.classList.toggle("hidden");
});

return repoUnvoteButton;
};
19 changes: 19 additions & 0 deletions src/content-scripts/components/RepoVoting/RepoVoteButton.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import "../../content-scripts.css";
import { createHtmlElement } from "../../../utils/createHtmlElement";
import { getAuthToken } from "../../../utils/checkAuthentication";
import { voteOrUnvoteRepo } from "../../../utils/fetchOpenSaucedApiData";
import { InsightsSelectDropdown } from "../InsightsSelectDropdown/InsightsSelectDropdown";

export const VoteRepoButton = (ownerName: string, repoName: string) => {
const voteRepoButton = createHtmlElement("li", {
Expand All @@ -10,6 +11,9 @@ export const VoteRepoButton = (ownerName: string, repoName: string) => {
innerHTML: `
<span>Upvote</span>
<svg stroke="currentColor" fill="currentColor" stroke-width="0" viewBox="0 0 24 24" height="1em" width="1em" xmlns="http://www.w3.org/2000/svg" class="align-middle"><g><path fill="none" d="M0 0h24v24H0z"></path><path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm-.997-6l7.07-7.071-1.414-1.414-5.656 5.657-2.829-2.829-1.414 1.414L11.003 16z"></path></g></svg>
<svg aria-hidden="true" height="16" viewBox="0 0 16 16" version="1.1" width="16" data-view-component="true" class="octicon octicon-triangle-down text-gray-200 align-middle" id="insights-dropdown-btn">
<path d="m4.427 7.427 3.396 3.396a.25.25 0 0 0 .354 0l3.396-3.396A.25.25 0 0 0 11.396 7H4.604a.25.25 0 0 0-.177.427Z"></path>
</svg>
`,
});

Expand All @@ -26,8 +30,23 @@ export const VoteRepoButton = (ownerName: string, repoName: string) => {
voteRepoButton.replaceWith(RepoUnvoteButton(ownerName, repoName));
} else {
console.log("Something went wrong");
voteRepoButton.innerHTML = `
<span>Upvote</span>
`;
}
});

const insightsDropdownBtn = voteRepoButton.querySelector(
"#insights-dropdown-btn",
)!;

insightsDropdownBtn.addEventListener("click", async e => {
e.stopPropagation();
const insightsDropdown = await InsightsSelectDropdown(ownerName, repoName) as HTMLDivElement;

voteRepoButton.appendChild(insightsDropdown);
insightsDropdown.classList.toggle("hidden");
});

return voteRepoButton;
};
6 changes: 6 additions & 0 deletions src/content-scripts/content-scripts.css
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,9 @@ code {
font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New",
monospace;
}

.SelectMenu-modal {
font-size: 12px;
color: var(--color-fg-default)!important;
line-height: 1.5!important;
}
6 changes: 6 additions & 0 deletions src/ts/InsightDto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export interface IInsight {
id: number;
name: string;
is_public: boolean;
repos: any[];
}
62 changes: 61 additions & 1 deletion src/utils/fetchOpenSaucedApiData.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { cachedFetch } from "./cache";
import { OPEN_SAUCED_USERS_ENDPOINT, OPEN_SAUCED_SESSION_ENDPOINT, OPEN_SAUCED_REPOS_ENDPOINT } from "../constants";
import { OPEN_SAUCED_USERS_ENDPOINT, OPEN_SAUCED_SESSION_ENDPOINT, OPEN_SAUCED_REPOS_ENDPOINT, OPEN_SAUCED_USER_INSIGHTS_ENDPOINT } from "../constants";
import { IInsight } from "../ts/InsightDto";

export const isOpenSaucedUser = async (username: string) => {
try {
Expand Down Expand Up @@ -100,6 +101,18 @@ export const repoExistsOnOpenSauced = async (ownerName: string, repoName: string
return response.status === 200;
};

export const getRepoData = async (ownerName: string, repoName: string, forceRefresh: boolean = false) => cachedFetch(`${OPEN_SAUCED_REPOS_ENDPOINT}/${ownerName}/${repoName}`, {
expireInSeconds: 2 * 60 * 60,
forceRefresh,
headers: { Accept: "application/json" },
}).then(async resp => {
if (!resp?.ok) {
console.log("error getting repo info");
}
return resp?.json();
})
.then(json => json);

export const voteOrUnvoteRepo = async (userToken: string, ownerName: string, repoName: string, method: "PUT" | "DELETE") => {
const response = await fetch(
`${OPEN_SAUCED_REPOS_ENDPOINT}/${ownerName}/${repoName}/vote`,
Expand All @@ -111,3 +124,50 @@ export const voteOrUnvoteRepo = async (userToken: string, ownerName: string, rep

return response.status === 200;
};

export const getUserInsightsData = async (userToken: string) => {
const response = await fetch(
`${OPEN_SAUCED_USER_INSIGHTS_ENDPOINT}`,
{
method: "GET",
headers: { Authorization: `Bearer ${userToken}` },
},
);

return response.json();
};

export const updateInsight = async (userToken: string, repoId: string, checked: boolean, repoFullName: string, insight: IInsight) => {
insight.repos = insight.repos.map((repo: any) => ({
id: repo.repo_id,
fullName: repo.full_name,
}));

const response = await fetch(
`${OPEN_SAUCED_USER_INSIGHTS_ENDPOINT}/${insight.id}`,
{
method: "PATCH",
headers: {
Authorization: `Bearer ${userToken}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
name: insight.name,
is_public: insight.is_public,
repos: checked
? [
...insight.repos,
{
id: repoId,
fullName: `${repoFullName}`,
},
]
: insight.repos.filter(
(repo: any) => repo.id !== repoId,
),
}),
},
);

return response.status === 200;
};

0 comments on commit 8e4a6ed

Please sign in to comment.