diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml index 39ad7740b2..95c5e39b41 100644 --- a/.github/workflows/labeler.yml +++ b/.github/workflows/labeler.yml @@ -14,5 +14,5 @@ jobs: fetch-depth: 0 - uses: actions/labeler@v5 with: - repo-token: ${{ secrets.GITHUB_TOKEN }} + repo-token: ${{ secrets.GH_TOKEN }} configuration-path: .github/labeler-config.yml \ No newline at end of file diff --git a/.github/workflows/pullRequests.yml b/.github/workflows/pullRequests.yml index 920fa31af1..b584e369c8 100644 --- a/.github/workflows/pullRequests.yml +++ b/.github/workflows/pullRequests.yml @@ -92,6 +92,7 @@ jobs: assignMilestone: name: Assign milestone needs: constants + if: needs.constants.outputs.is-fork-pr != 'true' steps: - uses: actions/setup-node@v4 with: @@ -99,6 +100,19 @@ jobs: - uses: actions/checkout@v4 - name: Print latest Webiny version run: echo ${{ needs.constants.outputs.latest-webiny-version }} + - id: get-milestone-to-assign + name: Get milestone to assign + run: >- + echo "milestone=$(node + .github/workflows/wac/utils/runNodeScripts/getMilestoneToAssign.js + '{"latestWebinyVersion":"${{ + needs.constants.outputs.latest-webiny-version }}","baseBranch":"${{ + github.base_ref }}"}')" >> $GITHUB_OUTPUT + - uses: zoispag/action-assign-milestone@v1 + if: steps.get-milestone-to-assign.outputs.milestone + with: + repo-token: ${{ secrets.GH_TOKEN }} + milestone: ${{ steps.get-milestone-to-assign.outputs.milestone }} runs-on: ubuntu-latest env: NODE_OPTIONS: '--max_old_space_size=4096' diff --git a/.github/workflows/wac/pullRequests.wac.ts b/.github/workflows/wac/pullRequests.wac.ts index 60f304b84e..723dcc6e57 100644 --- a/.github/workflows/wac/pullRequests.wac.ts +++ b/.github/workflows/wac/pullRequests.wac.ts @@ -210,10 +210,32 @@ export const pullRequests = createWorkflow({ assignMilestone: createJob({ name: "Assign milestone", needs: "constants", + if: "needs.constants.outputs.is-fork-pr != 'true'", steps: [ { name: "Print latest Webiny version", run: "echo ${{ needs.constants.outputs.latest-webiny-version }}" + }, + { + id: "get-milestone-to-assign", + name: "Get milestone to assign", + run: runNodeScript( + "getMilestoneToAssign", + JSON.stringify({ + latestWebinyVersion: + "${{ needs.constants.outputs.latest-webiny-version }}", + baseBranch: "${{ github.base_ref }}" + }), + { outputAs: "milestone" } + ) + }, + { + uses: "zoispag/action-assign-milestone@v1", + if: "steps.get-milestone-to-assign.outputs.milestone", + with: { + "repo-token": "${{ secrets.GH_TOKEN }}", + milestone: "${{ steps.get-milestone-to-assign.outputs.milestone }}" + } } ] }), diff --git a/.github/workflows/wac/utils/runNodeScripts/getMilestoneToAssign.js b/.github/workflows/wac/utils/runNodeScripts/getMilestoneToAssign.js new file mode 100644 index 0000000000..b1f7bba5ed --- /dev/null +++ b/.github/workflows/wac/utils/runNodeScripts/getMilestoneToAssign.js @@ -0,0 +1,17 @@ +// Returns the milestone to assign to the PR based on the base branch and the latest Webiny version. +const args = process.argv.slice(2); // Removes the first two elements +const [params] = args; +const { latestWebinyVersion, baseBranch } = JSON.parse(params); + +const [major, minor, patch] = latestWebinyVersion.split("."); + +switch (baseBranch) { + case "next": + console.log(`${major}.${parseInt(minor, 10) + 1}.0`); + break; + case "dev": + console.log(`${major}.${minor}.${parseInt(patch, 10) + 1}`); + break; + default: + console.log(""); +} diff --git a/packages/api-headless-cms/__tests__/contentAPI/references/publishedAndUnpublished.test.ts b/packages/api-headless-cms/__tests__/contentAPI/references/publishedAndUnpublished.test.ts new file mode 100644 index 0000000000..ea2c214bcc --- /dev/null +++ b/packages/api-headless-cms/__tests__/contentAPI/references/publishedAndUnpublished.test.ts @@ -0,0 +1,206 @@ +import { useCategoryManageHandler } from "../../testHelpers/useCategoryManageHandler"; +import { useArticleManageHandler } from "../../testHelpers/useArticleManageHandler"; +import { useArticleReadHandler } from "../../testHelpers/useArticleReadHandler"; +import { useGraphQLHandler } from "../../testHelpers/useGraphQLHandler"; +import { setupContentModelGroup, setupContentModels } from "../../testHelpers/setup"; +import { GenericRecord } from "@webiny/api/types"; +import slugify from "slugify"; + +interface ICreateCategoryItemPrams { + manager: ReturnType; + publish: boolean; + data: GenericRecord; +} + +const createCategoryItem = async ({ manager, publish, data }: ICreateCategoryItemPrams) => { + const [response] = await manager.createCategory({ data }); + const category = response?.data?.createCategory?.data; + const error = response?.data?.createCategory?.error; + if (!category?.id || error) { + console.log(error.message); + console.log(JSON.stringify(error.data)); + throw new Error("Could not create category."); + } + if (!publish) { + return category; + } + const [publishResponse] = await manager.publishCategory({ + revision: category.id + }); + if (publishResponse?.data?.publishCategory?.error) { + console.log(publishResponse?.data?.publishCategory?.error?.message); + throw new Error("Could not publish category."); + } + return publishResponse.data.publishCategory.data; +}; + +interface ICreateArticleItemPrams { + manager: ReturnType; + publish: boolean; + data: GenericRecord; +} + +const createArticleItem = async ({ manager, publish, data }: ICreateArticleItemPrams) => { + const [response] = await manager.createArticle({ data }); + const article = response?.data?.createArticle?.data; + const error = response?.data?.createArticle?.error; + if (!article?.id || error) { + console.log(error.message); + console.log(JSON.stringify(error.data)); + throw new Error("Could not create article."); + } + if (!publish) { + return article; + } + const [publishResponse] = await manager.publishArticle({ + revision: article.id + }); + if (publishResponse?.data?.publishArticle?.error) { + console.log(publishResponse?.data?.publishArticle?.error?.message); + throw new Error("Could not publish article."); + } + return publishResponse.data.publishArticle.data; +}; + +interface ICategoryItem { + id: string; + entryId: string; + title: string; + slug: string; + published: boolean; +} + +const categoryNames = ["Tech", "Health", "Space", "Food", "Science", "Sports"]; + +describe("published and unpublished references", () => { + const manageOpts = { path: "manage/en-US" }; + const readOpts = { path: "read/en-US" }; + + const mainManager = useGraphQLHandler(manageOpts); + + it("should populate reference field with some published and some unpublished records", async () => { + const group = await setupContentModelGroup(mainManager); + await setupContentModels(mainManager, group, ["category", "article"]); + + const categoryManager = useCategoryManageHandler(manageOpts); + const articleManager = useArticleManageHandler(manageOpts); + const articleRead = useArticleReadHandler(readOpts); + + const categories: ICategoryItem[] = []; + + for (const index in categoryNames) { + const title = categoryNames[index]; + const published = Number(index) % 2 === 0; + const category = await createCategoryItem({ + manager: categoryManager, + data: { + title: title, + slug: slugify(title) + }, + publish: published + }); + categories.push({ + ...category, + published + }); + } + expect(categories.length).toBe(categoryNames.length); + + const firstUnpublishedCategoryId = categories.find(c => !c.published)!.id; + expect(firstUnpublishedCategoryId).toMatch(/^([a-zA-Z0-9]+)#0001$/); + /** + * Create an article and make sure all the categories are in it. + */ + const createdArticle = await createArticleItem({ + manager: articleManager, + data: { + title: "Tech article", + body: null, + category: { + id: firstUnpublishedCategoryId, + modelId: "category" + }, + categories: categories.map(c => { + return { + id: c.id, + modelId: "category" + }; + }) + }, + publish: false + }); + + const expectedAllCategories = categories.map(c => { + return { + id: c.id, + entryId: c.entryId, + modelId: "category" + }; + }); + const expectedPublishedCategories = categories + .filter(c => c.published) + .map(c => { + return { + id: c.id, + entryId: c.entryId, + modelId: "category" + }; + }); + expect(expectedAllCategories).toHaveLength(expectedPublishedCategories.length * 2); + + expect(createdArticle.categories).toEqual(expectedAllCategories); + + const [articleManageGetResponse] = await articleManager.getArticle({ + revision: createdArticle.id + }); + expect(articleManageGetResponse?.data?.getArticle?.data?.categories).toEqual( + expectedAllCategories + ); + expect(articleManageGetResponse?.data?.getArticle?.data?.category).toMatchObject({ + id: firstUnpublishedCategoryId + }); + /** + * Now we can publish the article and check that references are still there. + */ + const [publishResponse] = await articleManager.publishArticle({ + revision: createdArticle.id + }); + expect(publishResponse?.data?.publishArticle?.data?.categories).toEqual( + expectedAllCategories + ); + expect(publishResponse?.data?.publishArticle?.data?.category).toMatchObject({ + id: firstUnpublishedCategoryId + }); + /** + * Now we can read the article, from manage endpoint, and check that references are still there. + * + * There must be all the categories present. + */ + const [articleManageGetPublishedResponse] = await articleManager.getArticle({ + revision: createdArticle.id + }); + expect(articleManageGetPublishedResponse?.data?.getArticle?.data?.categories).toEqual( + expectedAllCategories + ); + expect(articleManageGetPublishedResponse?.data?.getArticle?.data?.category).toMatchObject({ + id: firstUnpublishedCategoryId + }); + /** + * And read from the read endpoint... + * + * There must be only published categories present. + */ + const [articleReadGetPublishedResponse] = await articleRead.getArticle({ + where: { + id: createdArticle.id + } + }); + expect(articleReadGetPublishedResponse?.data?.getArticle?.data?.categories).toMatchObject( + expectedPublishedCategories + ); + expect(articleReadGetPublishedResponse?.data?.getArticle?.data?.categories).toHaveLength( + expectedPublishedCategories.length + ); + expect(articleReadGetPublishedResponse?.data?.getArticle?.data?.category).toBeNull(); + }); +});