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

Apply v3.17.5 changes to v3.19.3 Branch #114

Open
wants to merge 79 commits into
base: release/v3.19.3
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
b82ccdb
Customize Aim UI (#31)
jgiannuzzi Dec 21, 2023
f361332
Add support for system metrics (#33)
jgiannuzzi Jan 2, 2024
f6ce90b
Update caniuse-lite from 1.0.30001434 to 1.0.30001572 (#34)
jgiannuzzi Jan 2, 2024
9f5460e
Namespace Selector in AIM UI (#35)
fabiovincenzi Jan 18, 2024
94bb832
Export all matched runs in Aim UI (#37)
fabiovincenzi Jan 30, 2024
c043868
Preliminary ParamsScalePopover component (#36)
jescalada Feb 1, 2024
2ce717b
Fetch runs CSV from server on export runs (#40)
fabiovincenzi Feb 2, 2024
fced1a0
Disable Scaling for Point Params (#39)
jescalada Feb 3, 2024
ef4110c
Remove white shadow in metrics charts (#42)
fabiovincenzi Feb 16, 2024
efedea5
Metrics/Params selection dropdown (#43)
fabiovincenzi Feb 28, 2024
ec41c15
Fix Metric/Params selection dropdown search text disappears with left…
fabiovincenzi Mar 4, 2024
dd98f68
Experiment Selection Header (#41)
jescalada Mar 14, 2024
10e3820
Experiment Selection Header Searchbox + Quick Action Buttons (#48)
jescalada Mar 15, 2024
1559e45
Live update disable (#50)
suprjinx Mar 20, 2024
1ca286c
Experiment Regex Fixes (#49)
jescalada Mar 20, 2024
c1cc49e
Send List of Metric-Context to SearchMetrics endpoint (#46)
fabiovincenzi Mar 21, 2024
70549c0
Remove advanced search mode from Metric search (#51)
fabiovincenzi Mar 22, 2024
d00c169
change endpoint from admin/namespaces to chooser/namespaces (#53)
fabiovincenzi Mar 28, 2024
2f907fd
Fix various experiment header issues (#52)
jescalada Mar 29, 2024
b03f849
Fix initial experiment state namespace bug (#57)
jescalada Apr 1, 2024
2a3f82b
Link Run from Aim UI to MLFlow UI (#55)
fabiovincenzi Apr 1, 2024
4b4f209
refresh Metrics drop down with periodic refresh of project-params (#56)
suprjinx Apr 3, 2024
4c2e668
Change Search Metric to be a POST (#58)
fabiovincenzi Apr 4, 2024
7ba8118
Fix experiment header UI refresh issue (#61)
jescalada Apr 5, 2024
051d41e
Project Params Hotfix (#62)
jescalada Apr 8, 2024
d890342
Do not call projects/params endpoint if no experiment is selected (#63)
fabiovincenzi Apr 9, 2024
74eae27
feat removed tags page from ui-aim (#59)
Shugaba-Wuta Apr 10, 2024
772586e
fetch params on initialize and when experiment selection changes (#66)
fabiovincenzi Apr 12, 2024
0f81e87
Fix projects/params poll (#67)
fabiovincenzi Apr 15, 2024
994564f
Implement virtualization for Metric Autocomplete (#64)
fabiovincenzi Apr 15, 2024
2c3fd2d
Use the displayed experiments for requests in every tab (#68)
fabiovincenzi Apr 17, 2024
76021af
Fetch projects/params when select/deselect all in checked on the expe…
fabiovincenzi Apr 17, 2024
acf4aea
Run Selector UX Improvements (#60)
jescalada Apr 17, 2024
cb5f01b
Add experiments header to runs (#70)
suprjinx Apr 26, 2024
ea3f380
Implement virtualization on scatters dropdown (#71)
fabiovincenzi Apr 29, 2024
689c558
Experiment Selection Store Experiment IDs (#65)
jescalada Apr 30, 2024
04d464c
Remove old metrics on experiment change (#74)
jescalada Apr 30, 2024
79a3882
Remove old/deleted experiments from experiment selector (#72)
jescalada May 1, 2024
7c461e7
Flip scatters and params search box order (#73)
jescalada May 1, 2024
28f3ef8
Fix search query enter bug (#75)
jescalada May 2, 2024
2f62297
Table Column Filtering and Performance Improvements (#44)
jescalada May 3, 2024
99898be
Change context from string to object (#76)
fabiovincenzi May 6, 2024
4d03004
Limit metrics (#77)
suprjinx May 10, 2024
6346f8f
Show only the visible columns in Manage Columns popover (#78)
fabiovincenzi May 13, 2024
4133f41
Autofocus the experiment search (#81)
suprjinx May 13, 2024
1711e53
(bugfix) Get v3.19.3 working with v3.17.5 server
vinayan3 Aug 16, 2024
d4d6a49
pass selected experiments to getRunsData for params and scatters (#80)
fabiovincenzi May 14, 2024
63d4cef
Metrics counter (#79)
suprjinx May 14, 2024
3bbbdd4
Metric tooltip dynamic size (#83)
fabiovincenzi May 15, 2024
fbcd0ff
Make it possible to preselect run hash in Metrics Explorer (#85)
dsuhinin May 21, 2024
483407d
Add support for grouping charts by multiple conditions (#82)
jescalada May 22, 2024
3abb287
Metric Tooltip Rounding (#84)
jescalada May 22, 2024
cacae46
Fix dynamic grouping table cells
jescalada May 29, 2024
edf5cd8
Individual Plot Axis Properties (#88)
fabiovincenzi May 29, 2024
c93a23a
Disable search until (#89)
suprjinx May 29, 2024
e0c684c
Undefined AlignmentConfig (#91)
fabiovincenzi Aug 21, 2024
e850da2
Catch CreateResourceError and redirect to login (#90)
jescalada Jun 3, 2024
bf630e6
Add conditional grouping by stroke (#92)
jescalada Jun 5, 2024
9382ed7
Enable Run Logs tab
dsuhinin Jun 7, 2024
5d91272
Enable Run Logs tab
dsuhinin Jun 7, 2024
809d2ae
add options for metrics to autocomplete (#94)
fabiovincenzi Jun 8, 2024
8f0cefe
retrieve right description from tags (#97)
fabiovincenzi Jun 10, 2024
b027eb5
Add conditional grouping by color (#93)
jescalada Jun 12, 2024
3e95d30
Add Metrics filtering for Conditional Grouping (#86)
jescalada Jun 17, 2024
9e8feec
Add E2E testing framework (#98)
jescalada Jun 19, 2024
98d06b6
Add backend integration for E2E CI pipeline (#99)
jescalada Jun 19, 2024
821c51a
Dashboard E2E tests (#101)
jescalada Jun 25, 2024
09ae265
Fix Playwright versioning issue (#103)
jescalada Jun 26, 2024
0861213
Enable tags (#102)
suprjinx Jun 26, 2024
43de881
Add specific metric as run attribute (#107)
jescalada Jul 8, 2024
32cea02
Add range selection for runs (shift+click) (#104)
jescalada Jul 17, 2024
847e4ac
Toggle all params and all metrics on ManageColumnsPopover (#105)
jescalada Jul 17, 2024
e9b8d39
Runs E2E test suite (#106)
jescalada Jul 17, 2024
c259d53
(feat) Add Support for Power Usage Percentage and Watts (#109)
vinayan3 Jul 30, 2024
f023f54
Images Explorer search and extras (#108)
jescalada Jul 31, 2024
1cce6fa
Fix alignmentConfigs issue (temp)
jescalada Aug 22, 2024
dbf0f61
Add "Load More" button for Metrics table in Runs Overview tab (#110)
jescalada Aug 1, 2024
9ef9cb7
Images UI Search fixes (#111)
jescalada Aug 6, 2024
f42eda5
[fasttrackml][aim] Rename Trace Last Value to Values and Turn Off Adv…
vinayan3 Aug 30, 2024
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
70 changes: 70 additions & 0 deletions .github/workflows/playwright.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Playwright Tests

on:
push:
branches: [ main, release/* ]
pull_request:
branches: [ main, release/* ]
env:
PLAYWRIGHT_SKIP_BROWSER_DOWNLOAD: 1
PLAYWRIGHT_BROWSERS_PATH: 0

jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 16

- name: Install frontend dependencies
run: |
npx playwright install-deps
npm ci --legacy-peer-deps
working-directory: src

- name: Install wait-on
run: npm install -g wait-on

- name: Install Playwright browsers
run: npx playwright install
working-directory: src

- name: Build and run backend
run: |
docker run --rm -p 5000:5000 -e FML_DEV_MODE=true gresearch/fasttrackml:main &
npx wait-on http://localhost:5000

- name: Install k6
run: |
sudo apt-get update
sudo apt-get install -y gnupg software-properties-common ca-certificates curl
curl -s https://dl.k6.io/key.gpg | sudo apt-key add -
echo "deb https://dl.k6.io/deb stable main" | sudo tee /etc/apt/sources.list.d/k6.list
sudo apt-get update
sudo apt-get install -y k6

- name: Seed database with k6
run: |
wget https://raw.githubusercontent.com/G-Research/fasttrackml/main/docs/example/k6_load.js
k6 run k6_load.js

- name: Start frontend dev server and run E2E tests
run: |
npm start &
npx wait-on http://localhost:3000
npx playwright test src/e2e
working-directory: src

- name: Upload Playwright report
uses: actions/upload-artifact@v4
if: always()
with:
name: playwright-report
path: playwright-report/
retention-days: 30
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
embed/
.idea/
.vscode/
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ An embed directory will be created with the built UI, ready to be embedded in th

Once you're happy with them, create a pull request **against the `release/vX.Y.Z` branch*** that you started from (***not `main`!***). Once merged, the CI will run and build the UI. It will then push it to a new tag that is compatible with the Go module rules. For example, the first customization to `v3.16.2` of Aim will end up in a tag named `v0.31602.1`.

### How to run E2E tests?

The E2E tests can be run locally by following these steps:
1. Start the FastTrackML server
2. Run the Aim UI in development mode (on localhost:3000)
3. In another terminal, run `cd src/e2e`
4. Run `npx playwright test` to run the test suite

New tests can be added directly to the `src/e2e` directory. You may also run `npx playwright show-report` to see the test results.

For a guide on how to write a test, see [Playwright's example tests](https://github.com/microsoft/playwright/blob/main/examples/todomvc/tests/integration.spec.ts).

### How is this all enforced?

A GitHub app has been created with the `contents:write` permissions on this repo. Its App ID and private key are stored as secrets under the `restricted` environment. This environment is limited to the `main` and `release/v*` branches
Expand Down Expand Up @@ -244,4 +256,5 @@ for rule in $(gh api /repos/G-Research/fasttrackml-ui-aim/rulesets -q '.[].id')
do
gh api /repos/G-Research/fasttrackml-ui-aim/rulesets/$rule | jq '[{name: .name, target: .target, conditions: .conditions, rules: .rules, bypass_actors: .bypass_actors}]'
done | jq -s add
```
```

4 changes: 4 additions & 0 deletions src/.gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,7 @@ yarn-debug.log*
yarn-error.log*

Desktop
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
190 changes: 190 additions & 0 deletions src/e2e/Dashboard/DashboardContent.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
import { test, expect } from '@playwright/test';

const BASE_URL = 'http://localhost:3000';

test.describe('Dashboard', () => {
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL);
await page.waitForLoadState('networkidle');
});

// Test that the total number of runs is two. This assumes that the
// test database has been seeded with two runs by running `k6 run k6_load.js`
test('has two runs', async ({ page }) => {
const textContent = await page.textContent(
'p.ProjectStatistics__totalRuns',
);
expect(textContent).toBe('Total runs: 2');
});

test('has two experiments', async ({ page }) => {
const textContent = await page.textContent('h3.ExperimentsCard__title');
expect(textContent).toBe('Experiments (2)');
});

test('Default experiment exists and has no runs', async ({ page }) => {
const table = await page.$('.BaseTable__body');
const rows = await table?.$$('.BaseTable__row');
expect(rows).not.toBeNull();

const firstRow = rows?.[0];
const experimentName = await firstRow?.$eval(
'.ExperimentNameBox__experimentName a',
(el) => el.textContent,
);
expect(experimentName).toBe('Default');

const runCount = await firstRow?.$eval('p', (el) => el.textContent);
expect(runCount).toBe('0');
});

test('generated experiment exists and has two runs', async ({ page }) => {
const table = await page.$('.BaseTable__body');
const rows = await table?.$$('.BaseTable__row');
expect(rows).not.toBeNull();

const secondRow = rows?.[1];
const experimentName = await secondRow?.$eval(
'.ExperimentNameBox__experimentName a',
(el) => el.textContent,
);
expect(experimentName).toContain('experiment-');

const runCount = await secondRow?.$eval('p', (el) => el.textContent);
expect(runCount).toBe('2');
});

test('experiment search bar filters table experiments', async ({ page }) => {
const searchInput = await page.$('.SearchBar input#search');
await searchInput?.fill('experiment-');
const value = await searchInput?.inputValue();
expect(value).toBe('experiment-');

const table = await page.$('.BaseTable__body');
const rows = await table?.$$('.BaseTable__row');
expect(rows?.length).toBe(1);

const firstRow = rows?.[0];
const experimentName = await firstRow?.$eval(
'.ExperimentNameBox__experimentName a',
(el) => el.textContent,
);
expect(experimentName).toContain('experiment-');
});

test("contributions heatmap has runs on the generated experiment's day", async ({
page,
}) => {
// Get the current date in format MMM DD, YYYY based on the generated experiment's timestamp
const rows = await page.$$('.BaseTable__row');
const secondRow = rows?.[1];
const experimentName = await secondRow?.$eval(
'.ExperimentNameBox__experimentName a',
(el) => el.textContent,
);

const timestamp = experimentName?.split('-')[1] ?? '';
const currentDate = new Date(parseInt(timestamp)).toLocaleDateString(
'en-US',
{
month: 'short',
day: 'numeric',
year: 'numeric',
},
);

// Cells are colored on a scale from 0 to 4, and we only have one active day in the test data
const heatmap = await page.$('.CalendarHeatmap');
const currentDayCell = await heatmap?.$('.CalendarHeatmap__cell--scale-4');
expect(currentDayCell).not.toBeNull();

const tooltip = await currentDayCell?.getAttribute('title');
expect(tooltip).toContain(currentDate);
});

test("clicking on the heatmap cell queries that day's runs", async ({
page,
}) => {
// Get the current timestamp from the generated experiment to construct the expected query
const rows = await page.$$('.BaseTable__row');
const secondRow = rows?.[1];

const experimentName = await secondRow?.$eval(
'.ExperimentNameBox__experimentName a',
(el) => el.textContent,
);
const timestamp = experimentName?.split('-')[1].trim() ?? '';

const date = new Date(parseInt(timestamp));
const year = date.getUTCFullYear();
const month = date.getUTCMonth() + 1; // getUTCMonth() is zero-based
const day = date.getUTCDate();

const nextDayDate = new Date(date);
nextDayDate.setUTCDate(day + 1);

const nextYear = nextDayDate.getUTCFullYear();
const nextMonth = nextDayDate.getUTCMonth() + 1; // getUTCMonth() is zero-based
const nextDay = nextDayDate.getUTCDate();

const expectedText = `datetime(${year},${month},${day})<=run.created_at<datetime(${nextYear},${nextMonth},${nextDay})`;

// Locate and click the current day cell in the heatmap
const heatmap = await page.$('.CalendarHeatmap');
const currentDayCell = await heatmap?.$('.CalendarHeatmap__cell--scale-4');
await currentDayCell?.click();

await page.waitForLoadState('networkidle');

const textBox = page.locator('.view-lines.monaco-mouse-cursor-text');

const textContent = await textBox.evaluate((el) => el.innerText);

// Remove tabs and newlines that might be present in the text content
const trimmedTextContent = textContent.replace(/\s/g, '');

expect(trimmedTextContent).toContain(expectedText);
});

test("clicking on the Default experiment link redirects to the experiment's page", async ({
page,
}) => {
const table = await page.$('.BaseTable__body');
const rows = await table?.$$('.BaseTable__row');
expect(rows).not.toBeNull();

const firstRow = rows?.[0];
const experimentLink = await firstRow?.$('a');
await experimentLink?.click();

await page.waitForLoadState('networkidle');

const experimentNameElement = page.locator(
'.ExperimentHeader__headerContainer__appBarTitleBox__container',
);
const experimentName = await experimentNameElement.innerText();

expect(experimentName).toContain('Default');
});

test("clicking on the generated experiment link redirects to the experiment's page", async ({
page,
}) => {
const table = await page.$('.BaseTable__body');
const rows = await table?.$$('.BaseTable__row');
expect(rows).not.toBeNull();

const secondRow = rows?.[1];
const experimentLink = await secondRow?.$('a');
await experimentLink?.click();

await page.waitForLoadState('networkidle');

const experimentNameElement = page.locator(
'.ExperimentHeader__headerContainer__appBarTitleBox__container',
);
const experimentName = await experimentNameElement.innerText();

expect(experimentName).toContain('experiment-');
});
});
52 changes: 52 additions & 0 deletions src/e2e/Dashboard/DashboardLogic.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
import { test, expect } from '@playwright/test';

const BASE_URL = 'http://localhost:3000';

test.describe('Dashboard', () => {
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL);
});

test('has title', async ({ page }) => {
await expect(page).toHaveTitle('FastTrackML (modern)');
});

test('active runs link redirects correctly', async ({ page }) => {
await page.getByText('Active Runs').click({ force: true });
await page.waitForLoadState('networkidle');

const expectedText = 'run.active==True';
const textBox = page.locator('.view-lines.monaco-mouse-cursor-text');
const textContent = await textBox.evaluate((el) => el.innerText);
const trimmedTextContent = textContent.replace(/\s/g, '');

expect(trimmedTextContent).toContain(expectedText);
});

test('archived runs link redirects correctly', async ({ page }) => {
await page.getByText('Archived Runs').click({ force: true });
await page.waitForLoadState('networkidle');

const expectedText = 'run.archived==True';
const textBox = page.locator('.view-lines.monaco-mouse-cursor-text');
const textContent = await textBox.evaluate((el) => el.innerText);
const trimmedTextContent = textContent.replace(/\s/g, '');

expect(trimmedTextContent).toContain(expectedText);
});

test("last week's runs link redirects correctly", async ({ page }) => {
await page.getByText("Last Week's Runs").click({ force: true });
await page.waitForLoadState('networkidle');

// The text varies depending on the current date:
// Example: datetime(2024, 6, 3) <= run.created_at < datetime(2024, 6, 10)
const queryRegex =
/datetime\(\d+,\d+,\d+\)<=run\.created_at<datetime\(\d+,\d+,\d+\)/;
const textBox = page.locator('.view-lines.monaco-mouse-cursor-text');
const textContent = await textBox.evaluate((el) => el.innerText);
const trimmedTextContent = textContent.replace(/\s/g, '');

expect(queryRegex.test(trimmedTextContent)).toBeTruthy();
});
});
Loading
Loading