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();
+ }
}