diff --git a/.github/scripts/build_assets/SeleniumRunner.py b/.github/scripts/build_assets/SeleniumRunner.py index da5091aea..fac564bea 100644 --- a/.github/scripts/build_assets/SeleniumRunner.py +++ b/.github/scripts/build_assets/SeleniumRunner.py @@ -118,10 +118,13 @@ def upload_icomoon(self, icomoon_json_path: str): print("JSON file uploaded.") - def upload_svgs(self, svgs: List[str]): + def upload_svgs(self, svgs: List[str], screenshot_folder: str=""): """ Upload the SVGs provided in folder_info :param svgs: a list of svg Paths that we'll upload to icomoon. + :param screenshot_folder: the name of the screenshot_folder. If + the value is provided, it means the user want to take a screenshot + of each icon. """ try: print("Uploading SVGs...") @@ -133,17 +136,20 @@ def upload_svgs(self, svgs: List[str]): self.click_hamburger_input() - for svg in svgs: + for i in range(len(svgs)): import_btn = self.driver.find_element_by_css_selector( "li.file input[type=file]" ) - import_btn.send_keys(svg) - print(f"Uploaded {svg}") + import_btn.send_keys(svgs[i]) + print(f"Uploaded {svgs[i]}") self.test_for_possible_alert(self.SHORT_WAIT_IN_SEC, "Dismiss") - self.remove_color_from_icon() + self.click_on_just_added_icon(screenshot_folder, i) # take a screenshot of the icons that were just added - self.driver.save_screenshot("new_icons.png"); + new_icons_path = str(Path(screenshot_folder, "new_icons.png").resolve()) + self.driver.save_screenshot(new_icons_path); + + # select all the svgs so that the newly added svg are part of the collection self.click_hamburger_input() select_all_button = WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until( ec.element_to_be_clickable((By.XPATH, "//button[text()='Select All']")) @@ -191,38 +197,26 @@ def test_for_possible_alert(self, wait_period: float, btn_text: str): ) dismiss_btn.click() except SeleniumTimeoutException: - pass + pass # nothing found => everything is good - def remove_color_from_icon(self): + def click_on_just_added_icon(self, screenshot_folder: str, index: int): """ - Remove the color from the most recent uploaded icon. - :return: None. + Click on the most recently added icon so we can remove the colors + and take a snapshot if needed. """ try: recently_uploaded_icon = WebDriverWait(self.driver, self.LONG_WAIT_IN_SEC).until( ec.element_to_be_clickable((By.XPATH, "//div[@id='set0']//mi-box[1]//div")) ) recently_uploaded_icon.click() - except Exception as e: - self.close() - raise e - try: - color_tab = WebDriverWait(self.driver, self.SHORT_WAIT_IN_SEC).until( - ec.element_to_be_clickable((By.CSS_SELECTOR, "div.overlayWindow i.icon-droplet")) - ) - color_tab.click() + self.remove_color_from_icon() - remove_color_btn = self.driver \ - .find_element_by_css_selector("div.overlayWindow i.icon-droplet-cross") - remove_color_btn.click() - except SeleniumTimeoutException: - pass - except Exception as e: - self.close() - raise e + if screenshot_folder: + screenshot_path = str(Path(screenshot_folder, f"screenshot_{index}.png").resolve()) + self.driver.save_screenshot(screenshot_path) + print("Took screenshot and saved it at " + screenshot_path) - try: close_btn = self.driver \ .find_element_by_css_selector("div.overlayWindow i.icon-close") close_btn.click() @@ -230,6 +224,19 @@ def remove_color_from_icon(self): self.close() raise e + def remove_color_from_icon(self): + """ + Remove the color from the most recent uploaded icon. + """ + color_tab = WebDriverWait(self.driver, self.SHORT_WAIT_IN_SEC).until( + ec.element_to_be_clickable((By.CSS_SELECTOR, "div.overlayWindow i.icon-droplet")) + ) + color_tab.click() + + remove_color_btn = self.driver \ + .find_element_by_css_selector("div.overlayWindow i.icon-droplet-cross") + remove_color_btn.click() + def download_icomoon_fonts(self, zip_path: Path): """ Download the icomoon.zip from icomoon.io. diff --git a/.github/scripts/build_assets/util.py b/.github/scripts/build_assets/arg_getters.py similarity index 81% rename from .github/scripts/build_assets/util.py rename to .github/scripts/build_assets/arg_getters.py index 1c31e7771..fe2893a23 100644 --- a/.github/scripts/build_assets/util.py +++ b/.github/scripts/build_assets/arg_getters.py @@ -1,8 +1,8 @@ -from pathlib import Path from argparse import ArgumentParser from build_assets.PathResolverAction import PathResolverAction -def get_commandline_args(): + +def get_selenium_runner_args(peek_mode=False): parser = ArgumentParser(description="Upload svgs to Icomoon to create icon files.") parser.add_argument("--headless", @@ -26,8 +26,11 @@ def get_commandline_args(): action=PathResolverAction) parser.add_argument("download_path", - help="The path where you'd like to download the Icomoon files to", + help="The download destination of the Icomoon files", action=PathResolverAction) + if peek_mode: + parser.add_argument("--pr_title", + help="The title of the PR that we are peeking at") return parser.parse_args() \ No newline at end of file diff --git a/.github/scripts/build_assets/filehandler.py b/.github/scripts/build_assets/filehandler.py index 6fc69920b..65a1234cd 100644 --- a/.github/scripts/build_assets/filehandler.py +++ b/.github/scripts/build_assets/filehandler.py @@ -145,3 +145,24 @@ def rename_extracted_files(extract_path: str): os.replace(dict_["old"], dict_["new"]) print("Files renamed") + + +def create_screenshot_folder(dir, screenshot_name: str="screenshots/"): + """ + Create a screenshots folder in the dir. + :param dir, the dir where we want to create the folder. + :param screenshot_name, the name of the screenshot folder. + :raise Exception if the dir provided is not a directory. + :return the string name of the screenshot folder. + """ + folder = Path(dir).resolve() + if not folder.is_dir(): + raise Exception(f"This is not a dir: {str(folder)}. \ndir must be a valid directory") + + screenshot_folder = Path(folder, screenshot_name) + try: + os.mkdir(screenshot_folder) + except FileExistsError: + print(f"{screenshot_folder} already exist. Script will do nothing.") + finally: + return str(screenshot_folder) diff --git a/.github/scripts/generate_screenshot_markdown.py b/.github/scripts/generate_screenshot_markdown.py new file mode 100644 index 000000000..e5a516132 --- /dev/null +++ b/.github/scripts/generate_screenshot_markdown.py @@ -0,0 +1,10 @@ +import json +import os + + +if __name__ == "__main__": + img_urls_list = json.loads(os.environ["IMG_URLS"]) + template = "![Detailed Screenshot]({})" + markdown = [template.format(img_url) for img_url in img_urls_list] + print("\n\n".join(markdown)) + diff --git a/.github/scripts/icomoon_build.py b/.github/scripts/icomoon_build.py index 6b3eb352b..da8f02363 100644 --- a/.github/scripts/icomoon_build.py +++ b/.github/scripts/icomoon_build.py @@ -4,11 +4,11 @@ # pycharm complains that build_assets is an unresolved ref # don't worry about it, the script still runs from build_assets.SeleniumRunner import SeleniumRunner -from build_assets import filehandler, util +from build_assets import filehandler, arg_getters def main(): - args = util.get_commandline_args() + args = arg_getters.get_selenium_runner_args() new_icons = filehandler.find_new_icons(args.devicon_json_path, args.icomoon_json_path) if len(new_icons) == 0: print("No files need to be uploaded. Ending script...") diff --git a/.github/scripts/icomoon_peek.py b/.github/scripts/icomoon_peek.py index 6fd55f59c..2533fde71 100644 --- a/.github/scripts/icomoon_peek.py +++ b/.github/scripts/icomoon_peek.py @@ -1,33 +1,63 @@ +from typing import List +import re from selenium.common.exceptions import TimeoutException # pycharm complains that build_assets is an unresolved ref # don't worry about it, the script still runs from build_assets.SeleniumRunner import SeleniumRunner -from build_assets import filehandler, util +from build_assets import filehandler, arg_getters def main(): - args = util.get_commandline_args() + args = arg_getters.get_selenium_runner_args(True) new_icons = filehandler.find_new_icons(args.devicon_json_path, args.icomoon_json_path) + + # get only the icon object that has the name matching the pr title + filtered_icons = find_object_added_in_this_pr(new_icons, args.pr_title) + + # print list of new icons + print("List of new icons:", *new_icons, sep = "\n") + print("Icons being uploaded:", *filtered_icons, sep = "\n") + if len(new_icons) == 0: print("No files need to be uploaded. Ending script...") return - # print list of new icons - print("List of new icons:", *new_icons, sep = "\n") + screenshot_folder = filehandler.create_screenshot_folder("./") + if len(filtered_icons) == 0: + print("No icons found matching the icon name in the PR's title. Fallback to uploading all new icons found.") + screenshot_folder = "" runner = None try: runner = SeleniumRunner(args.download_path, args.geckodriver_path, args.headless) - svgs = filehandler.get_svgs_paths(new_icons, args.icons_folder_path) - runner.upload_svgs(svgs) + svgs = filehandler.get_svgs_paths(filtered_icons, args.icons_folder_path) + runner.upload_svgs(svgs, screenshot_folder) print("Task completed.") except TimeoutException as e: + print("Selenium Time Out Error: ", e.stacktrace, sep='\n') + except Exception as e: print(e) - print(e.stacktrace) finally: runner.close() +def find_object_added_in_this_pr(icons: List[dict], pr_title: str): + """ + Find the icon name from the PR title. + :param icons, a list of the font objects found in the devicon.json. + :pr_title, the title of the PR that this workflow was called on. + :return a list containing the dictionary with the "name" + entry's value matching the name in the pr_title. + If none can be found, return an empty list. + """ + try: + pattern = re.compile(r"(?<=^new icon: )\w+ (?=\(.+\))", re.I) + icon_name = pattern.findall(pr_title)[0].lower().strip() # should only have one match + return [icon for icon in icons if icon["name"] == icon_name] + except IndexError: # there are no match in the findall() + return [] + + if __name__ == "__main__": main() diff --git a/.github/workflows/build_icons.yml b/.github/workflows/build_icons.yml index 33a764e67..b3e821e16 100644 --- a/.github/workflows/build_icons.yml +++ b/.github/workflows/build_icons.yml @@ -19,29 +19,42 @@ jobs: pip install -r ./.github/scripts/requirements.txt npm install - name: Executing build and create fonts via icomoon - run: npm run build + run: > + python ./.github/scripts/icomoon_build.py + ./.github/scripts/build_assets/geckodriver-v0.27.0-win64/geckodriver.exe ./icomoon.json + ./devicon.json ./icons ./ --headless - name: Upload geckodriver.log for debugging purposes uses: actions/upload-artifact@v2 - if: ${{failure()}} + if: failure() with: name: geckodriver-log path: ./geckodriver.log + - name: Build devicon.min.css + if: success() + run: npm run build-css - name: Upload screenshot of the newly made icons - uses: actions/upload-artifact@v2 - if: ${{success()}} + id: imgur_step + uses: devicons/public-upload-to-imgur@v2 + if: success() with: - name: new_icons path: ./new_icons.png - - name: Build devicon.min.css - if: ${{ success() }} - run: npm run build-css + client_id: ${{secrets.IMGUR_CLIENT_ID}} - name: Create Pull Request - if: ${{ success() }} + if: success() uses: peter-evans/create-pull-request@v3 + env: + MESSAGE: | + Automated font-building task ran by GitHub Actions bot. This PR built new font files and devicon.css file. + + Here are all the files that were built: + + ![Files Built]({0}) + + More information can be found in the GitHub Action logs for this workflow. with: branch: 'master-build-result' base: 'master' commit-message: 'Built new icons, icomoon.json and devicon.css' title: 'bot:build new icons, icomoon.json and devicon.css' - body: 'Automated font-building task ran by GitHub Actions bot' + body: ${{ format(env.MESSAGE, steps.imgur_step.outputs.imgur_url ) }} delete-branch: true diff --git a/.github/workflows/peek_icons.yml b/.github/workflows/peek_icons.yml index 5bc2953a4..15b93fb91 100644 --- a/.github/workflows/peek_icons.yml +++ b/.github/workflows/peek_icons.yml @@ -16,35 +16,69 @@ jobs: uses: actions/setup-python@v2 with: python-version: 3.8 - - name: Install dependencies (python, pip) + - name: Install dependencies run: | python -m pip install --upgrade pip pip install -r ./.github/scripts/requirements.txt - name: Run icomoon_peek.py - run: npm run peek + env: + PR_TITLE: ${{ github.event.pull_request.title }} + shell: cmd + run: > + python ./.github/scripts/icomoon_peek.py + ./.github/scripts/build_assets/geckodriver-v0.27.0-win64/geckodriver.exe ./icomoon.json + ./devicon.json ./icons ./ --headless --pr_title "%PR_TITLE%" - name: Upload geckodriver.log for debugging purposes uses: actions/upload-artifact@v2 - if: ${{failure()}} + if: failure() with: name: geckodriver-log path: ./geckodriver.log - name: Upload screenshot of the newly made icons - uses: actions/upload-artifact@v2 - if: ${{success()}} + id: icons_overview_img_step + uses: devicons/public-upload-to-imgur@v2 + if: success() + with: + path: ./screenshots/new_icons.png + client_id: ${{secrets.IMGUR_CLIENT_ID}} + - name: Upload zoomed in screenshot of the newly made icons + id: icons_detailed_img_step + uses: devicons/public-upload-to-imgur@v2 + if: success() with: - name: new_icons - path: ./new_icons.png - # - name: Comment on the PR about the result - # uses: github-actions-up-and-running/pr-comment@v1.0.1 - # with: - # repo-token: ${{ secrets.GITHUB_TOKEN }} - # message: > - # Hi! I'm Devicons' GitHub Actions Bot! - - # I just peeked at the icons that you wanted to add and upload them to the - # [Actions page](https://github.com/devicons/devicon/actions). The maintainers - # will now take a look at it and decide whether to merge your PR. + path: ./screenshots/screenshot_*.png + client_id: ${{secrets.IMGUR_CLIENT_ID}} + - name: Generate the markdowns for the screenshot and put it in the DETAILED_IMGS_MARKDOWN env var + if: success() + env: + IMG_URLS: ${{ steps.icons_detailed_img_step.outputs.imgur_urls }} + run: | + echo 'DETAILED_IMGS_MARKDOWN<> $GITHUB_ENV + python ./.github/scripts/generate_screenshot_markdown.py >> $GITHUB_ENV + echo 'EOF' >> $GITHUB_ENV + shell: bash + - name: Comment on the PR about the result + uses: github-actions-up-and-running/pr-comment@v1.0.1 + env: + OVERVIEW_IMG_URL: ${{ fromJSON(steps.icons_overview_img_step.outputs.imgur_urls)[0] }} + MESSAGE: | + Hi! + + I'm Devicons' Peek Bot and I just peeked at the icons that you wanted to add using [icomoon.io](https://icomoon.io/app/#/select). + Here is the result below: + + ![Peeked Icons (top left)]({0}) - # Cheers :), + Here are the zoomed-in screenshots of the added icons: + {1} + + Note: If the images doesn't show up, it's probably because it has been autodeleted by Imgur after 6 months due to our API choice. + + The maintainers will now take a look at it and decide whether to merge your PR. + + Cheers :), - # Bot + Peek Bot + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + message: ${{format(env.MESSAGE, env.OVERVIEW_IMG_URL, env.DETAILED_IMGS_MARKDOWN)}} diff --git a/package-lock.json b/package-lock.json index 75efa408f..f995f26eb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1963,9 +1963,9 @@ "dev": true }, "ini": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", - "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==", + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true }, "interpret": { diff --git a/package.json b/package.json index 602e5c48e..651a8a7a8 100644 --- a/package.json +++ b/package.json @@ -5,8 +5,9 @@ "main": "devicon.min.css", "scripts": { "build-css": "gulp updateCss && gulp clean", - "build": "python ./.github/scripts/icomoon_build.py ./.github/scripts/build_assets/geckodriver-v0.27.0-win64/geckodriver.exe ./icomoon.json ./devicon.json ./icons ./ --headless", - "peek": "python ./.github/scripts/icomoon_peek.py ./.github/scripts/build_assets/geckodriver-v0.27.0-win64/geckodriver.exe ./icomoon.json ./devicon.json ./icons ./ --headless" + "peek-test": "python ./.github/scripts/icomoon_peek.py ./.github/scripts/build_assets/geckodriver-v0.27.0-win64/geckodriver.exe ./icomoon.json ./devicon.json ./icons ./ --pr_title \"%PR_TITLE%\"", + "build-test": "python ./.github/scripts/icomoon_build.py ./.github/scripts/build_assets/geckodriver-v0.27.0-win64/geckodriver.exe ./icomoon.json ./devicon.json ./icons ./" + }, "repository": { "type": "git",