From 256c1400c45f2f0476cf2ca9ee0c3f9b55526c8c Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:01:45 -0500 Subject: [PATCH 1/5] Bump JamesIves/github-pages-deploy-action from 4.6.9 to 4.7.1 (#133) Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.6.9 to 4.7.1. - [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases) - [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.6.9...v4.7.1) --- updated-dependencies: - dependency-name: JamesIves/github-pages-deploy-action dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs_deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs_deploy.yml b/.github/workflows/docs_deploy.yml index b59964a..5a30f05 100644 --- a/.github/workflows/docs_deploy.yml +++ b/.github/workflows/docs_deploy.yml @@ -55,7 +55,7 @@ jobs: # attempt_delay: 30000 # - name: Deploy to Github pages - uses: JamesIves/github-pages-deploy-action@v4.6.9 + uses: JamesIves/github-pages-deploy-action@v4.7.1 with: branch: github_pages folder: docs/build/html From 9fac54d9f9419b9502e4eafa6035c36ce14fe00b Mon Sep 17 00:00:00 2001 From: "pre-commit-ci[bot]" <66853113+pre-commit-ci[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:24:51 -0500 Subject: [PATCH 2/5] [pre-commit.ci] pre-commit autoupdate (#134) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit updates: - [github.com/astral-sh/ruff-pre-commit: v0.8.0 → v0.8.1](https://github.com/astral-sh/ruff-pre-commit/compare/v0.8.0...v0.8.1) Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- .pre-commit-config.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index eea4024..5e46fe6 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -16,7 +16,7 @@ repos: - id: check-toml - repo: https://github.com/astral-sh/ruff-pre-commit - rev: 'v0.8.0' + rev: 'v0.8.1' hooks: - id: ruff args: [--fix, --exit-non-zero-on-fix] From f0319f58338d2772d6e61b4d7ff30fa960857df8 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:25:11 -0500 Subject: [PATCH 3/5] Bump python-multipart from 0.0.9 to 0.0.18 (#135) Bumps [python-multipart](https://github.com/Kludex/python-multipart) from 0.0.9 to 0.0.18. - [Release notes](https://github.com/Kludex/python-multipart/releases) - [Changelog](https://github.com/Kludex/python-multipart/blob/master/CHANGELOG.md) - [Commits](https://github.com/Kludex/python-multipart/compare/0.0.9...0.0.18) --- updated-dependencies: - dependency-name: python-multipart dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- poetry.lock | 11 ++++------- pyproject.toml | 2 +- 2 files changed, 5 insertions(+), 8 deletions(-) diff --git a/poetry.lock b/poetry.lock index 1886f12..cadeb0c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -5369,18 +5369,15 @@ files = [ [[package]] name = "python-multipart" -version = "0.0.9" +version = "0.0.18" description = "A streaming multipart parser for Python" optional = false python-versions = ">=3.8" files = [ - {file = "python_multipart-0.0.9-py3-none-any.whl", hash = "sha256:97ca7b8ea7b05f977dc3849c3ba99d51689822fab725c3703af7c866a0c2b215"}, - {file = "python_multipart-0.0.9.tar.gz", hash = "sha256:03f54688c663f1b7977105f021043b0793151e4cb1c1a9d4a11fc13d622c4026"}, + {file = "python_multipart-0.0.18-py3-none-any.whl", hash = "sha256:efe91480f485f6a361427a541db4796f9e1591afc0fb8e7a4ba06bfbc6708996"}, + {file = "python_multipart-0.0.18.tar.gz", hash = "sha256:7a68db60c8bfb82e460637fa4750727b45af1d5e2ed215593f917f64694d34fe"}, ] -[package.extras] -dev = ["atomicwrites (==1.4.1)", "attrs (==23.2.0)", "coverage (==7.4.1)", "hatch", "invoke (==2.2.0)", "more-itertools (==10.2.0)", "pbr (==6.0.0)", "pluggy (==1.4.0)", "py (==1.11.0)", "pytest (==8.0.0)", "pytest-cov (==4.1.0)", "pytest-timeout (==2.2.0)", "pyyaml (==6.0.1)", "ruff (==0.2.1)"] - [[package]] name = "pytz" version = "2024.2" @@ -7791,4 +7788,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.0" python-versions = ">=3.10.0,<3.11" -content-hash = "aef6df7694d9da4efc41e5ce109ac67766a0dee7ebb3665ddedf5c66841983e5" +content-hash = "08cf217d59efdfc3bc04f1dcf51e9083328dd5852bd0b6556978d5a9cd33ae35" diff --git a/pyproject.toml b/pyproject.toml index 272a726..5cace6d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -20,7 +20,7 @@ uvicorn = {version = "^0.23.2", extras = ["standard"]} wandb = "^0.18.0" torchvision = "^0.18.0" redis = "^5.0.1" -python-multipart = "^0.0.9" +python-multipart = "^0.0.18" pydantic = "^1.10.15" motor = "^3.4.0" tqdm = "^4.66.3" From 7113923a93177885b0124515af7d5abfca7be6c6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 9 Dec 2024 10:53:27 -0500 Subject: [PATCH 4/5] Bump JamesIves/github-pages-deploy-action from 4.7.1 to 4.7.2 (#136) Bumps [JamesIves/github-pages-deploy-action](https://github.com/jamesives/github-pages-deploy-action) from 4.7.1 to 4.7.2. - [Release notes](https://github.com/jamesives/github-pages-deploy-action/releases) - [Commits](https://github.com/jamesives/github-pages-deploy-action/compare/v4.7.1...v4.7.2) --- updated-dependencies: - dependency-name: JamesIves/github-pages-deploy-action dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/docs_deploy.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/docs_deploy.yml b/.github/workflows/docs_deploy.yml index 5a30f05..e4fbfba 100644 --- a/.github/workflows/docs_deploy.yml +++ b/.github/workflows/docs_deploy.yml @@ -55,7 +55,7 @@ jobs: # attempt_delay: 30000 # - name: Deploy to Github pages - uses: JamesIves/github-pages-deploy-action@v4.7.1 + uses: JamesIves/github-pages-deploy-action@v4.7.2 with: branch: github_pages folder: docs/build/html From a6d0cf3a23f0651762823a2245c3af6d39a59f1a Mon Sep 17 00:00:00 2001 From: Marcelo Lotif Date: Mon, 9 Dec 2024 15:05:03 -0500 Subject: [PATCH 5/5] Download metrics as JSON (#132) * Implementing download metrics as json button * [pre-commit.ci] Add auto fixes from pre-commit.com hooks for more information, see https://pre-commit.ci * Code cleanup --------- Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com> --- florist/app/assets/css/florist.css | 5 ++ florist/app/jobs/details/page.tsx | 26 ++++++- .../tests/unit/app/jobs/details/page.test.tsx | 72 +++++++++++++++++++ 3 files changed, 101 insertions(+), 2 deletions(-) diff --git a/florist/app/assets/css/florist.css b/florist/app/assets/css/florist.css index 5a5e58d..ba23331 100644 --- a/florist/app/assets/css/florist.css +++ b/florist/app/assets/css/florist.css @@ -81,6 +81,11 @@ margin: 0; } +.job-details-download-button a.btn { + padding: 0; + margin: 15px 0 0 0; +} + .job-round-details { padding-left: 40px; } diff --git a/florist/app/jobs/details/page.tsx b/florist/app/jobs/details/page.tsx index a8eca5f..63d36f8 100644 --- a/florist/app/jobs/details/page.tsx +++ b/florist/app/jobs/details/page.tsx @@ -177,10 +177,12 @@ export function JobProgressBar({ metrics, totalEpochs, status, + clientIndex, }: { metrics: string; totalEpochs: number; status: status; + clientIndex: number; }): ReactElement { const [collapsed, setCollapsed] = useState(true); @@ -273,14 +275,16 @@ export function JobProgressBar({ -
{!collapsed ? : null}
+
+ {!collapsed ? : null} +
); } -export function JobProgressDetails({ metrics }: { metrics: Object }): ReactElement { +export function JobProgressDetails({ metrics, clientIndex }: { metrics: Object; clientIndex: number }): ReactElement { if (!metrics) { return null; } @@ -311,6 +315,9 @@ export function JobProgressDetails({ metrics }: { metrics: Object }): ReactEleme } } + let metricsFileName = metrics.host_type === "server" ? "server-metrics.json" : `client-metrics-${clientIndex}.json`; + let metricsFileURL = window.URL.createObjectURL(new Blob([JSON.stringify(metrics, null, 4)])); + return (
@@ -339,6 +346,20 @@ export function JobProgressDetails({ metrics }: { metrics: Object }): ReactEleme {roundMetricsArray.map((roundMetrics, i) => ( ))} + +
); } @@ -623,6 +644,7 @@ export function JobDetailsClientsInfoTable({
diff --git a/florist/tests/unit/app/jobs/details/page.test.tsx b/florist/tests/unit/app/jobs/details/page.test.tsx index 5bb9a39..2d920fb 100644 --- a/florist/tests/unit/app/jobs/details/page.test.tsx +++ b/florist/tests/unit/app/jobs/details/page.test.tsx @@ -27,6 +27,12 @@ function setupGetJobMock(data: JobData, isLoading: boolean = false, error = null }); } +function setupURLSpyMock(urlSpy, testURL: string = "foo") { + urlSpy = jest.spyOn(window, "URL"); + urlSpy.createObjectURL = jest.fn((_) => testURL); + return urlSpy; +} + function makeTestJob(): JobData { return { _id: testJobId, @@ -292,6 +298,14 @@ describe("Job Details Page", () => { expect(progressBar).toHaveClass("bg-danger"); }); describe("Details", () => { + let urlSpy; + afterEach(() => { + if (urlSpy) { + // making sure the mock is clear even on error, + // otherwise some weird errors start popping up + urlSpy.mockRestore(); + } + }); it("Should be collapsed by default", () => { setupGetJobMock(makeTestJob()); const { container } = render(); @@ -300,6 +314,7 @@ describe("Job Details Page", () => { }); it("Should open when the toggle button is clicked", () => { setupGetJobMock(makeTestJob()); + setupURLSpyMock(urlSpy); const { container } = render(); const toggleButton = container.querySelector(".job-details-toggle a"); expect(toggleButton).toHaveTextContent("Expand"); @@ -315,6 +330,7 @@ describe("Job Details Page", () => { const testJob = makeTestJob(); const serverMetrics = JSON.parse(testJob.server_metrics); setupGetJobMock(testJob); + setupURLSpyMock(urlSpy); const { container } = render(); const toggleButton = container.querySelector(".job-details-toggle a"); act(() => toggleButton.click()); @@ -365,6 +381,7 @@ describe("Job Details Page", () => { const testJob = makeTestJob(); const serverMetrics = JSON.parse(testJob.server_metrics); setupGetJobMock(testJob); + setupURLSpyMock(urlSpy); const { container } = render(); const progressToggleButton = container.querySelector(".job-details-toggle a"); act(() => progressToggleButton.click()); @@ -378,6 +395,7 @@ describe("Job Details Page", () => { const testJob = makeTestJob(); const serverMetrics = JSON.parse(testJob.server_metrics); setupGetJobMock(testJob); + setupURLSpyMock(urlSpy); const { container } = render(); const progressToggleButton = container.querySelector(".job-details-toggle a"); act(() => progressToggleButton.click()); @@ -398,6 +416,7 @@ describe("Job Details Page", () => { const testJob = makeTestJob(); const serverMetrics = JSON.parse(testJob.server_metrics); setupGetJobMock(testJob); + setupURLSpyMock(urlSpy); const { container } = render(); const progressToggleButton = container.querySelector(".job-details-toggle a"); act(() => progressToggleButton.click()); @@ -461,6 +480,58 @@ describe("Job Details Page", () => { ); }); }); + describe("Download metrics", () => { + it("Should render the download server metrics button correctly", async () => { + const testJob = makeTestJob(); + setupGetJobMock(testJob); + const { container } = render(); + + const testURL = "test url"; + urlSpy = setupURLSpyMock(urlSpy, testURL); + + const progressToggleButton = container.querySelector(".job-details-toggle a"); + act(() => progressToggleButton.click()); + + const expectedServerMetrics = JSON.stringify(JSON.parse(testJob.server_metrics), null, 4); + expect(urlSpy.createObjectURL).toHaveBeenCalledWith(new Blob([expectedServerMetrics])); + + const jobProgressDetailsComponent = container.querySelector(".job-progress-detail"); + const downloadMetricsButton = jobProgressDetailsComponent.querySelector(".download-metrics-button"); + expect(downloadMetricsButton.getAttribute("href")).toBe(testURL); + expect(downloadMetricsButton.getAttribute("download")).toBe("server-metrics.json"); + }); + it("Should render the download client metrics button correctly", () => { + const testJob = makeTestJob(); + setupGetJobMock(testJob); + const { container } = render(); + + const testURL = "test url"; + urlSpy = setupURLSpyMock(urlSpy, testURL); + + const testClientIndex = 1; + let toggleButton = container.querySelectorAll(".job-client-progress .job-details-toggle a")[ + testClientIndex + ]; + act(() => toggleButton.click()); + + const expectedClientMetrics = JSON.stringify( + JSON.parse(testJob.clients_info[testClientIndex].metrics), + null, + 4, + ); + expect(urlSpy.createObjectURL).toHaveBeenCalledWith(new Blob([expectedClientMetrics])); + + const clientProgressDetailsComponent = container.querySelector( + `#job-details-client-config-progress-${testClientIndex} .job-progress-detail`, + ); + const downloadMetricsButton = + clientProgressDetailsComponent.querySelector(".download-metrics-button"); + expect(downloadMetricsButton.getAttribute("href")).toBe(testURL); + expect(downloadMetricsButton.getAttribute("download")).toBe( + `client-metrics-${testClientIndex}.json`, + ); + }); + }); describe("Clients", () => { it("Renders their progress bars correctly", () => { const testJob = makeTestJob(); @@ -479,6 +550,7 @@ describe("Job Details Page", () => { it("Renders the progress details correctly", () => { const testJob = makeTestJob(); setupGetJobMock(testJob); + setupURLSpyMock(urlSpy); const { container } = render(); let toggleButton = container.querySelectorAll(".job-client-progress .job-details-toggle a")[0];