Merge branch 'master' into settings-enhancements
This commit is contained in:
commit
a58af2b7e4
|
@ -57,7 +57,10 @@
|
|||
"destructuredArrayIgnorePattern": "^_"
|
||||
}
|
||||
],
|
||||
"import/no-relative-packages": "error"
|
||||
"import/no-relative-packages": "error",
|
||||
"import/export": "error",
|
||||
"import/no-duplicates": "error",
|
||||
"import/newline-after-import": "error"
|
||||
},
|
||||
"globals": {
|
||||
"GeolocationPositionError": true
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
node_modules
|
||||
dist
|
||||
*.spec.js
|
||||
packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte
|
||||
packages/server/builder
|
||||
packages/server/coverage
|
||||
packages/worker/coverage
|
||||
packages/backend-core/coverage
|
||||
packages/server/client
|
||||
packages/server/src/definitions/openapi.ts
|
||||
packages/worker/coverage
|
||||
packages/backend-core/coverage
|
||||
packages/builder/.routify
|
||||
packages/sdk/sdk
|
||||
packages/pro/coverage
|
|
@ -46,11 +46,9 @@ spec:
|
|||
image: minio/minio
|
||||
imagePullPolicy: ""
|
||||
livenessProbe:
|
||||
exec:
|
||||
command:
|
||||
- curl
|
||||
- -f
|
||||
- http://localhost:9000/minio/health/live
|
||||
httpGet:
|
||||
path: /minio/health/live
|
||||
port: 9000
|
||||
failureThreshold: 3
|
||||
periodSeconds: 30
|
||||
timeoutSeconds: 20
|
||||
|
|
|
@ -1,24 +0,0 @@
|
|||
#!/bin/bash
|
||||
|
||||
echo ${TARGETBUILD} > /buildtarget.txt
|
||||
if [[ "${TARGETBUILD}" = "aas" ]]; then
|
||||
# Azure AppService uses /home for persistent data & SSH on port 2222
|
||||
DATA_DIR="${DATA_DIR:-/home}"
|
||||
WEBSITES_ENABLE_APP_SERVICE_STORAGE=true
|
||||
mkdir -p $DATA_DIR/{search,minio,couch}
|
||||
mkdir -p $DATA_DIR/couch/{dbs,views}
|
||||
chown -R couchdb:couchdb $DATA_DIR/couch/
|
||||
apt update
|
||||
apt-get install -y openssh-server
|
||||
echo "root:Docker!" | chpasswd
|
||||
mkdir -p /tmp
|
||||
chmod +x /tmp/ssh_setup.sh \
|
||||
&& (sleep 1;/tmp/ssh_setup.sh 2>&1 > /dev/null)
|
||||
cp /etc/sshd_config /etc/ssh/sshd_config
|
||||
/etc/init.d/ssh restart
|
||||
sed -i "s#DATA_DIR#/home#g" /opt/clouseau/clouseau.ini
|
||||
sed -i "s#DATA_DIR#/home#g" /opt/couchdb/etc/local.ini
|
||||
else
|
||||
sed -i "s#DATA_DIR#/data#g" /opt/clouseau/clouseau.ini
|
||||
sed -i "s#DATA_DIR#/data#g" /opt/couchdb/etc/local.ini
|
||||
fi
|
|
@ -25,7 +25,7 @@ if [[ $(curl -s -w "%{http_code}\n" http://localhost:4002/health -o /dev/null) -
|
|||
healthy=false
|
||||
fi
|
||||
|
||||
if [[ $(curl -s -w "%{http_code}\n" http://localhost:5984/ -o /dev/null) -ne 200 ]]; then
|
||||
if [[ $(curl -s -w "%{http_code}\n" http://localhost:5984/_up -o /dev/null) -ne 200 ]]; then
|
||||
echo 'ERROR: CouchDB is not running';
|
||||
healthy=false
|
||||
fi
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{
|
||||
"version": "2.13.10",
|
||||
"version": "2.13.14",
|
||||
"npmClient": "yarn",
|
||||
"packages": [
|
||||
"packages/*"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
const _passport = require("koa-passport")
|
||||
const LocalStrategy = require("passport-local").Strategy
|
||||
|
||||
import { getGlobalDB } from "../context"
|
||||
import { Cookie } from "../constants"
|
||||
import { getSessionsForUser, invalidateSessions } from "../security/sessions"
|
||||
|
@ -26,6 +27,7 @@ import { clearCookie, getCookie } from "../utils"
|
|||
import { ssoSaveUserNoOp } from "../middleware/passport/sso/sso"
|
||||
|
||||
const refresh = require("passport-oauth2-refresh")
|
||||
|
||||
export {
|
||||
auditLog,
|
||||
authError,
|
||||
|
|
|
@ -17,7 +17,6 @@ import { DocumentType, SEPARATOR } from "../constants"
|
|||
import { CacheKey, TTL, withCache } from "../cache"
|
||||
import * as context from "../context"
|
||||
import env from "../environment"
|
||||
import environment from "../environment"
|
||||
|
||||
// UTILS
|
||||
|
||||
|
@ -181,10 +180,10 @@ export async function getGoogleDatasourceConfig(): Promise<
|
|||
}
|
||||
|
||||
export function getDefaultGoogleConfig(): GoogleInnerConfig | undefined {
|
||||
if (environment.GOOGLE_CLIENT_ID && environment.GOOGLE_CLIENT_SECRET) {
|
||||
if (env.GOOGLE_CLIENT_ID && env.GOOGLE_CLIENT_SECRET) {
|
||||
return {
|
||||
clientID: environment.GOOGLE_CLIENT_ID!,
|
||||
clientSecret: environment.GOOGLE_CLIENT_SECRET!,
|
||||
clientID: env.GOOGLE_CLIENT_ID!,
|
||||
clientSecret: env.GOOGLE_CLIENT_SECRET!,
|
||||
activated: true,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { prefixed, DocumentType } from "@budibase/types"
|
||||
|
||||
export {
|
||||
SEPARATOR,
|
||||
UNICODE_MAX,
|
||||
|
|
|
@ -5,7 +5,6 @@ const { getDB } = require("../db")
|
|||
describe("db", () => {
|
||||
describe("getDB", () => {
|
||||
it("returns a db", async () => {
|
||||
|
||||
const dbName = structures.db.id()
|
||||
const db = getDB(dbName)
|
||||
expect(db).toBeDefined()
|
||||
|
|
|
@ -6,6 +6,7 @@ import { AppState, DeletedApp, getAppMetadata } from "../cache/appMetadata"
|
|||
import { isDevApp, isDevAppID, getProdAppID } from "../docIds/conversions"
|
||||
import { App, Database } from "@budibase/types"
|
||||
import { getStartEndKeyURL } from "../docIds"
|
||||
|
||||
export * from "../docIds"
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { APP_DEV_PREFIX, APP_PREFIX } from "../constants"
|
||||
import { App } from "@budibase/types"
|
||||
|
||||
const NO_APP_ERROR = "No app provided"
|
||||
|
||||
export function isDevAppID(appId?: string) {
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
import PosthogProcessor from "./PosthogProcessor"
|
||||
|
||||
export default PosthogProcessor
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
import { testEnv } from "../../../../../tests/extra"
|
||||
import PosthogProcessor from "../PosthogProcessor"
|
||||
import { Event, IdentityType, Hosting } from "@budibase/types"
|
||||
|
||||
const tk = require("timekeeper")
|
||||
|
||||
import * as cache from "../../../../cache/generic"
|
||||
import { CacheKey } from "../../../../cache/generic"
|
||||
import * as context from "../../../../context"
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import env from "../environment"
|
||||
import * as context from "../context"
|
||||
|
||||
export * from "./installation"
|
||||
|
||||
/**
|
||||
|
|
|
@ -38,6 +38,7 @@ export * as docIds from "./docIds"
|
|||
// circular dependencies
|
||||
import * as context from "./context"
|
||||
import * as _tenancy from "./tenancy"
|
||||
|
||||
export const tenancy = {
|
||||
..._tenancy,
|
||||
...context,
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import { newid } from "./utils"
|
||||
import * as events from "./events"
|
||||
import { StaticDatabases } from "./db"
|
||||
import { doWithDB } from "./db"
|
||||
import { StaticDatabases, doWithDB } from "./db"
|
||||
import { Installation, IdentityType, Database } from "@budibase/types"
|
||||
import * as context from "./context"
|
||||
import semver from "semver"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Header } from "../../constants"
|
||||
|
||||
const correlator = require("correlation-id")
|
||||
|
||||
export const setHeader = (headers: any) => {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import { Header } from "../../constants"
|
||||
import { v4 as uuid } from "uuid"
|
||||
|
||||
const correlator = require("correlation-id")
|
||||
|
||||
const correlation = (ctx: any, next: any) => {
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
import env from "../../environment"
|
||||
import { logger } from "./logger"
|
||||
import { IncomingMessage } from "http"
|
||||
|
||||
const pino = require("koa-pino-logger")
|
||||
|
||||
import { Options } from "pino-http"
|
||||
import { Ctx } from "@budibase/types"
|
||||
|
||||
const correlator = require("correlation-id")
|
||||
|
||||
export function pinoSettings(): Options {
|
||||
|
|
|
@ -2,6 +2,7 @@ export * as local from "./passport/local"
|
|||
export * as google from "./passport/sso/google"
|
||||
export * as oidc from "./passport/sso/oidc"
|
||||
import * as datasourceGoogle from "./passport/datasource/google"
|
||||
|
||||
export const datasource = {
|
||||
google: datasourceGoogle,
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
SaveSSOUserFunction,
|
||||
GoogleInnerConfig,
|
||||
} from "@budibase/types"
|
||||
|
||||
const GoogleStrategy = require("passport-google-oauth").OAuth2Strategy
|
||||
|
||||
export function buildVerifyFn(saveUserFn: SaveSSOUserFunction) {
|
||||
|
|
|
@ -6,6 +6,7 @@ const mockStrategy = require("passport-google-oauth").OAuth2Strategy
|
|||
|
||||
jest.mock("../sso")
|
||||
import * as _sso from "../sso"
|
||||
|
||||
const sso = jest.mocked(_sso)
|
||||
|
||||
const mockSaveUserFn = jest.fn()
|
||||
|
|
|
@ -11,6 +11,7 @@ const mockSaveUser = jest.fn()
|
|||
|
||||
jest.mock("../../../../users")
|
||||
import * as _users from "../../../../users"
|
||||
|
||||
const users = jest.mocked(_users)
|
||||
|
||||
const getErrorMessage = () => {
|
||||
|
|
|
@ -5,6 +5,7 @@ import { structures } from "../../../tests"
|
|||
import { ContextUser, ServiceType } from "@budibase/types"
|
||||
import { doInAppContext } from "../../context"
|
||||
import env from "../../environment"
|
||||
|
||||
env._set("SERVICE_TYPE", ServiceType.APPS)
|
||||
|
||||
const appId = "app_aaa"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const sanitize = require("sanitize-s3-objectkey")
|
||||
|
||||
import AWS from "aws-sdk"
|
||||
import stream, { Readable } from "stream"
|
||||
import fetch from "node-fetch"
|
||||
|
|
|
@ -3,6 +3,7 @@ import { getLockClient } from "./init"
|
|||
import { LockOptions, LockType } from "@budibase/types"
|
||||
import * as context from "../context"
|
||||
import env from "../environment"
|
||||
import { logWarn } from "../logging"
|
||||
|
||||
async function getClient(
|
||||
type: LockType,
|
||||
|
@ -116,7 +117,7 @@ export async function doWithLock<T>(
|
|||
const result = await task()
|
||||
return { executed: true, result }
|
||||
} catch (e: any) {
|
||||
console.warn("lock error")
|
||||
logWarn(`lock type: ${opts.type} error`, e)
|
||||
// lock limit exceeded
|
||||
if (e.name === "LockError") {
|
||||
if (opts.type === LockType.TRY_ONCE) {
|
||||
|
@ -124,11 +125,9 @@ export async function doWithLock<T>(
|
|||
// due to retry count (0) exceeded
|
||||
return { executed: false }
|
||||
} else {
|
||||
console.error(e)
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
console.error(e)
|
||||
throw e
|
||||
}
|
||||
} finally {
|
||||
|
|
|
@ -75,10 +75,12 @@ export function getRedisConnectionDetails() {
|
|||
}
|
||||
const [host, port] = url.split(":")
|
||||
|
||||
const portNumber = parseInt(port)
|
||||
return {
|
||||
host,
|
||||
password,
|
||||
port: parseInt(port),
|
||||
// assume default port for redis if invalid found
|
||||
port: isNaN(portNumber) ? 6379 : portNumber,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
import { BuiltinPermissionID, PermissionLevel } from "./permissions"
|
||||
import { prefixRoleID, getRoleParams, DocumentType, SEPARATOR } from "../db"
|
||||
import {
|
||||
prefixRoleID,
|
||||
getRoleParams,
|
||||
DocumentType,
|
||||
SEPARATOR,
|
||||
doWithDB,
|
||||
} from "../db"
|
||||
import { getAppDB } from "../context"
|
||||
import { doWithDB } from "../db"
|
||||
import { Screen, Role as RoleDoc } from "@budibase/types"
|
||||
import cloneDeep from "lodash/fp/cloneDeep"
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
const redis = require("../redis/init")
|
||||
const { v4: uuidv4 } = require("uuid")
|
||||
const { logWarn } = require("../logging")
|
||||
|
||||
import env from "../environment"
|
||||
import {
|
||||
Session,
|
||||
|
|
|
@ -1,9 +1,8 @@
|
|||
import env from "../environment"
|
||||
import * as eventHelpers from "./events"
|
||||
import * as accounts from "../accounts"
|
||||
import * as accountSdk from "../accounts"
|
||||
import * as cache from "../cache"
|
||||
import { getGlobalDB, getIdentity, getTenantId } from "../context"
|
||||
import { doInTenant, getGlobalDB, getIdentity, getTenantId } from "../context"
|
||||
import * as dbUtils from "../db"
|
||||
import { EmailUnavailableError, HTTPError } from "../errors"
|
||||
import * as platform from "../platform"
|
||||
|
@ -11,12 +10,10 @@ import * as sessions from "../security/sessions"
|
|||
import * as usersCore from "./users"
|
||||
import {
|
||||
Account,
|
||||
AllDocsResponse,
|
||||
BulkUserCreated,
|
||||
BulkUserDeleted,
|
||||
isSSOAccount,
|
||||
isSSOUser,
|
||||
RowResponse,
|
||||
SaveUserOpts,
|
||||
User,
|
||||
UserStatus,
|
||||
|
@ -467,7 +464,7 @@ export class UserDB {
|
|||
if (!env.SELF_HOSTED && !env.DISABLE_ACCOUNT_PORTAL) {
|
||||
// root account holder can't be deleted from inside budibase
|
||||
const email = dbUser.email
|
||||
const account = await accounts.getAccount(email)
|
||||
const account = await accountSdk.getAccount(email)
|
||||
if (account) {
|
||||
if (dbUser.userId === getIdentity()!._id) {
|
||||
throw new HTTPError('Please visit "Account" to delete this user', 400)
|
||||
|
@ -488,6 +485,37 @@ export class UserDB {
|
|||
await sessions.invalidateSessions(userId, { reason: "deletion" })
|
||||
}
|
||||
|
||||
static async createAdminUser(
|
||||
email: string,
|
||||
password: string,
|
||||
tenantId: string,
|
||||
opts?: { ssoId?: string; hashPassword?: boolean; requirePassword?: boolean }
|
||||
) {
|
||||
const user: User = {
|
||||
email: email,
|
||||
password: password,
|
||||
createdAt: Date.now(),
|
||||
roles: {},
|
||||
builder: {
|
||||
global: true,
|
||||
},
|
||||
admin: {
|
||||
global: true,
|
||||
},
|
||||
tenantId,
|
||||
}
|
||||
if (opts?.ssoId) {
|
||||
user.ssoId = opts.ssoId
|
||||
}
|
||||
// always bust checklist beforehand, if an error occurs but can proceed, don't get
|
||||
// stuck in a cycle
|
||||
await cache.bustCache(cache.CacheKey.CHECKLIST)
|
||||
return await UserDB.save(user, {
|
||||
hashPassword: opts?.hashPassword,
|
||||
requirePassword: opts?.requirePassword,
|
||||
})
|
||||
}
|
||||
|
||||
static async getGroups(groupIds: string[]) {
|
||||
return await this.groups.getBulk(groupIds)
|
||||
}
|
||||
|
|
|
@ -43,7 +43,7 @@ function removeUserPassword(users: User | User[]) {
|
|||
return users
|
||||
}
|
||||
|
||||
export const isSupportedUserSearch = (query: SearchQuery) => {
|
||||
export function isSupportedUserSearch(query: SearchQuery) {
|
||||
const allowed = [
|
||||
{ op: SearchQueryOperators.STRING, key: "email" },
|
||||
{ op: SearchQueryOperators.EQUAL, key: "_id" },
|
||||
|
@ -68,10 +68,10 @@ export const isSupportedUserSearch = (query: SearchQuery) => {
|
|||
return true
|
||||
}
|
||||
|
||||
export const bulkGetGlobalUsersById = async (
|
||||
export async function bulkGetGlobalUsersById(
|
||||
userIds: string[],
|
||||
opts?: GetOpts
|
||||
) => {
|
||||
) {
|
||||
const db = getGlobalDB()
|
||||
let users = (
|
||||
await db.allDocs({
|
||||
|
@ -85,7 +85,7 @@ export const bulkGetGlobalUsersById = async (
|
|||
return users
|
||||
}
|
||||
|
||||
export const getAllUserIds = async () => {
|
||||
export async function getAllUserIds() {
|
||||
const db = getGlobalDB()
|
||||
const startKey = `${DocumentType.USER}${SEPARATOR}`
|
||||
const response = await db.allDocs({
|
||||
|
@ -95,7 +95,7 @@ export const getAllUserIds = async () => {
|
|||
return response.rows.map(row => row.id)
|
||||
}
|
||||
|
||||
export const bulkUpdateGlobalUsers = async (users: User[]) => {
|
||||
export async function bulkUpdateGlobalUsers(users: User[]) {
|
||||
const db = getGlobalDB()
|
||||
return (await db.bulkDocs(users)) as BulkDocsResponse
|
||||
}
|
||||
|
@ -113,10 +113,10 @@ export async function getById(id: string, opts?: GetOpts): Promise<User> {
|
|||
* Given an email address this will use a view to search through
|
||||
* all the users to find one with this email address.
|
||||
*/
|
||||
export const getGlobalUserByEmail = async (
|
||||
export async function getGlobalUserByEmail(
|
||||
email: String,
|
||||
opts?: GetOpts
|
||||
): Promise<User | undefined> => {
|
||||
): Promise<User | undefined> {
|
||||
if (email == null) {
|
||||
throw "Must supply an email address to view"
|
||||
}
|
||||
|
@ -139,11 +139,23 @@ export const getGlobalUserByEmail = async (
|
|||
return user
|
||||
}
|
||||
|
||||
export const searchGlobalUsersByApp = async (
|
||||
export async function doesUserExist(email: string) {
|
||||
try {
|
||||
const user = await getGlobalUserByEmail(email)
|
||||
if (Array.isArray(user) || user != null) {
|
||||
return true
|
||||
}
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
export async function searchGlobalUsersByApp(
|
||||
appId: any,
|
||||
opts: DatabaseQueryOpts,
|
||||
getOpts?: GetOpts
|
||||
) => {
|
||||
) {
|
||||
if (typeof appId !== "string") {
|
||||
throw new Error("Must provide a string based app ID")
|
||||
}
|
||||
|
@ -167,10 +179,10 @@ export const searchGlobalUsersByApp = async (
|
|||
Return any user who potentially has access to the application
|
||||
Admins, developers and app users with the explicitly role.
|
||||
*/
|
||||
export const searchGlobalUsersByAppAccess = async (
|
||||
export async function searchGlobalUsersByAppAccess(
|
||||
appId: any,
|
||||
opts?: { limit?: number }
|
||||
) => {
|
||||
) {
|
||||
const roleSelector = `roles.${appId}`
|
||||
|
||||
let orQuery: any[] = [
|
||||
|
@ -205,7 +217,7 @@ export const searchGlobalUsersByAppAccess = async (
|
|||
return resp.rows
|
||||
}
|
||||
|
||||
export const getGlobalUserByAppPage = (appId: string, user: User) => {
|
||||
export function getGlobalUserByAppPage(appId: string, user: User) {
|
||||
if (!user) {
|
||||
return
|
||||
}
|
||||
|
@ -215,11 +227,11 @@ export const getGlobalUserByAppPage = (appId: string, user: User) => {
|
|||
/**
|
||||
* Performs a starts with search on the global email view.
|
||||
*/
|
||||
export const searchGlobalUsersByEmail = async (
|
||||
export async function searchGlobalUsersByEmail(
|
||||
email: string | unknown,
|
||||
opts: any,
|
||||
getOpts?: GetOpts
|
||||
) => {
|
||||
) {
|
||||
if (typeof email !== "string") {
|
||||
throw new Error("Must provide a string to search by")
|
||||
}
|
||||
|
@ -242,12 +254,12 @@ export const searchGlobalUsersByEmail = async (
|
|||
}
|
||||
|
||||
const PAGE_LIMIT = 8
|
||||
export const paginatedUsers = async ({
|
||||
export async function paginatedUsers({
|
||||
bookmark,
|
||||
query,
|
||||
appId,
|
||||
limit,
|
||||
}: SearchUsersRequest = {}) => {
|
||||
}: SearchUsersRequest = {}) {
|
||||
const db = getGlobalDB()
|
||||
const pageSize = limit ?? PAGE_LIMIT
|
||||
const pageLimit = pageSize + 1
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import env from "../environment"
|
||||
|
||||
export * from "../docIds/newid"
|
||||
const bcrypt = env.JS_BCRYPT ? require("bcryptjs") : require("bcrypt")
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ import {
|
|||
TenantResolutionStrategy,
|
||||
} from "@budibase/types"
|
||||
import type { SetOption } from "cookies"
|
||||
|
||||
const jwt = require("jsonwebtoken")
|
||||
|
||||
const APP_PREFIX = DocumentType.APP + SEPARATOR
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const _ = require('lodash/fp')
|
||||
const {structures} = require("../../../tests")
|
||||
const _ = require("lodash/fp")
|
||||
const { structures } = require("../../../tests")
|
||||
|
||||
jest.mock("../../../src/context")
|
||||
jest.mock("../../../src/db")
|
||||
|
@ -7,10 +7,9 @@ jest.mock("../../../src/db")
|
|||
const context = require("../../../src/context")
|
||||
const db = require("../../../src/db")
|
||||
|
||||
const {getCreatorCount} = require('../../../src/users/users')
|
||||
const { getCreatorCount } = require("../../../src/users/users")
|
||||
|
||||
describe("Users", () => {
|
||||
|
||||
let getGlobalDBMock
|
||||
let getGlobalUserParamsMock
|
||||
let paginationMock
|
||||
|
@ -26,26 +25,26 @@ describe("Users", () => {
|
|||
it("Retrieves the number of creators", async () => {
|
||||
const getUsers = (offset, limit, creators = false) => {
|
||||
const range = _.range(offset, limit)
|
||||
const opts = creators ? {builder: {global: true}} : undefined
|
||||
const opts = creators ? { builder: { global: true } } : undefined
|
||||
return range.map(() => structures.users.user(opts))
|
||||
}
|
||||
const page1Data = getUsers(0, 8)
|
||||
const page2Data = getUsers(8, 12, true)
|
||||
getGlobalDBMock.mockImplementation(() => ({
|
||||
name : "fake-db",
|
||||
name: "fake-db",
|
||||
allDocs: () => ({
|
||||
rows: [...page1Data, ...page2Data]
|
||||
})
|
||||
rows: [...page1Data, ...page2Data],
|
||||
}),
|
||||
}))
|
||||
paginationMock.mockImplementationOnce(() => ({
|
||||
data: page1Data,
|
||||
hasNextPage: true,
|
||||
nextPage: "1"
|
||||
nextPage: "1",
|
||||
}))
|
||||
paginationMock.mockImplementation(() => ({
|
||||
data: page2Data,
|
||||
hasNextPage: false,
|
||||
nextPage: undefined
|
||||
nextPage: undefined,
|
||||
}))
|
||||
const creatorsCount = await getCreatorCount()
|
||||
expect(creatorsCount).toBe(4)
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
jest.mock("../../../../src/logging/alerts")
|
||||
import * as _alerts from "../../../../src/logging/alerts"
|
||||
|
||||
export const alerts = jest.mocked(_alerts)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
jest.mock("../../../../src/accounts")
|
||||
import * as _accounts from "../../../../src/accounts"
|
||||
|
||||
export const accounts = jest.mocked(_accounts)
|
||||
|
||||
export * as date from "./date"
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
import Chance from "./Chance"
|
||||
|
||||
export const generator = new Chance()
|
||||
|
|
|
@ -9,6 +9,7 @@ mocks.fetch.enable()
|
|||
// mock all dates to 2020-01-01T00:00:00.000Z
|
||||
// use tk.reset() to use real dates in individual tests
|
||||
import tk from "timekeeper"
|
||||
|
||||
tk.freeze(mocks.date.MOCK_DATE)
|
||||
|
||||
if (!process.env.DEBUG) {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import "@spectrum-css/actiongroup/dist/index-vars.css"
|
||||
|
||||
export let vertical = false
|
||||
export let justified = false
|
||||
export let quiet = false
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import "@spectrum-css/avatar/dist/index-vars.css"
|
||||
|
||||
let sizes = new Map([
|
||||
["XXS", "--spectrum-alias-avatar-size-50"],
|
||||
["XS", "--spectrum-alias-avatar-size-75"],
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import "@spectrum-css/buttongroup/dist/index-vars.css"
|
||||
|
||||
export let vertical = false
|
||||
export let gap = ""
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import "@spectrum-css/divider/dist/index-vars.css"
|
||||
|
||||
export let size = "M"
|
||||
|
||||
export let vertical = false
|
||||
|
|
|
@ -3,8 +3,7 @@
|
|||
import Button from "../Button/Button.svelte"
|
||||
import Body from "../Typography/Body.svelte"
|
||||
import Heading from "../Typography/Heading.svelte"
|
||||
import { setContext } from "svelte"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { setContext, createEventDispatcher } from "svelte"
|
||||
import { generate } from "shortid"
|
||||
|
||||
export let title
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
export let disabled = false
|
||||
export let error = null
|
||||
export let size = "M"
|
||||
export let helpText = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -18,6 +19,6 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<Checkbox {error} {disabled} {text} {value} {size} on:change={onChange} />
|
||||
</Field>
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
export let error = null
|
||||
export let placeholder = "Choose an option or type"
|
||||
export let options = []
|
||||
export let helpText = null
|
||||
export let getOptionLabel = option => extractProperty(option, "label")
|
||||
export let getOptionValue = option => extractProperty(option, "value")
|
||||
|
||||
|
@ -27,7 +28,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<Combobox
|
||||
{error}
|
||||
{disabled}
|
||||
|
|
|
@ -4,7 +4,6 @@
|
|||
import { createEventDispatcher } from "svelte"
|
||||
|
||||
export let value = false
|
||||
export let error = null
|
||||
export let id = null
|
||||
export let text = null
|
||||
export let disabled = false
|
||||
|
@ -22,7 +21,6 @@
|
|||
|
||||
<label
|
||||
class="spectrum-Checkbox spectrum-Checkbox--emphasized {sizeClass}"
|
||||
class:is-invalid={!!error}
|
||||
class:checked={value}
|
||||
class:is-indeterminate={indeterminate}
|
||||
class:readonly
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
export let direction = "vertical"
|
||||
export let value = []
|
||||
export let options = []
|
||||
export let error = null
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let getOptionLabel = option => option
|
||||
|
@ -34,7 +33,6 @@
|
|||
<div
|
||||
title={getOptionLabel(option)}
|
||||
class="spectrum-Checkbox spectrum-FieldGroup-item"
|
||||
class:is-invalid={!!error}
|
||||
class:readonly
|
||||
>
|
||||
<label
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
export let placeholder = "Choose an option or type"
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let error = null
|
||||
export let options = []
|
||||
export let getOptionLabel = option => option
|
||||
export let getOptionValue = option => option
|
||||
|
@ -39,12 +38,10 @@
|
|||
<div
|
||||
class="spectrum-InputGroup"
|
||||
class:is-focused={open || focus}
|
||||
class:is-invalid={!!error}
|
||||
class:is-disabled={disabled}
|
||||
>
|
||||
<div
|
||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||
class:is-invalid={!!error}
|
||||
class:is-disabled={disabled}
|
||||
class:is-focused={open || focus}
|
||||
>
|
||||
|
|
|
@ -10,7 +10,6 @@
|
|||
export let id = null
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let error = null
|
||||
export let enableTime = true
|
||||
export let value = null
|
||||
export let placeholder = null
|
||||
|
@ -188,7 +187,6 @@
|
|||
<div
|
||||
id={flatpickrId}
|
||||
class:is-disabled={disabled || readonly}
|
||||
class:is-invalid={!!error}
|
||||
class="flatpickr spectrum-InputGroup spectrum-Datepicker"
|
||||
class:is-focused={open}
|
||||
aria-readonly="false"
|
||||
|
@ -199,17 +197,7 @@
|
|||
on:click={flatpickr?.open}
|
||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||
class:is-disabled={disabled}
|
||||
class:is-invalid={!!error}
|
||||
>
|
||||
{#if !!error}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
||||
</svg>
|
||||
{/if}
|
||||
<input
|
||||
{disabled}
|
||||
{readonly}
|
||||
|
@ -227,7 +215,6 @@
|
|||
class="spectrum-Picker spectrum-Picker--sizeM spectrum-InputGroup-button"
|
||||
tabindex="-1"
|
||||
class:is-disabled={disabled}
|
||||
class:is-invalid={!!error}
|
||||
on:click={flatpickr?.open}
|
||||
>
|
||||
<svg
|
||||
|
|
|
@ -22,7 +22,6 @@
|
|||
export let handleFileTooLarge = null
|
||||
export let handleTooManyFiles = null
|
||||
export let gallery = true
|
||||
export let error = null
|
||||
export let fileTags = []
|
||||
export let maximum = null
|
||||
export let extensions = "*"
|
||||
|
@ -222,7 +221,6 @@
|
|||
{#if showDropzone}
|
||||
<div
|
||||
class="spectrum-Dropzone"
|
||||
class:is-invalid={!!error}
|
||||
class:disabled
|
||||
role="region"
|
||||
tabindex="0"
|
||||
|
@ -351,9 +349,6 @@
|
|||
.spectrum-Dropzone {
|
||||
user-select: none;
|
||||
}
|
||||
.spectrum-Dropzone.is-invalid {
|
||||
border-color: var(--spectrum-global-color-red-400);
|
||||
}
|
||||
input[type="file"] {
|
||||
display: none;
|
||||
}
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let updateOnChange = true
|
||||
export let error = null
|
||||
export let options = []
|
||||
export let getOptionLabel = option => extractProperty(option, "label")
|
||||
export let getOptionValue = option => extractProperty(option, "value")
|
||||
|
@ -111,27 +110,12 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="spectrum-InputGroup"
|
||||
class:is-invalid={!!error}
|
||||
class:is-disabled={disabled}
|
||||
>
|
||||
<div class="spectrum-InputGroup" class:is-disabled={disabled}>
|
||||
<div
|
||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||
class:is-invalid={!!error}
|
||||
class:is-disabled={disabled}
|
||||
class:is-focused={focus}
|
||||
>
|
||||
{#if error}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
||||
</svg>
|
||||
{/if}
|
||||
|
||||
<input
|
||||
{id}
|
||||
on:click
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
export let id = null
|
||||
export let placeholder = null
|
||||
export let disabled = false
|
||||
export let error = null
|
||||
export let options = []
|
||||
export let getOptionLabel = option => option
|
||||
export let getOptionValue = option => option
|
||||
|
@ -84,7 +83,6 @@
|
|||
<Picker
|
||||
on:loadMore
|
||||
{id}
|
||||
{error}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{fieldText}
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
export let id = null
|
||||
export let disabled = false
|
||||
export let error = null
|
||||
export let fieldText = ""
|
||||
export let fieldIcon = ""
|
||||
export let fieldColour = ""
|
||||
|
@ -113,7 +112,6 @@
|
|||
class="spectrum-Picker spectrum-Picker--sizeM"
|
||||
class:spectrum-Picker--quiet={quiet}
|
||||
{disabled}
|
||||
class:is-invalid={!!error}
|
||||
class:is-open={open}
|
||||
aria-haspopup="listbox"
|
||||
on:click={onClick}
|
||||
|
@ -142,16 +140,6 @@
|
|||
>
|
||||
{fieldText}
|
||||
</span>
|
||||
{#if error}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Picker-validationIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
aria-label="Folder"
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
||||
</svg>
|
||||
{/if}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-UIIcon-ChevronDown100 spectrum-Picker-menuIcon"
|
||||
focusable="false"
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
export let id = null
|
||||
export let placeholder = "Choose an option or type"
|
||||
export let disabled = false
|
||||
export let error = null
|
||||
export let secondaryOptions = []
|
||||
export let primaryOptions = []
|
||||
export let secondaryFieldText = ""
|
||||
|
@ -105,14 +104,9 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<div
|
||||
class="spectrum-InputGroup"
|
||||
class:is-invalid={!!error}
|
||||
class:is-disabled={disabled}
|
||||
>
|
||||
<div class="spectrum-InputGroup" class:is-disabled={disabled}>
|
||||
<div
|
||||
class="spectrum-Textfield spectrum-InputGroup-textfield"
|
||||
class:is-invalid={!!error}
|
||||
class:is-disabled={disabled}
|
||||
class:is-focused={focus}
|
||||
class:is-full-width={!secondaryOptions.length}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
export let direction = "vertical"
|
||||
export let value = null
|
||||
export let options = []
|
||||
export let error = null
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let getOptionLabel = option => option
|
||||
|
@ -40,7 +39,6 @@
|
|||
<div
|
||||
title={getOptionTitle(option)}
|
||||
class="spectrum-Radio spectrum-FieldGroup-item spectrum-Radio--emphasized"
|
||||
class:is-invalid={!!error}
|
||||
class:readonly
|
||||
>
|
||||
<input
|
||||
|
|
|
@ -5,14 +5,13 @@
|
|||
export let placeholder = null
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let error = null
|
||||
export let height = null
|
||||
export let id = null
|
||||
export let fullScreenOffset = null
|
||||
export let easyMDEOptions = null
|
||||
</script>
|
||||
|
||||
<div class:error>
|
||||
<div>
|
||||
<MarkdownEditor
|
||||
{value}
|
||||
{placeholder}
|
||||
|
@ -27,18 +26,4 @@
|
|||
</div>
|
||||
|
||||
<style>
|
||||
.error :global(.EasyMDEContainer .editor-toolbar) {
|
||||
border-top-color: var(--spectrum-semantic-negative-color-default);
|
||||
border-left-color: var(--spectrum-semantic-negative-color-default);
|
||||
border-right-color: var(--spectrum-semantic-negative-color-default);
|
||||
}
|
||||
.error :global(.EasyMDEContainer .CodeMirror) {
|
||||
border-bottom-color: var(--spectrum-semantic-negative-color-default);
|
||||
border-left-color: var(--spectrum-semantic-negative-color-default);
|
||||
border-right-color: var(--spectrum-semantic-negative-color-default);
|
||||
}
|
||||
.error :global(.EasyMDEContainer .editor-preview-side) {
|
||||
border-bottom-color: var(--spectrum-semantic-negative-color-default);
|
||||
border-right-color: var(--spectrum-semantic-negative-color-default);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
export let id = null
|
||||
export let placeholder = "Choose an option"
|
||||
export let disabled = false
|
||||
export let error = null
|
||||
export let options = []
|
||||
export let getOptionLabel = option => option
|
||||
export let getOptionValue = option => option
|
||||
|
@ -71,7 +70,6 @@
|
|||
on:loadMore
|
||||
{quiet}
|
||||
{id}
|
||||
{error}
|
||||
{disabled}
|
||||
{readonly}
|
||||
{fieldText}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
export let value = null
|
||||
export let placeholder = null
|
||||
export let disabled = false
|
||||
export let error = null
|
||||
export let id = null
|
||||
export let readonly = false
|
||||
export let updateOnChange = true
|
||||
|
@ -98,20 +97,9 @@
|
|||
<div
|
||||
class="spectrum-Stepper"
|
||||
class:spectrum-Stepper--quiet={quiet}
|
||||
class:is-invalid={!!error}
|
||||
class:is-disabled={disabled}
|
||||
class:is-focused={focus}
|
||||
>
|
||||
{#if error}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
||||
</svg>
|
||||
{/if}
|
||||
|
||||
<div class="spectrum-Textfield spectrum-Stepper-textfield">
|
||||
<input
|
||||
{disabled}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
export let placeholder = null
|
||||
export let disabled = false
|
||||
export let readonly = false
|
||||
export let error = null
|
||||
export let id = null
|
||||
export let height = null
|
||||
export let minHeight = null
|
||||
|
@ -41,20 +40,9 @@
|
|||
<div
|
||||
style={`${heightString}${minHeightString}`}
|
||||
class="spectrum-Textfield spectrum-Textfield--multiline"
|
||||
class:is-invalid={!!error}
|
||||
class:is-disabled={disabled}
|
||||
class:is-focused={focus}
|
||||
>
|
||||
{#if error}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-Icon--sizeM
|
||||
spectrum-Textfield-validationIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
||||
</svg>
|
||||
{/if}
|
||||
<!-- prettier-ignore -->
|
||||
<textarea
|
||||
bind:this={textarea}
|
||||
|
|
|
@ -6,7 +6,6 @@
|
|||
export let placeholder = null
|
||||
export let type = "text"
|
||||
export let disabled = false
|
||||
export let error = null
|
||||
export let id = null
|
||||
export let readonly = false
|
||||
export let updateOnChange = true
|
||||
|
@ -78,19 +77,9 @@
|
|||
<div
|
||||
class="spectrum-Textfield"
|
||||
class:spectrum-Textfield--quiet={quiet}
|
||||
class:is-invalid={!!error}
|
||||
class:is-disabled={disabled}
|
||||
class:is-focused={focus}
|
||||
>
|
||||
{#if error}
|
||||
<svg
|
||||
class="spectrum-Icon spectrum-Icon--sizeM spectrum-Textfield-validationIcon"
|
||||
focusable="false"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<use xlink:href="#spectrum-icon-18-Alert" />
|
||||
</svg>
|
||||
{/if}
|
||||
<input
|
||||
bind:this={field}
|
||||
{disabled}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
export let appendTo = undefined
|
||||
export let ignoreTimezones = false
|
||||
export let range = false
|
||||
export let helpText = null
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const onChange = e => {
|
||||
|
@ -30,7 +31,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<DatePicker
|
||||
{error}
|
||||
{disabled}
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
export let fileTags = []
|
||||
export let maximum = undefined
|
||||
export let compact = false
|
||||
export let helpText = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -25,7 +26,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<CoreDropzone
|
||||
{error}
|
||||
{disabled}
|
||||
|
|
|
@ -16,6 +16,7 @@
|
|||
export let autofocus
|
||||
export let variables
|
||||
export let showModal
|
||||
export let helpText = null
|
||||
export let environmentVariablesEnabled
|
||||
export let handleUpgradePanel
|
||||
const dispatch = createEventDispatcher()
|
||||
|
@ -25,7 +26,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<EnvDropdown
|
||||
{updateOnChange}
|
||||
{error}
|
||||
|
|
|
@ -1,11 +1,13 @@
|
|||
<script>
|
||||
import "@spectrum-css/fieldlabel/dist/index-vars.css"
|
||||
import FieldLabel from "./FieldLabel.svelte"
|
||||
import Icon from "../Icon/Icon.svelte"
|
||||
|
||||
export let id = null
|
||||
export let label = null
|
||||
export let labelPosition = "above"
|
||||
export let error = null
|
||||
export let helpText = null
|
||||
export let tooltip = ""
|
||||
</script>
|
||||
|
||||
|
@ -17,6 +19,10 @@
|
|||
<slot />
|
||||
{#if error}
|
||||
<div class="error">{error}</div>
|
||||
{:else if helpText}
|
||||
<div class="helpText">
|
||||
<Icon name="HelpOutline" /> <span>{helpText}</span>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
@ -39,4 +45,21 @@
|
|||
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||
margin-top: var(--spectrum-global-dimension-size-75);
|
||||
}
|
||||
|
||||
.helpText {
|
||||
display: flex;
|
||||
margin-top: var(--spectrum-global-dimension-size-75);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.helpText :global(svg) {
|
||||
width: 14px;
|
||||
color: var(--grey-5);
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.helpText span {
|
||||
color: var(--grey-7);
|
||||
font-size: var(--spectrum-global-dimension-font-size-75);
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
export let title = null
|
||||
export let value = null
|
||||
export let tooltip = null
|
||||
export let helpText = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -22,7 +23,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error} {tooltip}>
|
||||
<Field {helpText} {label} {labelPosition} {error} {tooltip}>
|
||||
<CoreFile
|
||||
{error}
|
||||
{disabled}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
export let quiet = false
|
||||
export let autofocus
|
||||
export let autocomplete
|
||||
export let helpText = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -23,7 +24,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<TextField
|
||||
{updateOnChange}
|
||||
{error}
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
export let updateOnChange = true
|
||||
export let quiet = false
|
||||
export let autofocus
|
||||
export let helpText = null
|
||||
export let options = []
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
@ -29,7 +30,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<InputDropdown
|
||||
{updateOnChange}
|
||||
{error}
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
export let autocomplete = false
|
||||
export let searchTerm = null
|
||||
export let customPopoverHeight
|
||||
export let helpText = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -26,7 +27,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<Multiselect
|
||||
{error}
|
||||
{disabled}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
export let secondaryOptions = []
|
||||
export let searchTerm
|
||||
export let showClearIcon = true
|
||||
export let helpText = null
|
||||
|
||||
let primaryLabel
|
||||
let secondaryLabel
|
||||
|
@ -93,7 +94,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<PickerDropdown
|
||||
{searchTerm}
|
||||
{autocomplete}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
export let getOptionLabel = option => extractProperty(option, "label")
|
||||
export let getOptionValue = option => extractProperty(option, "value")
|
||||
export let getOptionTitle = option => extractProperty(option, "label")
|
||||
export let helpText = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -27,7 +28,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<RadioGroup
|
||||
{error}
|
||||
{disabled}
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
export let id = null
|
||||
export let fullScreenOffset = null
|
||||
export let easyMDEOptions = null
|
||||
export let helpText = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -21,7 +22,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<RichTextField
|
||||
{error}
|
||||
{disabled}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
export let updateOnChange = true
|
||||
export let quiet = false
|
||||
export let inputRef
|
||||
export let helpText = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -19,7 +20,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition}>
|
||||
<Field {helpText} {label} {labelPosition}>
|
||||
<Search
|
||||
{updateOnChange}
|
||||
{disabled}
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
export let align
|
||||
export let footer = null
|
||||
export let tag = null
|
||||
export let helpText = null
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
value = e.detail
|
||||
|
@ -40,7 +41,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error} {tooltip}>
|
||||
<Field {helpText} {label} {labelPosition} {error} {tooltip}>
|
||||
<Select
|
||||
{quiet}
|
||||
{error}
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
export let step = 1
|
||||
export let disabled = false
|
||||
export let error = null
|
||||
export let helpText = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -19,6 +20,6 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<Slider {disabled} {value} {min} {max} {step} on:change={onChange} />
|
||||
</Field>
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
export let min = null
|
||||
export let max = null
|
||||
export let step = 1
|
||||
export let helpText = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -23,7 +24,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<Stepper
|
||||
{updateOnChange}
|
||||
{error}
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
export let getCaretPosition = null
|
||||
export let height = null
|
||||
export let minHeight = null
|
||||
export let helpText = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -20,7 +21,7 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<TextArea
|
||||
bind:getCaretPosition
|
||||
{error}
|
||||
|
|
|
@ -9,6 +9,7 @@
|
|||
export let text = null
|
||||
export let disabled = false
|
||||
export let error = null
|
||||
export let helpText = null
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
const onChange = e => {
|
||||
|
@ -17,6 +18,6 @@
|
|||
}
|
||||
</script>
|
||||
|
||||
<Field {label} {labelPosition} {error}>
|
||||
<Field {helpText} {label} {labelPosition} {error}>
|
||||
<Switch {error} {disabled} {text} {value} on:change={onChange} />
|
||||
</Field>
|
||||
|
|
|
@ -16,10 +16,9 @@
|
|||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
const onClick = e => {
|
||||
const onClick = () => {
|
||||
if (!disabled) {
|
||||
dispatch("click")
|
||||
e.stopPropagation()
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
<script>
|
||||
import Input from "../Form/Input.svelte"
|
||||
|
||||
let value = ""
|
||||
</script>
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
import Icon from "../Icon/Icon.svelte"
|
||||
import Popover from "../Popover/Popover.svelte"
|
||||
import { onMount } from "svelte"
|
||||
|
||||
const flipDurationMs = 150
|
||||
|
||||
export let constraints
|
||||
|
|
|
@ -1,11 +1,10 @@
|
|||
<script>
|
||||
import "@spectrum-css/popover/dist/index-vars.css"
|
||||
import Portal from "svelte-portal"
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { createEventDispatcher, getContext } from "svelte"
|
||||
import positionDropdown from "../Actions/position_dropdown"
|
||||
import clickOutside from "../Actions/click_outside"
|
||||
import { fly } from "svelte/transition"
|
||||
import { getContext } from "svelte"
|
||||
import Context from "../context"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
<script>
|
||||
import { getContext } from "svelte"
|
||||
|
||||
const multilevel = getContext("sidenav-type")
|
||||
import Badge from "../Badge/Badge.svelte"
|
||||
|
||||
export let href = ""
|
||||
export let external = false
|
||||
export let heading = ""
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { setContext } from "svelte"
|
||||
import "@spectrum-css/sidenav/dist/index-vars.css"
|
||||
|
||||
export let multilevel = false
|
||||
setContext("sidenav-type", multilevel)
|
||||
</script>
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import "@spectrum-css/label/dist/index-vars.css"
|
||||
import Badge from "../Badge/Badge.svelte"
|
||||
|
||||
export let value
|
||||
|
||||
const displayLimit = 5
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
<script>
|
||||
import { getContext, onMount, createEventDispatcher } from "svelte"
|
||||
import Portal from "svelte-portal"
|
||||
|
||||
export let title
|
||||
export let icon = ""
|
||||
export let id
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { helpers } from "@budibase/shared-core"
|
||||
|
||||
export const deepGet = helpers.deepGet
|
||||
|
||||
/**
|
||||
|
|
|
@ -4,11 +4,10 @@ import { getTemporalStore } from "./store/temporal"
|
|||
import { getThemeStore } from "./store/theme"
|
||||
import { getUserStore } from "./store/users"
|
||||
import { getDeploymentStore } from "./store/deployments"
|
||||
import { derived, writable } from "svelte/store"
|
||||
import { derived, writable, get } from "svelte/store"
|
||||
import { findComponent, findComponentPath } from "./componentUtils"
|
||||
import { RoleUtils } from "@budibase/frontend-core"
|
||||
import { createHistoryStore } from "builderStore/store/history"
|
||||
import { get } from "svelte/store"
|
||||
|
||||
export const store = getFrontendStore()
|
||||
export const automationStore = getAutomationStore()
|
||||
|
|
|
@ -7,11 +7,9 @@ import {
|
|||
} from "builderStore"
|
||||
import { datasources, tables } from "stores/backend"
|
||||
import { get } from "svelte/store"
|
||||
import { auth } from "stores/portal"
|
||||
import { SocketEvent, BuilderSocketEvent } from "@budibase/shared-core"
|
||||
import { apps } from "stores/portal"
|
||||
import { auth, apps } from "stores/portal"
|
||||
import { SocketEvent, BuilderSocketEvent, helpers } from "@budibase/shared-core"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { helpers } from "@budibase/shared-core"
|
||||
|
||||
export const createBuilderWebsocket = appId => {
|
||||
const socket = createWebsocket("/socket/builder")
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
<script>
|
||||
import { automationStore, selectedAutomation } from "builderStore"
|
||||
import {
|
||||
automationStore,
|
||||
selectedAutomation,
|
||||
automationHistoryStore,
|
||||
} from "builderStore"
|
||||
import ConfirmDialog from "components/common/ConfirmDialog.svelte"
|
||||
import FlowItem from "./FlowItem.svelte"
|
||||
import TestDataModal from "./TestDataModal.svelte"
|
||||
|
@ -8,7 +12,6 @@
|
|||
import { Icon, notifications, Modal } from "@budibase/bbui"
|
||||
import { ActionStepID } from "constants/backend/automations"
|
||||
import UndoRedoControl from "components/common/UndoRedoControl.svelte"
|
||||
import { automationHistoryStore } from "builderStore"
|
||||
|
||||
export let automation
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<script>
|
||||
import { automationStore } from "builderStore"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import {
|
||||
notifications,
|
||||
Input,
|
||||
InlineAlert,
|
||||
ModalContent,
|
||||
|
|
|
@ -1,7 +1,12 @@
|
|||
<script>
|
||||
import { automationStore } from "builderStore"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { Icon, Input, ModalContent, Modal } from "@budibase/bbui"
|
||||
import {
|
||||
notifications,
|
||||
Icon,
|
||||
Input,
|
||||
ModalContent,
|
||||
Modal,
|
||||
} from "@budibase/bbui"
|
||||
|
||||
export let automation
|
||||
export let onCancel = undefined
|
||||
|
|
|
@ -38,12 +38,11 @@
|
|||
EditorModes,
|
||||
} from "components/common/CodeEditor"
|
||||
import FilterDrawer from "components/design/settings/controls/FilterEditor/FilterDrawer.svelte"
|
||||
import { LuceneUtils } from "@budibase/frontend-core"
|
||||
import { LuceneUtils, Utils } from "@budibase/frontend-core"
|
||||
import {
|
||||
getSchemaForDatasourcePlus,
|
||||
getEnvironmentBindings,
|
||||
} from "builderStore/dataBinding"
|
||||
import { Utils } from "@budibase/frontend-core"
|
||||
import { TriggerStepID, ActionStepID } from "constants/backend/automations"
|
||||
import { onMount } from "svelte"
|
||||
import { cloneDeep } from "lodash/fp"
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
import { Button, Select, Input, Label } from "@budibase/bbui"
|
||||
import { onMount, createEventDispatcher } from "svelte"
|
||||
import { flags } from "stores/backend"
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
||||
export let value
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
<script>
|
||||
import { Icon, notifications } from "@budibase/bbui"
|
||||
import { Icon, notifications, ModalContent } from "@budibase/bbui"
|
||||
import { automationStore, selectedAutomation } from "builderStore"
|
||||
import WebhookDisplay from "./WebhookDisplay.svelte"
|
||||
import { ModalContent } from "@budibase/bbui"
|
||||
import { onMount, onDestroy } from "svelte"
|
||||
|
||||
const POLL_RATE_MS = 2500
|
||||
|
|
|
@ -1,11 +1,15 @@
|
|||
<script>
|
||||
import { createEventDispatcher } from "svelte"
|
||||
import { tables } from "stores/backend"
|
||||
import { roles } from "stores/backend"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { tables, roles } from "stores/backend"
|
||||
import {
|
||||
notifications,
|
||||
keepOpen,
|
||||
ModalContent,
|
||||
Select,
|
||||
Link,
|
||||
} from "@budibase/bbui"
|
||||
import RowFieldControl from "../RowFieldControl.svelte"
|
||||
import { API } from "api"
|
||||
import { keepOpen, ModalContent, Select, Link } from "@budibase/bbui"
|
||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||
import { goto } from "@roxi/routify"
|
||||
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
<script>
|
||||
import { keepOpen, ModalContent, Select, Input, Button } from "@budibase/bbui"
|
||||
import {
|
||||
keepOpen,
|
||||
ModalContent,
|
||||
Select,
|
||||
Input,
|
||||
Button,
|
||||
notifications,
|
||||
} from "@budibase/bbui"
|
||||
import { onMount } from "svelte"
|
||||
import { API } from "api"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import ErrorsBox from "components/common/ErrorsBox.svelte"
|
||||
import { roles } from "stores/backend"
|
||||
|
||||
|
|
|
@ -8,7 +8,8 @@
|
|||
} from "@budibase/bbui"
|
||||
import download from "downloadjs"
|
||||
import { API } from "api"
|
||||
import { Constants, LuceneUtils } from "@budibase/frontend-core"
|
||||
import { LuceneUtils } from "@budibase/frontend-core"
|
||||
import { utils } from "@budibase/shared-core"
|
||||
import { ROW_EXPORT_FORMATS } from "constants/backend"
|
||||
|
||||
export let view
|
||||
|
@ -32,6 +33,8 @@
|
|||
},
|
||||
]
|
||||
|
||||
$: appliedFilters = filters?.filter(filter => !filter.onEmptyFilter)
|
||||
|
||||
$: options = FORMATS.filter(format => {
|
||||
if (formats && !formats.includes(format.key)) {
|
||||
return false
|
||||
|
@ -46,23 +49,20 @@
|
|||
exportFormat = Array.isArray(options) ? options[0]?.key : []
|
||||
}
|
||||
|
||||
$: luceneFilter = LuceneUtils.buildLuceneQuery(filters)
|
||||
$: exportOpDisplay = buildExportOpDisplay(sorting, filterDisplay, filters)
|
||||
$: luceneFilter = LuceneUtils.buildLuceneQuery(appliedFilters)
|
||||
$: exportOpDisplay = buildExportOpDisplay(
|
||||
sorting,
|
||||
filterDisplay,
|
||||
appliedFilters
|
||||
)
|
||||
|
||||
const buildFilterLookup = () => {
|
||||
return Object.keys(Constants.OperatorOptions).reduce((acc, key) => {
|
||||
const op = Constants.OperatorOptions[key]
|
||||
acc[op.value] = op.label
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
filterLookup = buildFilterLookup()
|
||||
filterLookup = utils.filterValueToLabel()
|
||||
|
||||
const filterDisplay = () => {
|
||||
if (!filters) {
|
||||
if (!appliedFilters) {
|
||||
return []
|
||||
}
|
||||
return filters.map(filter => {
|
||||
return appliedFilters.map(filter => {
|
||||
let newFieldName = filter.field + ""
|
||||
const parts = newFieldName.split(":")
|
||||
parts.shift()
|
||||
|
@ -77,7 +77,7 @@
|
|||
|
||||
const buildExportOpDisplay = (sorting, filterDisplay) => {
|
||||
let filterDisplayConfig = filterDisplay()
|
||||
if (sorting) {
|
||||
if (sorting?.sortColumn) {
|
||||
filterDisplayConfig = [
|
||||
...filterDisplayConfig,
|
||||
{
|
||||
|
@ -132,7 +132,7 @@
|
|||
format: exportFormat,
|
||||
})
|
||||
downloadWithBlob(data, `export.${exportFormat}`)
|
||||
} else if (filters || sorting) {
|
||||
} else if (appliedFilters || sorting) {
|
||||
let response
|
||||
try {
|
||||
response = await API.exportRows({
|
||||
|
@ -163,29 +163,33 @@
|
|||
title="Export Data"
|
||||
confirmText="Export"
|
||||
onConfirm={exportRows}
|
||||
size={filters?.length || sorting ? "M" : "S"}
|
||||
size={appliedFilters?.length || sorting ? "M" : "S"}
|
||||
>
|
||||
{#if selectedRows?.length}
|
||||
<Body size="S">
|
||||
<strong>{selectedRows?.length}</strong>
|
||||
{`row${selectedRows?.length > 1 ? "s" : ""} will be exported`}
|
||||
<span data-testid="exporting-n-rows">
|
||||
<strong>{selectedRows?.length}</strong>
|
||||
{`row${selectedRows?.length > 1 ? "s" : ""} will be exported`}
|
||||
</span>
|
||||
</Body>
|
||||
{:else if filters || (sorting?.sortOrder && sorting?.sortColumn)}
|
||||
{:else if appliedFilters?.length || (sorting?.sortOrder && sorting?.sortColumn)}
|
||||
<Body size="S">
|
||||
{#if !filters}
|
||||
Exporting <strong>all</strong> rows
|
||||
{#if !appliedFilters}
|
||||
<span data-testid="exporting-rows">
|
||||
Exporting <strong>all</strong> rows
|
||||
</span>
|
||||
{:else}
|
||||
Filters applied
|
||||
<span data-testid="filters-applied">Filters applied</span>
|
||||
{/if}
|
||||
</Body>
|
||||
|
||||
<div class="table-wrap">
|
||||
<div class="table-wrap" data-testid="export-config-table">
|
||||
<Table
|
||||
schema={displaySchema}
|
||||
data={exportOpDisplay}
|
||||
{filters}
|
||||
{appliedFilters}
|
||||
loading={false}
|
||||
rowCount={filters?.length + 1}
|
||||
rowCount={appliedFilters?.length + 1}
|
||||
disableSorting={true}
|
||||
allowSelectRows={false}
|
||||
allowEditRows={false}
|
||||
|
@ -196,18 +200,21 @@
|
|||
</div>
|
||||
{:else}
|
||||
<Body size="S">
|
||||
Exporting <strong>all</strong> rows
|
||||
<span data-testid="export-all-rows">
|
||||
Exporting <strong>all</strong> rows
|
||||
</span>
|
||||
</Body>
|
||||
{/if}
|
||||
|
||||
<Select
|
||||
label="Format"
|
||||
bind:value={exportFormat}
|
||||
{options}
|
||||
placeholder={null}
|
||||
getOptionLabel={x => x.name}
|
||||
getOptionValue={x => x.key}
|
||||
/>
|
||||
<span data-testid="format-select">
|
||||
<Select
|
||||
label="Format"
|
||||
bind:value={exportFormat}
|
||||
{options}
|
||||
placeholder={null}
|
||||
getOptionLabel={x => x.name}
|
||||
getOptionValue={x => x.key}
|
||||
/>
|
||||
</span>
|
||||
</ModalContent>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -0,0 +1,240 @@
|
|||
import { it, expect, describe, vi } from "vitest"
|
||||
import { render, screen } from "@testing-library/svelte"
|
||||
import "@testing-library/jest-dom"
|
||||
|
||||
import ExportModal from "./ExportModal.svelte"
|
||||
import { utils } from "@budibase/shared-core"
|
||||
|
||||
const labelLookup = utils.filterValueToLabel()
|
||||
|
||||
const rowText = filter => {
|
||||
let readableField = filter.field.split(":")[1]
|
||||
let rowLabel = labelLookup[filter.operator]
|
||||
let value = Array.isArray(filter.value)
|
||||
? JSON.stringify(filter.value)
|
||||
: filter.value
|
||||
return `${readableField}${rowLabel}${value}`.trim()
|
||||
}
|
||||
|
||||
const defaultFilters = [
|
||||
{
|
||||
onEmptyFilter: "all",
|
||||
},
|
||||
]
|
||||
|
||||
vi.mock("svelte", async () => {
|
||||
return {
|
||||
getContext: () => {
|
||||
return {
|
||||
hide: vi.fn(),
|
||||
cancel: vi.fn(),
|
||||
}
|
||||
},
|
||||
createEventDispatcher: vi.fn(),
|
||||
onDestroy: vi.fn(),
|
||||
}
|
||||
})
|
||||
|
||||
vi.mock("api", async () => {
|
||||
return {
|
||||
API: {
|
||||
exportView: vi.fn(),
|
||||
exportRows: vi.fn(),
|
||||
},
|
||||
}
|
||||
})
|
||||
|
||||
describe("Export Modal", () => {
|
||||
it("show default messaging with no export config specified", () => {
|
||||
render(ExportModal, {
|
||||
props: {},
|
||||
})
|
||||
|
||||
expect(screen.getByTestId("export-all-rows")).toBeVisible()
|
||||
expect(screen.getByTestId("export-all-rows")).toHaveTextContent(
|
||||
"Exporting all rows"
|
||||
)
|
||||
|
||||
expect(screen.queryByTestId("export-config-table")).toBe(null)
|
||||
})
|
||||
|
||||
it("indicate that a filter is being applied to the export", () => {
|
||||
const propsCfg = {
|
||||
filters: [
|
||||
{
|
||||
id: "MOQkMx9p9",
|
||||
field: "1:Cost",
|
||||
operator: "rangeHigh",
|
||||
value: "100",
|
||||
valueType: "Value",
|
||||
type: "number",
|
||||
noValue: false,
|
||||
},
|
||||
...defaultFilters,
|
||||
],
|
||||
}
|
||||
|
||||
render(ExportModal, {
|
||||
props: propsCfg,
|
||||
})
|
||||
|
||||
expect(screen.getByTestId("filters-applied")).toBeVisible()
|
||||
expect(screen.getByTestId("filters-applied").textContent).toBe(
|
||||
"Filters applied"
|
||||
)
|
||||
|
||||
const ele = screen.queryByTestId("export-config-table")
|
||||
expect(ele).toBeVisible()
|
||||
|
||||
const rows = ele.getElementsByClassName("spectrum-Table-row")
|
||||
|
||||
expect(rows.length).toBe(1)
|
||||
let rowTextContent = rowText(propsCfg.filters[0])
|
||||
|
||||
//"CostLess than or equal to100"
|
||||
expect(rows[0].textContent?.trim()).toEqual(rowTextContent)
|
||||
})
|
||||
|
||||
it("Show only selected row messaging if rows are supplied", () => {
|
||||
const propsCfg = {
|
||||
filters: [
|
||||
{
|
||||
id: "MOQkMx9p9",
|
||||
field: "1:Cost",
|
||||
operator: "rangeHigh",
|
||||
value: "100",
|
||||
valueType: "Value",
|
||||
type: "number",
|
||||
noValue: false,
|
||||
},
|
||||
...defaultFilters,
|
||||
],
|
||||
sorting: {
|
||||
sortColumn: "Cost",
|
||||
sortOrder: "descending",
|
||||
},
|
||||
selectedRows: [
|
||||
{
|
||||
_id: "ro_ta_bb_expenses_57d5f6fe1b6640d8bb22b15f5eae62cd",
|
||||
},
|
||||
{
|
||||
_id: "ro_ta_bb_expenses_99ce5760a53a430bab4349cd70335a07",
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
render(ExportModal, {
|
||||
props: propsCfg,
|
||||
})
|
||||
|
||||
expect(screen.queryByTestId("export-config-table")).toBeNull()
|
||||
expect(screen.queryByTestId("filters-applied")).toBeNull()
|
||||
|
||||
expect(screen.queryByTestId("exporting-n-rows")).toBeVisible()
|
||||
expect(screen.queryByTestId("exporting-n-rows").textContent).toEqual(
|
||||
"2 rows will be exported"
|
||||
)
|
||||
})
|
||||
|
||||
it("Show only the configured sort when no filters are specified", () => {
|
||||
const propsCfg = {
|
||||
filters: [...defaultFilters],
|
||||
sorting: {
|
||||
sortColumn: "Cost",
|
||||
sortOrder: "descending",
|
||||
},
|
||||
}
|
||||
render(ExportModal, {
|
||||
props: propsCfg,
|
||||
})
|
||||
|
||||
expect(screen.queryByTestId("export-config-table")).toBeVisible()
|
||||
const ele = screen.queryByTestId("export-config-table")
|
||||
const rows = ele.getElementsByClassName("spectrum-Table-row")
|
||||
|
||||
expect(rows.length).toBe(1)
|
||||
expect(rows[0].textContent?.trim()).toEqual(
|
||||
`${propsCfg.sorting.sortColumn}Order By${propsCfg.sorting.sortOrder}`
|
||||
)
|
||||
})
|
||||
|
||||
it("Display all currently configured filters and applied sort", () => {
|
||||
const propsCfg = {
|
||||
filters: [
|
||||
{
|
||||
id: "MOQkMx9p9",
|
||||
field: "1:Cost",
|
||||
operator: "rangeHigh",
|
||||
value: "100",
|
||||
valueType: "Value",
|
||||
type: "number",
|
||||
noValue: false,
|
||||
},
|
||||
{
|
||||
id: "2ot-aB0gE",
|
||||
field: "2:Expense Tags",
|
||||
operator: "contains",
|
||||
value: ["Equipment", "Services"],
|
||||
valueType: "Value",
|
||||
type: "array",
|
||||
noValue: false,
|
||||
},
|
||||
...defaultFilters,
|
||||
],
|
||||
sorting: {
|
||||
sortColumn: "Payment Due",
|
||||
sortOrder: "ascending",
|
||||
},
|
||||
}
|
||||
|
||||
render(ExportModal, {
|
||||
props: propsCfg,
|
||||
})
|
||||
|
||||
const ele = screen.queryByTestId("export-config-table")
|
||||
expect(ele).toBeVisible()
|
||||
|
||||
const rows = ele.getElementsByClassName("spectrum-Table-row")
|
||||
expect(rows.length).toBe(3)
|
||||
|
||||
let rowTextContent1 = rowText(propsCfg.filters[0])
|
||||
expect(rows[0].textContent?.trim()).toEqual(rowTextContent1)
|
||||
|
||||
let rowTextContent2 = rowText(propsCfg.filters[1])
|
||||
expect(rows[1].textContent?.trim()).toEqual(rowTextContent2)
|
||||
|
||||
expect(rows[2].textContent?.trim()).toEqual(
|
||||
`${propsCfg.sorting.sortColumn}Order By${propsCfg.sorting.sortOrder}`
|
||||
)
|
||||
})
|
||||
|
||||
it("show only the valid, configured download formats", () => {
|
||||
const propsCfg = {
|
||||
formats: ["badger", "json"],
|
||||
}
|
||||
|
||||
render(ExportModal, {
|
||||
props: propsCfg,
|
||||
})
|
||||
|
||||
let ele = screen.getByTestId("format-select")
|
||||
expect(ele).toBeVisible()
|
||||
|
||||
let formatDisplay = ele.getElementsByTagName("button")[0]
|
||||
|
||||
expect(formatDisplay.textContent.trim()).toBe("JSON")
|
||||
})
|
||||
|
||||
it("Load the default format config when no explicit formats are configured", () => {
|
||||
render(ExportModal, {
|
||||
props: {},
|
||||
})
|
||||
|
||||
let ele = screen.getByTestId("format-select")
|
||||
expect(ele).toBeVisible()
|
||||
|
||||
let formatDisplay = ele.getElementsByTagName("button")[0]
|
||||
|
||||
expect(formatDisplay.textContent.trim()).toBe("CSV")
|
||||
})
|
||||
})
|
|
@ -1,8 +1,7 @@
|
|||
<script>
|
||||
import { get } from "svelte/store"
|
||||
import { datasources, integrations } from "stores/backend"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import { Input, ModalContent, Modal } from "@budibase/bbui"
|
||||
import { notifications, Input, ModalContent, Modal } from "@budibase/bbui"
|
||||
import { integrationForDatasource } from "stores/selectors"
|
||||
|
||||
let error = ""
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue