From e52707d12c29f6155bf24faf306ef4e3534efe68 Mon Sep 17 00:00:00 2001 From: itaigilo Date: Mon, 30 Sep 2024 11:07:05 +0300 Subject: [PATCH] Pull Requests for Data (#8235) * Enable PRs tab * Initial playwright test * Fix test * Improve test * Cleanup --- webui/src/lib/components/repository/tabs.jsx | 15 +- .../repository/pulls/pullsList.jsx | 6 +- webui/test/e2e/common/quickstart.spec.ts | 215 +++++++++++------- webui/test/e2e/poms/pullsPage.ts | 53 +++++ webui/test/e2e/poms/repositoryPage.ts | 38 ++++ 5 files changed, 228 insertions(+), 99 deletions(-) create mode 100644 webui/test/e2e/poms/pullsPage.ts diff --git a/webui/src/lib/components/repository/tabs.jsx b/webui/src/lib/components/repository/tabs.jsx index f050023702f..9721c9b1ea0 100644 --- a/webui/src/lib/components/repository/tabs.jsx +++ b/webui/src/lib/components/repository/tabs.jsx @@ -8,9 +8,6 @@ import {Link, NavItem} from "../nav"; import {useRouter} from "../../hooks/router"; import {RefTypeBranch} from "../../../constants"; -// TODO (gilo): this is temp, until PRfD will be ready -const showPulls = false; - export const RepositoryNavTabs = ({ active }) => { const { reference } = useRefs(); const router = useRouter(); @@ -60,14 +57,10 @@ export const RepositoryNavTabs = ({ active }) => { Tags - { - // TODO (gilo): this is temp, until PRfD will be ready - showPulls && - - {/* TODO (gilo): the icon is very similar to the compare icon, consider changing it*/} - Pull Requests - - } + + {/* TODO (gilo): the icon is very similar to the compare icon, consider changing it*/} + Pull Requests + Compare diff --git a/webui/src/pages/repositories/repository/pulls/pullsList.jsx b/webui/src/pages/repositories/repository/pulls/pullsList.jsx index 6c732bdc1bd..9d4e701ce8e 100644 --- a/webui/src/pages/repositories/repository/pulls/pullsList.jsx +++ b/webui/src/pages/repositories/repository/pulls/pullsList.jsx @@ -48,14 +48,14 @@ const PullWidget = ({repo, pull}) => {
{" "} - {pull.title}
- {getDescription()} + {getDescription()}
{pull.destination_branch}
@@ -81,7 +81,7 @@ const PullsList = ({repo, after, prefix, onPaginate}) => { else content = (results && !!results.length ? <> - + {results.map(pull => ( ))} diff --git a/webui/test/e2e/common/quickstart.spec.ts b/webui/test/e2e/common/quickstart.spec.ts index ab60d011c3b..5ee1880643f 100644 --- a/webui/test/e2e/common/quickstart.spec.ts +++ b/webui/test/e2e/common/quickstart.spec.ts @@ -2,101 +2,146 @@ import { test, expect } from "@playwright/test"; import { RepositoriesPage } from "../poms/repositoriesPage"; import { RepositoryPage } from "../poms/repositoryPage"; import { ObjectViewerPage } from "../poms/objectViewerPage"; +import { PullsPage } from "../poms/pullsPage"; const QUICKSTART_REPO_NAME = "quickstart"; const PARQUET_OBJECT_NAME = "lakes.parquet"; const NEW_BRANCH_NAME = "denmark-lakes"; const SELECT_QUERY = - "SELECT country, COUNT(*) FROM READ_PARQUET('lakefs://quickstart/main/lakes.parquet') GROUP BY country ORDER BY COUNT(*) DESC LIMIT 5;"; + "SELECT country, COUNT(*) FROM READ_PARQUET('lakefs://quickstart/main/lakes.parquet') GROUP BY country ORDER BY COUNT(*) DESC LIMIT 5;"; const CREATE_TABLE_QUERY = - "CREATE OR REPLACE TABLE lakes AS SELECT * FROM READ_PARQUET('lakefs://quickstart/denmark-lakes/lakes.parquet');"; + "CREATE OR REPLACE TABLE lakes AS SELECT * FROM READ_PARQUET('lakefs://quickstart/denmark-lakes/lakes.parquet');"; const DELETE_QUERY = "DELETE FROM lakes WHERE Country != 'Denmark';"; const COPY_QUERY = - "COPY lakes TO 'lakefs://quickstart/denmark-lakes/lakes.parquet';"; + "COPY lakes TO 'lakefs://quickstart/denmark-lakes/lakes.parquet';"; const SELECT_NEW_BRANCH = - "DROP TABLE lakes; SELECT country, COUNT(*) FROM READ_PARQUET('lakefs://quickstart/denmark-lakes/lakes.parquet') GROUP BY country ORDER BY COUNT(*) DESC LIMIT 5;"; + "DROP TABLE lakes; SELECT country, COUNT(*) FROM READ_PARQUET('lakefs://quickstart/denmark-lakes/lakes.parquet') GROUP BY country ORDER BY COUNT(*) DESC LIMIT 5;"; test.describe("Quickstart", () => { - test.describe.configure({ mode: "serial" }); - test("create repo w/ sample data", async ({ page }) => { - const repositoriesPage = new RepositoriesPage(page); - await repositoriesPage.goto(); - await repositoriesPage.createRepository(QUICKSTART_REPO_NAME, true); - }); - - test("view and query parquet object", async ({ page }) => { - const repositoriesPage = new RepositoriesPage(page); - await repositoriesPage.goto(); - await repositoriesPage.goToRepository(QUICKSTART_REPO_NAME); - - const repositoryPage = new RepositoryPage(page); - await repositoryPage.clickObject(PARQUET_OBJECT_NAME); - await expect(page.getByText("Loading...")).not.toBeVisible(); - - const objectViewerPage = new ObjectViewerPage(page); - await objectViewerPage.enterQuery(SELECT_QUERY); - await objectViewerPage.clickExecuteButton(); - await expect(await objectViewerPage.getResultRowCount()).toEqual(5); - }); - - test("transforming data", async ({ page }) => { - const repositoriesPage = new RepositoriesPage(page); - await repositoriesPage.goto(); - await repositoriesPage.goToRepository(QUICKSTART_REPO_NAME); - - const repositoryPage = new RepositoryPage(page); - await repositoryPage.createBranch(NEW_BRANCH_NAME); - - await repositoryPage.gotoObjectsTab(); - await repositoryPage.clickObject(PARQUET_OBJECT_NAME); - await expect(page.getByText("Loading...")).not.toBeVisible(); - - const objectViewerPage = new ObjectViewerPage(page); - await objectViewerPage.enterQuery(CREATE_TABLE_QUERY); - await objectViewerPage.clickExecuteButton(); - - await objectViewerPage.enterQuery(DELETE_QUERY); - await objectViewerPage.clickExecuteButton(); - - await objectViewerPage.enterQuery(COPY_QUERY); - await objectViewerPage.clickExecuteButton(); - - await objectViewerPage.enterQuery(SELECT_NEW_BRANCH); - await objectViewerPage.clickExecuteButton(); - await expect(await objectViewerPage.getResultRowCount()).toEqual(1); - }); - - test("commit and merge", async ({ page }) => { - const repositoriesPage = new RepositoriesPage(page); - await repositoriesPage.goto(); - await repositoriesPage.goToRepository(QUICKSTART_REPO_NAME); - - const repositoryPage = new RepositoryPage(page); - await repositoryPage.gotoUncommittedChangeTab(); - await repositoryPage.switchBranch(NEW_BRANCH_NAME); - await expect( - await page.getByText("Showing changes for branch") - ).toBeVisible(); - await expect(await repositoryPage.getUncommittedCount()).toEqual(1); - - await repositoryPage.commitChanges("denmark"); - await expect(page.getByText("No changes")).toBeVisible(); - - await repositoryPage.gotoCompareTab(); - await repositoryPage.switchBaseBranch("main"); - await expect(await page.getByText("Showing changes between")).toBeVisible(); - await expect(await repositoryPage.getUncommittedCount()).toEqual(1); - await repositoryPage.merge("merge commit"); - await expect(page.getByText("No changes")).toBeVisible(); - - await repositoriesPage.goto(); - await repositoriesPage.goToRepository(QUICKSTART_REPO_NAME); - await repositoryPage.clickObject(PARQUET_OBJECT_NAME); - await expect(page.getByText("Loading...")).not.toBeVisible(); - const objectViewerPage = new ObjectViewerPage(page); - await objectViewerPage.enterQuery(SELECT_QUERY); - await objectViewerPage.clickExecuteButton(); - await expect(await objectViewerPage.getResultRowCount()).toEqual(1); - }); + test.describe.configure({mode: "serial"}); + test("create repo w/ sample data", async ({page}) => { + const repositoriesPage = new RepositoriesPage(page); + await repositoriesPage.goto(); + await repositoriesPage.createRepository(QUICKSTART_REPO_NAME, true); + }); + + test("view and query parquet object", async ({page}) => { + const repositoriesPage = new RepositoriesPage(page); + await repositoriesPage.goto(); + await repositoriesPage.goToRepository(QUICKSTART_REPO_NAME); + + const repositoryPage = new RepositoryPage(page); + await repositoryPage.clickObject(PARQUET_OBJECT_NAME); + await expect(page.getByText("Loading...")).not.toBeVisible(); + + const objectViewerPage = new ObjectViewerPage(page); + await objectViewerPage.enterQuery(SELECT_QUERY); + await objectViewerPage.clickExecuteButton(); + expect(await objectViewerPage.getResultRowCount()).toEqual(5); + }); + + test("transforming data", async ({page}) => { + const repositoriesPage = new RepositoriesPage(page); + await repositoriesPage.goto(); + await repositoriesPage.goToRepository(QUICKSTART_REPO_NAME); + + const repositoryPage = new RepositoryPage(page); + await repositoryPage.createBranch(NEW_BRANCH_NAME); + + await repositoryPage.gotoObjectsTab(); + await repositoryPage.clickObject(PARQUET_OBJECT_NAME); + await expect(page.getByText("Loading...")).not.toBeVisible(); + + const objectViewerPage = new ObjectViewerPage(page); + await objectViewerPage.enterQuery(CREATE_TABLE_QUERY); + await objectViewerPage.clickExecuteButton(); + + await objectViewerPage.enterQuery(DELETE_QUERY); + await objectViewerPage.clickExecuteButton(); + + await objectViewerPage.enterQuery(COPY_QUERY); + await objectViewerPage.clickExecuteButton(); + + await objectViewerPage.enterQuery(SELECT_NEW_BRANCH); + await objectViewerPage.clickExecuteButton(); + expect(await objectViewerPage.getResultRowCount()).toEqual(1); + }); + + test("commit and merge", async ({page}) => { + const repositoriesPage = new RepositoriesPage(page); + await repositoriesPage.goto(); + await repositoriesPage.goToRepository(QUICKSTART_REPO_NAME); + + const repositoryPage = new RepositoryPage(page); + await repositoryPage.gotoUncommittedChangeTab(); + await repositoryPage.switchBranch(NEW_BRANCH_NAME); + await expect(page.getByText("Showing changes for branch")).toBeVisible(); + expect(await repositoryPage.getUncommittedCount()).toEqual(1); + + await repositoryPage.commitChanges("denmark"); + await expect(page.getByText("No changes")).toBeVisible(); + + await repositoryPage.gotoCompareTab(); + await repositoryPage.switchBaseBranch("main"); + await expect(page.getByText("Showing changes between")).toBeVisible(); + expect(await repositoryPage.getUncommittedCount()).toEqual(1); + await repositoryPage.merge("merge commit"); + await expect(page.getByText("No changes")).toBeVisible(); + + await repositoriesPage.goto(); + await repositoriesPage.goToRepository(QUICKSTART_REPO_NAME); + await repositoryPage.clickObject(PARQUET_OBJECT_NAME); + await expect(page.getByText("Loading...")).not.toBeVisible(); + const objectViewerPage = new ObjectViewerPage(page); + await objectViewerPage.enterQuery(SELECT_QUERY); + await objectViewerPage.clickExecuteButton(); + expect(await objectViewerPage.getResultRowCount()).toEqual(1); + }); + + test("pull requests", async ({page}) => { + const repositoriesPage = new RepositoriesPage(page); + await repositoriesPage.goto(); + await repositoriesPage.goToRepository(QUICKSTART_REPO_NAME); + + const branchNameForPull = "branch-for-pull-1"; + + const repositoryPage = new RepositoryPage(page); + await repositoryPage.createBranch(branchNameForPull); + + // delete a file in the branch + await repositoryPage.gotoObjectsTab(); + await repositoryPage.switchBranch(branchNameForPull); + await repositoryPage.deleteFirstObjectInDirectory("images/"); + + // commit the change + await repositoryPage.gotoUncommittedChangeTab(); + expect(await repositoryPage.getUncommittedCount()).toEqual(1); + await repositoryPage.commitChanges("Commit for pull-1"); + await expect(page.getByText("No changes")).toBeVisible(); + + // pulls list sanity + await repositoryPage.gotoPullRequestsTab(); + await expect(page.getByText("Create Pull Request")).toBeVisible(); + const pullsPage = new PullsPage(page); + expect(await pullsPage.getPullsListCount()).toEqual(0); + + // create a pull request + await pullsPage.clickCreatePullButton(); + await expect(page.getByRole("heading", {name: "Create Pull Request"})).toBeVisible(); + await pullsPage.switchCompareBranch(branchNameForPull); + const pullDetails = {title: "PR for branch 1", description: "A description for PR 1"}; + await pullsPage.fillPullTitle(pullDetails.title); + await pullsPage.fillPullDescription(pullDetails.description); + await pullsPage.clickCreatePullButton(); + expect(await pullsPage.getBranchesCompareURI()).toEqual(`main...${branchNameForPull}/`); + + // merge the pull request + await pullsPage.clickMergePullButton(); + await repositoryPage.gotoPullRequestsTab(); + await pullsPage.gotoPullsTab("closed"); + const firstPullRowDetails = await pullsPage.getFirstPullsRowDetails(); + expect(firstPullRowDetails.title).toEqual(pullDetails.title); + expect(firstPullRowDetails.description).toMatch(/^Merged/) + }); }); diff --git a/webui/test/e2e/poms/pullsPage.ts b/webui/test/e2e/poms/pullsPage.ts new file mode 100644 index 00000000000..a0deab64c13 --- /dev/null +++ b/webui/test/e2e/poms/pullsPage.ts @@ -0,0 +1,53 @@ +import { Locator, Page } from "@playwright/test"; + +export class PullsPage { + private page: Page; + + constructor(page: Page) { + this.page = page; + } + + async getPullsListCount(): Promise { + await this.page.locator("div.pulls-list").isVisible(); + return this.page + .locator("div.pulls-list") + .locator("pull-row") + .count(); + } + + async switchCompareBranch(name: string): Promise { + await this.page.getByRole("button", {name: "to branch: "}).click(); + await this.page.getByRole("button", {name}).click(); + } + + async clickCreatePullButton(): Promise { + await this.page.getByRole("button", {name: "Create Pull Request"}).click(); + } + + async getBranchesCompareURI(): Promise { + return await this.page.locator("div.lakefs-uri").innerText(); + } + + async clickMergePullButton(): Promise { + await this.page.getByRole("button", {name: "Merge pull request"}).click(); + } + + async fillPullTitle(title: string): Promise { + await this.page.getByPlaceholder("Add a title...").fill(title); + } + + async fillPullDescription(description: string): Promise { + await this.page.getByPlaceholder("Describe your changes...").fill(description); + } + + async gotoPullsTab(id: string): Promise { + await this.page.locator(`#pulls-tabs-tab-${id}`).click(); + } + + async getFirstPullsRowDetails(): Promise<{title: string, description: string}> { + const firstPullRow = this.page.locator("div.pull-row").first(); + const title = await firstPullRow.locator(".pull-title").innerText(); + const description = await firstPullRow.locator(".pull-description").innerText(); + return {title, description}; + } +} diff --git a/webui/test/e2e/poms/repositoryPage.ts b/webui/test/e2e/poms/repositoryPage.ts index e8190cb89a8..6368ddd8ead 100644 --- a/webui/test/e2e/poms/repositoryPage.ts +++ b/webui/test/e2e/poms/repositoryPage.ts @@ -21,6 +21,8 @@ export class RepositoryPage { .click(); } + // branch operations + async createBranch(name: string): Promise { await this.page .getByRole("link", { name: "Branches", exact: false }) @@ -37,6 +39,34 @@ export class RepositoryPage { await this.page.getByRole("button", { name }).click(); } + // file manipulation operations + + async deleteFirstObjectInDirectory(dirName: string): Promise { + await this.page.getByRole("link", {name: dirName}).click(); + + const getFirstObjectRow = (page: Page) => page + .locator("table.table") + .locator("tbody") + .locator("tr") + .first(); + + await getFirstObjectRow(this.page) + .locator("div.dropdown") + .hover(); + await getFirstObjectRow(this.page) + .locator("div.dropdown") + .locator("button") + .click(); + await this.page + .locator("div.dropdown") + .locator(".dropdown-item") + .last() + .click(); + await this.page.getByRole("button", {name: "Yes"}).click(); + } + + // uncommitted changes operations + async getUncommittedCount(): Promise { await this.page.locator("div.card").isVisible(); return this.page @@ -57,6 +87,8 @@ export class RepositoryPage { .click(); } + // merge operations + async merge(commitMsg: string): Promise { await this.page.getByRole("button", { name: "Merge" }).click(); if (commitMsg?.length) { @@ -75,6 +107,8 @@ export class RepositoryPage { await this.page.getByRole("button", { name }).click(); } + // navigation + async gotoObjectsTab(): Promise { await this.page.getByRole("link", { name: "Objects" }).click(); } @@ -86,4 +120,8 @@ export class RepositoryPage { async gotoCompareTab(): Promise { await this.page.getByRole("link", { name: "Compare" }).click(); } + + async gotoPullRequestsTab(): Promise { + await this.page.getByRole("link", { name: "Pull Requests" }).click(); + } }