diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index c62e8ee38..85216dbb8 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,11 +11,11 @@ jobs: - uses: actions/checkout@v3 - uses: actions/setup-node@v3 with: - node-version: 16 + node-version: 21 - run: npm install - # - name: Start Redis - # uses: supercharge/redis-github-action@1.4.0 - # - run: npm run verify + - name: Start Redis + uses: supercharge/redis-github-action@1.4.0 + - run: npm run verify - uses: JS-DevTools/npm-publish@v1 with: token: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 0bf915109..e589f1a41 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml @@ -2,22 +2,25 @@ name: CI on: push: - branches: [ master ] + branches: [master] pull_request: - branches: [ master ] + branches: [master] jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - name: Install modules - run: npm install - - name: Start Redis - uses: supercharge/redis-github-action@1.4.0 - - name: Run Verification - run: npm run verify - - name: Publish code coverage to CodeClimate - uses: paambaati/codeclimate-action@v3.0.0 - env: - CC_TEST_REPORTER_ID: 00fa2af1b50105f491c0e31d7841ecf1e7c495f1b7522ffa6b458d3db566de08 \ No newline at end of file + - uses: actions/checkout@v3 + - uses: actions/setup-node@v3 + with: + node-version: 21 + - name: Install modules + run: npm install + - name: Start Redis + uses: supercharge/redis-github-action@1.4.0 + - name: Run Verification + run: npm run verify + - name: Publish code coverage to CodeClimate + uses: paambaati/codeclimate-action@v3.0.0 + env: + CC_TEST_REPORTER_ID: 00fa2af1b50105f491c0e31d7841ecf1e7c495f1b7522ffa6b458d3db566de08 diff --git a/package-lock.json b/package-lock.json index 819ef3450..2357e027b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { - "name": "hadmean", - "version": "0.20.11", + "name": "dashpress", + "version": "1.0.1", "lockfileVersion": 2, "requires": true, "packages": { "": { - "name": "hadmean", - "version": "0.20.11", + "name": "dashpress", + "version": "1.0.1", "license": "GPL-3.0-or-later", "dependencies": { "@dashpress/bacteria": "^0.0.11", @@ -77,7 +77,7 @@ "zustand": "3.4.2" }, "bin": { - "hadmean": "bin/dashpress" + "dashpress": "bin/dashpress" }, "devDependencies": { "@babel/core": "^7.18.0", diff --git a/package.json b/package.json index d2c2634ce..cea50cb9b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "hadmean", - "version": "0.20.11", + "name": "dashpress", + "version": "1.0.1", "license": "GPL-3.0-or-later", "scripts": { "dev": "next dev", diff --git a/src/__tests__/_/api-handlers/roles.ts b/src/__tests__/_/api-handlers/roles.ts index f469d6f0f..ac4be551f 100644 --- a/src/__tests__/_/api-handlers/roles.ts +++ b/src/__tests__/_/api-handlers/roles.ts @@ -24,7 +24,7 @@ let PERMISSIONS = [ "CAN_MANAGE_USERS", "CAN_RESET_PASSWORD", "CAN_MANAGE_PERMISSIONS", - "CAN_MANAGE_ALL_ENTITIES", + "CAN_MANAGE_DASHBOARD", "CAN_ACCESS_ENTITY:ENTITY-2--show", "CAN_ACCESS_ENTITY:DISABLED-ENTITY-2--show", ]; diff --git a/src/__tests__/roles/[roleId]/index.spec.tsx b/src/__tests__/roles/[roleId]/index.spec.tsx index 1e59aee31..c566fed83 100644 --- a/src/__tests__/roles/[roleId]/index.spec.tsx +++ b/src/__tests__/roles/[roleId]/index.spec.tsx @@ -10,19 +10,18 @@ import { setupApiHandlers } from "__tests__/_/setupApihandlers"; setupApiHandlers(); const closeAllToasts = async () => { - const allToasts = await screen.findAllByRole("button", { + const toast = screen.getByRole("button", { name: "Close Toast", }); - for (const toast of allToasts) { - await userEvent.click(toast); - } + await userEvent.click(toast); - // await waitFor(() => { - // expect( - // screen.queryByRole("button", { name: "Close Toast" }) - // ).not.toBeInTheDocument(); - // }); + await waitFor( + () => { + expect(toast).not.toBeInTheDocument(); + }, + { timeout: 20000 } + ); }; describe("pages/roles/[roleId]/index", () => { @@ -36,7 +35,7 @@ describe("pages/roles/[roleId]/index", () => { isReady: true, })); - it("should select all admin permissions", async () => { + it("should select all user enabled admin permissions", async () => { render( @@ -69,7 +68,7 @@ describe("pages/roles/[roleId]/index", () => { expect( within(currentTab).getByRole("checkbox", { - name: "Can Manage Dashboard", + name: "Can Manage All Entities", }) ).not.toBeChecked(); @@ -81,48 +80,12 @@ describe("pages/roles/[roleId]/index", () => { expect( within(currentTab).getByRole("checkbox", { - name: "Can Manage All Entities", + name: "Can Manage Dashboard", }) ).toBeChecked(); }); - it("should show entities checkbox only when 'Can Manage All Entities' is checked", async () => { - render( - - - - ); - - await userEvent.click(await screen.findByRole("tab", { name: "Entities" })); - - const currentTab = screen.getByRole("tabpanel"); - - expect(within(currentTab).queryByRole("checkbox")).not.toBeInTheDocument(); - - await userEvent.click( - within(currentTab).queryByRole("button", { - name: "Can Manage All Entities", - }) - ); - - expect(await within(currentTab).findAllByRole("checkbox")).toHaveLength(5); - - await userEvent.click( - within(currentTab).queryByRole("button", { - name: "Can Manage All Entities", - }) - ); - - expect(within(currentTab).queryByRole("checkbox")).not.toBeInTheDocument(); - - await userEvent.click( - within(currentTab).queryByRole("button", { - name: "Can Manage All Entities", - }) - ); - }); - - it("should select all entities permissions", async () => { + it("should select all user enabled entities permissions", async () => { render( @@ -165,162 +128,330 @@ describe("pages/roles/[roleId]/index", () => { name: "Plural disabled-entity-2", }) ).toBeChecked(); - - await closeAllToasts(); }); - it("should update admin permissions", async () => { + it("should update entity permissions", async () => { render( ); - const currentTab = await screen.findByRole("tabpanel"); + await userEvent.click(await screen.findByRole("tab", { name: "Entities" })); + + const currentTab = screen.getByRole("tabpanel"); + + await waitFor(async () => { + expect( + await within(currentTab).findByRole("checkbox", { + name: "Plural entity-3", + }) + ).not.toBeChecked(); + }); await userEvent.click( await within(currentTab).findByRole("button", { - name: "Can Reset Password", + name: "Plural entity-2", }) ); - expect(await screen.findByRole("status")).toHaveTextContent( "Role Permission Deleted Successfully" ); + await closeAllToasts(); + await userEvent.click( - within(currentTab).getByRole("button", { name: "Can Manage Dashboard" }) + within(currentTab).getByRole("button", { name: "Plural entity-1" }) ); - expect((await screen.findAllByRole("status"))[0]).toHaveTextContent( + + expect(await screen.findByRole("status")).toHaveTextContent( "Role Permission Created Successfully" ); + await closeAllToasts(); + await userEvent.click( - within(currentTab).getByRole("button", { name: "Can Manage Users" }) + within(currentTab).getByRole("button", { + name: "Plural disabled-entity-2", + }) ); - expect((await screen.findAllByRole("status"))[0]).toHaveTextContent( - "Role Permission Created Successfully" + + expect(await screen.findByRole("status")).toHaveTextContent( + "Role Permission Deleted Successfully" ); + + await closeAllToasts(); + await userEvent.click( - within(currentTab).getByRole("button", { name: "Can Manage Users" }) + within(currentTab).getByRole("button", { + name: "Plural disabled-entity-2", + }) ); - expect((await screen.findAllByRole("status"))[0]).toHaveTextContent( - "Role Permission Deleted Successfully" + expect(await screen.findByRole("status")).toHaveTextContent( + "Role Permission Created Successfully" ); }); - it("should show updated admin permissions", async () => { + it("should show updated entity permissions", async () => { render( ); + await userEvent.click(await screen.findByRole("tab", { name: "Entities" })); + const currentTab = screen.getByRole("tabpanel"); await waitFor(async () => { expect( await within(currentTab).findByRole("checkbox", { - name: "Can Reset Password", + name: "Plural entity-2", }) ).not.toBeChecked(); }); expect( - within(currentTab).getByRole("checkbox", { name: "Can Manage Users" }) + within(currentTab).getByRole("checkbox", { name: "Plural entity-1" }) ).toBeChecked(); expect( within(currentTab).getByRole("checkbox", { - name: "Can Manage Dashboard", - }) - ).not.toBeChecked(); - - expect( - within(currentTab).getByRole("checkbox", { - name: "Can Manage Permissions", + name: "Plural disabled-entity-2", }) ).toBeChecked(); }); - it("should update entity permissions", async () => { + it("should toggle entities checkbox when 'Can Manage All Entities' is toggled", async () => { render( ); - await closeAllToasts(); - await userEvent.click(await screen.findByRole("tab", { name: "Entities" })); const currentTab = screen.getByRole("tabpanel"); + expect(await within(currentTab).findAllByRole("checkbox")).toHaveLength(5); + + await userEvent.click( + within(currentTab).queryByRole("button", { + name: "Can Manage All Entities", + }) + ); + + await closeAllToasts(); + + expect(within(currentTab).queryByRole("checkbox")).not.toBeInTheDocument(); + + await userEvent.click( + within(currentTab).queryByRole("button", { + name: "Can Manage All Entities", + }) + ); + + expect(await within(currentTab).findAllByRole("checkbox")).toHaveLength(5); + + await closeAllToasts(); + }); + + it("should update admin permissions", async () => { + render( + + + + ); + + const currentTab = await screen.findByRole("tabpanel"); + await userEvent.click( await within(currentTab).findByRole("button", { - name: "Plural entity-2", + name: "Can Reset Password", }) ); - expect((await screen.findAllByRole("status"))[0]).toHaveTextContent( - "Role Permission Created Successfully" + + expect(await screen.findByRole("status")).toHaveTextContent( + "Role Permission Deleted Successfully" ); + await closeAllToasts(); + await userEvent.click( - within(currentTab).getByRole("button", { name: "Plural entity-1" }) + within(currentTab).getByRole("button", { name: "Can Configure App" }) ); - expect((await screen.findAllByRole("status"))[0]).toHaveTextContent( + + expect(await screen.findByRole("status")).toHaveTextContent( "Role Permission Created Successfully" ); await closeAllToasts(); await userEvent.click( - within(currentTab).getByRole("button", { - name: "Plural disabled-entity-2", - }) + within(currentTab).getByRole("button", { name: "Can Manage Users" }) ); - expect((await screen.findAllByRole("status"))[0]).toHaveTextContent( + + expect(await screen.findByRole("status")).toHaveTextContent( "Role Permission Deleted Successfully" ); + + await closeAllToasts(); + await userEvent.click( - within(currentTab).getByRole("button", { - name: "Plural disabled-entity-2", - }) + within(currentTab).getByRole("button", { name: "Can Manage Users" }) ); - expect((await screen.findAllByRole("status"))[0]).toHaveTextContent( + + expect(await screen.findByRole("status")).toHaveTextContent( "Role Permission Created Successfully" ); }); - it("should show updated entity permissions", async () => { + it("should show updated admin permissions", async () => { render( ); - await userEvent.click(await screen.findByRole("tab", { name: "Entities" })); - const currentTab = screen.getByRole("tabpanel"); await waitFor(async () => { expect( await within(currentTab).findByRole("checkbox", { - name: "Plural entity-2", + name: "Can Reset Password", }) ).not.toBeChecked(); }); expect( - within(currentTab).getByRole("checkbox", { name: "Plural entity-1" }) - ).not.toBeChecked(); + within(currentTab).getByRole("checkbox", { name: "Can Manage Users" }) + ).toBeChecked(); expect( within(currentTab).getByRole("checkbox", { - name: "Plural disabled-entity-2", + name: "Can Configure App", + }) + ).toBeChecked(); + + expect( + within(currentTab).getByRole("checkbox", { + name: "Can Manage Permissions", }) ).toBeChecked(); }); - // TEST: test heirachy + describe("Heirachy", () => { + it("should toggle heirachy permissions on correctly", async () => { + render( + + + + ); + + await closeAllToasts(); + + const currentTab = await screen.findByRole("tabpanel"); + + // De-select the child permission + await userEvent.click( + await within(currentTab).findByRole("button", { + name: "Can Manage All Entities", + }) + ); + + expect(await screen.findByRole("status")).toHaveTextContent( + "Role Permission Deleted Successfully" + ); + + await closeAllToasts(); + + expect( + within(currentTab).queryByRole("checkbox", { + name: "Can Configure App", + }) + ).not.toBeChecked(); + + // Select the parent permission + await userEvent.click( + within(currentTab).getByRole("button", { + name: "Can Manage App Credentials", + }) + ); + + expect(await screen.findByRole("status")).toHaveTextContent( + "Role Permission Created Successfully" + ); + }); + + it("should toggle heirachy permissions off correctly", async () => { + render( + + + + ); + + await closeAllToasts(); + + const currentTab = await screen.findByRole("tabpanel"); + + // See the children permissions are turned on correctly + expect( + within(currentTab).getByRole("checkbox", { + name: "Can Manage App Credentials", + }) + ).toBeChecked(); + + expect( + within(currentTab).getByRole("checkbox", { + name: "Can Configure App", + }) + ).toBeChecked(); + + expect( + within(currentTab).getByRole("checkbox", { + name: "Can Manage All Entities", + }) + ).toBeChecked(); + + // De-select the parent permission + await userEvent.click( + await within(currentTab).findByRole("button", { + name: "Can Manage All Entities", + }) + ); + + expect(await screen.findByRole("status")).toHaveTextContent( + "Role Permission Deleted Successfully" + ); + }); + + it("should show turned off children permissions correctly", async () => { + render( + + + + ); + + const currentTab = await screen.findByRole("tabpanel"); + + // See the the child permissions are un-selected + expect( + within(currentTab).queryByRole("checkbox", { + name: "Can Manage App Credentials", + }) + ).not.toBeChecked(); + + expect( + within(currentTab).queryByRole("checkbox", { + name: "Can Configure App", + }) + ).not.toBeChecked(); + + expect( + within(currentTab).queryByRole("checkbox", { + name: "Can Manage All Entities", + }) + ).not.toBeChecked(); + }); + }); }); diff --git a/src/backend/data/data-access/RDBMS.ts b/src/backend/data/data-access/RDBMS.ts index df08aa713..b0820d059 100644 --- a/src/backend/data/data-access/RDBMS.ts +++ b/src/backend/data/data-access/RDBMS.ts @@ -20,7 +20,7 @@ export class RDBMSDataApiService extends BaseDataAccessService = { and: { - [QueryOperators.IS_NULL]: (query, column) => query.where(column), + [QueryOperators.IS_NULL]: (query, column) => query.whereNull(column), [QueryOperators.IS_NOT_NULL]: (query, column) => query.whereNotNull(column), [QueryOperators.EQUAL_TO]: (query, column, value) => @@ -32,7 +32,7 @@ export class RDBMSDataApiService extends BaseDataAccessService query.whereILike(column, `%${value}%`), [QueryOperators.IN]: (query, column, value) => - query.whereIn(column, value as string[]), + query.whereIn(column, value as string[]), // TODO csv [QueryOperators.NOT_IN]: (query, column, value) => query.whereNotIn(column, value as string[]), [QueryOperators.NOT_EQUAL]: (query, column, value) => diff --git a/src/backend/data/data-access/_Base.ts b/src/backend/data/data-access/_Base.ts index c1d88155e..8888b8db3 100644 --- a/src/backend/data/data-access/_Base.ts +++ b/src/backend/data/data-access/_Base.ts @@ -56,7 +56,11 @@ export abstract class BaseDataAccessService { return query; } - if (operator !== FilterOperators.IS_NULL && !value) { + if ( + operator !== FilterOperators.IS_NULL && + operator !== FilterOperators.IS_NOT_NULL && + !value + ) { return query; } diff --git a/src/backend/data/data-access/__tests__/query-generation.spec.ts b/src/backend/data/data-access/__tests__/query-generation.spec.ts index 6c020e661..e8a73bdda 100644 --- a/src/backend/data/data-access/__tests__/query-generation.spec.ts +++ b/src/backend/data/data-access/__tests__/query-generation.spec.ts @@ -20,6 +20,12 @@ const filterSchema: FieldQueryFilter[] = [ operator: FilterOperators.IS_NULL, }, }, + { + id: "is_not_null", + value: { + operator: FilterOperators.IS_NOT_NULL, + }, + }, { id: "less_than", value: { @@ -98,7 +104,7 @@ describe("query-generation", () => { .transformQueryFilterSchema(query, queryFilters) .toQuery() ).toMatchInlineSnapshot( - `"select * from \`tests\` where (\`equal_field\` = '_equal' and \`is_null\` is null and \`less_than\` < '_lessthan' and \`greater_than\` > '_greater_than' and \`contains\` ilike '%_contains%' and \`in\` in ('in1', 'in2') and \`not_in\` not in ('not_in1', 'not_in2') and not \`not_equal\` = 'not_equal' and \`between\` between 'btw_1' and 'btw_2') or (\`equal_field\` = '_equal' or \`is_null\` is null or \`less_than\` < '_lessthan' or \`greater_than\` > '_greater_than' or \`contains\` ilike '%_contains%' or \`in\` in ('in1', 'in2') or \`not_in\` not in ('not_in1', 'not_in2') or not \`not_equal\` = 'not_equal' or \`between\` between 'btw_1' and 'btw_2')"` + `"select * from \`tests\` where (\`equal_field\` = '_equal' and \`is_null\` is null and \`is_not_null\` is not null and \`less_than\` < '_lessthan' and \`greater_than\` > '_greater_than' and \`contains\` ilike '%_contains%' and \`in\` in ('in1', 'in2') and \`not_in\` not in ('not_in1', 'not_in2') and not \`not_equal\` = 'not_equal' and \`between\` between 'btw_1' and 'btw_2') or (\`equal_field\` = '_equal' or \`is_null\` is null or \`is_not_null\` is not null or \`less_than\` < '_lessthan' or \`greater_than\` > '_greater_than' or \`contains\` ilike '%_contains%' or \`in\` in ('in1', 'in2') or \`not_in\` not in ('not_in1', 'not_in2') or not \`not_equal\` = 'not_equal' or \`between\` between 'btw_1' and 'btw_2')"` ); }); }); diff --git a/src/bin/index.ts b/src/bin/index.ts index a52259bed..c4181c5c3 100644 --- a/src/bin/index.ts +++ b/src/bin/index.ts @@ -57,12 +57,12 @@ const replaceRandomCharaters = (envContent: string) => { console.log(` /$$ /$$ | $$ | $$ - /$$$$$$$ /$$$$$$ /$$$$$$ | $$$$$$$ /$$$$$$ /$$$$$$ / $$$$$$$$ /$$$$$$$ /$$$$$$$ - /$$__ $$ |____ $$ /$$____/ | $$__ $$ / $$__ $$ / $$__ $$ | $$__ $$ |$$_____//$$____/ -| $$ | $$ /$$$$$$$ | $$$$$$ | $$ \\ $$ | $$ \\ $$| $$ \\__ | $$$$$$$$ |$$$$$$$| $$$$$$ -| $$ | $$ / $$__ $$ \\_____$$ | $$ | $$ | $$ | $$| $$ | $$_____/ \\____ $$ \\____ $$ -| $$$$$$$ | $$$$$$$ /$$$$$$$/| $$ | $$ | $$$$$$$/| $$ | $$$$$$$ /$$$$$$$//$$$$$$$/ -\\_______/ \\_______/|_______/ |__/ |__/ | $$____/ |__/ \\_______/|_______/|_______/ + /$$$$$$$ /$$$$$$ / $$$$$ | $$$$$$$ /$$$$$$ /$$$$$$ / $$$$$$ / $$$$$ / $$$$$ + /$$__ $$ |____$$ | $$____ | $$__ $$ / $$__ $$ / $$__ $$ | $$___$$ | $$____ | $$____ +| $$ | $$ /$$$$$$$ | $$$$$$ | $$ \\ $$ | $$ \\ $$ | $$ \\__ | $$$$$$$ | $$$$$$ | $$$$$$ +| $$ | $$ | $$__ $$ \\___ $$ | $$ | $$ | $$ | $$ | $$ | $$____/ \\___ $$ \\___ $$ +| $$$$$$$$ | $$$$$$$ | $$$$$$ | $$ | $$ | $$$$$$$/ | $$ | $$$$$$$ | $$$$$$ | $$$$$$ +\\_______/ \\______/ \\_____/ |__/ |__/ | $$____/ |__/ \\_______/ \\_____/ \\______/ | $$ | $$ |__/ @@ -89,11 +89,11 @@ const replaceRandomCharaters = (envContent: string) => { "https://github.com/dashpresshq/dashpress" )} -- ${terminalLink( + - ${terminalLink( "💬 If you have questions? Join our community", "https://discord.gg/aV6DxwXhzN" )} - `); + `); const { stdout, stderr } = execa("npm", ["run", "start"], { cwd: path.join(__dirname, ".."), @@ -106,11 +106,11 @@ const replaceRandomCharaters = (envContent: string) => { const WAIT_FOR_NEXT_TO_START = 1000; /* - We want to ping the application to bootstrap itself from here - Else it boostraps on the first request which messes a lot of things up - We dont want the ping to crash the application if the port is not ready yet - Hence the catch(() => {}); - */ + We want to ping the application to bootstrap itself from here + Else it boostraps on the first request which messes a lot of things up + We dont want the ping to crash the application if the port is not ready yet + Hence the catch(() => {}); + */ setTimeout(() => { fetch(`${endpoint}/api/healthcheck`).catch(() => {}); }, WAIT_FOR_NEXT_TO_START);