mirror of https://github.com/Budibase/budibase.git
180 changed files with 5629 additions and 2282 deletions
@ -0,0 +1,7 @@ |
|||
export const getAccount = jest.fn() |
|||
export const getAccountByTenantId = jest.fn() |
|||
|
|||
jest.mock("../../../src/cloud/accounts", () => ({ |
|||
getAccount, |
|||
getAccountByTenantId, |
|||
})) |
|||
@ -1,2 +0,0 @@ |
|||
exports.MOCK_DATE = new Date("2020-01-01T00:00:00.000Z") |
|||
exports.MOCK_DATE_TIMESTAMP = 1577836800000 |
|||
@ -0,0 +1,2 @@ |
|||
export const MOCK_DATE = new Date("2020-01-01T00:00:00.000Z") |
|||
export const MOCK_DATE_TIMESTAMP = 1577836800000 |
|||
@ -1,9 +0,0 @@ |
|||
const posthog = require("./posthog") |
|||
const events = require("./events") |
|||
const date = require("./date") |
|||
|
|||
module.exports = { |
|||
posthog, |
|||
date, |
|||
events, |
|||
} |
|||
@ -0,0 +1,4 @@ |
|||
import "./posthog" |
|||
import "./events" |
|||
export * as accounts from "./accounts" |
|||
export * as date from "./date" |
|||
@ -0,0 +1,178 @@ |
|||
import filterTests from "../../support/filterTests" |
|||
// const interact = require("../support/interact")
|
|||
|
|||
filterTests(["smoke", "all"], () => { |
|||
context("Auth Configuration", () => { |
|||
before(() => { |
|||
cy.login() |
|||
}) |
|||
|
|||
after(() => { |
|||
cy.get(".spectrum-SideNav li").contains("Auth").click() |
|||
cy.location().should(loc => { |
|||
expect(loc.pathname).to.eq("/builder/portal/manage/auth") |
|||
}) |
|||
|
|||
cy.get("[data-cy=new-scope-input]").clear() |
|||
|
|||
cy.get("div.content").scrollTo("bottom") |
|||
cy.get("[data-cy=oidc-active]").click() |
|||
|
|||
cy.get("[data-cy=oidc-active]").should('not.be.checked') |
|||
|
|||
cy.intercept("POST", "/api/global/configs").as("updateAuth") |
|||
cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) |
|||
cy.wait("@updateAuth") |
|||
cy.get("@updateAuth").its("response.statusCode").should("eq", 200) |
|||
|
|||
cy.get(".spectrum-Toast-content") |
|||
.contains("Settings saved") |
|||
.should("be.visible") |
|||
}) |
|||
|
|||
it("Should allow updating of the OIDC config", () => { |
|||
cy.get(".spectrum-SideNav li").contains("Auth").click() |
|||
cy.location().should(loc => { |
|||
expect(loc.pathname).to.eq("/builder/portal/manage/auth") |
|||
}) |
|||
cy.get("div.content").scrollTo("bottom") |
|||
cy.get(".spectrum-Toast .spectrum-ClearButton").click() |
|||
|
|||
cy.get("input[data-cy=configUrl]").type("http://budi-auth.com/v2") |
|||
cy.get("input[data-cy=clientID]").type("34ac6a13-f24a-4b52-c70d-fa544ffd11b2") |
|||
cy.get("input[data-cy=clientSecret]").type("12A8Q~4nS_DWhOOJ2vWIRsNyDVsdtXPD.Zxa9df_") |
|||
|
|||
cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); |
|||
|
|||
cy.intercept("POST", "/api/global/configs").as("updateAuth") |
|||
cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) |
|||
cy.wait("@updateAuth") |
|||
cy.get("@updateAuth").its("response.statusCode").should("eq", 200) |
|||
|
|||
cy.get(".spectrum-Toast-content") |
|||
.contains("Settings saved") |
|||
.should("be.visible") |
|||
}) |
|||
|
|||
it("Should display default scopes in advanced config.", () => { |
|||
cy.get(".spectrum-SideNav li").contains("Auth").click() |
|||
cy.location().should(loc => { |
|||
expect(loc.pathname).to.eq("/builder/portal/manage/auth") |
|||
}) |
|||
cy.get("div.content").scrollTo("bottom") |
|||
|
|||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) |
|||
|
|||
cy.get(".spectrum-Tags-item").contains("openid") |
|||
cy.get(".spectrum-Tags-item").contains("openid").find(".spectrum-ClearButton").should("not.exist") |
|||
|
|||
cy.get(".spectrum-Tags-item").contains("offline_access") |
|||
cy.get(".spectrum-Tags-item").contains("email") |
|||
cy.get(".spectrum-Tags-item").contains("profile") |
|||
}) |
|||
|
|||
it("Add a new scopes", () => { |
|||
cy.get(".spectrum-SideNav li").contains("Auth").click() |
|||
cy.location().should(loc => { |
|||
expect(loc.pathname).to.eq("/builder/portal/manage/auth") |
|||
}) |
|||
cy.get("div.content").scrollTo("bottom") |
|||
|
|||
cy.get("[data-cy=new-scope-input]").type("Sample{enter}") |
|||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 5) |
|||
cy.get(".spectrum-Tags-item").contains("Sample") |
|||
|
|||
cy.get(".auth-form input.spectrum-Textfield-input").type("Another ") |
|||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 6) |
|||
cy.get(".spectrum-Tags-item").contains("Another") |
|||
|
|||
cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); |
|||
|
|||
cy.intercept("POST", "/api/global/configs").as("updateAuth") |
|||
cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) |
|||
cy.wait("@updateAuth") |
|||
cy.get("@updateAuth").its("response.statusCode").should("eq", 200) |
|||
|
|||
cy.reload() |
|||
|
|||
cy.get("div.content").scrollTo("bottom") |
|||
|
|||
cy.get(".spectrum-Tags-item").contains("openid") |
|||
cy.get(".spectrum-Tags-item").contains("offline_access") |
|||
cy.get(".spectrum-Tags-item").contains("email") |
|||
cy.get(".spectrum-Tags-item").contains("profile") |
|||
cy.get(".spectrum-Tags-item").contains("Sample") |
|||
cy.get(".spectrum-Tags-item").contains("Another") |
|||
}) |
|||
|
|||
it("Should allow the removal of auth scopes", () => { |
|||
cy.get(".spectrum-SideNav li").contains("Auth").click() |
|||
cy.location().should(loc => { |
|||
expect(loc.pathname).to.eq("/builder/portal/manage/auth") |
|||
}) |
|||
cy.get("div.content").scrollTo("bottom") |
|||
|
|||
cy.get(".spectrum-Tags-item").contains("offline_access").parent().find(".spectrum-ClearButton").click() |
|||
cy.get(".spectrum-Tags-item").contains("profile").parent().find(".spectrum-ClearButton").click() |
|||
|
|||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) |
|||
|
|||
cy.get(".spectrum-Tags-item").contains("offline_access").should("not.exist") |
|||
cy.get(".spectrum-Tags-item").contains("profile").should("not.exist") |
|||
|
|||
cy.get("button[data-cy=oidc-save]").should("not.be.disabled"); |
|||
|
|||
cy.intercept("POST", "/api/global/configs").as("updateAuth") |
|||
cy.get("button[data-cy=oidc-save]").contains("Save").click({force: true}) |
|||
cy.wait("@updateAuth") |
|||
cy.get("@updateAuth").its("response.statusCode").should("eq", 200) |
|||
|
|||
cy.get(".spectrum-Toast-content") |
|||
.contains("Settings saved") |
|||
.should("be.visible") |
|||
|
|||
cy.reload() |
|||
|
|||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) |
|||
|
|||
cy.get(".spectrum-Tags-item").contains("offline_access").should("not.exist") |
|||
cy.get(".spectrum-Tags-item").contains("profile").should("not.exist") |
|||
}) |
|||
|
|||
it("Should allow auth scopes to be reset to the core defaults.", () => { |
|||
cy.get(".spectrum-SideNav li").contains("Auth").click() |
|||
|
|||
cy.get("div.content").scrollTo("bottom") |
|||
|
|||
cy.get("[data-cy=restore-oidc-default-scopes]").click({force: true}) |
|||
|
|||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) |
|||
|
|||
cy.get(".spectrum-Tags-item").contains("openid") |
|||
cy.get(".spectrum-Tags-item").contains("offline_access") |
|||
cy.get(".spectrum-Tags-item").contains("email") |
|||
cy.get(".spectrum-Tags-item").contains("profile") |
|||
}) |
|||
|
|||
it("Should not allow invalid characters in the auth scopes", () => { |
|||
cy.get("[data-cy=new-scope-input]").type("thisIsInvalid\\{enter}") |
|||
cy.get(".spectrum-Form-itemField .error").contains("Auth scopes cannot contain spaces, double quotes or backslashes") |
|||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) |
|||
|
|||
cy.get("[data-cy=new-scope-input]").clear() |
|||
|
|||
cy.get("[data-cy=new-scope-input]").type("alsoInvalid\"{enter}") |
|||
cy.get(".spectrum-Form-itemField .error").contains("Auth scopes cannot contain spaces, double quotes or backslashes") |
|||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) |
|||
|
|||
cy.get("[data-cy=new-scope-input]").clear() |
|||
}) |
|||
|
|||
it("Should not allow duplicate auth scopes", () => { |
|||
cy.get("[data-cy=new-scope-input]").type("offline_access{enter}") |
|||
cy.get(".spectrum-Form-itemField .error").contains("Auth scope already exists") |
|||
cy.get(".spectrum-Tags").find(".spectrum-Tags-item").its("length").should("eq", 4) |
|||
}) |
|||
|
|||
}) |
|||
}) |
|||
@ -0,0 +1,61 @@ |
|||
<script> |
|||
import { Select, Label, Checkbox } from "@budibase/bbui" |
|||
import { onMount } from "svelte" |
|||
import DrawerBindableInput from "components/common/bindings/DrawerBindableInput.svelte" |
|||
|
|||
export let parameters |
|||
export let bindings = [] |
|||
|
|||
const types = [ |
|||
{ |
|||
label: "Success", |
|||
value: "success", |
|||
}, |
|||
{ |
|||
label: "Warning", |
|||
value: "warning", |
|||
}, |
|||
{ |
|||
label: "Error", |
|||
value: "error", |
|||
}, |
|||
{ |
|||
label: "Info", |
|||
value: "info", |
|||
}, |
|||
] |
|||
|
|||
onMount(() => { |
|||
if (!parameters.type) { |
|||
parameters.type = "success" |
|||
} |
|||
if (parameters.autoDismiss == null) { |
|||
parameters.autoDismiss = true |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
<div class="root"> |
|||
<Label>Type</Label> |
|||
<Select bind:value={parameters.type} options={types} placeholder={null} /> |
|||
<Label>Message</Label> |
|||
<DrawerBindableInput |
|||
{bindings} |
|||
value={parameters.message} |
|||
on:change={e => (parameters.message = e.detail)} |
|||
/> |
|||
<Label /> |
|||
<Checkbox text="Auto dismiss" bind:value={parameters.autoDismiss} /> |
|||
</div> |
|||
|
|||
<style> |
|||
.root { |
|||
display: grid; |
|||
column-gap: var(--spacing-l); |
|||
row-gap: var(--spacing-s); |
|||
grid-template-columns: 60px 1fr; |
|||
align-items: center; |
|||
max-width: 400px; |
|||
margin: 0 auto; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,118 @@ |
|||
<script> |
|||
import { onMount } from "svelte" |
|||
import { selectedComponent, selectedScreen, store } from "builderStore" |
|||
import { findComponent } from "builderStore/componentUtils" |
|||
import { goto, isActive } from "@roxi/routify" |
|||
import { notifications } from "@budibase/bbui" |
|||
import ConfirmDialog from "components/common/ConfirmDialog.svelte" |
|||
|
|||
let confirmDeleteDialog |
|||
let componentToDelete |
|||
|
|||
const keyHandlers = { |
|||
["^ArrowUp"]: async component => { |
|||
await store.actions.components.moveUp(component) |
|||
}, |
|||
["^ArrowDown"]: async component => { |
|||
await store.actions.components.moveDown(component) |
|||
}, |
|||
["^c"]: component => { |
|||
store.actions.components.copy(component, false) |
|||
}, |
|||
["^x"]: component => { |
|||
store.actions.components.copy(component, true) |
|||
}, |
|||
["^v"]: async component => { |
|||
await store.actions.components.paste(component, "inside") |
|||
}, |
|||
["^d"]: async component => { |
|||
store.actions.components.copy(component) |
|||
await store.actions.components.paste(component, "below") |
|||
}, |
|||
["^Enter"]: () => { |
|||
$goto("./new") |
|||
}, |
|||
["Delete"]: component => { |
|||
// Don't show confirmation for the screen itself |
|||
if (component?._id === $selectedScreen.props._id) { |
|||
return false |
|||
} |
|||
componentToDelete = component |
|||
confirmDeleteDialog.show() |
|||
}, |
|||
["ArrowUp"]: () => { |
|||
store.actions.components.selectPrevious() |
|||
}, |
|||
["ArrowDown"]: () => { |
|||
store.actions.components.selectNext() |
|||
}, |
|||
["Escape"]: () => { |
|||
if (!$isActive("/new")) { |
|||
return false |
|||
} |
|||
$goto("./") |
|||
}, |
|||
} |
|||
|
|||
const handleKeyAction = async (component, key, ctrlKey = false) => { |
|||
if (!component || !key) { |
|||
return false |
|||
} |
|||
try { |
|||
// Delete and backspace are the same |
|||
if (key === "Backspace") { |
|||
key = "Delete" |
|||
} |
|||
// Prefix key with a caret for ctrl modifier |
|||
if (ctrlKey) { |
|||
key = "^" + key |
|||
} |
|||
const handler = keyHandlers[key] |
|||
if (!handler) { |
|||
return false |
|||
} |
|||
return handler(component) |
|||
} catch (error) { |
|||
console.error(error) |
|||
notifications.error("Error handling key press") |
|||
} |
|||
} |
|||
|
|||
const handleKeyPress = async e => { |
|||
// Ignore repeating events |
|||
if (e.repeat) { |
|||
return |
|||
} |
|||
// Ignore events when typing |
|||
const activeTag = document.activeElement?.tagName.toLowerCase() |
|||
if (["input", "textarea"].indexOf(activeTag) !== -1 && e.key !== "Escape") { |
|||
return |
|||
} |
|||
// Key events are always for the selected component |
|||
return handleKeyAction($selectedComponent, e.key, e.ctrlKey || e.metaKey) |
|||
} |
|||
|
|||
const handleComponentMenu = async e => { |
|||
// Menu events can be for any component |
|||
const { id, key, ctrlKey } = e.detail |
|||
const component = findComponent($selectedScreen.props, id) |
|||
return await handleKeyAction(component, key, ctrlKey) |
|||
} |
|||
|
|||
onMount(() => { |
|||
document.addEventListener("keydown", handleKeyPress) |
|||
document.addEventListener("component-menu", handleComponentMenu) |
|||
return () => { |
|||
document.removeEventListener("keydown", handleKeyPress) |
|||
document.removeEventListener("component-menu", handleComponentMenu) |
|||
} |
|||
}) |
|||
</script> |
|||
|
|||
<ConfirmDialog |
|||
bind:this={confirmDeleteDialog} |
|||
title="Confirm Deletion" |
|||
body={`Are you sure you want to delete "${componentToDelete?._instanceName}"?`} |
|||
okText="Delete Component" |
|||
onOk={() => store.actions.components.delete(componentToDelete)} |
|||
/> |
|||
@ -0,0 +1,82 @@ |
|||
<script> |
|||
import { setContext } from "svelte" |
|||
import { dndStore } from "./dndStore" |
|||
import { notifications } from "@budibase/bbui" |
|||
|
|||
let scrollRef |
|||
|
|||
const scrollTo = bounds => { |
|||
if (!bounds) { |
|||
return |
|||
} |
|||
const sidebarWidth = 259 |
|||
const navItemHeight = 32 |
|||
const { scrollLeft, scrollTop, offsetHeight } = scrollRef |
|||
let scrollBounds = scrollRef.getBoundingClientRect() |
|||
let newOffsets = {} |
|||
|
|||
// Calculate left offset |
|||
const offsetX = bounds.left + bounds.width + scrollLeft - 36 |
|||
if (offsetX > sidebarWidth) { |
|||
newOffsets.left = offsetX - sidebarWidth |
|||
} else { |
|||
newOffsets.left = 0 |
|||
} |
|||
if (newOffsets.left === scrollLeft) { |
|||
delete newOffsets.left |
|||
} |
|||
|
|||
// Calculate top offset |
|||
const offsetY = bounds.top - scrollBounds?.top + scrollTop |
|||
if (offsetY > scrollTop + offsetHeight - 2 * navItemHeight) { |
|||
newOffsets.top = offsetY - offsetHeight + 2 * navItemHeight |
|||
} else if (offsetY < scrollTop + navItemHeight) { |
|||
newOffsets.top = offsetY - navItemHeight |
|||
} else { |
|||
delete newOffsets.top |
|||
} |
|||
|
|||
// Skip if offset is unchanged |
|||
if (newOffsets.left == null && newOffsets.top == null) { |
|||
return |
|||
} |
|||
|
|||
// Smoothly scroll to the offset |
|||
scrollRef.scroll({ |
|||
...newOffsets, |
|||
behavior: "smooth", |
|||
}) |
|||
} |
|||
|
|||
// Set scroll context so components can invoke scrolling when selected |
|||
setContext("scroll", { |
|||
scrollTo, |
|||
}) |
|||
|
|||
const onDrop = async () => { |
|||
try { |
|||
await dndStore.actions.drop() |
|||
} catch (error) { |
|||
console.error(error) |
|||
notifications.error("Error saving component") |
|||
} |
|||
} |
|||
</script> |
|||
|
|||
<div |
|||
bind:this={scrollRef} |
|||
on:drop={onDrop} |
|||
ondragover="return false" |
|||
ondragenter="return false" |
|||
> |
|||
<slot /> |
|||
</div> |
|||
|
|||
<style> |
|||
div { |
|||
padding: var(--spacing-xl) 0; |
|||
flex: 1 1 auto; |
|||
overflow: auto; |
|||
height: 0; |
|||
} |
|||
</style> |
|||
@ -0,0 +1,73 @@ |
|||
<script> |
|||
import { Body, ModalContent, Table } from "@budibase/bbui" |
|||
import { onMount } from "svelte" |
|||
|
|||
export let userData |
|||
export let deleteUsersResponse |
|||
|
|||
let successCount |
|||
let failureCount |
|||
let title |
|||
let unsuccessfulUsers |
|||
let message |
|||
|
|||
const setTitle = () => { |
|||
if (successCount) { |
|||
title = `${successCount} users deleted` |
|||
} else { |
|||
title = "Oops!" |
|||
} |
|||
} |
|||
|
|||
const setMessage = () => { |
|||
if (successCount) { |
|||
message = "However there was a problem deleting some users." |
|||
} else { |
|||
message = "There was a problem deleting some users." |
|||
} |
|||
} |
|||
|
|||
const setUsers = () => { |
|||
unsuccessfulUsers = deleteUsersResponse.unsuccessful.map(user => { |
|||
return { |
|||
email: user.email, |
|||
reason: user.reason, |
|||
} |
|||
}) |
|||
} |
|||
|
|||
onMount(() => { |
|||
successCount = deleteUsersResponse.successful.length |
|||
failureCount = deleteUsersResponse.unsuccessful.length |
|||
setTitle() |
|||
setMessage() |
|||
setUsers() |
|||
}) |
|||
|
|||
const schema = { |
|||
email: {}, |
|||
reason: {}, |
|||
} |
|||
</script> |
|||
|
|||
<ModalContent |
|||
size="M" |
|||
{title} |
|||
confirmText="Close" |
|||
showCloseIcon={false} |
|||
showCancelButton={false} |
|||
> |
|||
<Body size="XS"> |
|||
{message} |
|||
</Body> |
|||
<Table |
|||
{schema} |
|||
data={unsuccessfulUsers} |
|||
allowEditColumns={false} |
|||
allowEditRows={false} |
|||
allowSelectRows={false} |
|||
/> |
|||
</ModalContent> |
|||
|
|||
<style> |
|||
</style> |
|||
@ -0,0 +1,75 @@ |
|||
<script> |
|||
import { Body, ModalContent, Table } from "@budibase/bbui" |
|||
import { onMount } from "svelte" |
|||
|
|||
export let inviteUsersResponse |
|||
|
|||
let hasSuccess |
|||
let hasFailure |
|||
let title |
|||
let failureMessage |
|||
|
|||
let unsuccessfulUsers |
|||
|
|||
const setTitle = () => { |
|||
if (hasSuccess) { |
|||
title = "Users invited!" |
|||
} else if (hasFailure) { |
|||
title = "Oops!" |
|||
} |
|||
} |
|||
|
|||
const setFailureMessage = () => { |
|||
if (hasSuccess) { |
|||
failureMessage = "However there was a problem inviting some users." |
|||
} else { |
|||
failureMessage = "There was a problem inviting users." |
|||
} |
|||
} |
|||
|
|||
const setUsers = () => { |
|||
unsuccessfulUsers = inviteUsersResponse.unsuccessful.map(user => { |
|||
return { |
|||
email: user.email, |
|||
reason: user.reason, |
|||
} |
|||
}) |
|||
} |
|||
|
|||
onMount(() => { |
|||
hasSuccess = inviteUsersResponse.successful.length |
|||
hasFailure = inviteUsersResponse.unsuccessful.length |
|||
setTitle() |
|||
setFailureMessage() |
|||
setUsers() |
|||
}) |
|||
|
|||
const failedSchema = { |
|||
email: {}, |
|||
reason: {}, |
|||
} |
|||
</script> |
|||
|
|||
<ModalContent showCancelButton={false} {title} confirmText="Done"> |
|||
{#if hasSuccess} |
|||
<Body size="XS"> |
|||
Your users should now receive an email invite to get access to their |
|||
Budibase account |
|||
</Body> |
|||
{/if} |
|||
{#if hasFailure} |
|||
<Body size="XS"> |
|||
{failureMessage} |
|||
</Body> |
|||
<Table |
|||
schema={failedSchema} |
|||
data={unsuccessfulUsers} |
|||
allowEditColumns={false} |
|||
allowEditRows={false} |
|||
allowSelectRows={false} |
|||
/> |
|||
{/if} |
|||
</ModalContent> |
|||
|
|||
<style> |
|||
</style> |
|||
File diff suppressed because it is too large
File diff suppressed because one or more lines are too long
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue