From ee07aaa01dc25481247a8ae4539333c43f5ee8c8 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 2 Mar 2022 09:06:31 +0000 Subject: [PATCH 001/206] Don't set explicit height of tables when total row count is equal or less than the desired rows, to avoid tiny overflow due to horizontal scrollbars --- packages/bbui/src/Table/Table.svelte | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/packages/bbui/src/Table/Table.svelte b/packages/bbui/src/Table/Table.svelte index c9d7f12339..1d957b3f30 100644 --- a/packages/bbui/src/Table/Table.svelte +++ b/packages/bbui/src/Table/Table.svelte @@ -56,6 +56,7 @@ $: if (!loading) loaded = true $: fields = getFields(schema, showAutoColumns, autoSortColumns) $: rows = fields?.length ? data || [] : [] + $: totalRowCount = rows?.length || 0 $: visibleRowCount = getVisibleRowCount( loaded, height, @@ -63,7 +64,12 @@ rowCount, rowHeight ) - $: contentStyle = getContentStyle(visibleRowCount, rowCount, rowHeight) + $: heightStyle = getHeightStyle( + visibleRowCount, + rowCount, + totalRowCount, + rowHeight + ) $: sortedRows = sortRows(rows, sortColumn, sortOrder) $: gridStyle = getGridStyle(fields, schema, showEditColumn) $: showEditColumn = allowEditRows || allowSelectRows @@ -107,11 +113,16 @@ return Math.min(allRows, Math.ceil(height / rowHeight)) } - const getContentStyle = (visibleRows, rowCount, rowHeight) => { - if (!rowCount || !visibleRows) { + const getHeightStyle = ( + visibleRowCount, + rowCount, + totalRowCount, + rowHeight + ) => { + if (!rowCount || !visibleRowCount || totalRowCount <= rowCount) { return "" } - return `height: ${headerHeight + visibleRows * rowHeight}px;` + return `height: ${headerHeight + visibleRowCount * rowHeight}px;` } const getGridStyle = (fields, schema, showEditColumn) => { @@ -264,11 +275,11 @@ style={`--row-height: ${rowHeight}px; --header-height: ${headerHeight}px;`} > {#if !loaded} -
+
{:else} -
+
{#if fields.length}
{#if showEditColumn} From e411354538c2098e9e86954680fdb661724d6e3c Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 2 Mar 2022 17:45:01 +0000 Subject: [PATCH 002/206] Improve copy and paste to support keeping all data bindings valid --- .../src/builderStore/componentUtils.js | 61 +++++++++++++++++++ .../src/builderStore/store/frontend.js | 23 ++----- .../ComponentDropdownMenu.svelte | 6 +- packages/string-templates/src/index.cjs | 1 + packages/string-templates/src/index.js | 9 +++ packages/string-templates/src/index.mjs | 1 + 6 files changed, 79 insertions(+), 22 deletions(-) diff --git a/packages/builder/src/builderStore/componentUtils.js b/packages/builder/src/builderStore/componentUtils.js index 04a87998fe..557d83a00c 100644 --- a/packages/builder/src/builderStore/componentUtils.js +++ b/packages/builder/src/builderStore/componentUtils.js @@ -1,4 +1,10 @@ import { store } from "./index" +import { Helpers } from "@budibase/bbui" +import { + decodeJSBinding, + encodeJSBinding, + findAllBindings, +} from "@budibase/string-templates" /** * Recursively searches for a specific component ID @@ -161,3 +167,58 @@ export const getComponentSettings = componentType => { return settings } + +/** + * Randomises a components ID's, including all child component IDs, and also + * updates all data bindings to still be valid. + * This mutates the object in place. + * @param component the component to randomise + */ +export const makeComponentUnique = component => { + if (!component) { + return + } + + // Replace component ID + const oldId = component._id + const newId = Helpers.uuid() + component._id = newId + + if (component._children?.length) { + let children = JSON.stringify(component._children) + + // Replace all instances of this ID in child HBS bindings + children = children.replace(new RegExp(oldId, "g"), newId) + + // Replace all instances of this ID in child JS bindings + const bindings = findAllBindings(children) + bindings.forEach(binding => { + // JSON.stringify will have escaped double quotes, so we need + // to account for that + let sanitizedBinding = binding.replace(/\\"/g, '"') + + // Check if this is a valid JS binding + let js = decodeJSBinding(sanitizedBinding) + if (js != null) { + // Replace ID inside JS binding + js = js.replace(new RegExp(oldId, "g"), newId) + + // Create new valid JS binding + let newBinding = encodeJSBinding(js) + + // Replace escaped double quotes + newBinding = newBinding.replace(/"/g, '\\"') + + // Insert new JS back into binding. + // A single string replace here is better than a regex as + // the binding contains special characters, and we only need + // to replace a single instance. + children = children.replace(binding, newBinding) + } + }) + + // Recurse on all children + component._children = JSON.parse(children) + component._children.forEach(makeComponentUnique) + } +} diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index d8118c9c60..c8e4c85b06 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -24,9 +24,9 @@ import { findAllMatchingComponents, findComponent, getComponentSettings, + makeComponentUnique, } from "../componentUtils" import { Helpers } from "@budibase/bbui" -import { removeBindings } from "../dataBinding" const INITIAL_FRONTEND_STATE = { apps: [], @@ -490,37 +490,22 @@ export const getFrontendStore = () => { } } }, - paste: async (targetComponent, mode, preserveBindings = false) => { + paste: async (targetComponent, mode) => { let promises = [] store.update(state => { // Stop if we have nothing to paste if (!state.componentToPaste) { return state } - - // defines if this is a copy or a cut const cut = state.componentToPaste.isCut - // immediately need to remove bindings, currently these aren't valid when pasted - if (!cut && !preserveBindings) { - state.componentToPaste = removeBindings(state.componentToPaste, "") - } - - // Clone the component to paste - // Retain the same ID if cutting as things may be referencing this component + // Clone the component to paste and make unique if copying delete state.componentToPaste.isCut let componentToPaste = cloneDeep(state.componentToPaste) if (cut) { state.componentToPaste = null } else { - const randomizeIds = component => { - if (!component) { - return - } - component._id = Helpers.uuid() - component._children?.forEach(randomizeIds) - } - randomizeIds(componentToPaste) + makeComponentUnique(componentToPaste) } if (mode === "inside") { diff --git a/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte b/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte index 0fcd43b58e..c8796fab25 100644 --- a/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte @@ -61,7 +61,7 @@ const duplicateComponent = () => { storeComponentForCopy(false) - pasteComponent("below", true) + pasteComponent("below") } const deleteComponent = async () => { @@ -77,10 +77,10 @@ store.actions.components.copy(component, cut) } - const pasteComponent = (mode, preserveBindings = false) => { + const pasteComponent = mode => { try { // lives in store - also used by drag drop - store.actions.components.paste(component, mode, preserveBindings) + store.actions.components.paste(component, mode) } catch (error) { notifications.error("Error saving component") } diff --git a/packages/string-templates/src/index.cjs b/packages/string-templates/src/index.cjs index 5a84c45d78..9584374880 100644 --- a/packages/string-templates/src/index.cjs +++ b/packages/string-templates/src/index.cjs @@ -18,6 +18,7 @@ module.exports.processObject = templates.processObject module.exports.doesContainStrings = templates.doesContainStrings module.exports.doesContainString = templates.doesContainString module.exports.disableEscaping = templates.disableEscaping +module.exports.findAllBindings = templates.findAllBindings /** * Use vm2 to run JS scripts in a node env diff --git a/packages/string-templates/src/index.js b/packages/string-templates/src/index.js index eedd9423ee..76ad979670 100644 --- a/packages/string-templates/src/index.js +++ b/packages/string-templates/src/index.js @@ -322,3 +322,12 @@ module.exports.doesContainStrings = (template, strings) => { module.exports.doesContainString = (template, string) => { return exports.doesContainStrings(template, [string]) } + +/** + * Finds all regular (double bracketed) expressions inside a string. + * @param string the string to search + * @return {[]} all matching bindings + */ +module.exports.findAllBindings = string => { + return findDoubleHbsInstances(string) +} diff --git a/packages/string-templates/src/index.mjs b/packages/string-templates/src/index.mjs index f2cffdf378..a9745b3a34 100644 --- a/packages/string-templates/src/index.mjs +++ b/packages/string-templates/src/index.mjs @@ -18,6 +18,7 @@ export const processObject = templates.processObject export const doesContainStrings = templates.doesContainStrings export const doesContainString = templates.doesContainString export const disableEscaping = templates.disableEscaping +export const findAllBindings = templates.findAllBindings /** * Use polyfilled vm to run JS scripts in a browser Env From f2fbd3d1d0bb930c70f79c99f92718a65437eb46 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 2 Mar 2022 19:10:18 +0000 Subject: [PATCH 003/206] Rewrite screen wizard to make modals reusable and fix some edge case URL bugs --- .../store/screenTemplates/index.js | 7 +- .../ComponentDropdownMenu.svelte | 2 - .../ScreenDropdownMenu.svelte | 5 + .../NavigationPanel/NewScreenModal.svelte | 48 ++--- .../NavigationPanel/ScreenDetailsModal.svelte | 43 ++-- .../NavigationPanel/ScreenWizard.svelte | 200 +++++++++--------- 6 files changed, 143 insertions(+), 162 deletions(-) diff --git a/packages/builder/src/builderStore/store/screenTemplates/index.js b/packages/builder/src/builderStore/store/screenTemplates/index.js index 38ae434753..51496bdeb3 100644 --- a/packages/builder/src/builderStore/store/screenTemplates/index.js +++ b/packages/builder/src/builderStore/store/screenTemplates/index.js @@ -10,17 +10,18 @@ const allTemplates = tables => [ ] // Allows us to apply common behaviour to all create() functions -const createTemplateOverride = (frontendState, create) => () => { - const screen = create() +const createTemplateOverride = (frontendState, template) => () => { + const screen = template.create() screen.name = screen.props._id screen.routing.route = screen.routing.route.toLowerCase() + screen.template = template.id return screen } export default (frontendState, tables) => { const enrichTemplate = template => ({ ...template, - create: createTemplateOverride(frontendState, template.create), + create: createTemplateOverride(frontendState, template), }) const fromScratch = enrichTemplate(createFromScratchScreen) diff --git a/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte b/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte index c8796fab25..df6be7e88f 100644 --- a/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte @@ -73,13 +73,11 @@ } const storeComponentForCopy = (cut = false) => { - // lives in store - also used by drag drop store.actions.components.copy(component, cut) } const pasteComponent = mode => { try { - // lives in store - also used by drag drop store.actions.components.paste(component, mode) } catch (error) { notifications.error("Error saving component") diff --git a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte index 38ed79649e..47c9fb65b1 100644 --- a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte @@ -10,6 +10,8 @@ $: screen = $allScreens.find(screen => screen._id === screenId) + const duplicateScreen = () => {} + const deleteScreen = async () => { try { await store.actions.screens.delete(screen) @@ -25,6 +27,9 @@
+ + Duplicate + Delete diff --git a/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte b/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte index c48ba33efa..cd83d81235 100644 --- a/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte +++ b/packages/builder/src/components/design/NavigationPanel/NewScreenModal.svelte @@ -10,39 +10,19 @@ ProgressCircle, } from "@budibase/bbui" import getTemplates from "builderStore/store/screenTemplates" - import { onDestroy } from "svelte" - import { createEventDispatcher } from "svelte" - - export let chooseModal - export let save + export let onConfirm + export let onCancel export let showProgressCircle = false - let selectedScreens = [] - const blankScreen = "createFromScratch" - const dispatch = createEventDispatcher() - function setScreens() { - dispatch("save", { - screens: selectedScreens, - }) - } + let selectedScreens = [] + let templates = getTemplates($store, $tables.list) $: blankSelected = selectedScreens?.length === 1 $: autoSelected = selectedScreens?.length > 0 && !blankSelected - let templates = getTemplates($store, $tables.list) - - const confirm = async () => { - if (autoSelected) { - setScreens() - await save() - } else { - setScreens() - chooseModal(1) - } - } const toggleScreenSelection = table => { if (selectedScreens.find(s => s.table === table.name)) { selectedScreens = selectedScreens.filter( @@ -56,25 +36,25 @@ } } - onDestroy(() => { - selectedScreens = [] - }) + const confirmScreenSelection = async () => { + await onConfirm(selectedScreens) + }
confirm()} + onConfirm={confirmScreenSelection} + {onCancel} disabled={!selectedScreens.length} size="L" > - Please select the screens you would like to add to your application. - Autogenerated screens come with CRUD functionality. - + + Please select the screens you would like to add to your application. + Autogenerated screens come with CRUD functionality. + Blank screen
{ if (!event.detail.startsWith("/")) { - url = "/" + event.detail + screenUrl = "/" + event.detail } - url = sanitizeUrl(url) - - if (routeExists(url, roleId)) { + screenUrl = sanitizeUrl(screenUrl) + if (routeExists(screenUrl)) { routeError = "This URL is already taken for this access role" } else { - routeError = "" + routeError = null } } - const routeExists = (url, roleId) => { - return $allScreens.some( + const routeExists = url => { + const roleId = get(selectedAccessRole) || "BASIC" + return get(allScreens).some( screen => screen.routing.route.toLowerCase() === url.toLowerCase() && screen.routing.roleId === roleId ) } - onDestroy(() => { - screenName = "" - url = "" - }) + const confirmScreenDetails = async () => { + await onConfirm({ + screenName, + screenUrl, + }) + } chooseModal(0)} - onConfirm={() => save()} + onConfirm={confirmScreenDetails} + {onCancel} cancelText={"Back"} - disabled={!screenName || !url || routeError} + disabled={!screenName || !screenUrl || routeError} >
diff --git a/packages/builder/src/components/design/NavigationPanel/ScreenWizard.svelte b/packages/builder/src/components/design/NavigationPanel/ScreenWizard.svelte index 144592035c..fd37490c53 100644 --- a/packages/builder/src/components/design/NavigationPanel/ScreenWizard.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ScreenWizard.svelte @@ -3,141 +3,137 @@ import NewScreenModal from "components/design/NavigationPanel/NewScreenModal.svelte" import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl" import { Modal, notifications } from "@budibase/bbui" - import { store, selectedAccessRole, allScreens } from "builderStore" + import { store, selectedAccessRole } from "builderStore" import analytics, { Events } from "analytics" + import { get } from "svelte/store" - let newScreenModal - let navigationSelectionModal - let screenDetailsModal - let screenName = "" - let url = "" - let selectedScreens = [] + let pendingScreen let showProgressCircle = false - let routeError - let createdScreens = [] - $: roleId = $selectedAccessRole || "BASIC" + // Modal refs + let newScreenModal + let screenDetailsModal - const createScreens = async () => { - for (let screen of selectedScreens) { - let test = screen.create() - createdScreens.push(test) - analytics.captureEvent(Events.SCREEN.CREATED, { - template: screen.id || screen.name, - }) - } - } + // External handler to show the screen wizard + export const showModal = () => { + newScreenModal.show() - const save = async () => { - showProgressCircle = true - try { - await createScreens() - for (let screen of createdScreens) { - await saveScreens(screen) - } - await store.actions.routing.fetch() - selectedScreens = [] - createdScreens = [] - screenName = "" - url = "" - } catch (error) { - notifications.error("Error creating screens") - } + // Reset state when showing modal again + pendingScreen = null showProgressCircle = false } - const saveScreens = async draftScreen => { - let existingScreenCount = $store.screens.filter( - s => s.props._instanceName == draftScreen.props._instanceName - ).length - if (existingScreenCount > 0) { - let oldUrlArr = draftScreen.routing.route.split("/") - oldUrlArr[1] = `${oldUrlArr[1]}-${existingScreenCount + 1}` - draftScreen.routing.route = oldUrlArr.join("/") + // Creates an array of screens, checking and sanitising their URLs + const createScreens = async screens => { + if (!screens?.length) { + return } + showProgressCircle = true - let route = url ? sanitizeUrl(`${url}`) : draftScreen.routing.route - if (draftScreen) { - if (!route) { - routeError = "URL is required" - } else { - if (routeExists(route, roleId)) { - routeError = "This URL is already taken for this access role" - } else { - routeError = "" + try { + for (let screen of screens) { + // Check we aren't clashing with an existing URL + if (hasExistingUrl(screen.routing.route)) { + let suffix = 2 + let candidateUrl = makeCandidateUrl(screen, suffix) + while (hasExistingUrl(candidateUrl)) { + candidateUrl = makeCandidateUrl(screen, ++suffix) + } + screen.routing.route = candidateUrl } - } - if (routeError) return false + // Sanitise URL + screen.routing.route = sanitizeUrl(screen.routing.route) - if (screenName) { - draftScreen.props._instanceName = screenName - } + // Use the currently selected role + screen.routing.roleId = get(selectedAccessRole) || "BASIC" - draftScreen.routing.route = route - draftScreen.routing.roleId = roleId + // Create the screen + await store.actions.screens.save(screen) - await store.actions.screens.save(draftScreen) - if (draftScreen.props._instanceName.endsWith("List")) { - try { + // Analytics + if (screen.template) { + analytics.captureEvent(Events.SCREEN.CREATED, { + template: screen.template, + }) + } + + // Add link in layout for list screens + if (screen.props._instanceName.endsWith("List")) { await store.actions.components.links.save( - draftScreen.routing.route, - draftScreen.routing.route.split("/")[1] + screen.routing.route, + screen.routing.route.split("/")[1] ) - } catch (error) { - notifications.error("Error creating link to screen") } } + + // Refresh routing info since we have new screens + await store.actions.routing.fetch() + } catch (error) { + console.log(error) + notifications.error("Error creating screens") + } + + showProgressCircle = false + } + + // Checks if any screens exist in the store with the given route and + // currently selected role + const hasExistingUrl = url => { + const roleId = get(selectedAccessRole) || "BASIC" + const screens = get(store).screens.filter(s => s.routing.roleId === roleId) + return !!screens.find(s => s.routing?.route === url) + } + + // Constructs a candidate URL for a new screen, suffixing the base of the + // screen's URL with a given suffix. + // e.g. "/sales/:id" => "/sales-1/:id" + const makeCandidateUrl = (screen, suffix) => { + let url = screen.routing?.route || "" + if (url.startsWith("/")) { + url = url.slice(1) + } + if (!url.includes("/")) { + return `/${url}-${suffix}` + } else { + const split = url.split("/") + return `/${split[0]}-${suffix}/${split.join("/")}` } } - const routeExists = (route, roleId) => { - return $allScreens.some( - screen => - screen.routing.route.toLowerCase() === route.toLowerCase() && - screen.routing.roleId === roleId - ) - } - - export const showModal = () => { - newScreenModal.show() - } - - const setScreens = evt => { - selectedScreens = evt.detail.screens - } - - const chooseModal = index => { - /* - 0 = newScreenModal - 1 = screenDetailsModal - 2 = navigationSelectionModal - */ - if (index === 0) { - newScreenModal.show() - } else if (index === 1) { + // Handler for NewScreenModal + const confirmScreenSelection = async templates => { + // Handle template selection + if (templates?.length > 1) { + // Autoscreens, so create immediately + const screens = templates.map(template => template.create()) + await createScreens(screens) + } else { + // Empty screen, so proceed to the next modal + pendingScreen = templates[0].create() screenDetailsModal.show() - } else if (index === 2) { - navigationSelectionModal.show() } } + + // Handler for ScreenDetailsModal + const confirmScreenDetails = async ({ screenName, screenUrl }) => { + if (!pendingScreen) { + return + } + pendingScreen.props._instanceName = screenName + pendingScreen.routing.route = screenUrl + await createScreens([pendingScreen]) + } - + newScreenModal.show()} /> From f5ff71f9d1a41b53e5c78108073a22519bbc6c56 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 2 Mar 2022 19:26:43 +0000 Subject: [PATCH 004/206] Allow duplication of screens --- .../ScreenDropdownMenu.svelte | 57 +++++++++++++++++-- .../NavigationPanel/ScreenDetailsModal.svelte | 8 ++- .../NavigationPanel/ScreenWizard.svelte | 4 -- 3 files changed, 57 insertions(+), 12 deletions(-) diff --git a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte index 47c9fb65b1..33e4d89dda 100644 --- a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte @@ -2,15 +2,56 @@ import { goto } from "@roxi/routify" import { store, allScreens } from "builderStore" import ConfirmDialog from "components/common/ConfirmDialog.svelte" - import { ActionMenu, MenuItem, Icon, notifications } from "@budibase/bbui" + import { + ActionMenu, + MenuItem, + Icon, + Modal, + Helpers, + notifications, + } from "@budibase/bbui" + import ScreenDetailsModal from "../ScreenDetailsModal.svelte" + import sanitizeUrl from "builderStore/store/screenTemplates/utils/sanitizeUrl" + import analytics, { Events } from "analytics" + import { makeComponentUnique } from "builderStore/componentUtils" export let screenId let confirmDeleteDialog + let screenDetailsModal $: screen = $allScreens.find(screen => screen._id === screenId) - const duplicateScreen = () => {} + const duplicateScreen = () => { + screenDetailsModal.show() + } + + const createDuplicateScreen = async ({ screenName, screenUrl }) => { + // Create a dupe and ensure it is unique + let duplicateScreen = Helpers.cloneDeep(screen) + delete duplicateScreen._id + delete duplicateScreen._rev + makeComponentUnique(duplicateScreen.props) + + // Attach the new name and URL + duplicateScreen.routing.route = sanitizeUrl(screenUrl) + duplicateScreen.props._instanceName = screenName + + try { + // Create the screen + await store.actions.screens.save(duplicateScreen) + + // Analytics + if (screen.template) { + analytics.captureEvent(Events.SCREEN.CREATED, { + template: "createFromScratch", + }) + } + } catch (error) { + notifications.error("Error duplicating screen") + console.log(error) + } + } const deleteScreen = async () => { try { @@ -27,9 +68,7 @@
- - Duplicate - + Duplicate Delete @@ -40,3 +79,11 @@ okText="Delete Screen" onOk={deleteScreen} /> + + + + diff --git a/packages/builder/src/components/design/NavigationPanel/ScreenDetailsModal.svelte b/packages/builder/src/components/design/NavigationPanel/ScreenDetailsModal.svelte index 0fae114268..82b90be6a6 100644 --- a/packages/builder/src/components/design/NavigationPanel/ScreenDetailsModal.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ScreenDetailsModal.svelte @@ -7,15 +7,17 @@ export let onConfirm export let onCancel export let showProgressCircle = false + export let screenName + export let screenUrl - let screenName - let screenUrl let routeError + let touched = false const routeChanged = event => { if (!event.detail.startsWith("/")) { screenUrl = "/" + event.detail } + touched = true screenUrl = sanitizeUrl(screenUrl) if (routeExists(screenUrl)) { routeError = "This URL is already taken for this access role" @@ -48,7 +50,7 @@ onConfirm={confirmScreenDetails} {onCancel} cancelText={"Back"} - disabled={!screenName || !screenUrl || routeError} + disabled={!screenName || !screenUrl || routeError || !touched} > Date: Wed, 2 Mar 2022 19:52:10 +0000 Subject: [PATCH 005/206] Allow bulk deleting all screens under a route --- .../ComponentDropdownMenu.svelte | 0 .../ComponentTree.svelte | 2 +- .../LayoutDropdownMenu.svelte | 2 +- .../PathDropdownMenu.svelte | 78 +++++++++++++++++++ .../ComponentNavigationTree/PathTree.svelte | 5 +- .../ScreenDropdownMenu.svelte | 2 +- 6 files changed, 85 insertions(+), 4 deletions(-) rename packages/builder/src/components/design/NavigationPanel/{ => ComponentNavigationTree}/ComponentDropdownMenu.svelte (100%) create mode 100644 packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/PathDropdownMenu.svelte diff --git a/packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ComponentDropdownMenu.svelte similarity index 100% rename from packages/builder/src/components/design/NavigationPanel/ComponentDropdownMenu.svelte rename to packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ComponentDropdownMenu.svelte diff --git a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ComponentTree.svelte b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ComponentTree.svelte index c5dfd63cf9..d0095b2c0d 100644 --- a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ComponentTree.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ComponentTree.svelte @@ -1,7 +1,7 @@ + + +
+ +
+ + Delete all screens + +
+ + + +
+ Are you sure you want to delete all screens under the {path} route? +
+
The following screens will be deleted:
+
+ {#each screens as screen} +
{screen.route}
+ {/each} +
+
+
+ + diff --git a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/PathTree.svelte b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/PathTree.svelte index 841d8970f4..ee6de5c797 100644 --- a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/PathTree.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/PathTree.svelte @@ -8,6 +8,7 @@ import instantiateStore from "./dragDropStore" import ComponentTree from "./ComponentTree.svelte" import NavItem from "components/common/NavItem.svelte" + import PathDropdownMenu from "./PathDropdownMenu.svelte" import ScreenDropdownMenu from "./ScreenDropdownMenu.svelte" import { get } from "svelte/store" @@ -73,7 +74,9 @@ opened={routeOpened} {border} withArrow={route.subpaths} - /> + > + + {#if routeOpened} {#each filteredScreens as screen (screen.id)} diff --git a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte index 33e4d89dda..7d0b88c235 100644 --- a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte @@ -76,7 +76,7 @@ bind:this={confirmDeleteDialog} title="Confirm Deletion" body={"Are you sure you wish to delete this screen?"} - okText="Delete Screen" + okText="Delete screen" onOk={deleteScreen} /> From 97509b561b83cd5a76ca120ce0f163dec93f913e Mon Sep 17 00:00:00 2001 From: Maurits Lourens Date: Thu, 3 Mar 2022 00:18:15 +0100 Subject: [PATCH 006/206] initial implementation of openapi 3 --- .../src/api/controllers/query/import/index.ts | 5 +- .../query/import/sources/openapi3.ts | 194 +++ .../tests/openapi3/data/crud/crud.json | 231 ++++ .../tests/openapi3/data/crud/crud.yaml | 156 +++ .../openapi3/data/petstore/petstore.json | 1225 +++++++++++++++++ .../openapi3/data/petstore/petstore.yaml | 804 +++++++++++ .../sources/tests/openapi3/openapi3.spec.js | 256 ++++ 7 files changed, 2869 insertions(+), 2 deletions(-) create mode 100644 packages/server/src/api/controllers/query/import/sources/openapi3.ts create mode 100644 packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.json create mode 100644 packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.yaml create mode 100644 packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/petstore/petstore.json create mode 100644 packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/petstore/petstore.yaml create mode 100644 packages/server/src/api/controllers/query/import/sources/tests/openapi3/openapi3.spec.js diff --git a/packages/server/src/api/controllers/query/import/index.ts b/packages/server/src/api/controllers/query/import/index.ts index 593fb05fd3..4573fd27f7 100644 --- a/packages/server/src/api/controllers/query/import/index.ts +++ b/packages/server/src/api/controllers/query/import/index.ts @@ -1,7 +1,8 @@ import { queryValidation } from "../validation" import { generateQueryID } from "../../../../db/utils" import { ImportInfo, ImportSource } from "./sources/base" -import { OpenAPI2 } from "./sources/openapi2" +import { OpenAPI3 } from "./sources/openapi2" +import { OpenAPI3 } from "./sources/openapi3" import { Query } from "./../../../../definitions/common" import { Curl } from "./sources/curl" // @ts-ignore @@ -18,7 +19,7 @@ export class RestImporter { constructor(data: string) { this.data = data - this.sources = [new OpenAPI2(), new Curl()] + this.sources = [new OpenAPI3(), new OpenAPI3(), new Curl()] } init = async () => { diff --git a/packages/server/src/api/controllers/query/import/sources/openapi3.ts b/packages/server/src/api/controllers/query/import/sources/openapi3.ts new file mode 100644 index 0000000000..8c40c49c9d --- /dev/null +++ b/packages/server/src/api/controllers/query/import/sources/openapi3.ts @@ -0,0 +1,194 @@ +import { ImportInfo } from "./base" +import { Query, QueryParameter } from "../../../../../definitions/datasource" +import { OpenAPIV3 } from "openapi-types" +import { OpenAPISource } from "./base/openapi" +import { URL } from "url" +import { + getGlobalDB, + getTenantId, + isMultiTenant, +} from "@budibase/backend-core/tenancy" +import { getScopedConfig } from "@budibase/backend-core/db" + +const jsonMimeType = "application/json" +const parameterNotRef = ( + param: OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject +): param is OpenAPIV3.ParameterObject => { + // all refs are deferenced by parser library + return true +} + +const requestBodyNotRef = ( + param: OpenAPIV3.RequestBodyObject | OpenAPIV3.ReferenceObject | undefined +): param is OpenAPIV3.RequestBodyObject => { + // all refs are deferenced by parser library + return param !== undefined +} + +const isOpenAPI3 = (document: any): document is OpenAPIV3.Document => { + return document.openapi.includes("3") +} + +const methods: string[] = Object.values(OpenAPIV3.HttpMethods) + +const isOperation = ( + key: string, + pathItem: any +): pathItem is OpenAPIV3.OperationObject => { + return methods.includes(key) +} + +const isParameter = ( + key: string, + pathItem: any +): pathItem is OpenAPIV3.ParameterObject => { + return !isOperation(key, pathItem) +} + +const getRequestBody = (operation: OpenAPIV3.OperationObject) => { + if (requestBodyNotRef(operation.requestBody)) { + const request: OpenAPIV3.RequestBodyObject = + operation.requestBody as OpenAPIV3.RequestBodyObject + return request.content[jsonMimeType]?.example + } + return null +} + +/** + * OpenAPI Version 3.1 - aka "Swagger" + * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md + */ +export class OpenAPI3 extends OpenAPISource { + document!: OpenAPIV3.Document + + isSupported = async (data: string): Promise => { + try { + const document: any = await this.parseData(data) + if (isOpenAPI3(document)) { + this.document = document + return true + } else { + return false + } + } catch (err) { + return false + } + } + + getPlatformUrl = async (): Promise => { + const db = getGlobalDB() + const publicConfig = await getScopedConfig(db, { + type: "settings", + }) + + let url = publicConfig.platformUrl + if (isMultiTenant()) { + url += `/${getTenantId()}` + } + return url + } + + getUrl = async (): Promise => { + const platformUrl = await this.getPlatformUrl() + let url = this.document.servers ? this.document.servers[0].url : null + if (url) { + // check if url is relative or absolute + if (url.includes("http") || url.includes("https")) { + return new URL(url) + } + + return new URL(`${platformUrl}${url}`) + } + // if the specification doesn't contain a servers object, return the PLATFORM_URM environment variable + return new URL(platformUrl) + } + + getInfo = async (): Promise => { + const name = this.document.info.title || "Swagger Import" + return { + name, + } + } + + getQueries = async (datasourceId: string): Promise => { + const url: URL = await this.getUrl() + const queries: Query[] = [] + + for (let [path, pathItemObject] of Object.entries(this.document.paths)) { + // parameters that apply to every operation in the path + let pathParams: OpenAPIV3.ParameterObject[] = [] + + // pathItemObject can be undefined + if (!pathItemObject) { + continue + } + + for (let [key, opOrParams] of Object.entries(pathItemObject)) { + if (isParameter(key, opOrParams)) { + const pathParameters = opOrParams as OpenAPIV3.ParameterObject[] + pathParams.push(...pathParameters) + continue + } + // can not be a parameter, must be an operation + const operation = opOrParams as OpenAPIV3.OperationObject + + const methodName = key + const name = operation.operationId || path + let queryString = "" + const headers: any = {} + let requestBody = getRequestBody(operation) + const parameters: QueryParameter[] = [] + + // combine the path parameters with the operation parameters + const operationParams = operation.parameters || [] + const allParams = [...pathParams, ...operationParams] + + for (let param of allParams) { + if (parameterNotRef(param)) { + switch (param.in) { + case "query": + let prefix = "" + if (queryString) { + prefix = "&" + } + queryString = `${queryString}${prefix}${param.name}={{${param.name}}}` + break + case "header": + headers[param.name] = `{{${param.name}}}` + break + case "path": + // do nothing: param is already in the path + break + case "formData": + // future enhancement + break + } + + // add the parameter if it can be bound in our config + if (["query", "header", "path"].includes(param.in)) { + parameters.push({ + name: param.name, + default: "", + }) + } + } + } + + const query = this.constructQuery( + datasourceId, + name, + methodName, + path, + url, + queryString, + headers, + parameters, + requestBody + ) + queries.push(query) + } + } + + return queries + } +} diff --git a/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.json b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.json new file mode 100644 index 0000000000..c1cf4c2b18 --- /dev/null +++ b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.json @@ -0,0 +1,231 @@ +{ + "openapi": "3.0.2", + "info": { + "description": "A basic swagger file", + "version": "1.0.0", + "title": "CRUD" + }, + "tags": [ + { + "name": "entity" + } + ], + "paths": { + "/entities": { + "post": { + "tags": [ + "entity" + ], + "operationId": "createEntity", + "parameters": [ + { + "$ref": "#/components/schemas/CreateEntityParameter" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/components/schemas/Entity" + } + } + } + }, + "get": { + "tags": [ + "entity" + ], + "operationId": "getEntities", + "produces": [ + "application/json" + ], + "parameters": [ + { + "$ref": "#/components/schemas/PageParameter" + }, + { + "$ref": "#/components/schemas/SizeParameter" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/components/schemas/Entities" + } + } + } + } + }, + "/entities/{entityId}": { + "parameters": [ + { + "$ref": "#/components/schemas/EntityIdParameter" + } + ], + "get": { + "tags": [ + "entity" + ], + "operationId": "getEntity", + "produces": [ + "application/json" + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/components/schemas/Entity" + } + } + } + }, + "put": { + "tags": [ + "entity" + ], + "operationId": "updateEntity", + "parameters": [ + { + "$ref": "#/components/schemas/EntityParameter" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/components/schemas/Entity" + } + } + } + }, + "patch": { + "tags": [ + "entity" + ], + "operationId": "patchEntity", + "parameters": [ + { + "$ref": "#/components/schemas/EntityParameter" + } + ], + "responses": { + "200": { + "description": "successful operation", + "schema": { + "$ref": "#/components/schemas/Entity" + } + } + } + }, + "delete": { + "tags": [ + "entity" + ], + "parameters": [ + { + "type": "string", + "name": "x-api-key", + "in": "header", + "required": false + } + ], + "operationId": "deleteEntity", + "responses": { + "204": { + "description": "successful operation" + } + } + } + } + }, + "components": { + "schemas": { + "EntityIdParameter": { + "type": "integer", + "format": "int64", + "name": "entityId", + "in": "path", + "required": true + }, + "CreateEntityParameter": { + "name": "entity", + "in": "body", + "required": true, + "schema": { + "$ref": "#/components/schemas/CreateEntity" + } + }, + "EntityParameter": { + "name": "entity", + "in": "body", + "required": true, + "schema": { + "$ref": "#/components/schemas/Entity" + } + }, + "PageParameter": { + "type": "integer", + "format": "int32", + "name": "page", + "in": "query", + "required": false + }, + "SizeParameter": { + "type": "integer", + "format": "int32", + "name": "size", + "in": "query", + "required": false + }, + "APIKeyParameter": { + "type": "string", + "name": "x-api-key", + "in": "header", + "required": false + }, + "CreateEntity": { + "type": "object", + "properties": { + "name": { + "type": "string" + }, + "type": { + "type": "string" + } + }, + "example": { + "name": "name", + "type": "type" + } + }, + "Entity": { + "allOf": [ + { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + } + } + }, + { + "$ref": "#/components/schemas/CreateEntity" + } + ], + "example": { + "id": 1, + "name": "name", + "type": "type" + } + }, + "Entities" : { + "type": "array", + "items": { + "$ref": "#/components/schemas/Entity" + } + } + } + } +} diff --git a/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.yaml b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.yaml new file mode 100644 index 0000000000..f87b3e347c --- /dev/null +++ b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.yaml @@ -0,0 +1,156 @@ +--- +openapi: "3.0.2" +info: + description: A basic swagger file + version: 1.0.0 + title: CRUD +host: example.com +tags: + - name: entity +paths: + "/entities": + post: + tags: + - entity + operationId: createEntity + produces: + - application/json + parameters: + - "$ref": "#/parameters/CreateEntity" + responses: + "200": + description: successful operation + schema: + "$ref": "#/definitions/Entity" + get: + tags: + - entity + operationId: getEntities + produces: + - application/json + parameters: + - "$ref": "#/parameters/Page" + - "$ref": "#/parameters/Size" + responses: + "200": + description: successful operation + schema: + "$ref": "#/definitions/Entities" + "/entities/{entityId}": + parameters: + - "$ref": "#/parameters/EntityId" + get: + tags: + - entity + operationId: getEntity + produces: + - application/json + responses: + "200": + description: successful operation + schema: + "$ref": "#/definitions/Entity" + put: + tags: + - entity + operationId: updateEntity + consumes: + - application/json + produces: + - application/json + parameters: + - "$ref": "#/parameters/Entity" + responses: + "200": + description: successful operation + schema: + "$ref": "#/definitions/Entity" + patch: + tags: + - entity + operationId: patchEntity + consumes: + - application/json + produces: + - application/json + parameters: + - "$ref": "#/parameters/Entity" + responses: + "200": + description: successful operation + schema: + "$ref": "#/definitions/Entity" + delete: + tags: + - entity + parameters: + - "$ref": "#/parameters/APIKey" + operationId: deleteEntity + produces: + - application/json + responses: + "204": + description: successful operation +parameters: + EntityId: + type: integer + format: int64 + name: entityId + in: path + required: true + CreateEntity: + name: entity + in: body + required: true + schema: + "$ref": "#/definitions/CreateEntity" + Entity: + name: entity + in: body + required: true + schema: + "$ref": "#/definitions/Entity" + Page: + type: integer + format: int32 + name: page + in: query + required: false + Size: + type: integer + format: int32 + name: size + in: query + required: false + APIKey: + type: string + name: x-api-key + in: header + required: false +definitions: + CreateEntity: + type: object + properties: + name: + type: string + type: + type: string + example: + name: name + type: type + Entity: + allOf: + - type: object + properties: + id: + type: integer + format: int64 + - "$ref": "#/definitions/CreateEntity" + example: + id: 1 + name: name + type: type + Entities: + type: array + items: + "$ref": "#/definitions/Entity" diff --git a/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/petstore/petstore.json b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/petstore/petstore.json new file mode 100644 index 0000000000..14115066d6 --- /dev/null +++ b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/petstore/petstore.json @@ -0,0 +1,1225 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "Swagger Petstore - OpenAPI 3.0", + "description": "This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about\nSwagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach!\nYou can now help us improve the API whether it's by making changes to the definition itself or to the code.\nThat way, with time, we can improve the API in general, and expose some of the new features in OAS3.\n\nSome useful links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n- [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)", + "termsOfService": "http://swagger.io/terms/", + "contact": { + "email": "apiteam@swagger.io" + }, + "license": { + "name": "Apache 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + }, + "version": "1.0.11" + }, + "externalDocs": { + "description": "Find out more about Swagger", + "url": "http://swagger.io" + }, + "servers": [ + { + "url": "/api/v3" + } + ], + "tags": [ + { + "name": "pet", + "description": "Everything about your Pets", + "externalDocs": { + "description": "Find out more", + "url": "http://swagger.io" + } + }, + { + "name": "store", + "description": "Access to Petstore orders", + "externalDocs": { + "description": "Find out more about our store", + "url": "http://swagger.io" + } + }, + { + "name": "user", + "description": "Operations about user" + } + ], + "paths": { + "/pet": { + "put": { + "tags": [ + "pet" + ], + "summary": "Update an existing pet", + "description": "Update an existing pet by Id", + "operationId": "updatePet", + "requestBody": { + "description": "Update an existent pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + }, + "405": { + "description": "Validation exception" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Add a new pet to the store", + "description": "Add a new pet to the store", + "operationId": "addPet", + "requestBody": { + "description": "Create a new pet in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByStatus": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by status", + "description": "Multiple status values can be provided with comma separated strings", + "operationId": "findPetsByStatus", + "parameters": [ + { + "name": "status", + "in": "query", + "description": "Status values that need to be considered for filter", + "required": false, + "explode": true, + "schema": { + "type": "string", + "default": "available", + "enum": [ + "available", + "pending", + "sold" + ] + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid status value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/findByTags": { + "get": { + "tags": [ + "pet" + ], + "summary": "Finds Pets by tags", + "description": "Multiple tags can be provided with comma separated strings. Use tag1, tag2, tag3 for testing.", + "operationId": "findPetsByTags", + "parameters": [ + { + "name": "tags", + "in": "query", + "description": "Tags to filter by", + "required": false, + "explode": true, + "schema": { + "type": "array", + "items": { + "type": "string" + } + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + }, + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Pet" + } + } + } + } + }, + "400": { + "description": "Invalid tag value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}": { + "get": { + "tags": [ + "pet" + ], + "summary": "Find pet by ID", + "description": "Returns a single pet", + "operationId": "getPetById", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to return", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Pet not found" + } + }, + "security": [ + { + "api_key": [] + }, + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "post": { + "tags": [ + "pet" + ], + "summary": "Updates a pet in the store with form data", + "description": "", + "operationId": "updatePetWithForm", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet that needs to be updated", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "name", + "in": "query", + "description": "Name of pet that needs to be updated", + "schema": { + "type": "string" + } + }, + { + "name": "status", + "in": "query", + "description": "Status of pet that needs to be updated", + "schema": { + "type": "string" + } + } + ], + "responses": { + "405": { + "description": "Invalid input" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + }, + "delete": { + "tags": [ + "pet" + ], + "summary": "Deletes a pet", + "description": "", + "operationId": "deletePet", + "parameters": [ + { + "name": "api_key", + "in": "header", + "description": "", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "petId", + "in": "path", + "description": "Pet id to delete", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "400": { + "description": "Invalid pet value" + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/pet/{petId}/uploadImage": { + "post": { + "tags": [ + "pet" + ], + "summary": "uploads an image", + "description": "", + "operationId": "uploadFile", + "parameters": [ + { + "name": "petId", + "in": "path", + "description": "ID of pet to update", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + }, + { + "name": "additionalMetadata", + "in": "query", + "description": "Additional Metadata", + "required": false, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "content": { + "application/octet-stream": { + "schema": { + "type": "string", + "format": "binary" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ApiResponse" + } + } + } + } + }, + "security": [ + { + "petstore_auth": [ + "write:pets", + "read:pets" + ] + } + ] + } + }, + "/store/inventory": { + "get": { + "tags": [ + "store" + ], + "summary": "Returns pet inventories by status", + "description": "Returns a map of status codes to quantities", + "operationId": "getInventory", + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "type": "object", + "additionalProperties": { + "type": "integer", + "format": "int32" + } + } + } + } + } + }, + "security": [ + { + "api_key": [] + } + ] + } + }, + "/store/order": { + "post": { + "tags": [ + "store" + ], + "summary": "Place an order for a pet", + "description": "Place a new order in the store", + "operationId": "placeOrder", + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "405": { + "description": "Invalid input" + } + } + } + }, + "/store/order/{orderId}": { + "get": { + "tags": [ + "store" + ], + "summary": "Find purchase order by ID", + "description": "For valid response try integer IDs with value <= 5 or > 10. Other values will generate exceptions.", + "operationId": "getOrderById", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of order that needs to be fetched", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Order" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/Order" + } + } + } + }, + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + }, + "delete": { + "tags": [ + "store" + ], + "summary": "Delete purchase order by ID", + "description": "For valid response try integer IDs with value < 1000. Anything above 1000 or nonintegers will generate API errors", + "operationId": "deleteOrder", + "parameters": [ + { + "name": "orderId", + "in": "path", + "description": "ID of the order that needs to be deleted", + "required": true, + "schema": { + "type": "integer", + "format": "int64" + } + } + ], + "responses": { + "400": { + "description": "Invalid ID supplied" + }, + "404": { + "description": "Order not found" + } + } + } + }, + "/user": { + "post": { + "tags": [ + "user" + ], + "summary": "Create user", + "description": "This can only be done by the logged in user.", + "operationId": "createUser", + "requestBody": { + "description": "Created user object", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "default": { + "description": "successful operation", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + } + }, + "/user/createWithList": { + "post": { + "tags": [ + "user" + ], + "summary": "Creates list of users with given input array", + "description": "Creates list of users with given input array", + "operationId": "createUsersWithListInput", + "requestBody": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + }, + "responses": { + "200": { + "description": "Successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "default": { + "description": "successful operation" + } + } + } + }, + "/user/login": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs user into the system", + "description": "", + "operationId": "loginUser", + "parameters": [ + { + "name": "username", + "in": "query", + "description": "The user name for login", + "required": false, + "schema": { + "type": "string" + } + }, + { + "name": "password", + "in": "query", + "description": "The password for login in clear text", + "required": false, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "headers": { + "X-Rate-Limit": { + "description": "calls per hour allowed by the user", + "schema": { + "type": "integer", + "format": "int32" + } + }, + "X-Expires-After": { + "description": "date in UTC when token expires", + "schema": { + "type": "string", + "format": "date-time" + } + } + }, + "content": { + "application/xml": { + "schema": { + "type": "string" + } + }, + "application/json": { + "schema": { + "type": "string" + } + } + } + }, + "400": { + "description": "Invalid username/password supplied" + } + } + } + }, + "/user/logout": { + "get": { + "tags": [ + "user" + ], + "summary": "Logs out current logged in user session", + "description": "", + "operationId": "logoutUser", + "parameters": [], + "responses": { + "default": { + "description": "successful operation" + } + } + } + }, + "/user/{username}": { + "get": { + "tags": [ + "user" + ], + "summary": "Get user by user name", + "description": "", + "operationId": "getUserByName", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be fetched. Use user1 for testing. ", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "200": { + "description": "successful operation", + "content": { + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + }, + "put": { + "tags": [ + "user" + ], + "summary": "Update user", + "description": "This can only be done by the logged in user.", + "operationId": "updateUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "name that need to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "requestBody": { + "description": "Update an existent user in the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/User" + } + }, + "application/x-www-form-urlencoded": { + "schema": { + "$ref": "#/components/schemas/User" + } + } + } + }, + "responses": { + "default": { + "description": "successful operation" + } + } + }, + "delete": { + "tags": [ + "user" + ], + "summary": "Delete user", + "description": "This can only be done by the logged in user.", + "operationId": "deleteUser", + "parameters": [ + { + "name": "username", + "in": "path", + "description": "The name that needs to be deleted", + "required": true, + "schema": { + "type": "string" + } + } + ], + "responses": { + "400": { + "description": "Invalid username supplied" + }, + "404": { + "description": "User not found" + } + } + } + } + }, + "components": { + "schemas": { + "Order": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "petId": { + "type": "integer", + "format": "int64", + "example": 198772 + }, + "quantity": { + "type": "integer", + "format": "int32", + "example": 7 + }, + "shipDate": { + "type": "string", + "format": "date-time" + }, + "status": { + "type": "string", + "description": "Order Status", + "example": "approved", + "enum": [ + "placed", + "approved", + "delivered" + ] + }, + "complete": { + "type": "boolean" + } + }, + "xml": { + "name": "order" + } + }, + "Customer": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 100000 + }, + "username": { + "type": "string", + "example": "fehguy" + }, + "address": { + "type": "array", + "xml": { + "name": "addresses", + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Address" + } + } + }, + "xml": { + "name": "customer" + } + }, + "Address": { + "type": "object", + "properties": { + "street": { + "type": "string", + "example": "437 Lytton" + }, + "city": { + "type": "string", + "example": "Palo Alto" + }, + "state": { + "type": "string", + "example": "CA" + }, + "zip": { + "type": "string", + "example": "94301" + } + }, + "xml": { + "name": "address" + } + }, + "Category": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 1 + }, + "name": { + "type": "string", + "example": "Dogs" + } + }, + "xml": { + "name": "category" + } + }, + "User": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "username": { + "type": "string", + "example": "theUser" + }, + "firstName": { + "type": "string", + "example": "John" + }, + "lastName": { + "type": "string", + "example": "James" + }, + "email": { + "type": "string", + "example": "john@email.com" + }, + "password": { + "type": "string", + "example": "12345" + }, + "phone": { + "type": "string", + "example": "12345" + }, + "userStatus": { + "type": "integer", + "description": "User Status", + "format": "int32", + "example": 1 + } + }, + "xml": { + "name": "user" + } + }, + "Tag": { + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64" + }, + "name": { + "type": "string" + } + }, + "xml": { + "name": "tag" + } + }, + "Pet": { + "required": [ + "name", + "photoUrls" + ], + "type": "object", + "properties": { + "id": { + "type": "integer", + "format": "int64", + "example": 10 + }, + "name": { + "type": "string", + "example": "doggie" + }, + "category": { + "$ref": "#/components/schemas/Category" + }, + "photoUrls": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "type": "string", + "xml": { + "name": "photoUrl" + } + } + }, + "tags": { + "type": "array", + "xml": { + "wrapped": true + }, + "items": { + "$ref": "#/components/schemas/Tag" + } + }, + "status": { + "type": "string", + "description": "pet status in the store", + "enum": [ + "available", + "pending", + "sold" + ] + } + }, + "xml": { + "name": "pet" + } + }, + "ApiResponse": { + "type": "object", + "properties": { + "code": { + "type": "integer", + "format": "int32" + }, + "type": { + "type": "string" + }, + "message": { + "type": "string" + } + }, + "xml": { + "name": "##default" + } + } + }, + "requestBodies": { + "Pet": { + "description": "Pet object that needs to be added to the store", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + }, + "application/xml": { + "schema": { + "$ref": "#/components/schemas/Pet" + } + } + } + }, + "UserArray": { + "description": "List of user object", + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/User" + } + } + } + } + } + }, + "securitySchemes": { + "petstore_auth": { + "type": "oauth2", + "flows": { + "implicit": { + "authorizationUrl": "https://petstore3.swagger.io/oauth/authorize", + "scopes": { + "write:pets": "modify pets in your account", + "read:pets": "read your pets" + } + } + } + }, + "api_key": { + "type": "apiKey", + "name": "api_key", + "in": "header" + } + } + } +} diff --git a/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/petstore/petstore.yaml b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/petstore/petstore.yaml new file mode 100644 index 0000000000..0f39b90c16 --- /dev/null +++ b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/petstore/petstore.yaml @@ -0,0 +1,804 @@ +--- +openapi: 3.0.2 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: |- + This is a sample Pet Store Server based on the OpenAPI 3.0 specification. You can find out more about + Swagger at [http://swagger.io](http://swagger.io). In the third iteration of the pet store, we've switched to the design first approach! + You can now help us improve the API whether it's by making changes to the definition itself or to the code. + That way, with time, we can improve the API in general, and expose some of the new features in OAS3. + + Some useful links: + - [The Pet Store repository](https://github.com/swagger-api/swagger-petstore) + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml) + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.11 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +servers: + - url: "/api/v3" +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io + - name: store + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: http://swagger.io + - name: user + description: Operations about user +paths: + "/pet": + put: + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + "$ref": "#/components/schemas/Pet" + application/xml: + schema: + "$ref": "#/components/schemas/Pet" + application/x-www-form-urlencoded: + schema: + "$ref": "#/components/schemas/Pet" + required: true + responses: + '200': + description: Successful operation + content: + application/xml: + schema: + "$ref": "#/components/schemas/Pet" + application/json: + schema: + "$ref": "#/components/schemas/Pet" + '400': + description: Invalid ID supplied + '404': + description: Pet not found + '405': + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + "$ref": "#/components/schemas/Pet" + application/xml: + schema: + "$ref": "#/components/schemas/Pet" + application/x-www-form-urlencoded: + schema: + "$ref": "#/components/schemas/Pet" + required: true + responses: + '200': + description: Successful operation + content: + application/xml: + schema: + "$ref": "#/components/schemas/Pet" + application/json: + schema: + "$ref": "#/components/schemas/Pet" + '405': + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + "/pet/findByStatus": + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: false + explode: true + schema: + type: string + default: available + enum: + - available + - pending + - sold + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + "$ref": "#/components/schemas/Pet" + application/json: + schema: + type: array + items: + "$ref": "#/components/schemas/Pet" + '400': + description: Invalid status value + security: + - petstore_auth: + - write:pets + - read:pets + "/pet/findByTags": + get: + tags: + - pet + summary: Finds Pets by tags + description: Multiple tags can be provided with comma separated strings. Use + tag1, tag2, tag3 for testing. + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: false + explode: true + schema: + type: array + items: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + type: array + items: + "$ref": "#/components/schemas/Pet" + application/json: + schema: + type: array + items: + "$ref": "#/components/schemas/Pet" + '400': + description: Invalid tag value + security: + - petstore_auth: + - write:pets + - read:pets + "/pet/{petId}": + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + "$ref": "#/components/schemas/Pet" + application/json: + schema: + "$ref": "#/components/schemas/Pet" + '400': + description: Invalid ID supplied + '404': + description: Pet not found + security: + - api_key: [] + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: '' + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + '405': + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet + description: '' + operationId: deletePet + parameters: + - name: api_key + in: header + description: '' + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid pet value + security: + - petstore_auth: + - write:pets + - read:pets + "/pet/{petId}/uploadImage": + post: + tags: + - pet + summary: uploads an image + description: '' + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + '200': + description: successful operation + content: + application/json: + schema: + "$ref": "#/components/schemas/ApiResponse" + security: + - petstore_auth: + - write:pets + - read:pets + "/store/inventory": + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + '200': + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + "/store/order": + post: + tags: + - store + summary: Place an order for a pet + description: Place a new order in the store + operationId: placeOrder + requestBody: + content: + application/json: + schema: + "$ref": "#/components/schemas/Order" + application/xml: + schema: + "$ref": "#/components/schemas/Order" + application/x-www-form-urlencoded: + schema: + "$ref": "#/components/schemas/Order" + responses: + '200': + description: successful operation + content: + application/json: + schema: + "$ref": "#/components/schemas/Order" + '405': + description: Invalid input + "/store/order/{orderId}": + get: + tags: + - store + summary: Find purchase order by ID + description: For valid response try integer IDs with value <= 5 or > 10. Other + values will generate exceptions. + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + '200': + description: successful operation + content: + application/xml: + schema: + "$ref": "#/components/schemas/Order" + application/json: + schema: + "$ref": "#/components/schemas/Order" + '400': + description: Invalid ID supplied + '404': + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: For valid response try integer IDs with value < 1000. Anything + above 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + '400': + description: Invalid ID supplied + '404': + description: Order not found + "/user": + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + "$ref": "#/components/schemas/User" + application/xml: + schema: + "$ref": "#/components/schemas/User" + application/x-www-form-urlencoded: + schema: + "$ref": "#/components/schemas/User" + responses: + default: + description: successful operation + content: + application/json: + schema: + "$ref": "#/components/schemas/User" + application/xml: + schema: + "$ref": "#/components/schemas/User" + "/user/createWithList": + post: + tags: + - user + summary: Creates list of users with given input array + description: Creates list of users with given input array + operationId: createUsersWithListInput + requestBody: + content: + application/json: + schema: + type: array + items: + "$ref": "#/components/schemas/User" + responses: + '200': + description: Successful operation + content: + application/xml: + schema: + "$ref": "#/components/schemas/User" + application/json: + schema: + "$ref": "#/components/schemas/User" + default: + description: successful operation + "/user/login": + get: + tags: + - user + summary: Logs user into the system + description: '' + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + '200': + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when token expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + '400': + description: Invalid username/password supplied + "/user/logout": + get: + tags: + - user + summary: Logs out current logged in user session + description: '' + operationId: logoutUser + parameters: [] + responses: + default: + description: successful operation + "/user/{username}": + get: + tags: + - user + summary: Get user by user name + description: '' + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + '200': + description: successful operation + content: + application/xml: + schema: + "$ref": "#/components/schemas/User" + application/json: + schema: + "$ref": "#/components/schemas/User" + '400': + description: Invalid username supplied + '404': + description: User not found + put: + tags: + - user + summary: Update user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + "$ref": "#/components/schemas/User" + application/xml: + schema: + "$ref": "#/components/schemas/User" + application/x-www-form-urlencoded: + schema: + "$ref": "#/components/schemas/User" + responses: + default: + description: successful operation + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + '400': + description: Invalid username supplied + '404': + description: User not found +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + Customer: + type: object + properties: + id: + type: integer + format: int64 + example: 100000 + username: + type: string + example: fehguy + address: + type: array + xml: + name: addresses + wrapped: true + items: + "$ref": "#/components/schemas/Address" + xml: + name: customer + Address: + type: object + properties: + street: + type: string + example: 437 Lytton + city: + type: string + example: Palo Alto + state: + type: string + example: CA + zip: + type: string + example: '94301' + xml: + name: address + Category: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + User: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: '12345' + phone: + type: string + example: '12345' + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + Pet: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + "$ref": "#/components/schemas/Category" + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + "$ref": "#/components/schemas/Tag" + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: "##default" + requestBodies: + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + "$ref": "#/components/schemas/Pet" + application/xml: + schema: + "$ref": "#/components/schemas/Pet" + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + "$ref": "#/components/schemas/User" + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/packages/server/src/api/controllers/query/import/sources/tests/openapi3/openapi3.spec.js b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/openapi3.spec.js new file mode 100644 index 0000000000..6278e0fece --- /dev/null +++ b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/openapi3.spec.js @@ -0,0 +1,256 @@ +const fs = require("fs") +const path = require("path") +const getGlobalDB = jest.fn().mockReturnValue("db") +const getTenantId = jest.fn().mockReturnValue("1") +const isMultiTenant = jest.fn().mockReturnValue(true) +const getScopedConfig = jest + .fn() + .mockResolvedValue({ platformUrl: "somePlatform" }) + +jest.mock("@budibase/backend-core/db", () => ({ + getScopedConfig, +})) + +jest.mock("@budibase/backend-core/tenancy", () => ({ + getTenantId, + getGlobalDB, + isMultiTenant, +})) +const { OpenAPI3 } = require("../../openapi3") + +const getData = (file, extension) => { + return fs.readFileSync( + path.join(__dirname, `./data/${file}/${file}.${extension}`), + "utf8" + ) +} + +describe("OpenAPI3 Import", () => { + let openapi3 + + beforeEach(() => { + openapi3 = new OpenAPI3() + }) + + it("validates unsupported data", async () => { + let data + let supported + + // non json / yaml + data = "curl http://example.com" + supported = await openapi3.isSupported(data) + expect(supported).toBe(false) + + // Empty + data = "" + supported = await openapi3.isSupported(data) + expect(supported).toBe(false) + }) + + const runTests = async (filename, test, assertions) => { + /*for (let extension of ["json", "yaml"]) { + await test(filename, extension, assertions) + }*/ + await test(filename, "json", assertions) + } + + const testImportInfo = async (file, extension) => { + await openapi3.isSupported(getData(file, extension)) + const info = await openapi3.getInfo() + expect(info.name).toBe("Swagger Petstore - OpenAPI 3.0") + } + + it("returns import info", async () => { + await runTests("petstore", testImportInfo) + }) + + describe("Returns queries", () => { + const indexQueries = queries => { + return queries.reduce((acc, query) => { + acc[query.name] = query + return acc + }, {}) + } + + const getQueries = async (file, extension) => { + await openapi3.isSupported(getData(file, extension)) + const queries = await openapi3.getQueries() + expect(queries.length).toBe(6) + return indexQueries(queries) + } + + const testVerb = async (file, extension, assertions) => { + const queries = await getQueries(file, extension) + for (let [operationId, method] of Object.entries(assertions)) { + expect(queries[operationId].queryVerb).toBe(method) + } + } + + it("populates verb", async () => { + const assertions = { + createEntity: "create", + getEntities: "read", + getEntity: "read", + updateEntity: "update", + patchEntity: "patch", + deleteEntity: "delete", + } + await runTests("crud", testVerb, assertions) + }) + + const testPath = async (file, extension, assertions) => { + const queries = await getQueries(file, extension) + for (let [operationId, urlPath] of Object.entries(assertions)) { + expect(queries[operationId].fields.path).toBe(urlPath) + } + } + + it("populates path", async () => { + const assertions = { + createEntity: "http://example.com/entities", + getEntities: "http://example.com/entities", + getEntity: "http://example.com/entities/{{entityId}}", + updateEntity: "http://example.com/entities/{{entityId}}", + patchEntity: "http://example.com/entities/{{entityId}}", + deleteEntity: "http://example.com/entities/{{entityId}}", + } + await runTests("crud", testPath, assertions) + }) + + const testHeaders = async (file, extension, assertions) => { + const queries = await getQueries(file, extension) + for (let [operationId, headers] of Object.entries(assertions)) { + expect(queries[operationId].fields.headers).toStrictEqual(headers) + } + } + + const contentTypeHeader = { + "Content-Type": "application/json", + } + + it("populates headers", async () => { + const assertions = { + createEntity: { + ...contentTypeHeader, + }, + getEntities: {}, + getEntity: {}, + updateEntity: { + ...contentTypeHeader, + }, + patchEntity: { + ...contentTypeHeader, + }, + deleteEntity: { + "x-api-key": "{{x-api-key}}", + }, + } + + await runTests("crud", testHeaders, assertions) + }) + + const testQuery = async (file, extension, assertions) => { + const queries = await getQueries(file, extension) + for (let [operationId, queryString] of Object.entries(assertions)) { + expect(queries[operationId].fields.queryString).toStrictEqual( + queryString + ) + } + } + + it("populates query", async () => { + const assertions = { + createEntity: "", + getEntities: "page={{page}}&size={{size}}", + getEntity: "", + updateEntity: "", + patchEntity: "", + deleteEntity: "", + } + await runTests("crud", testQuery, assertions) + }) + + const testParameters = async (file, extension, assertions) => { + const queries = await getQueries(file, extension) + for (let [operationId, parameters] of Object.entries(assertions)) { + expect(queries[operationId].parameters).toStrictEqual(parameters) + } + } + + it("populates parameters", async () => { + const assertions = { + createEntity: [], + getEntities: [ + { + name: "page", + default: "", + }, + { + name: "size", + default: "", + }, + ], + getEntity: [ + { + name: "entityId", + default: "", + }, + ], + updateEntity: [ + { + name: "entityId", + default: "", + }, + ], + patchEntity: [ + { + name: "entityId", + default: "", + }, + ], + deleteEntity: [ + { + name: "entityId", + default: "", + }, + { + name: "x-api-key", + default: "", + }, + ], + } + await runTests("crud", testParameters, assertions) + }) + + const testBody = async (file, extension, assertions) => { + const queries = await getQueries(file, extension) + for (let [operationId, body] of Object.entries(assertions)) { + expect(queries[operationId].fields.requestBody).toStrictEqual( + JSON.stringify(body, null, 2) + ) + } + } + it("populates body", async () => { + const assertions = { + createEntity: { + name: "name", + type: "type", + }, + getEntities: undefined, + getEntity: undefined, + updateEntity: { + id: 1, + name: "name", + type: "type", + }, + patchEntity: { + id: 1, + name: "name", + type: "type", + }, + deleteEntity: undefined, + } + await runTests("crud", testBody, assertions) + }) + }) +}) From 5bc9051efac6d07c16355b41a3d8cf62e0fe88e8 Mon Sep 17 00:00:00 2001 From: Maurits Lourens Date: Thu, 3 Mar 2022 15:19:36 +0100 Subject: [PATCH 007/206] fix openapi 3 test doc and tests --- .../query/import/sources/base/index.ts | 6 +- .../query/import/sources/openapi3.ts | 71 +++--- .../tests/openapi3/data/crud/crud.json | 144 +++++++----- .../tests/openapi3/data/crud/crud.yaml | 209 +++++++++--------- .../sources/tests/openapi3/openapi3.spec.js | 20 +- 5 files changed, 225 insertions(+), 225 deletions(-) diff --git a/packages/server/src/api/controllers/query/import/sources/base/index.ts b/packages/server/src/api/controllers/query/import/sources/base/index.ts index e666fdc193..096fa10e92 100644 --- a/packages/server/src/api/controllers/query/import/sources/base/index.ts +++ b/packages/server/src/api/controllers/query/import/sources/base/index.ts @@ -23,7 +23,7 @@ export abstract class ImportSource { name: string, method: string, path: string, - url: URL, + url: URL | null, queryString: string, headers: object = {}, parameters: QueryParameter[] = [], @@ -34,7 +34,9 @@ export abstract class ImportSource { const transformer = "return data" const schema = {} path = this.processPath(path) - path = `${url.origin}/${path}` + if (url) { + path = `${url.origin}/${path}` + } queryString = this.processQuery(queryString) const requestBody = JSON.stringify(body, null, 2) diff --git a/packages/server/src/api/controllers/query/import/sources/openapi3.ts b/packages/server/src/api/controllers/query/import/sources/openapi3.ts index 8c40c49c9d..c9e49595b2 100644 --- a/packages/server/src/api/controllers/query/import/sources/openapi3.ts +++ b/packages/server/src/api/controllers/query/import/sources/openapi3.ts @@ -3,14 +3,7 @@ import { Query, QueryParameter } from "../../../../../definitions/datasource" import { OpenAPIV3 } from "openapi-types" import { OpenAPISource } from "./base/openapi" import { URL } from "url" -import { - getGlobalDB, - getTenantId, - isMultiTenant, -} from "@budibase/backend-core/tenancy" -import { getScopedConfig } from "@budibase/backend-core/db" -const jsonMimeType = "application/json" const parameterNotRef = ( param: OpenAPIV3.ParameterObject | OpenAPIV3.ReferenceObject ): param is OpenAPIV3.ParameterObject => { @@ -25,6 +18,13 @@ const requestBodyNotRef = ( return param !== undefined } +const schemaNotRef = ( + param: OpenAPIV3.SchemaObject | OpenAPIV3.ReferenceObject | undefined +): param is OpenAPIV3.SchemaObject => { + // all refs are deferenced by parser library + return param !== undefined +} + const isOpenAPI3 = (document: any): document is OpenAPIV3.Document => { return document.openapi.includes("3") } @@ -49,9 +49,25 @@ const getRequestBody = (operation: OpenAPIV3.OperationObject) => { if (requestBodyNotRef(operation.requestBody)) { const request: OpenAPIV3.RequestBodyObject = operation.requestBody as OpenAPIV3.RequestBodyObject - return request.content[jsonMimeType]?.example + const supportedMimeTypes = getMimeTypes(operation) + return supportedMimeTypes.length > 0 && + schemaNotRef(request.content[supportedMimeTypes[0]].schema) + ? ( + request.content[supportedMimeTypes[0]] + .schema as OpenAPIV3.SchemaObject + ).example + : undefined } - return null + return undefined +} + +const getMimeTypes = (operation: OpenAPIV3.OperationObject): string[] => { + if (requestBodyNotRef(operation.requestBody)) { + const request: OpenAPIV3.RequestBodyObject = + operation.requestBody as OpenAPIV3.RequestBodyObject + return Object.keys(request.content) + } + return [] } /** @@ -75,34 +91,6 @@ export class OpenAPI3 extends OpenAPISource { } } - getPlatformUrl = async (): Promise => { - const db = getGlobalDB() - const publicConfig = await getScopedConfig(db, { - type: "settings", - }) - - let url = publicConfig.platformUrl - if (isMultiTenant()) { - url += `/${getTenantId()}` - } - return url - } - - getUrl = async (): Promise => { - const platformUrl = await this.getPlatformUrl() - let url = this.document.servers ? this.document.servers[0].url : null - if (url) { - // check if url is relative or absolute - if (url.includes("http") || url.includes("https")) { - return new URL(url) - } - - return new URL(`${platformUrl}${url}`) - } - // if the specification doesn't contain a servers object, return the PLATFORM_URM environment variable - return new URL(platformUrl) - } - getInfo = async (): Promise => { const name = this.document.info.title || "Swagger Import" return { @@ -111,7 +99,9 @@ export class OpenAPI3 extends OpenAPISource { } getQueries = async (datasourceId: string): Promise => { - const url: URL = await this.getUrl() + const url: URL | null = this.document.servers + ? new URL(this.document.servers[0].url) + : null const queries: Query[] = [] for (let [path, pathItemObject] of Object.entries(this.document.paths)) { @@ -138,6 +128,11 @@ export class OpenAPI3 extends OpenAPISource { const headers: any = {} let requestBody = getRequestBody(operation) const parameters: QueryParameter[] = [] + const mimeTypes = getMimeTypes(operation) + + if (mimeTypes.length > 0) { + headers["Content-Type"] = mimeTypes[0] + } // combine the path parameters with the operation parameters const operationParams = operation.parameters || [] diff --git a/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.json b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.json index c1cf4c2b18..a859eec8e5 100644 --- a/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.json +++ b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.json @@ -5,6 +5,11 @@ "version": "1.0.0", "title": "CRUD" }, + "servers": [ + { + "url": "http://example.com" + } + ], "tags": [ { "name": "entity" @@ -17,16 +22,24 @@ "entity" ], "operationId": "createEntity", - "parameters": [ - { - "$ref": "#/components/schemas/CreateEntityParameter" + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/CreateEntity" + } + } } - ], + }, "responses": { "200": { "description": "successful operation", - "schema": { - "$ref": "#/components/schemas/Entity" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Entity" + } + } } } } @@ -36,22 +49,23 @@ "entity" ], "operationId": "getEntities", - "produces": [ - "application/json" - ], "parameters": [ { - "$ref": "#/components/schemas/PageParameter" + "$ref": "#/components/parameters/PageParameter" }, { - "$ref": "#/components/schemas/SizeParameter" + "$ref": "#/components/parameters/SizeParameter" } ], "responses": { "200": { "description": "successful operation", - "schema": { - "$ref": "#/components/schemas/Entities" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Entities" + } + } } } } @@ -60,7 +74,7 @@ "/entities/{entityId}": { "parameters": [ { - "$ref": "#/components/schemas/EntityIdParameter" + "$ref": "#/components/parameters/EntityIdParameter" } ], "get": { @@ -68,14 +82,15 @@ "entity" ], "operationId": "getEntity", - "produces": [ - "application/json" - ], "responses": { "200": { "description": "successful operation", - "schema": { - "$ref": "#/components/schemas/Entity" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Entity" + } + } } } } @@ -85,16 +100,24 @@ "entity" ], "operationId": "updateEntity", - "parameters": [ - { - "$ref": "#/components/schemas/EntityParameter" + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Entity" + } + } } - ], + }, "responses": { "200": { "description": "successful operation", - "schema": { - "$ref": "#/components/schemas/Entity" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Entity" + } + } } } } @@ -104,16 +127,24 @@ "entity" ], "operationId": "patchEntity", - "parameters": [ - { - "$ref": "#/components/schemas/EntityParameter" + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Entity" + } + } } - ], + }, "responses": { "200": { "description": "successful operation", - "schema": { - "$ref": "#/components/schemas/Entity" + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Entity" + } + } } } } @@ -124,10 +155,7 @@ ], "parameters": [ { - "type": "string", - "name": "x-api-key", - "in": "header", - "required": false + "$ref": "#/components/parameters/APIKeyParameter" } ], "operationId": "deleteEntity", @@ -140,50 +168,44 @@ } }, "components": { - "schemas": { + "parameters": { "EntityIdParameter": { - "type": "integer", - "format": "int64", + "schema": { + "type": "integer", + "format": "int64" + }, "name": "entityId", "in": "path", "required": true }, - "CreateEntityParameter": { - "name": "entity", - "in": "body", - "required": true, - "schema": { - "$ref": "#/components/schemas/CreateEntity" - } - }, - "EntityParameter": { - "name": "entity", - "in": "body", - "required": true, - "schema": { - "$ref": "#/components/schemas/Entity" - } - }, - "PageParameter": { - "type": "integer", - "format": "int32", + "PageParameter": { + "schema": { + "type": "integer", + "format": "int32" + }, "name": "page", "in": "query", "required": false }, "SizeParameter": { - "type": "integer", - "format": "int32", + "schema": { + "type": "integer", + "format": "int32" + }, "name": "size", "in": "query", "required": false }, "APIKeyParameter": { - "type": "string", + "schema": { + "type": "string" + }, "name": "x-api-key", "in": "header", "required": false - }, + } + }, + "schemas": { "CreateEntity": { "type": "object", "properties": { diff --git a/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.yaml b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.yaml index f87b3e347c..0a44a50a7b 100644 --- a/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.yaml +++ b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/data/crud/crud.yaml @@ -1,10 +1,11 @@ --- -openapi: "3.0.2" +openapi: 3.0.2 info: description: A basic swagger file version: 1.0.0 title: CRUD -host: example.com +servers: + - url: http://example.com tags: - name: entity paths: @@ -13,144 +14,140 @@ paths: tags: - entity operationId: createEntity - produces: - - application/json - parameters: - - "$ref": "#/parameters/CreateEntity" + requestBody: + content: + application/json: + schema: + "$ref": "#/components/schemas/CreateEntity" responses: - "200": + '200': description: successful operation - schema: - "$ref": "#/definitions/Entity" + content: + application/json: + schema: + "$ref": "#/components/schemas/Entity" get: tags: - entity operationId: getEntities - produces: - - application/json parameters: - - "$ref": "#/parameters/Page" - - "$ref": "#/parameters/Size" + - "$ref": "#/components/parameters/PageParameter" + - "$ref": "#/components/parameters/SizeParameter" responses: - "200": + '200': description: successful operation - schema: - "$ref": "#/definitions/Entities" + content: + application/json: + schema: + "$ref": "#/components/schemas/Entities" "/entities/{entityId}": parameters: - - "$ref": "#/parameters/EntityId" + - "$ref": "#/components/parameters/EntityIdParameter" get: tags: - entity operationId: getEntity - produces: - - application/json responses: - "200": + '200': description: successful operation - schema: - "$ref": "#/definitions/Entity" + content: + application/json: + schema: + "$ref": "#/components/schemas/Entity" put: tags: - entity operationId: updateEntity - consumes: - - application/json - produces: - - application/json - parameters: - - "$ref": "#/parameters/Entity" + requestBody: + content: + application/json: + schema: + "$ref": "#/components/schemas/Entity" responses: - "200": + '200': description: successful operation - schema: - "$ref": "#/definitions/Entity" + content: + application/json: + schema: + "$ref": "#/components/schemas/Entity" patch: tags: - entity operationId: patchEntity - consumes: - - application/json - produces: - - application/json - parameters: - - "$ref": "#/parameters/Entity" + requestBody: + content: + application/json: + schema: + "$ref": "#/components/schemas/Entity" responses: - "200": + '200': description: successful operation - schema: - "$ref": "#/definitions/Entity" + content: + application/json: + schema: + "$ref": "#/components/schemas/Entity" delete: tags: - entity parameters: - - "$ref": "#/parameters/APIKey" + - "$ref": "#/components/parameters/APIKeyParameter" operationId: deleteEntity - produces: - - application/json responses: - "204": + '204': description: successful operation -parameters: - EntityId: - type: integer - format: int64 - name: entityId - in: path - required: true - CreateEntity: - name: entity - in: body - required: true - schema: - "$ref": "#/definitions/CreateEntity" - Entity: - name: entity - in: body - required: true - schema: - "$ref": "#/definitions/Entity" - Page: - type: integer - format: int32 - name: page - in: query - required: false - Size: - type: integer - format: int32 - name: size - in: query - required: false - APIKey: - type: string - name: x-api-key - in: header - required: false -definitions: - CreateEntity: - type: object - properties: - name: +components: + parameters: + EntityIdParameter: + schema: + type: integer + format: int64 + name: entityId + in: path + required: true + PageParameter: + schema: + type: integer + format: int32 + name: page + in: query + required: false + SizeParameter: + schema: + type: integer + format: int32 + name: size + in: query + required: false + APIKeyParameter: + schema: type: string - type: - type: string - example: - name: name - type: type - Entity: - allOf: - - type: object - properties: - id: - type: integer - format: int64 - - "$ref": "#/definitions/CreateEntity" - example: - id: 1 - name: name - type: type - Entities: - type: array - items: - "$ref": "#/definitions/Entity" + name: x-api-key + in: header + required: false + schemas: + CreateEntity: + type: object + properties: + name: + type: string + type: + type: string + example: + name: name + type: type + Entity: + allOf: + - type: object + properties: + id: + type: integer + format: int64 + - "$ref": "#/components/schemas/CreateEntity" + example: + id: 1 + name: name + type: type + Entities: + type: array + items: + "$ref": "#/components/schemas/Entity" diff --git a/packages/server/src/api/controllers/query/import/sources/tests/openapi3/openapi3.spec.js b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/openapi3.spec.js index 6278e0fece..731c245d4e 100644 --- a/packages/server/src/api/controllers/query/import/sources/tests/openapi3/openapi3.spec.js +++ b/packages/server/src/api/controllers/query/import/sources/tests/openapi3/openapi3.spec.js @@ -1,21 +1,6 @@ const fs = require("fs") const path = require("path") -const getGlobalDB = jest.fn().mockReturnValue("db") -const getTenantId = jest.fn().mockReturnValue("1") -const isMultiTenant = jest.fn().mockReturnValue(true) -const getScopedConfig = jest - .fn() - .mockResolvedValue({ platformUrl: "somePlatform" }) -jest.mock("@budibase/backend-core/db", () => ({ - getScopedConfig, -})) - -jest.mock("@budibase/backend-core/tenancy", () => ({ - getTenantId, - getGlobalDB, - isMultiTenant, -})) const { OpenAPI3 } = require("../../openapi3") const getData = (file, extension) => { @@ -48,10 +33,9 @@ describe("OpenAPI3 Import", () => { }) const runTests = async (filename, test, assertions) => { - /*for (let extension of ["json", "yaml"]) { + for (let extension of ["json", "yaml"]) { await test(filename, extension, assertions) - }*/ - await test(filename, "json", assertions) + } } const testImportInfo = async (file, extension) => { From 530024bd632d6ad47d0bf066c0192c3ed5d8dc37 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 7 Mar 2022 08:16:55 +0000 Subject: [PATCH 008/206] Add component name to settings panel --- .../design/PropertiesPanel/ComponentSettingsSection.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/src/components/design/PropertiesPanel/ComponentSettingsSection.svelte b/packages/builder/src/components/design/PropertiesPanel/ComponentSettingsSection.svelte index f9fa56f739..88455fb003 100644 --- a/packages/builder/src/components/design/PropertiesPanel/ComponentSettingsSection.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/ComponentSettingsSection.svelte @@ -32,7 +32,7 @@ const customSections = settings.filter(setting => setting.section) return [ { - name: "General", + name: componentDefinition?.name || "General", info: componentDefinition?.info, settings: generalSettings, }, From 5ee150566b8f90261d8f9ac7f102fbbf696f98f1 Mon Sep 17 00:00:00 2001 From: Rory Powell Date: Mon, 7 Mar 2022 10:41:28 +0000 Subject: [PATCH 009/206] Fix build, additional tests, updated body detection, support variables in urls --- .../src/api/controllers/query/import/index.ts | 4 +- .../query/import/sources/base/index.ts | 8 +++- .../query/import/sources/openapi3.ts | 44 +++++++++++++------ .../query/import/tests/index.spec.js | 43 ++++++++++++++++++ 4 files changed, 81 insertions(+), 18 deletions(-) diff --git a/packages/server/src/api/controllers/query/import/index.ts b/packages/server/src/api/controllers/query/import/index.ts index 4573fd27f7..d6f5beedf5 100644 --- a/packages/server/src/api/controllers/query/import/index.ts +++ b/packages/server/src/api/controllers/query/import/index.ts @@ -1,7 +1,7 @@ import { queryValidation } from "../validation" import { generateQueryID } from "../../../../db/utils" import { ImportInfo, ImportSource } from "./sources/base" -import { OpenAPI3 } from "./sources/openapi2" +import { OpenAPI2 } from "./sources/openapi2" import { OpenAPI3 } from "./sources/openapi3" import { Query } from "./../../../../definitions/common" import { Curl } from "./sources/curl" @@ -19,7 +19,7 @@ export class RestImporter { constructor(data: string) { this.data = data - this.sources = [new OpenAPI3(), new OpenAPI3(), new Curl()] + this.sources = [new OpenAPI2(), new OpenAPI3(), new Curl()] } init = async () => { diff --git a/packages/server/src/api/controllers/query/import/sources/base/index.ts b/packages/server/src/api/controllers/query/import/sources/base/index.ts index 096fa10e92..d1584093cf 100644 --- a/packages/server/src/api/controllers/query/import/sources/base/index.ts +++ b/packages/server/src/api/controllers/query/import/sources/base/index.ts @@ -23,7 +23,7 @@ export abstract class ImportSource { name: string, method: string, path: string, - url: URL | null, + url: URL | string | undefined, queryString: string, headers: object = {}, parameters: QueryParameter[] = [], @@ -35,7 +35,11 @@ export abstract class ImportSource { const schema = {} path = this.processPath(path) if (url) { - path = `${url.origin}/${path}` + if (typeof url === "string") { + path = `${url}/${path}` + } else { + path = `${url.origin}/${path}` + } } queryString = this.processQuery(queryString) const requestBody = JSON.stringify(body, null, 2) diff --git a/packages/server/src/api/controllers/query/import/sources/openapi3.ts b/packages/server/src/api/controllers/query/import/sources/openapi3.ts index c9e49595b2..65719ef71d 100644 --- a/packages/server/src/api/controllers/query/import/sources/openapi3.ts +++ b/packages/server/src/api/controllers/query/import/sources/openapi3.ts @@ -47,24 +47,32 @@ const isParameter = ( const getRequestBody = (operation: OpenAPIV3.OperationObject) => { if (requestBodyNotRef(operation.requestBody)) { - const request: OpenAPIV3.RequestBodyObject = - operation.requestBody as OpenAPIV3.RequestBodyObject + const request: OpenAPIV3.RequestBodyObject = operation.requestBody const supportedMimeTypes = getMimeTypes(operation) - return supportedMimeTypes.length > 0 && - schemaNotRef(request.content[supportedMimeTypes[0]].schema) - ? ( - request.content[supportedMimeTypes[0]] - .schema as OpenAPIV3.SchemaObject - ).example - : undefined + if (supportedMimeTypes.length > 0) { + const mimeType = supportedMimeTypes[0] + + // try get example from request + const content = request.content[mimeType] + if (content.example) { + return content.example + } + + // try get example from schema + if (schemaNotRef(content.schema)) { + const schema = content.schema + if (schema.example) { + return schema.example + } + } + } } return undefined } const getMimeTypes = (operation: OpenAPIV3.OperationObject): string[] => { if (requestBodyNotRef(operation.requestBody)) { - const request: OpenAPIV3.RequestBodyObject = - operation.requestBody as OpenAPIV3.RequestBodyObject + const request: OpenAPIV3.RequestBodyObject = operation.requestBody return Object.keys(request.content) } return [] @@ -99,9 +107,17 @@ export class OpenAPI3 extends OpenAPISource { } getQueries = async (datasourceId: string): Promise => { - const url: URL | null = this.document.servers - ? new URL(this.document.servers[0].url) - : null + let url: string | URL | undefined + if (this.document.servers?.length) { + url = this.document.servers[0].url + try { + url = new URL(url) + } catch (err) { + // unable to construct url, e.g. with variables + // proceed with string form of url + } + } + const queries: Query[] = [] for (let [path, pathItemObject] of Object.entries(this.document.paths)) { diff --git a/packages/server/src/api/controllers/query/import/tests/index.spec.js b/packages/server/src/api/controllers/query/import/tests/index.spec.js index 8d074ea885..0cfb4f4f19 100644 --- a/packages/server/src/api/controllers/query/import/tests/index.spec.js +++ b/packages/server/src/api/controllers/query/import/tests/index.spec.js @@ -23,14 +23,27 @@ const oapi2CrudYaml = getData("openapi2/data/crud/crud.json") const oapi2PetstoreJson = getData("openapi2/data/petstore/petstore.json") const oapi2PetstoreYaml = getData("openapi2/data/petstore/petstore.json") +// openapi3 +const oapi3CrudJson = getData("openapi3/data/crud/crud.json") +const oapi3CrudYaml = getData("openapi3/data/crud/crud.json") +const oapi3PetstoreJson = getData("openapi3/data/petstore/petstore.json") +const oapi3PetstoreYaml = getData("openapi3/data/petstore/petstore.json") + // curl const curl = getData("curl/data/post.txt") const datasets = { + // openapi2 (swagger) oapi2CrudJson, oapi2CrudYaml, oapi2PetstoreJson, oapi2PetstoreYaml, + // openapi3 + oapi3CrudJson, + oapi3CrudYaml, + oapi3PetstoreJson, + oapi3PetstoreYaml, + // curl curl } @@ -56,6 +69,7 @@ describe("Rest Importer", () => { it("gets info", async () => { const assertions = { + // openapi2 (swagger) "oapi2CrudJson" : { name: "CRUD", }, @@ -68,6 +82,20 @@ describe("Rest Importer", () => { "oapi2PetstoreYaml" :{ name: "Swagger Petstore", }, + // openapi3 + "oapi3CrudJson" : { + name: "CRUD", + }, + "oapi3CrudYaml" : { + name: "CRUD", + }, + "oapi3PetstoreJson" : { + name: "Swagger Petstore - OpenAPI 3.0", + }, + "oapi3PetstoreYaml" :{ + name: "Swagger Petstore - OpenAPI 3.0", + }, + // curl "curl": { name: "example.com", } @@ -89,6 +117,7 @@ describe("Rest Importer", () => { // simple sanity assertions that the whole dataset // makes it through the importer const assertions = { + // openapi2 (swagger) "oapi2CrudJson" : { count: 6, }, @@ -101,6 +130,20 @@ describe("Rest Importer", () => { "oapi2PetstoreYaml" :{ count: 20, }, + // openapi3 + "oapi3CrudJson" : { + count: 6, + }, + "oapi3CrudYaml" :{ + count: 6, + }, + "oapi3PetstoreJson" : { + count: 19, + }, + "oapi3PetstoreYaml" :{ + count: 19, + }, + // curl "curl": { count: 1 } From 4c88a046fd7de958ac91ceea943bce5c87006c07 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 7 Mar 2022 10:41:52 +0000 Subject: [PATCH 010/206] Update confirm text button when duplicating screens --- .../ComponentNavigationTree/ScreenDropdownMenu.svelte | 1 + .../design/NavigationPanel/ScreenDetailsModal.svelte | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte index 7d0b88c235..3a5e60ae57 100644 --- a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte @@ -85,5 +85,6 @@ onConfirm={createDuplicateScreen} screenName={screen?.props._instanceName} screenUrl={screen?.routing.route} + confirmText="Duplicate" /> diff --git a/packages/builder/src/components/design/NavigationPanel/ScreenDetailsModal.svelte b/packages/builder/src/components/design/NavigationPanel/ScreenDetailsModal.svelte index 82b90be6a6..75977e51a2 100644 --- a/packages/builder/src/components/design/NavigationPanel/ScreenDetailsModal.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ScreenDetailsModal.svelte @@ -9,6 +9,7 @@ export let showProgressCircle = false export let screenName export let screenUrl + export let confirmText = "Continue" let routeError let touched = false @@ -46,7 +47,7 @@ Date: Mon, 7 Mar 2022 13:58:04 +0100 Subject: [PATCH 011/206] implement review comments --- .../src/api/controllers/query/import/sources/openapi3.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/server/src/api/controllers/query/import/sources/openapi3.ts b/packages/server/src/api/controllers/query/import/sources/openapi3.ts index 65719ef71d..e6bdbe8682 100644 --- a/packages/server/src/api/controllers/query/import/sources/openapi3.ts +++ b/packages/server/src/api/controllers/query/import/sources/openapi3.ts @@ -26,7 +26,7 @@ const schemaNotRef = ( } const isOpenAPI3 = (document: any): document is OpenAPIV3.Document => { - return document.openapi.includes("3") + return document.openapi.includes("3.0") } const methods: string[] = Object.values(OpenAPIV3.HttpMethods) @@ -79,8 +79,8 @@ const getMimeTypes = (operation: OpenAPIV3.OperationObject): string[] => { } /** - * OpenAPI Version 3.1 - aka "Swagger" - * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.1.0.md + * OpenAPI Version 3.0 + * https://github.com/OAI/OpenAPI-Specification/blob/main/versions/3.0.0.md */ export class OpenAPI3 extends OpenAPISource { document!: OpenAPIV3.Document @@ -100,7 +100,7 @@ export class OpenAPI3 extends OpenAPISource { } getInfo = async (): Promise => { - const name = this.document.info.title || "Swagger Import" + const name = this.document.info.title || "OpenAPI Import" return { name, } From d2b13cf3fa0b68d6313e8366c0a48b18d3b926b9 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 7 Mar 2022 13:56:06 +0000 Subject: [PATCH 012/206] Add option to paste inside screens --- .../ScreenDropdownMenu.svelte | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte index 3a5e60ae57..972414ae0b 100644 --- a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ScreenDropdownMenu.svelte @@ -62,6 +62,14 @@ notifications.error("Error deleting screen") } } + + const pasteComponent = mode => { + try { + store.actions.components.paste(screen?.props, mode) + } catch (error) { + notifications.error("Error saving component") + } + } @@ -69,6 +77,13 @@
Duplicate + pasteComponent("inside")} + disabled={!$store.componentToPaste} + > + Paste inside + Delete From e84e4f98996ea8625f1daaf2fb1195d3cc4b201e Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Mon, 7 Mar 2022 14:05:26 +0000 Subject: [PATCH 013/206] Add duplicate action to settings bar --- .../design/AppPreview/CurrentItemPreview.svelte | 5 +++++ .../client/src/components/preview/SettingsBar.svelte | 10 ++++++++++ packages/client/src/stores/builder.js | 3 +++ 3 files changed, 18 insertions(+) diff --git a/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte b/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte index 2a886fab0c..b0b7c16c68 100644 --- a/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte +++ b/packages/builder/src/components/design/AppPreview/CurrentItemPreview.svelte @@ -159,6 +159,11 @@ await store.actions.components.updateProp(data.prop, data.value) } else if (type === "delete-component" && data.id) { confirmDeleteComponent(data.id) + } else if (type === "duplicate-component" && data.id) { + const rootComponent = get(currentAsset).props + const component = findComponent(rootComponent, data.id) + store.actions.components.copy(component) + await store.actions.components.paste(component) } else if (type === "preview-loaded") { // Wait for this event to show the client library if intelligent // loading is supported diff --git a/packages/client/src/components/preview/SettingsBar.svelte b/packages/client/src/components/preview/SettingsBar.svelte index a4e3ca4d72..c5ad8bef6c 100644 --- a/packages/client/src/components/preview/SettingsBar.svelte +++ b/packages/client/src/components/preview/SettingsBar.svelte @@ -146,6 +146,15 @@
{/if} {/each} + { + builderStore.actions.duplicateComponent( + $builderStore.selectedComponent._id + ) + }} + title="Duplicate component" + /> { @@ -153,6 +162,7 @@ $builderStore.selectedComponent._id ) }} + title="Delete component" />
{/if} diff --git a/packages/client/src/stores/builder.js b/packages/client/src/stores/builder.js index 719909b538..27c8bbe2a2 100644 --- a/packages/client/src/stores/builder.js +++ b/packages/client/src/stores/builder.js @@ -62,6 +62,9 @@ const createBuilderStore = () => { deleteComponent: id => { dispatchEvent("delete-component", { id }) }, + duplicateComponent: id => { + dispatchEvent("duplicate-component", { id }) + }, notifyLoaded: () => { dispatchEvent("preview-loaded") }, From 8131bae496687e6d9fbba82fd19d8d39065c8089 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 8 Mar 2022 08:46:07 +0000 Subject: [PATCH 014/206] Update cypress tests for different button casing --- packages/builder/cypress/support/commands.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/builder/cypress/support/commands.js b/packages/builder/cypress/support/commands.js index ede1038a58..699cf1fbf0 100644 --- a/packages/builder/cypress/support/commands.js +++ b/packages/builder/cypress/support/commands.js @@ -247,7 +247,7 @@ Cypress.Commands.add("createScreen", (screenName, route) => { cy.get("[aria-label=AddCircle]").click() cy.get(".spectrum-Modal").within(() => { cy.get(".item").contains("Blank").click() - cy.get(".spectrum-Button").contains("Add Screens").click({ force: true }) + cy.get(".spectrum-Button").contains("Add screens").click({ force: true }) cy.wait(500) }) cy.get(".spectrum-Dialog-grid").within(() => { @@ -265,7 +265,7 @@ Cypress.Commands.add("createAutogeneratedScreens", screenNames => { for (let i = 0; i < screenNames.length; i++) { cy.get(".item").contains(screenNames[i]).click() } - cy.get(".spectrum-Button").contains("Add Screens").click({ force: true }) + cy.get(".spectrum-Button").contains("Add screens").click({ force: true }) cy.wait(4000) }) From d8cb5bfd246f82704def4276f3bafc9a7953e62a Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 8 Mar 2022 14:14:57 +0000 Subject: [PATCH 015/206] Fix screen renaming when same route already exists --- packages/builder/cypress.json | 2 +- .../src/components/design/NavigationPanel/ScreenWizard.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/builder/cypress.json b/packages/builder/cypress.json index edad1f39d3..3e4d4c4d1b 100644 --- a/packages/builder/cypress.json +++ b/packages/builder/cypress.json @@ -1,6 +1,6 @@ { "baseUrl": "http://localhost:4100", - "video": false, + "video": true, "projectId": "bmbemn", "env": { "PORT": "4100", diff --git a/packages/builder/src/components/design/NavigationPanel/ScreenWizard.svelte b/packages/builder/src/components/design/NavigationPanel/ScreenWizard.svelte index 76ee253e00..c30241868a 100644 --- a/packages/builder/src/components/design/NavigationPanel/ScreenWizard.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ScreenWizard.svelte @@ -93,7 +93,7 @@ return `/${url}-${suffix}` } else { const split = url.split("/") - return `/${split[0]}-${suffix}/${split.join("/")}` + return `/${split[0]}-${suffix}/${split.slice(1).join("/")}` } } From 5bef8849ca041c10baf349e89d681b0ff25cf0e7 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 8 Mar 2022 14:45:47 +0000 Subject: [PATCH 016/206] Remove cypress video flag again --- packages/builder/cypress.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/builder/cypress.json b/packages/builder/cypress.json index 3e4d4c4d1b..edad1f39d3 100644 --- a/packages/builder/cypress.json +++ b/packages/builder/cypress.json @@ -1,6 +1,6 @@ { "baseUrl": "http://localhost:4100", - "video": true, + "video": false, "projectId": "bmbemn", "env": { "PORT": "4100", From c2d48bebd7100d7949de4555759de543a00b9256 Mon Sep 17 00:00:00 2001 From: Maurits Lourens Date: Tue, 8 Mar 2022 17:31:36 +0100 Subject: [PATCH 017/206] initial setup for google firebase integration --- .../DatasourceNavigator/icons/Firebase.svelte | 54 ++++++ .../DatasourceNavigator/icons/firebase.svg | 91 ++++++++++ .../DatasourceNavigator/icons/index.js | 2 + .../builder/src/constants/backend/index.js | 2 + packages/server/package.json | 1 + .../server/src/api/routes/public/index.ts | 4 +- packages/server/src/definitions/datasource.ts | 1 + packages/server/src/integrations/firebase.ts | 161 ++++++++++++++++++ packages/server/src/integrations/index.ts | 3 + 9 files changed, 317 insertions(+), 2 deletions(-) create mode 100644 packages/builder/src/components/backend/DatasourceNavigator/icons/Firebase.svelte create mode 100644 packages/builder/src/components/backend/DatasourceNavigator/icons/firebase.svg create mode 100644 packages/server/src/integrations/firebase.ts diff --git a/packages/builder/src/components/backend/DatasourceNavigator/icons/Firebase.svelte b/packages/builder/src/components/backend/DatasourceNavigator/icons/Firebase.svelte new file mode 100644 index 0000000000..3a776a9217 --- /dev/null +++ b/packages/builder/src/components/backend/DatasourceNavigator/icons/Firebase.svelte @@ -0,0 +1,54 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/builder/src/components/backend/DatasourceNavigator/icons/firebase.svg b/packages/builder/src/components/backend/DatasourceNavigator/icons/firebase.svg new file mode 100644 index 0000000000..dc569606ad --- /dev/null +++ b/packages/builder/src/components/backend/DatasourceNavigator/icons/firebase.svg @@ -0,0 +1,91 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/packages/builder/src/components/backend/DatasourceNavigator/icons/index.js b/packages/builder/src/components/backend/DatasourceNavigator/icons/index.js index 350fccf73f..515f20a93b 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/icons/index.js +++ b/packages/builder/src/components/backend/DatasourceNavigator/icons/index.js @@ -12,6 +12,7 @@ import Rest from "./Rest.svelte" import Budibase from "./Budibase.svelte" import Oracle from "./Oracle.svelte" import GoogleSheets from "./GoogleSheets.svelte" +import Firebase from "./Firebase.svelte" export default { BUDIBASE: Budibase, @@ -28,4 +29,5 @@ export default { REST: Rest, ORACLE: Oracle, GOOGLE_SHEETS: GoogleSheets, + FIREBASE: Firebase, } diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js index 4d6a7e3884..9a623241ca 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.js @@ -178,6 +178,7 @@ export const IntegrationTypes = { ORACLE: "ORACLE", INTERNAL: "INTERNAL", GOOGLE_SHEETS: "GOOGLE_SHEETS", + FIREBASE: "FIREBASE", } export const IntegrationNames = { @@ -195,6 +196,7 @@ export const IntegrationNames = { [IntegrationTypes.ORACLE]: "Oracle", [IntegrationTypes.INTERNAL]: "Internal", [IntegrationTypes.GOOGLE_SHEETS]: "Google Sheets", + [IntegrationTypes.FIREBASE]: "Firebase", } export const SchemaTypeOptions = [ diff --git a/packages/server/package.json b/packages/server/package.json index 30e21a7170..b80b99ab69 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -77,6 +77,7 @@ "@bull-board/api": "^3.7.0", "@bull-board/koa": "^3.7.0", "@elastic/elasticsearch": "7.10.0", + "@google-cloud/firestore": "^5.0.2", "@koa/router": "8.0.0", "@sendgrid/mail": "7.1.1", "@sentry/node": "^6.0.0", diff --git a/packages/server/src/api/routes/public/index.ts b/packages/server/src/api/routes/public/index.ts index 800eae6101..63a6946606 100644 --- a/packages/server/src/api/routes/public/index.ts +++ b/packages/server/src/api/routes/public/index.ts @@ -29,7 +29,7 @@ function getApiLimitPerSecond(): number { return parseInt(env.API_REQ_LIMIT_PER_SEC) } -if (!env.isTest()) { +/*if (!env.isTest()) { const REDIS_OPTS = getRedisOptions() RateLimit.defaultOptions({ store: new Stores.Redis({ @@ -42,7 +42,7 @@ if (!env.isTest()) { database: 1, }), }) -} +}*/ // rate limiting, allows for 2 requests per second const limiter = RateLimit.middleware({ interval: { sec: 1 }, diff --git a/packages/server/src/definitions/datasource.ts b/packages/server/src/definitions/datasource.ts index 6c8c8dc07d..208b2dc0a3 100644 --- a/packages/server/src/definitions/datasource.ts +++ b/packages/server/src/definitions/datasource.ts @@ -48,6 +48,7 @@ export enum SourceNames { REST = "REST", ORACLE = "ORACLE", GOOGLE_SHEETS = "GOOGLE_SHEETS", + FIREBASE = "FIREBASE", } export enum IncludeRelationships { diff --git a/packages/server/src/integrations/firebase.ts b/packages/server/src/integrations/firebase.ts new file mode 100644 index 0000000000..8bcf198b7d --- /dev/null +++ b/packages/server/src/integrations/firebase.ts @@ -0,0 +1,161 @@ +import { + DatasourceFieldTypes, + Integration, + QueryTypes, +} from "../definitions/datasource" +import { IntegrationBase } from "./base/IntegrationBase" +import { Firestore, WhereFilterOp } from "@google-cloud/firestore" + +module Firebase { + interface FirebaseConfig { + email: string + privateKey: string + projectId: string + } + + const SCHEMA: Integration = { + docs: "https://firebase.google.com/docs/firestore/quickstart", + friendlyName: "Firestore", + description: + "Cloud Firestore is a flexible, scalable database for mobile, web, and server development from Firebase and Google Cloud.", + datasource: { + email: { + type: DatasourceFieldTypes.STRING, + required: true, + }, + privateKey: { + type: DatasourceFieldTypes.STRING, + required: true, + }, + projectId: { + type: DatasourceFieldTypes.STRING, + required: true, + }, + }, + query: { + create: { + type: QueryTypes.JSON, + }, + read: { + type: QueryTypes.JSON, + }, + update: { + type: QueryTypes.JSON, + }, + delete: { + type: QueryTypes.JSON, + }, + }, + extra: { + collection: { + displayName: "Collection", + type: DatasourceFieldTypes.STRING, + required: true, + }, + filter: { + displayName: "Filter query", + type: DatasourceFieldTypes.LIST, + required: false, + data: { + read: [ + "==", + "<", + "<=", + "==", + "!=", + ">=", + ">", + "array-contains", + "in", + "not-in", + "array-contains-any", + ], + }, + }, + queryValue: { + displayName: "Query value", + type: DatasourceFieldTypes.STRING, + required: false, + }, + }, + } + + class FirebaseIntegration implements IntegrationBase { + private config: FirebaseConfig + private db: Firestore + + constructor(config: FirebaseConfig) { + this.config = config + this.db = new Firestore({ + projectId: config.projectId, + credential: { + clientEmail: config.email, + privateKey: config.privateKey, + }, + }) + } + + async create(query: { json: object; extra: { [key: string]: string } }) { + try { + return await this.db.collection(query.extra.collection).add(query.json) + } catch (err) { + console.error("Error writing to Firestore", err) + throw err + } + } + + async read(query: { + field: string + opStr: WhereFilterOp + value: any + extra: { [key: string]: string } + }) { + try { + const snapshot = await this.db + .collection(query.extra.collection) + .where(query.field, query.opStr, query.value) + .get() + const result: any[] = [] + snapshot.forEach(doc => result.push(doc.data())) + + return result + } catch (err) { + console.error("Error querying Firestore", err) + throw err + } + } + + async update(query: { + id: string + json: object + extra: { [key: string]: string } + }) { + try { + return await this.db + .collection(query.extra.collection) + .doc(query.id) + .update(query.json) + } catch (err) { + console.error("Error writing to mongodb", err) + throw err + } + } + + async delete(query: { id: string; extra: { [key: string]: string } }) { + try { + return await this.db + .collection(query.extra.collection) + .doc(query.id) + .delete() + } catch (err) { + console.error("Error writing to mongodb", err) + throw err + } + } + } + + module.exports = { + schema: SCHEMA, + integration: FirebaseIntegration, + } +} diff --git a/packages/server/src/integrations/index.ts b/packages/server/src/integrations/index.ts index 00b00c25fb..07f3211fcb 100644 --- a/packages/server/src/integrations/index.ts +++ b/packages/server/src/integrations/index.ts @@ -10,6 +10,7 @@ const mysql = require("./mysql") const arangodb = require("./arangodb") const rest = require("./rest") const googlesheets = require("./googlesheets") +const firebase = require("./firebase") const { SourceNames } = require("../definitions/datasource") const environment = require("../environment") @@ -25,6 +26,7 @@ const DEFINITIONS = { [SourceNames.MYSQL]: mysql.schema, [SourceNames.ARANGODB]: arangodb.schema, [SourceNames.REST]: rest.schema, + [SourceNames.FIREBASE]: firebase.schema, } const INTEGRATIONS = { @@ -39,6 +41,7 @@ const INTEGRATIONS = { [SourceNames.MYSQL]: mysql.integration, [SourceNames.ARANGODB]: arangodb.integration, [SourceNames.REST]: rest.integration, + [SourceNames.FIREBASE]: firebase.integration, } // optionally add oracle integration if the oracle binary can be installed From b67b34928a415522253f9625e455df329aa70e8e Mon Sep 17 00:00:00 2001 From: Dean Date: Tue, 8 Mar 2022 16:41:21 +0000 Subject: [PATCH 018/206] Initial Commit for Issue/3819. World map component added and a small change to the Component draggable behaviour to accomodate it. --- .eslintrc.json | 3 + .../design/AppPreview/componentStructure.json | 3 +- packages/client/manifest.json | 76 +++++ packages/client/package.json | 2 + .../client/src/components/Component.svelte | 7 +- .../src/components/app/EmbeddedMap.svelte | 266 ++++++++++++++++++ .../src/components/app/EmbeddedMapControls.js | 192 +++++++++++++ packages/client/src/components/app/index.js | 1 + 8 files changed, 548 insertions(+), 2 deletions(-) create mode 100644 packages/client/src/components/app/EmbeddedMap.svelte create mode 100644 packages/client/src/components/app/EmbeddedMapControls.js diff --git a/.eslintrc.json b/.eslintrc.json index 4dc11c0d65..8f4f68036b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -28,5 +28,8 @@ ], "rules": { "no-self-assign": "off" + }, + "globals": { + "GeolocationPositionError": true } } diff --git a/packages/builder/src/components/design/AppPreview/componentStructure.json b/packages/builder/src/components/design/AppPreview/componentStructure.json index cacd70a89b..6873ae547d 100644 --- a/packages/builder/src/components/design/AppPreview/componentStructure.json +++ b/packages/builder/src/components/design/AppPreview/componentStructure.json @@ -82,7 +82,8 @@ "link", "icon", "embed", - "markdownviewer" + "markdownviewer", + "embeddedmap" ] } ] diff --git a/packages/client/manifest.json b/packages/client/manifest.json index d8e589588f..11c927700e 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2474,6 +2474,82 @@ } ] }, + "embeddedmap": { + "name": "Embedded Map", + "icon": "Location", + "styles": ["size"], + "editable": true, + "draggable": false, + "illegalChildren": [ + "section" + ], + "settings": [ + { + "type": "dataProvider", + "label": "Provider", + "key": "dataProvider" + }, + { + "type": "select", + "label": "Map Type", + "key": "mapType", + "options": [ + "Roadmap", + "Terrain", + "Satellite", + "Hybrid" + ], + "defaultValue": "Roadmap" + }, + { + "type": "boolean", + "label": "Enable Fullscreen", + "key": "fullScreenEnabled", + "defaultValue": true + }, + { + "type": "boolean", + "label": "Enabled Location", + "key": "locationEnabled", + "defaultValue": false + }, + { + "type": "boolean", + "label": "Enable Zoom", + "key": "zoomEnabled", + "defaultValue": true + }, + { + "type": "number", + "label": "Zoom Level (0-100)", + "key": "zoomLevel", + "defaultValue": 72, + "max" : 100, + "min" : 0 + }, + { + "type": "field", + "label": "Latitude Key", + "key": "latitudeKey" + }, + { + "type": "field", + "label": "Longitude Key", + "key": "longitudeKey" + }, + { + "type": "field", + "label": "Title Key", + "key": "titleKey" + }, + { + "type": "text", + "label": "Tile URL", + "key": "tileURL", + "defaultValue": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" + } + ] + }, "attachmentfield": { "name": "Attachment", "icon": "Attach", diff --git a/packages/client/package.json b/packages/client/package.json index 8772b9135f..16a2f34c8e 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -32,8 +32,10 @@ "@spectrum-css/vars": "^3.0.1", "apexcharts": "^3.22.1", "dayjs": "^1.10.5", + "leaflet": "^1.7.1", "regexparam": "^1.3.0", "rollup-plugin-polyfill-node": "^0.8.0", + "screenfull": "^6.0.1", "shortid": "^2.2.15", "svelte": "^3.38.2", "svelte-apexcharts": "^1.0.2", diff --git a/packages/client/src/components/Component.svelte b/packages/client/src/components/Component.svelte index 78fff52426..e4176587ee 100644 --- a/packages/client/src/components/Component.svelte +++ b/packages/client/src/components/Component.svelte @@ -103,7 +103,12 @@ ($builderStore.previewType === "layout" || insideScreenslot) && !isBlock $: editing = editable && selected && $builderStore.editMode - $: draggable = !inDragPath && interactive && !isLayout && !isScreen + $: draggable = + !inDragPath && + interactive && + !isLayout && + !isScreen && + definition?.draggable !== false $: droppable = interactive && !isLayout && !isScreen // Empty components are those which accept children but do not have any. diff --git a/packages/client/src/components/app/EmbeddedMap.svelte b/packages/client/src/components/app/EmbeddedMap.svelte new file mode 100644 index 0000000000..63b98ba651 --- /dev/null +++ b/packages/client/src/components/app/EmbeddedMap.svelte @@ -0,0 +1,266 @@ + + +
+ {#if error} +
{error}
+ {/if} + +
+
+ + diff --git a/packages/client/src/components/app/EmbeddedMapControls.js b/packages/client/src/components/app/EmbeddedMapControls.js new file mode 100644 index 0000000000..ca1b1ed22a --- /dev/null +++ b/packages/client/src/components/app/EmbeddedMapControls.js @@ -0,0 +1,192 @@ +import L from "leaflet" +import screenfull from "screenfull" + +const createButton = function (html, title, className, container, fn) { + let link = L.DomUtil.create("a", className, container) + link.innerHTML = html + link.href = "#" + link.title = title + + link.setAttribute("role", "button") + link.setAttribute("aria-label", title) + + L.DomEvent.disableClickPropagation(link) + L.DomEvent.on(link, "click", L.DomEvent.stop) + L.DomEvent.on(link, "click", fn, this) + L.DomEvent.on(link, "click", this._refocusOnMap, this) + + return link +} + +// Full Screen Control + +const FullScreenControl = L.Control.extend({ + options: { + position: "topright", + fullScreenContent: + '' + + '' + + '', + fullScreenTitle: "Enter Fullscreen", + }, + onAdd: function () { + var fullScreenClassName = "leaflet-control-fullscreen", + container = L.DomUtil.create("div", fullScreenClassName + " leaflet-bar"), + options = this.options + + this._fullScreenButton = this._createButton( + options.fullScreenContent, + options.fullScreenTitle, + "map-fullscreen", + container, + this._fullScreen + ) + + return container + }, + _fullScreen: function () { + var map = this._map + if (screenfull.isEnabled) { + screenfull.toggle(map.getContainer()) + } + }, + _createButton: createButton, +}) + +const initFullScreenControl = () => { + L.Map.mergeOptions({ + fullScreen: false, + }) + + L.Map.addInitHook(function () { + if (this.options.fullScreen) { + this.fullScreenControl = new FullScreenControl() + this.addControl(this.fullScreenControl) + } else { + this.fullScreenControl = null + } + }) +} + +// Location Control + +const LocationControl = L.Control.extend({ + options: { + position: "topright", + locationContent: + '' + + '' + + '', + locationTitle: "Show Your Location", + }, + onAdd: function () { + var locationClassName = "leaflet-control-location", + container = L.DomUtil.create("div", locationClassName + " leaflet-bar"), + options = this.options + + this._locationButton = this._createButton( + options.locationContent, + options.locationTitle, + "map-location", + container, + this._location + ) + + this._updateDisabled() + + return container + }, + disable: function () { + this._disabled = true + this._updateDisabled() + return this + }, + enable: function () { + this._disabled = false + this._updateDisabled() + return this + }, + _location: function () { + if (this._disabled == true) { + return + } + this.disable() + + const success = pos => { + this._map.closePopup() + if (typeof this.options.onLocationSuccess === "function") { + this.options.onLocationSuccess({ + lat: pos.coords.latitude, + lng: pos.coords.longitude, + }) + } + } + + const error = err => { + if (typeof this.options.onLocationFail === "function") { + this.options.onLocationFail(err) + } + } + + this._getPosition() + .then(success) + .catch(error) + .finally(() => { + this.enable() + }) + }, + _getPosition: function () { + var options = { + enableHighAccuracy: false, + timeout: 5000, + maximumAge: 30000, + } + + return new Promise((resolve, reject) => { + navigator.geolocation.getCurrentPosition(resolve, reject, options) + }) + }, + + _createButton: createButton, + + _updateDisabled: function () { + let disabledClassName = "leaflet-disabled" + L.DomUtil.removeClass(this._locationButton, disabledClassName) + this._locationButton.setAttribute("aria-disabled", "false") + + if (this._disabled) { + L.DomUtil.addClass(this._locationButton, disabledClassName) + this._locationButton.setAttribute("aria-disabled", "true") + } + }, +}) + +const initLocationControl = () => { + L.Map.mergeOptions({ + location: false, + onLocationFail: null, + onLocationSuccess: null, + }) + + L.Map.addInitHook(function () { + if (this.options.location) { + this.localControl = new LocationControl() + this.addControl(this.LocationControl) + } else { + this.localControl = null + } + }) +} + +const initMapControls = () => { + initFullScreenControl() + initLocationControl() +} + +export { + initFullScreenControl, + initLocationControl, + initMapControls, + FullScreenControl, + LocationControl, +} diff --git a/packages/client/src/components/app/index.js b/packages/client/src/components/app/index.js index 5af62201e5..e23659620b 100644 --- a/packages/client/src/components/app/index.js +++ b/packages/client/src/components/app/index.js @@ -31,6 +31,7 @@ export { default as cardstat } from "./CardStat.svelte" export { default as spectrumcard } from "./SpectrumCard.svelte" export { default as tag } from "./Tag.svelte" export { default as markdownviewer } from "./MarkdownViewer.svelte" +export { default as embeddedmap } from "./EmbeddedMap.svelte" export * from "./charts" export * from "./forms" export * from "./table" From f78dce39505d4fbaa1a0d9556b55c047b45fb3a3 Mon Sep 17 00:00:00 2001 From: Dean Date: Tue, 8 Mar 2022 16:59:10 +0000 Subject: [PATCH 019/206] Added yarn lock files for Screenfull and LeafletJS --- packages/bbui/yarn.lock | 935 +--------------------------------------- 1 file changed, 6 insertions(+), 929 deletions(-) diff --git a/packages/bbui/yarn.lock b/packages/bbui/yarn.lock index 33c3c391be..5baad30282 100644 --- a/packages/bbui/yarn.lock +++ b/packages/bbui/yarn.lock @@ -28,43 +28,6 @@ chalk "^2.0.0" js-tokens "^4.0.0" -"@budibase/handlebars-helpers@^0.11.7": - version "0.11.8" - resolved "https://registry.yarnpkg.com/@budibase/handlebars-helpers/-/handlebars-helpers-0.11.8.tgz#6953d29673a8c5c407e096c0a84890465c7ce841" - integrity sha512-ggWJUt0GqsHFAEup5tlWlcrmYML57nKhpNGGLzVsqXVYN8eVmf3xluYmmMe7fDYhQH0leSprrdEXmsdFQF3HAQ== - dependencies: - array-sort "^1.0.0" - define-property "^2.0.2" - extend-shallow "^3.0.2" - for-in "^1.0.2" - get-object "^0.2.0" - get-value "^3.0.1" - handlebars "^4.7.7" - handlebars-utils "^1.0.6" - has-value "^2.0.2" - helper-md "^0.2.2" - html-tag "^2.0.0" - is-even "^1.0.0" - is-glob "^4.0.1" - kind-of "^6.0.3" - micromatch "^3.1.5" - relative "^3.0.2" - striptags "^3.1.1" - to-gfm-code-block "^0.1.1" - year "^0.2.1" - -"@budibase/string-templates@^1.0.72-alpha.0": - version "1.0.75" - resolved "https://registry.yarnpkg.com/@budibase/string-templates/-/string-templates-1.0.75.tgz#5b4061f1a626160ec092f32f036541376298100c" - integrity sha512-hPgr6n5cpSCGFEha5DS/P+rtRXOLc72M6y4J/scl59JvUi/ZUJkjRgJdpQPdBLu04CNKp89V59+rAqAuDjOC0g== - dependencies: - "@budibase/handlebars-helpers" "^0.11.7" - dayjs "^1.10.4" - handlebars "^4.7.6" - handlebars-utils "^1.0.6" - lodash "^4.17.20" - vm2 "^3.9.4" - "@rollup/plugin-commonjs@^16.0.0": version "16.0.0" resolved "https://registry.yarnpkg.com/@rollup/plugin-commonjs/-/plugin-commonjs-16.0.0.tgz#169004d56cd0f0a1d0f35915d31a036b0efe281f" @@ -367,21 +330,11 @@ accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" -acorn-walk@^8.2.0: - version "8.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" - integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== - acorn@^7.3.1: version "7.4.1" resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== -acorn@^8.7.0: - version "8.7.0" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" - integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ== - alphanum-sort@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3" @@ -409,87 +362,28 @@ anymatch@~3.1.1: normalize-path "^3.0.0" picomatch "^2.0.4" -argparse@^1.0.10, argparse@^1.0.7: +argparse@^1.0.7: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== dependencies: sprintf-js "~1.0.2" -arr-diff@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" - integrity sha1-1kYQdP6/7HHn4VI1dhoyml3HxSA= - -arr-flatten@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.1.0.tgz#36048bbff4e7b47e136644316c99669ea5ae91f1" - integrity sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg== - -arr-union@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/arr-union/-/arr-union-3.1.0.tgz#e39b09aea9def866a8f206e288af63919bae39c4" - integrity sha1-45sJrqne+Gao8gbiiK9jkZuuOcQ= - array-flatten@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" integrity sha1-ml9pkFGx5wczKPKgCJaLZOopVdI= -array-sort@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/array-sort/-/array-sort-1.0.0.tgz#e4c05356453f56f53512a7d1d6123f2c54c0a88a" - integrity sha512-ihLeJkonmdiAsD7vpgN3CRcx2J2S0TiYW+IS/5zHBI7mKUq3ySvBdzzBfD236ubDBQFiiyG3SWCPc+msQ9KoYg== - dependencies: - default-compare "^1.0.0" - get-value "^2.0.6" - kind-of "^5.0.2" - -array-unique@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.3.2.tgz#a894b75d4bc4f6cd679ef3244a9fd8f46ae2d428" - integrity sha1-qJS3XUvE9s1nnvMkSp/Y9Gri1Cg= - -assign-symbols@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/assign-symbols/-/assign-symbols-1.0.0.tgz#59667f41fadd4f20ccbc2bb96b8d4f7f78ec0367" - integrity sha1-WWZ/QfrdTyDMvCu5a41Pf3jsA2c= - async-limiter@~1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/async-limiter/-/async-limiter-1.0.1.tgz#dd379e94f0db8310b08291f9d64c3209766617fd" integrity sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ== -atob@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/atob/-/atob-2.1.2.tgz#6d9517eb9e030d2436666651e86bd9f6f13533c9" - integrity sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg== - -autolinker@~0.28.0: - version "0.28.1" - resolved "https://registry.yarnpkg.com/autolinker/-/autolinker-0.28.1.tgz#0652b491881879f0775dace0cdca3233942a4e47" - integrity sha1-BlK0kYgYefB3XazgzcoyM5QqTkc= - dependencies: - gulp-header "^1.7.1" - balanced-match@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== -base@^0.11.1: - version "0.11.2" - resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f" - integrity sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg== - dependencies: - cache-base "^1.0.1" - class-utils "^0.3.5" - component-emitter "^1.2.1" - define-property "^1.0.0" - isobject "^3.0.1" - mixin-deep "^1.2.0" - pascalcase "^0.1.1" - big.js@^5.2.2: version "5.2.2" resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328" @@ -529,22 +423,6 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^2.3.1: - version "2.3.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-2.3.2.tgz#5979fd3f14cd531565e5fa2df1abfff1dfaee729" - integrity sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w== - dependencies: - arr-flatten "^1.1.0" - array-unique "^0.3.2" - extend-shallow "^2.0.1" - fill-range "^4.0.0" - isobject "^3.0.1" - repeat-element "^1.1.2" - snapdragon "^0.8.1" - snapdragon-node "^2.0.1" - split-string "^3.0.2" - to-regex "^3.0.1" - braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" @@ -578,21 +456,6 @@ bytes@3.1.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" integrity sha512-zauLjrfCG+xvoyaqLoV8bLVXXNGC4JqlxFCutSDWA6fJrTo2ZuvLYTqZ7aHBLZSMOopbzwv8f+wZcVzfVTI2Dg== -cache-base@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/cache-base/-/cache-base-1.0.1.tgz#0a7f46416831c8b662ee36fe4e7c59d76f666ab2" - integrity sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ== - dependencies: - collection-visit "^1.0.0" - component-emitter "^1.2.1" - get-value "^2.0.6" - has-value "^1.0.0" - isobject "^3.0.1" - set-value "^2.0.0" - to-object-path "^0.3.0" - union-value "^1.0.0" - unset-value "^1.0.0" - call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -672,16 +535,6 @@ chokidar@^3.0.0: optionalDependencies: fsevents "~2.3.1" -class-utils@^0.3.5: - version "0.3.6" - resolved "https://registry.yarnpkg.com/class-utils/-/class-utils-0.3.6.tgz#f93369ae8b9a7ce02fd41faad0ca83033190c463" - integrity sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg== - dependencies: - arr-union "^3.1.0" - define-property "^0.2.5" - isobject "^3.0.0" - static-extend "^0.1.1" - coa@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/coa/-/coa-2.0.2.tgz#43f6c21151b4ef2bf57187db0d73de229e3e7ec3" @@ -703,14 +556,6 @@ codemirror@^5.63.1: resolved "https://registry.yarnpkg.com/codemirror/-/codemirror-5.65.1.tgz#5988a812c974c467f964bcc1a00c944e373de502" integrity sha512-s6aac+DD+4O2u1aBmdxhB7yz2XU7tG3snOyQ05Kxifahz7hoxnfxIRHxiCSEv3TUC38dIVH8G+lZH9UWSfGQxA== -collection-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/collection-visit/-/collection-visit-1.0.0.tgz#4bc0373c164bc3291b4d368c829cf1a80a59dca0" - integrity sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA= - dependencies: - map-visit "^1.0.0" - object-visit "^1.0.0" - color-convert@^1.9.0, color-convert@^1.9.1: version "1.9.3" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8" @@ -766,17 +611,12 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -component-emitter@^1.2.1: - version "1.3.0" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.3.0.tgz#16e4070fba8ae29b679f2215853ee181ab2eabc0" - integrity sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg== - concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= -concat-with-sourcemaps@*, concat-with-sourcemaps@^1.1.0: +concat-with-sourcemaps@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/concat-with-sourcemaps/-/concat-with-sourcemaps-1.1.0.tgz#d4ea93f05ae25790951b99e7b3b09e3908a4082e" integrity sha512-4gEjHJFT9e+2W/77h/DS5SGUgwDaOwprX8L/gl5+3ixnzkVJJsZWDSelmN3Oilw3LNDZjZV0yqH1hLG3k6nghg== @@ -812,16 +652,6 @@ cookie@0.4.0: resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.0.tgz#beb437e7022b3b6d49019d088665303ebe9c14ba" integrity sha512-+Hp8fLp57wnUSt0tY0tHEXh4voZRDnoIrZPqlo3DPiI4y9lwg/jqx+1Om94/W6ZaPDOUbnjOt/99w66zk+l1Xg== -copy-descriptor@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/copy-descriptor/-/copy-descriptor-0.1.1.tgz#676f6eb3c39997c2ee1ac3a924fd6124748f578d" - integrity sha1-Z29us8OZl8LuGsOpJP1hJHSPV40= - -core-util-is@~1.0.0: - version "1.0.3" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" - integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== - cosmiconfig@^5.0.0: version "5.2.1" resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a" @@ -993,7 +823,7 @@ dayjs@^1.10.4: resolved "https://registry.yarnpkg.com/dayjs/-/dayjs-1.10.4.tgz#8e544a9b8683f61783f570980a8a80eaf54ab1e2" integrity sha512-RI/Hh4kqRc1UKLOAf/T5zdMMX5DQIlDxwUe3wSyMMnEbGunnpENCdbUgM+dW7kXidZqCttBrmw7BhN4TMddkCw== -debug@2.6.9, debug@^2.2.0, debug@^2.3.3: +debug@2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -1007,23 +837,11 @@ debug@^3.0.1: dependencies: ms "^2.1.1" -decode-uri-component@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545" - integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU= - deepmerge@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955" integrity sha512-FJ3UgI4gIl+PHZm53knsuSFpE+nESMr7M4v9QcgB7S63Kj/6WqMiFQJpBBYz1Pt+66bZpP3Q7Lye0Oo9MPKEdg== -default-compare@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/default-compare/-/default-compare-1.0.0.tgz#cb61131844ad84d84788fb68fd01681ca7781a2f" - integrity sha512-QWfXlM0EkAbqOCbD/6HjdwT19j7WCkMyiRhWilc4H9/5h/RzTF9gv5LYh1+CmDV5d1rki6KAWLtQale0xt20eQ== - dependencies: - kind-of "^5.0.2" - define-properties@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.1.3.tgz#cf88da6cbee26fe6db7094f61d870cbd84cee9f1" @@ -1031,28 +849,6 @@ define-properties@^1.1.3: dependencies: object-keys "^1.0.12" -define-property@^0.2.5: - version "0.2.5" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-0.2.5.tgz#c35b1ef918ec3c990f9a5bc57be04aacec5c8116" - integrity sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY= - dependencies: - is-descriptor "^0.1.0" - -define-property@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-1.0.0.tgz#769ebaaf3f4a63aad3af9e8d304c9bbe79bfb0e6" - integrity sha1-dp66rz9KY6rTr56NMEybvnm/sOY= - dependencies: - is-descriptor "^1.0.0" - -define-property@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/define-property/-/define-property-2.0.2.tgz#d459689e8d654ba77e02a817f8710d702cb16e9d" - integrity sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ== - dependencies: - is-descriptor "^1.0.2" - isobject "^3.0.1" - depd@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9" @@ -1127,11 +923,6 @@ encodeurl@~1.0.2: resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" integrity sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k= -ent@^2.2.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/ent/-/ent-2.2.0.tgz#e964219325a21d05f44466a2f686ed6ce5f5dd1d" - integrity sha1-6WQhkyWiHQX0RGai9obtbOX13R0= - entities@^2.0.0: version "2.2.0" resolved "https://registry.yarnpkg.com/entities/-/entities-2.2.0.tgz#098dc90ebb83d8dffa089d55256b351d34c4da55" @@ -1225,19 +1016,6 @@ eventemitter3@^4.0.4: resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.7.tgz#2de9b68f6528d5644ef5c59526a1b4a07306169f" integrity sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw== -expand-brackets@^2.1.4: - version "2.1.4" - resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-2.1.4.tgz#b77735e315ce30f6b6eff0f83b04151a22449622" - integrity sha1-t3c14xXOMPa27/D4OwQVGiJEliI= - dependencies: - debug "^2.3.3" - define-property "^0.2.5" - extend-shallow "^2.0.1" - posix-character-classes "^0.1.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - express-history-api-fallback@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/express-history-api-fallback/-/express-history-api-fallback-2.2.1.tgz#3a2ad27f7bebc90fc533d110d7c6d83097bcd057" @@ -1295,45 +1073,6 @@ express@^4.16.3: utils-merge "1.0.1" vary "~1.1.2" -extend-shallow@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-2.0.1.tgz#51af7d614ad9a9f610ea1bafbb989d6b1c56890f" - integrity sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8= - dependencies: - is-extendable "^0.1.0" - -extend-shallow@^3.0.0, extend-shallow@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/extend-shallow/-/extend-shallow-3.0.2.tgz#26a71aaf073b39fb2127172746131c2704028db8" - integrity sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg= - dependencies: - assign-symbols "^1.0.0" - is-extendable "^1.0.1" - -extglob@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/extglob/-/extglob-2.0.4.tgz#ad00fe4dc612a9232e8718711dc5cb5ab0285543" - integrity sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw== - dependencies: - array-unique "^0.3.2" - define-property "^1.0.0" - expand-brackets "^2.1.4" - extend-shallow "^2.0.1" - fragment-cache "^0.2.1" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - -fill-range@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-4.0.0.tgz#d544811d428f98eb06a63dc402d2403c328c38f7" - integrity sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc= - dependencies: - extend-shallow "^2.0.1" - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range "^2.1.0" - fill-range@^7.0.1: version "7.0.1" resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" @@ -1359,33 +1098,16 @@ flatpickr@^4.5.2: resolved "https://registry.yarnpkg.com/flatpickr/-/flatpickr-4.6.9.tgz#9a13383e8a6814bda5d232eae3fcdccb97dc1499" integrity sha512-F0azNNi8foVWKSF+8X+ZJzz8r9sE1G4hl06RyceIaLvyltKvDl6vqk9Lm/6AUUCi5HWaIjiUbk7UpeE/fOXOpw== -for-in@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" - integrity sha1-gQaNKVqBQuwKxybG4iAMMPttXoA= - forwarded@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.1.2.tgz#98c23dab1175657b8c0573e8ceccd91b0ff18c84" integrity sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ= -fragment-cache@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/fragment-cache/-/fragment-cache-0.2.1.tgz#4290fad27f13e89be7f33799c6bc5a0abfff0d19" - integrity sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk= - dependencies: - map-cache "^0.2.2" - fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac= -fs-exists-sync@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/fs-exists-sync/-/fs-exists-sync-0.1.0.tgz#982d6893af918e72d08dec9e8673ff2b5a8d6add" - integrity sha1-mC1ok6+RjnLQjeyehnP/K1qNat0= - fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1417,26 +1139,6 @@ get-intrinsic@^1.0.2, get-intrinsic@^1.1.1: has "^1.0.3" has-symbols "^1.0.1" -get-object@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/get-object/-/get-object-0.2.0.tgz#d92ff7d5190c64530cda0543dac63a3d47fe8c0c" - integrity sha1-2S/31RkMZFMM2gVD2sY6PUf+jAw= - dependencies: - is-number "^2.0.2" - isobject "^0.2.0" - -get-value@^2.0.3, get-value@^2.0.6: - version "2.0.6" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-2.0.6.tgz#dc15ca1c672387ca76bd37ac0a395ba2042a2c28" - integrity sha1-3BXKHGcjh8p2vTesCjlbogQqLCg= - -get-value@^3.0.0, get-value@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/get-value/-/get-value-3.0.1.tgz#5efd2a157f1d6a516d7524e124ac52d0a39ef5a8" - integrity sha512-mKZj9JLQrwMBtj5wxi6MH8Z5eSKaERpAwjg43dPtlGI1ZVEgH/qC7T8/6R2OBSUA+zzHBZgICsVJaEIV2tKTDA== - dependencies: - isobject "^3.0.1" - glob-parent@~5.1.0: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" @@ -1456,35 +1158,6 @@ glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" -gulp-header@^1.7.1: - version "1.8.12" - resolved "https://registry.yarnpkg.com/gulp-header/-/gulp-header-1.8.12.tgz#ad306be0066599127281c4f8786660e705080a84" - integrity sha512-lh9HLdb53sC7XIZOYzTXM4lFuXElv3EVkSDhsd7DoJBj7hm+Ni7D3qYbb+Rr8DuM8nRanBvkVO9d7askreXGnQ== - dependencies: - concat-with-sourcemaps "*" - lodash.template "^4.4.0" - through2 "^2.0.0" - -handlebars-utils@^1.0.6: - version "1.0.6" - resolved "https://registry.yarnpkg.com/handlebars-utils/-/handlebars-utils-1.0.6.tgz#cb9db43362479054782d86ffe10f47abc76357f9" - integrity sha512-d5mmoQXdeEqSKMtQQZ9WkiUcO1E3tPbWxluCK9hVgIDPzQa9WsKo3Lbe/sGflTe7TomHEeZaOgwIkyIr1kfzkw== - dependencies: - kind-of "^6.0.0" - typeof-article "^0.1.1" - -handlebars@^4.7.6, handlebars@^4.7.7: - version "4.7.7" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.7.7.tgz#9ce33416aad02dbd6c8fafa8240d5d98004945a1" - integrity sha512-aAcXm5OAfE/8IXkcZvCepKU3VzW1/39Fb5ZuqMtgI/hT8X2YgoMvBY5dLhq/cpOvw7Lk1nK/UF71aLG/ZnVYRA== - dependencies: - minimist "^1.2.5" - neo-async "^2.6.0" - source-map "^0.6.1" - wordwrap "^1.0.0" - optionalDependencies: - uglify-js "^3.1.4" - has-bigints@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.1.tgz#64fe6acb020673e3b78db035a5af69aa9d07b113" @@ -1505,52 +1178,6 @@ has-symbols@^1.0.1, has-symbols@^1.0.2: resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.2.tgz#165d3070c00309752a1236a479331e3ac56f1423" integrity sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw== -has-value@^0.3.1: - version "0.3.1" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-0.3.1.tgz#7b1f58bada62ca827ec0a2078025654845995e1f" - integrity sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8= - dependencies: - get-value "^2.0.3" - has-values "^0.1.4" - isobject "^2.0.0" - -has-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-1.0.0.tgz#18b281da585b1c5c51def24c930ed29a0be6b177" - integrity sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc= - dependencies: - get-value "^2.0.6" - has-values "^1.0.0" - isobject "^3.0.0" - -has-value@^2.0.2: - version "2.0.2" - resolved "https://registry.yarnpkg.com/has-value/-/has-value-2.0.2.tgz#d0f12e8780ba8e90e66ad1a21c707fdb67c25658" - integrity sha512-ybKOlcRsK2MqrM3Hmz/lQxXHZ6ejzSPzpNabKB45jb5qDgJvKPa3SdapTsTLwEb9WltgWpOmNax7i+DzNOk4TA== - dependencies: - get-value "^3.0.0" - has-values "^2.0.1" - -has-values@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-0.1.4.tgz#6d61de95d91dfca9b9a02089ad384bff8f62b771" - integrity sha1-bWHeldkd/Km5oCCJrThL/49it3E= - -has-values@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-1.0.0.tgz#95b0b63fec2146619a6fe57fe75628d5a39efe4f" - integrity sha1-lbC2P+whRmGab+V/51Yo1aOe/k8= - dependencies: - is-number "^3.0.0" - kind-of "^4.0.0" - -has-values@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/has-values/-/has-values-2.0.1.tgz#3876200ff86d8a8546a9264a952c17d5fc17579d" - integrity sha512-+QdH3jOmq9P8GfdjFg0eJudqx1FqU62NQJ4P16rOEHeRdl7ckgwn6uqQjzYE0ZoHVV/e5E2esuJ5Gl5+HUW19w== - dependencies: - kind-of "^6.0.2" - has@^1.0.0, has@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" @@ -1558,16 +1185,6 @@ has@^1.0.0, has@^1.0.3: dependencies: function-bind "^1.1.1" -helper-md@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/helper-md/-/helper-md-0.2.2.tgz#c1f59d7e55bbae23362fd8a0e971607aec69d41f" - integrity sha1-wfWdflW7riM2L9ig6XFgeuxp1B8= - dependencies: - ent "^2.2.0" - extend-shallow "^2.0.1" - fs-exists-sync "^0.1.0" - remarkable "^1.6.2" - hex-color-regex@^1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e" @@ -1583,14 +1200,6 @@ hsla-regex@^1.0.0: resolved "https://registry.yarnpkg.com/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38" integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg= -html-tag@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/html-tag/-/html-tag-2.0.0.tgz#36c3bc8d816fd30b570d5764a497a641640c2fed" - integrity sha512-XxzooSo6oBoxBEUazgjdXj7VwTn/iSTSZzTYKzYY6I916tkaYzypHxy+pbVU1h+0UQ9JlVf5XkNQyxOAiiQO1g== - dependencies: - is-self-closing "^1.0.1" - kind-of "^6.0.0" - http-errors@1.7.2: version "1.7.2" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.7.2.tgz#4f5029cf13239f31036e5b2e55292bcfbcc85c8f" @@ -1673,7 +1282,7 @@ inflight@^1.0.4: once "^1.3.0" wrappy "1" -inherits@2, inherits@2.0.4, inherits@~2.0.3: +inherits@2, inherits@2.0.4: version "2.0.4" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== @@ -1693,20 +1302,6 @@ is-absolute-url@^2.0.0: resolved "https://registry.yarnpkg.com/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6" integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY= -is-accessor-descriptor@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz#a9e12cb3ae8d876727eeef3843f8a0897b5c98d6" - integrity sha1-qeEss66Nh2cn7u84Q/igiXtcmNY= - dependencies: - kind-of "^3.0.2" - -is-accessor-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz#169c2f6d3df1f992618072365c9b0ea1f6878656" - integrity sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ== - dependencies: - kind-of "^6.0.0" - is-arrayish@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" @@ -1736,11 +1331,6 @@ is-boolean-object@^1.1.0: dependencies: call-bind "^1.0.0" -is-buffer@^1.1.5: - version "1.1.6" - resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be" - integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w== - is-callable@^1.1.4, is-callable@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.3.tgz#8b1e0500b73a1d76c70487636f368e519de8db8e" @@ -1765,67 +1355,16 @@ is-core-module@^2.2.0: dependencies: has "^1.0.3" -is-data-descriptor@^0.1.4: - version "0.1.4" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz#0b5ee648388e2c860282e793f1856fec3f301b56" - integrity sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y= - dependencies: - kind-of "^3.0.2" - -is-data-descriptor@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz#d84876321d0e7add03990406abbbbd36ba9268c7" - integrity sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ== - dependencies: - kind-of "^6.0.0" - is-date-object@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.2.tgz#bda736f2cd8fd06d32844e7743bfa7494c3bfd7e" integrity sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g== -is-descriptor@^0.1.0: - version "0.1.6" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-0.1.6.tgz#366d8240dde487ca51823b1ab9f07a10a78251ca" - integrity sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg== - dependencies: - is-accessor-descriptor "^0.1.6" - is-data-descriptor "^0.1.4" - kind-of "^5.0.0" - -is-descriptor@^1.0.0, is-descriptor@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-descriptor/-/is-descriptor-1.0.2.tgz#3b159746a66604b04f8c81524ba365c5f14d86ec" - integrity sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg== - dependencies: - is-accessor-descriptor "^1.0.0" - is-data-descriptor "^1.0.0" - kind-of "^6.0.2" - is-directory@^0.3.1: version "0.3.1" resolved "https://registry.yarnpkg.com/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1" integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE= -is-even@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-even/-/is-even-1.0.0.tgz#76b5055fbad8d294a86b6a949015e1c97b717c06" - integrity sha1-drUFX7rY0pSoa2qUkBXhyXtxfAY= - dependencies: - is-odd "^0.1.2" - -is-extendable@^0.1.0, is-extendable@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" - integrity sha1-YrEQ4omkcUGOPsNqYX1HLjAd/Ik= - -is-extendable@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-1.0.1.tgz#a7470f9e426733d81bd81e1155264e3a3507cab4" - integrity sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA== - dependencies: - is-plain-object "^2.0.4" - is-extglob@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" @@ -1853,20 +1392,6 @@ is-number-object@^1.0.4: resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.4.tgz#36ac95e741cf18b283fc1ddf5e83da798e3ec197" integrity sha512-zohwelOAur+5uXtk8O3GPQ1eAcu4ZX3UwxQhUlfFFMNpUd83gXgjbhJh6HmB6LUNV/ieOLQuDwJO3dWJosUeMw== -is-number@^2.0.2: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" - integrity sha1-Afy7s5NGOlSPL0ZszhbezknbkI8= - dependencies: - kind-of "^3.0.2" - -is-number@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" - integrity sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU= - dependencies: - kind-of "^3.0.2" - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -1877,20 +1402,6 @@ is-obj@^2.0.0: resolved "https://registry.yarnpkg.com/is-obj/-/is-obj-2.0.0.tgz#473fb05d973705e3fd9620545018ca8e22ef4982" integrity sha512-drqDG3cbczxxEJRoOXcOjtdp1J/lyp1mNn0xaznRs8+muBhgQcrnbspox5X5fOw0HnMnbfDzvnEMEtqDEJEo8w== -is-odd@^0.1.2: - version "0.1.2" - resolved "https://registry.yarnpkg.com/is-odd/-/is-odd-0.1.2.tgz#bc573b5ce371ef2aad6e6f49799b72bef13978a7" - integrity sha1-vFc7XONx7yqtbm9JeZtyvvE5eKc= - dependencies: - is-number "^3.0.0" - -is-plain-object@^2.0.3, is-plain-object@^2.0.4: - version "2.0.4" - resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-2.0.4.tgz#2c163b3fafb1b606d9d17928f05c2a1c38e07677" - integrity sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og== - dependencies: - isobject "^3.0.1" - is-reference@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/is-reference/-/is-reference-1.2.1.tgz#8b2dac0b371f4bc994fdeaba9eb542d03002d0b7" @@ -1911,13 +1422,6 @@ is-resolvable@^1.0.0: resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88" integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg== -is-self-closing@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/is-self-closing/-/is-self-closing-1.0.1.tgz#5f406b527c7b12610176320338af0fa3896416e4" - integrity sha512-E+60FomW7Blv5GXTlYee2KDrnG6srxF7Xt1SjrhWUGUEsTFIqY/nq2y3DaftCsgUMdh89V07IVfhY9KIJhLezg== - dependencies: - self-closing-tags "^1.0.1" - is-string@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.5.tgz#40493ed198ef3ff477b8c7f92f644ec82a5cd3a6" @@ -1930,38 +1434,11 @@ is-symbol@^1.0.2, is-symbol@^1.0.3: dependencies: has-symbols "^1.0.1" -is-windows@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/is-windows/-/is-windows-1.0.2.tgz#d1850eb9791ecd18e6182ce12a30f396634bb19d" - integrity sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA== - -isarray@1.0.0, isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= - isexe@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" integrity sha1-6PvzdNxVb/iUehDcsFctYz8s+hA= -isobject@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-0.2.0.tgz#a3432192f39b910b5f02cc989487836ec70aa85e" - integrity sha1-o0MhkvObkQtfAsyYlIeDbscKqF4= - -isobject@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" - integrity sha1-8GVWEJaj8dou9GJy+BXIQNh+DIk= - dependencies: - isarray "1.0.0" - -isobject@^3.0.0, isobject@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" - integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= - jest-worker@^26.2.1: version "26.6.2" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-26.6.2.tgz#7f72cbc4d643c365e27b9fd775f9d0eaa9c7a8ed" @@ -2001,30 +1478,6 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -kind-of@^3.0.2, kind-of@^3.0.3, kind-of@^3.1.0, kind-of@^3.2.0: - version "3.2.2" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" - integrity sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ= - dependencies: - is-buffer "^1.1.5" - -kind-of@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" - integrity sha1-IIE989cSkosgc3hpGkUGb65y3Vc= - dependencies: - is-buffer "^1.1.5" - -kind-of@^5.0.0, kind-of@^5.0.2: - version "5.1.0" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-5.1.0.tgz#729c91e2d857b7a419a1f9aa65685c4c33f5845d" - integrity sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw== - -kind-of@^6.0.0, kind-of@^6.0.2, kind-of@^6.0.3: - version "6.0.3" - resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" - integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== - lines-and-columns@^1.1.6: version "1.1.6" resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00" @@ -2039,11 +1492,6 @@ loader-utils@^1.1.0: emojis-list "^3.0.0" json5 "^1.0.1" -lodash._reinterpolate@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" - integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= - lodash.camelcase@^4.3.0: version "4.3.0" resolved "https://registry.yarnpkg.com/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz#b28aa6288a2b9fc651035c7711f65ab6190331a6" @@ -2054,31 +1502,11 @@ lodash.memoize@^4.1.2: resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe" integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4= -lodash.template@^4.4.0: - version "4.5.0" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" - integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.templatesettings "^4.0.0" - -lodash.templatesettings@^4.0.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" - integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== - dependencies: - lodash._reinterpolate "^3.0.0" - lodash.uniq@^4.5.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773" integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M= -lodash@^4.17.20: - version "4.17.21" - resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" - integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== - magic-string@^0.25.7: version "0.25.7" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.7.tgz#3f497d6fd34c669c6798dcb821f2ef31f5445051" @@ -2086,18 +1514,6 @@ magic-string@^0.25.7: dependencies: sourcemap-codec "^1.4.4" -map-cache@^0.2.2: - version "0.2.2" - resolved "https://registry.yarnpkg.com/map-cache/-/map-cache-0.2.2.tgz#c32abd0bd6525d9b051645bb4f26ac5dc98a0dbf" - integrity sha1-wyq9C9ZSXZsFFkW7TyasXcmKDb8= - -map-visit@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/map-visit/-/map-visit-1.0.0.tgz#ecdca8f13144e660f1b5bd41f12f3479d98dfb8f" - integrity sha1-7Nyo8TFE5mDxtb1B8S80edmN+48= - dependencies: - object-visit "^1.0.0" - marked@^4.0.10: version "4.0.12" resolved "https://registry.yarnpkg.com/marked/-/marked-4.0.12.tgz#2262a4e6fd1afd2f13557726238b69a48b982f7d" @@ -2133,25 +1549,6 @@ methods@~1.1.2: resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micromatch@^3.1.5: - version "3.1.10" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" - integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - braces "^2.3.1" - define-property "^2.0.2" - extend-shallow "^3.0.2" - extglob "^2.0.4" - fragment-cache "^0.2.1" - kind-of "^6.0.2" - nanomatch "^1.2.9" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.2" - mime-db@1.47.0: version "1.47.0" resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.47.0.tgz#8cb313e59965d3c05cfbf898915a267af46a335c" @@ -2181,14 +1578,6 @@ minimist@^1.2.0, minimist@^1.2.5: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.5.tgz#67d66014b66a6a8aaa0c083c5fd58df4e4e97602" integrity sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw== -mixin-deep@^1.2.0: - version "1.3.2" - resolved "https://registry.yarnpkg.com/mixin-deep/-/mixin-deep-1.3.2.tgz#1120b43dc359a785dce65b55b82e257ccf479566" - integrity sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA== - dependencies: - for-in "^1.0.2" - is-extendable "^1.0.1" - mkdirp@~0.5.1: version "0.5.5" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.5.tgz#d91cefd62d1436ca0f41620e251288d420099def" @@ -2216,33 +1605,11 @@ nanoid@^3.1.22: resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.0.tgz#5906f776fd886c66c24f3653e0c46fcb1d4ad6b0" integrity sha512-JzxqqT5u/x+/KOFSd7JP15DOo9nOoHpx6DYatqIHUW2+flybkm+mdcraotSQR5WcnZr+qhGVh8Ted0KdfSMxlg== -nanomatch@^1.2.9: - version "1.2.13" - resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119" - integrity sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA== - dependencies: - arr-diff "^4.0.0" - array-unique "^0.3.2" - define-property "^2.0.2" - extend-shallow "^3.0.2" - fragment-cache "^0.2.1" - is-windows "^1.0.2" - kind-of "^6.0.2" - object.pick "^1.3.0" - regex-not "^1.0.0" - snapdragon "^0.8.1" - to-regex "^3.0.1" - negotiator@0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== -neo-async@^2.6.0: - version "2.6.2" - resolved "https://registry.yarnpkg.com/neo-async/-/neo-async-2.6.2.tgz#b4aafb93e3aeb2d8174ca53cf163ab7d7308305f" - integrity sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw== - node-releases@^1.1.71: version "1.1.73" resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-1.1.73.tgz#dd4e81ddd5277ff846b80b52bb40c49edf7a7b20" @@ -2283,15 +1650,6 @@ nth-check@^1.0.2: dependencies: boolbase "~1.0.0" -object-copy@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/object-copy/-/object-copy-0.1.0.tgz#7e7d858b781bd7c991a41ba975ed3812754e998c" - integrity sha1-fn2Fi3gb18mRpBupde04EnVOmYw= - dependencies: - copy-descriptor "^0.1.0" - define-property "^0.2.5" - kind-of "^3.0.3" - object-inspect@^1.9.0: version "1.9.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.9.0.tgz#c90521d74e1127b67266ded3394ad6116986533a" @@ -2302,13 +1660,6 @@ object-keys@^1.0.12, object-keys@^1.1.1: resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e" integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA== -object-visit@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/object-visit/-/object-visit-1.0.1.tgz#f79c4493af0c5377b59fe39d395e41042dd045bb" - integrity sha1-95xEk68MU3e1n+OdOV5BBC3QRbs= - dependencies: - isobject "^3.0.0" - object.assign@^4.1.2: version "4.1.2" resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.2.tgz#0ed54a342eceb37b38ff76eb831a0e788cb63940" @@ -2328,13 +1679,6 @@ object.getownpropertydescriptors@^2.1.0: define-properties "^1.1.3" es-abstract "^1.18.0-next.2" -object.pick@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/object.pick/-/object.pick-1.3.0.tgz#87a10ac4c1694bd2e1cbf53591a66141fb5dd747" - integrity sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c= - dependencies: - isobject "^3.0.1" - object.values@^1.1.0: version "1.1.3" resolved "https://registry.yarnpkg.com/object.values/-/object.values-1.1.3.tgz#eaa8b1e17589f02f698db093f7c62ee1699742ee" @@ -2409,11 +1753,6 @@ parseurl@~1.3.3: resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" integrity sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ== -pascalcase@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/pascalcase/-/pascalcase-0.1.1.tgz#b363e55e8006ca6fe21784d2db22bd15d7917f14" - integrity sha1-s2PlXoAGym/iF4TS2yK9FdeRfxQ= - path-is-absolute@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" @@ -2449,11 +1788,6 @@ pify@^5.0.0: resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f" integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA== -posix-character-classes@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab" - integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs= - postcss-calc@^7.0.1: version "7.0.5" resolved "https://registry.yarnpkg.com/postcss-calc/-/postcss-calc-7.0.5.tgz#f8a6e99f12e619c2ebc23cf6c486fdc15860933e" @@ -2797,11 +2131,6 @@ postcss@^8.2.9: nanoid "^3.1.22" source-map "^0.6.1" -process-nextick-args@~2.0.0: - version "2.0.1" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" - integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== - promise.series@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/promise.series/-/promise.series-0.2.0.tgz#2cc7ebe959fc3a6619c04ab4dbdc9e452d864bbd" @@ -2857,19 +2186,6 @@ raw-body@^2.3.0: iconv-lite "0.4.24" unpipe "1.0.0" -readable-stream@~2.3.6: - version "2.3.7" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.7.tgz#1eca1cf711aef814c04f62252a36a62f6cb23b57" - integrity sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw== - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~2.0.0" - safe-buffer "~5.1.1" - string_decoder "~1.1.1" - util-deprecate "~1.0.1" - readdirp@~3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.5.0.tgz#9ba74c019b15d365278d2e91bb8c48d7b4d42c9e" @@ -2877,39 +2193,6 @@ readdirp@~3.5.0: dependencies: picomatch "^2.2.1" -regex-not@^1.0.0, regex-not@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/regex-not/-/regex-not-1.0.2.tgz#1f4ece27e00b0b65e0247a6810e6a85d83a5752c" - integrity sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A== - dependencies: - extend-shallow "^3.0.2" - safe-regex "^1.1.0" - -relative@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/relative/-/relative-3.0.2.tgz#0dcd8ec54a5d35a3c15e104503d65375b5a5367f" - integrity sha1-Dc2OxUpdNaPBXhBFA9ZTdbWlNn8= - dependencies: - isobject "^2.0.0" - -remarkable@^1.6.2: - version "1.7.4" - resolved "https://registry.yarnpkg.com/remarkable/-/remarkable-1.7.4.tgz#19073cb960398c87a7d6546eaa5e50d2022fcd00" - integrity sha512-e6NKUXgX95whv7IgddywbeN/ItCkWbISmc2DiqHJb0wTrqZIexqdco5b8Z3XZoo/48IdNVKM9ZCvTPJ4F5uvhg== - dependencies: - argparse "^1.0.10" - autolinker "~0.28.0" - -repeat-element@^1.1.2: - version "1.1.4" - resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.4.tgz#be681520847ab58c7568ac75fbfad28ed42d39e9" - integrity sha512-LFiNfRcSu7KK3evMyYOuCzv3L10TW7yC1G2/+StMjK8Y6Vqd2MG7r/Qjw4ghtuCOjFvlnms/iMmLqpvW/ES/WQ== - -repeat-string@^1.6.1: - version "1.6.1" - resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" - integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc= - require-relative@^0.8.7: version "0.8.7" resolved "https://registry.yarnpkg.com/require-relative/-/require-relative-0.8.7.tgz#7999539fc9e047a37928fa196f8e1563dabd36de" @@ -2930,11 +2213,6 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== -resolve-url@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/resolve-url/-/resolve-url-0.2.1.tgz#2c637fe77c893afd2a663fe21aa9080068e2052a" - integrity sha1-LGN/53yJOv0qZj/iGqkIAGjiBSo= - resolve@^1.17.0, resolve@^1.19.0: version "1.20.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.20.0.tgz#629a013fb3f70755d6f0b7935cc1c2c5378b1975" @@ -2943,11 +2221,6 @@ resolve@^1.17.0, resolve@^1.19.0: is-core-module "^2.2.0" path-parse "^1.0.6" -ret@~0.1.10: - version "0.1.15" - resolved "https://registry.yarnpkg.com/ret/-/ret-0.1.15.tgz#b8a4825d5bdb1fc3f6f53c2bc33f81388681c7bc" - integrity sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg== - rgb-regex@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1" @@ -3009,7 +2282,7 @@ rollup@^2.45.2: optionalDependencies: fsevents "~2.3.1" -safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: +safe-buffer@5.1.2, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== @@ -3024,13 +2297,6 @@ safe-identifier@^0.4.2: resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.4.2.tgz#cf6bfca31c2897c588092d1750d30ef501d59fcb" integrity sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w== -safe-regex@^1.1.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/safe-regex/-/safe-regex-1.1.0.tgz#40a3669f3b077d1e943d44629e157dd48023bf2e" - integrity sha1-QKNmnzsHfR6UPURinhV91IAjvy4= - dependencies: - ret "~0.1.10" - "safer-buffer@>= 2.1.2 < 3": version "2.1.2" resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a" @@ -3041,11 +2307,6 @@ sax@~1.2.4: resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9" integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw== -self-closing-tags@^1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/self-closing-tags/-/self-closing-tags-1.0.1.tgz#6c5fa497994bb826b484216916371accee490a5d" - integrity sha512-7t6hNbYMxM+VHXTgJmxwgZgLGktuXtVVD5AivWzNTdJBM4DBjnDKDzkf2SrNjihaArpeJYNjxkELBu1evI4lQA== - send@0.17.1: version "0.17.1" resolved "https://registry.yarnpkg.com/send/-/send-0.17.1.tgz#c1d8b059f7900f7466dd4938bdc44e11ddb376c8" @@ -3082,16 +2343,6 @@ serve-static@1.14.1: parseurl "~1.3.3" send "0.17.1" -set-value@^2.0.0, set-value@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/set-value/-/set-value-2.0.1.tgz#a18d40530e6f07de4228c7defe4227af8cad005b" - integrity sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw== - dependencies: - extend-shallow "^2.0.1" - is-extendable "^0.1.1" - is-plain-object "^2.0.3" - split-string "^3.0.1" - setprototypeof@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/setprototypeof/-/setprototypeof-1.1.1.tgz#7e95acb24aa92f5885e0abef5ba131330d4ae683" @@ -3116,53 +2367,12 @@ simple-swizzle@^0.2.2: dependencies: is-arrayish "^0.3.1" -snapdragon-node@^2.0.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/snapdragon-node/-/snapdragon-node-2.1.1.tgz#6c175f86ff14bdb0724563e8f3c1b021a286853b" - integrity sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw== - dependencies: - define-property "^1.0.0" - isobject "^3.0.0" - snapdragon-util "^3.0.1" - -snapdragon-util@^3.0.1: - version "3.0.1" - resolved "https://registry.yarnpkg.com/snapdragon-util/-/snapdragon-util-3.0.1.tgz#f956479486f2acd79700693f6f7b805e45ab56e2" - integrity sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ== - dependencies: - kind-of "^3.2.0" - -snapdragon@^0.8.1: - version "0.8.2" - resolved "https://registry.yarnpkg.com/snapdragon/-/snapdragon-0.8.2.tgz#64922e7c565b0e14204ba1aa7d6964278d25182d" - integrity sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg== - dependencies: - base "^0.11.1" - debug "^2.2.0" - define-property "^0.2.5" - extend-shallow "^2.0.1" - map-cache "^0.2.2" - source-map "^0.5.6" - source-map-resolve "^0.5.0" - use "^3.1.0" - "source-map-fast@npm:source-map@0.7.3", source-map@~0.7.2: name source-map-fast version "0.7.3" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.3.tgz#5302f8169031735226544092e64981f751750383" integrity sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ== -source-map-resolve@^0.5.0: - version "0.5.3" - resolved "https://registry.yarnpkg.com/source-map-resolve/-/source-map-resolve-0.5.3.tgz#190866bece7553e1f8f267a2ee82c606b5509a1a" - integrity sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw== - dependencies: - atob "^2.1.2" - decode-uri-component "^0.2.0" - resolve-url "^0.2.1" - source-map-url "^0.4.0" - urix "^0.1.0" - source-map-support@~0.5.19: version "0.5.19" resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.19.tgz#a98b62f86dcaf4f67399648c085291ab9e8fed61" @@ -3171,11 +2381,6 @@ source-map-support@~0.5.19: buffer-from "^1.0.0" source-map "^0.6.0" -source-map-url@^0.4.0: - version "0.4.1" - resolved "https://registry.yarnpkg.com/source-map-url/-/source-map-url-0.4.1.tgz#0af66605a745a5a2f91cf1bbf8a7afbc283dec56" - integrity sha512-cPiFOTLUKvJFIg4SKVScy4ilPPW6rFgMgfuZJPNoDuMs3nC1HbMUycBoJw77xFIp6z1UJQJOfx6C9GMH80DiTw== - source-map@^0.5.6: version "0.5.7" resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc" @@ -3191,13 +2396,6 @@ sourcemap-codec@^1.4.4: resolved "https://registry.yarnpkg.com/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz#ea804bd94857402e6992d05a38ef1ae35a9ab4c4" integrity sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA== -split-string@^3.0.1, split-string@^3.0.2: - version "3.1.0" - resolved "https://registry.yarnpkg.com/split-string/-/split-string-3.1.0.tgz#7cb09dda3a86585705c64b39a6466038682e8fe2" - integrity sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw== - dependencies: - extend-shallow "^3.0.0" - sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -3208,14 +2406,6 @@ stable@^0.1.8: resolved "https://registry.yarnpkg.com/stable/-/stable-0.1.8.tgz#836eb3c8382fe2936feaf544631017ce7d47a3cf" integrity sha512-ji9qxRnOVfcuLDySj9qzhGSEFVobyt1kIOSkj1qZzYLzq7Tos/oUUWvotUPQLlrsidqsK6tBH89Bc9kL5zHA6w== -static-extend@^0.1.1: - version "0.1.2" - resolved "https://registry.yarnpkg.com/static-extend/-/static-extend-0.1.2.tgz#60809c39cbff55337226fd5e0b520f341f1fb5c6" - integrity sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY= - dependencies: - define-property "^0.2.5" - object-copy "^0.1.0" - "statuses@>= 1.5.0 < 2", statuses@~1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" @@ -3242,18 +2432,6 @@ string.prototype.trimstart@^1.0.4: call-bind "^1.0.2" define-properties "^1.1.3" -string_decoder@~1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.1.1.tgz#9cf1611ba62685d7030ae9e4ba34149c3af03fc8" - integrity sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg== - dependencies: - safe-buffer "~5.1.0" - -striptags@^3.1.1: - version "3.2.0" - resolved "https://registry.yarnpkg.com/striptags/-/striptags-3.2.0.tgz#cc74a137db2de8b0b9a370006334161f7dd67052" - integrity sha512-g45ZOGzHDMe2bdYMdIvdAfCQkCTDMGBazSw1ypMowwGIee7ZQ5dU0rBJ8Jqgl+jAKIv4dbeE1jscZq9wid1Tkw== - style-inject@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/style-inject/-/style-inject-0.3.0.tgz#d21c477affec91811cc82355832a700d22bf8dd3" @@ -3334,39 +2512,11 @@ terser@^5.0.0: source-map "~0.7.2" source-map-support "~0.5.19" -through2@^2.0.0: - version "2.0.5" - resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" - integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== - dependencies: - readable-stream "~2.3.6" - xtend "~4.0.1" - timsort@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= -to-gfm-code-block@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/to-gfm-code-block/-/to-gfm-code-block-0.1.1.tgz#25d045a5fae553189e9637b590900da732d8aa82" - integrity sha1-JdBFpfrlUxielje1kJANpzLYqoI= - -to-object-path@^0.3.0: - version "0.3.0" - resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" - integrity sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68= - dependencies: - kind-of "^3.0.2" - -to-regex-range@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-2.1.1.tgz#7c80c17b9dfebe599e27367e0d4dd5590141db38" - integrity sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg= - dependencies: - is-number "^3.0.0" - repeat-string "^1.6.1" - to-regex-range@^5.0.1: version "5.0.1" resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" @@ -3374,16 +2524,6 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" -to-regex@^3.0.1, to-regex@^3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/to-regex/-/to-regex-3.0.2.tgz#13cfdd9b336552f30b51f33a8ae1b42a7a7599ce" - integrity sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw== - dependencies: - define-property "^2.0.2" - extend-shallow "^3.0.2" - regex-not "^1.0.2" - safe-regex "^1.1.0" - toidentifier@1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.0.tgz#7e1be3470f1e77948bc43d94a3c8f4d7752ba553" @@ -3397,23 +2537,11 @@ type-is@~1.6.17, type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typeof-article@^0.1.1: - version "0.1.1" - resolved "https://registry.yarnpkg.com/typeof-article/-/typeof-article-0.1.1.tgz#9f07e733c3fbb646ffa9e61c08debacd460e06af" - integrity sha1-nwfnM8P7tkb/qeYcCN66zUYOBq8= - dependencies: - kind-of "^3.1.0" - typo-js@*: version "1.2.1" resolved "https://registry.yarnpkg.com/typo-js/-/typo-js-1.2.1.tgz#334a0d8c3f6c56f2f1e15fdf6c31677793cbbe9b" integrity sha512-bTGLjbD3WqZDR3CgEFkyi9Q/SS2oM29ipXrWfDb4M74ea69QwKAECVceYpaBu0GfdnASMg9Qfl67ttB23nePHg== -uglify-js@^3.1.4: - version "3.15.1" - resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-3.15.1.tgz#9403dc6fa5695a6172a91bc983ea39f0f7c9086d" - integrity sha512-FAGKF12fWdkpvNJZENacOH0e/83eG6JyVQyanIJaBXCN1J11TUQv1T1/z8S+Z0CG0ZPk1nPcreF/c7lrTd0TEQ== - unbox-primitive@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.1.tgz#085e215625ec3162574dc8859abee78a59b14471" @@ -3424,16 +2552,6 @@ unbox-primitive@^1.0.0: has-symbols "^1.0.2" which-boxed-primitive "^1.0.2" -union-value@^1.0.0: - version "1.0.1" - resolved "https://registry.yarnpkg.com/union-value/-/union-value-1.0.1.tgz#0b6fe7b835aecda61c6ea4d4f02c14221e109847" - integrity sha512-tJfXmxMeWYnczCVs7XAEvIV7ieppALdyepWMkHkwciRpZraG/xwT+s2JN8+pr1+8jCRf80FFzvr+MpQeeoF4Xg== - dependencies: - arr-union "^3.1.0" - get-value "^2.0.6" - is-extendable "^0.1.1" - set-value "^2.0.1" - uniq@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff" @@ -3454,25 +2572,7 @@ unquote@~1.1.1: resolved "https://registry.yarnpkg.com/unquote/-/unquote-1.1.1.tgz#8fded7324ec6e88a0ff8b905e7c098cdc086d544" integrity sha1-j97XMk7G6IoP+LkF58CYzcCG1UQ= -unset-value@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/unset-value/-/unset-value-1.0.0.tgz#8376873f7d2335179ffb1e6fc3a8ed0dfc8ab559" - integrity sha1-g3aHP30jNRef+x5vw6jtDfyKtVk= - dependencies: - has-value "^0.3.1" - isobject "^3.0.0" - -urix@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/urix/-/urix-0.1.0.tgz#da937f7a62e21fec1fd18d49b35c2935067a6c72" - integrity sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI= - -use@^3.1.0: - version "3.1.1" - resolved "https://registry.yarnpkg.com/use/-/use-3.1.1.tgz#d50c8cac79a19fbc20f2911f56eb973f4e10070f" - integrity sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ== - -util-deprecate@^1.0.2, util-deprecate@~1.0.1: +util-deprecate@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8= @@ -3502,14 +2602,6 @@ vendors@^1.0.0: resolved "https://registry.yarnpkg.com/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e" integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w== -vm2@^3.9.4: - version "3.9.8" - resolved "https://registry.yarnpkg.com/vm2/-/vm2-3.9.8.tgz#e99c000db042735cd2f94d8db6c42163a17be04e" - integrity sha512-/1PYg/BwdKzMPo8maOZ0heT7DLI0DAFTm7YQaz/Lim9oIaFZsJs3EdtalvXuBfZwczNwsYhju75NW4d6E+4q+w== - dependencies: - acorn "^8.7.0" - acorn-walk "^8.2.0" - which-boxed-primitive@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6" @@ -3528,11 +2620,6 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -wordwrap@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" - integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus= - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -3545,17 +2632,7 @@ ws@^5.2.0: dependencies: async-limiter "~1.0.0" -xtend@~4.0.1: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - yaml@^1.10.0: version "1.10.2" resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -year@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/year/-/year-0.2.1.tgz#4083ae520a318b23ec86037f3000cb892bdf9bb0" - integrity sha1-QIOuUgoxiyPshgN/MADLiSvfm7A= From 990442b45e06614344ff728f26b25e1a1954720b Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Tue, 8 Mar 2022 17:57:36 +0000 Subject: [PATCH 020/206] Add optional chaining to all current asset references to account for nullish values --- packages/builder/src/builderStore/dataBinding.js | 4 ++-- packages/builder/src/builderStore/index.js | 2 +- packages/builder/src/builderStore/store/frontend.js | 4 ++-- .../ComponentNavigationTree/ComponentDropdownMenu.svelte | 4 ++-- .../ButtonActionEditor/actions/S3Upload.svelte | 2 +- .../PropertyControls/DataProviderSelect.svelte | 2 +- .../PropertiesPanel/PropertyControls/FormFieldSelect.svelte | 2 +- .../PropertiesPanel/PropertyControls/ResetFieldsButton.svelte | 2 +- .../app/[application]/design/[assetType]/_layout.svelte | 2 +- 9 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/builder/src/builderStore/dataBinding.js b/packages/builder/src/builderStore/dataBinding.js index 5b9bebcbf5..41174a9d9d 100644 --- a/packages/builder/src/builderStore/dataBinding.js +++ b/packages/builder/src/builderStore/dataBinding.js @@ -126,7 +126,7 @@ export const getDatasourceForProvider = (asset, component) => { if (dataProviderSetting) { const settingValue = component[dataProviderSetting.key] const providerId = extractLiteralHandlebarsID(settingValue) - const provider = findComponent(asset.props, providerId) + const provider = findComponent(asset?.props, providerId) return getDatasourceForProvider(asset, provider) } @@ -458,7 +458,7 @@ export const getSchemaForDatasource = (asset, datasource, options) => { // Determine the entity which backs this datasource. // "provider" datasources are those targeting another data provider if (type === "provider") { - const component = findComponent(asset.props, datasource.providerId) + const component = findComponent(asset?.props, datasource.providerId) const source = getDatasourceForProvider(asset, component) return getSchemaForDatasource(asset, source, options) } diff --git a/packages/builder/src/builderStore/index.js b/packages/builder/src/builderStore/index.js index 5181e756c6..82556c74cf 100644 --- a/packages/builder/src/builderStore/index.js +++ b/packages/builder/src/builderStore/index.js @@ -25,7 +25,7 @@ export const selectedComponent = derived( if (!$currentAsset || !$store.selectedComponentId) { return null } - return findComponent($currentAsset.props, $store.selectedComponentId) + return findComponent($currentAsset?.props, $store.selectedComponentId) } ) diff --git a/packages/builder/src/builderStore/store/frontend.js b/packages/builder/src/builderStore/store/frontend.js index c8e4c85b06..fd29414e3c 100644 --- a/packages/builder/src/builderStore/store/frontend.js +++ b/packages/builder/src/builderStore/store/frontend.js @@ -400,11 +400,11 @@ export const getFrontendStore = () => { parentComponent = selected } else { // Otherwise we need to use the parent of this component - parentComponent = findComponentParent(asset.props, selected._id) + parentComponent = findComponentParent(asset?.props, selected._id) } } else { // Use screen or layout if no component is selected - parentComponent = asset.props + parentComponent = asset?.props } // Attach component diff --git a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ComponentDropdownMenu.svelte b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ComponentDropdownMenu.svelte index df6be7e88f..75601084a0 100644 --- a/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ComponentDropdownMenu.svelte +++ b/packages/builder/src/components/design/NavigationPanel/ComponentNavigationTree/ComponentDropdownMenu.svelte @@ -21,7 +21,7 @@ const moveUpComponent = () => { const asset = get(currentAsset) - const parent = findComponentParent(asset.props, component._id) + const parent = findComponentParent(asset?.props, component._id) if (!parent) { return } @@ -41,7 +41,7 @@ const moveDownComponent = () => { const asset = get(currentAsset) - const parent = findComponentParent(asset.props, component._id) + const parent = findComponentParent(asset?.props, component._id) if (!parent) { return } diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/S3Upload.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/S3Upload.svelte index 76cccf58c5..2e374f165f 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/S3Upload.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ButtonActionEditor/actions/S3Upload.svelte @@ -5,7 +5,7 @@ export let parameters - $: components = findAllMatchingComponents($currentAsset.props, component => + $: components = findAllMatchingComponents($currentAsset?.props, component => component._component.endsWith("s3upload") ) diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte index d7118fd3ec..a5b7a08255 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/DataProviderSelect.svelte @@ -10,7 +10,7 @@ const dispatch = createEventDispatcher() const getValue = component => `{{ literal ${makePropSafe(component._id)} }}` - $: path = findComponentPath($currentAsset.props, $store.selectedComponentId) + $: path = findComponentPath($currentAsset?.props, $store.selectedComponentId) $: providers = path.filter(c => c._component?.endsWith("/dataprovider")) // Set initial value to closest data provider diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FormFieldSelect.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FormFieldSelect.svelte index 94a542480f..1f08c56ff5 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FormFieldSelect.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/FormFieldSelect.svelte @@ -12,7 +12,7 @@ export let type $: form = findClosestMatchingComponent( - $currentAsset.props, + $currentAsset?.props, componentInstance._id, component => component._component === "@budibase/standard-components/form" ) diff --git a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ResetFieldsButton.svelte b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ResetFieldsButton.svelte index a76a93d7f6..e927526b92 100644 --- a/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ResetFieldsButton.svelte +++ b/packages/builder/src/components/design/PropertiesPanel/PropertyControls/ResetFieldsButton.svelte @@ -11,7 +11,7 @@ const resetFormFields = async () => { const form = findClosestMatchingComponent( - $currentAsset.props, + $currentAsset?.props, componentInstance._id, component => component._component.endsWith("/form") ) diff --git a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte index dc565e5f39..97697c7449 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/design/[assetType]/_layout.svelte @@ -135,7 +135,7 @@ if (asset?._id) { url += `/${asset._id}` if (componentId) { - const componentPath = findComponentPath(asset.props, componentId) + const componentPath = findComponentPath(asset?.props, componentId) const componentURL = componentPath .slice(1) .map(comp => comp._id) From 3b9b4d92772f8ebc61c505d929c4b5d8a39868fd Mon Sep 17 00:00:00 2001 From: Dean Date: Wed, 9 Mar 2022 09:23:20 +0000 Subject: [PATCH 021/206] Removed unused map property definition --- packages/client/manifest.json | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 11c927700e..c7f25f8270 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2489,18 +2489,6 @@ "label": "Provider", "key": "dataProvider" }, - { - "type": "select", - "label": "Map Type", - "key": "mapType", - "options": [ - "Roadmap", - "Terrain", - "Satellite", - "Hybrid" - ], - "defaultValue": "Roadmap" - }, { "type": "boolean", "label": "Enable Fullscreen", From 2560bec3e841417f221d5ffc8e46878220ce9f68 Mon Sep 17 00:00:00 2001 From: Dean Date: Wed, 9 Mar 2022 10:28:55 +0000 Subject: [PATCH 022/206] Screenfull and leafletjs additions --- packages/client/yarn.lock | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/client/yarn.lock b/packages/client/yarn.lock index 54d3ae755f..d87edfcc7b 100644 --- a/packages/client/yarn.lock +++ b/packages/client/yarn.lock @@ -684,6 +684,11 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" +leaflet@^1.7.1: + version "1.7.1" + resolved "https://registry.yarnpkg.com/leaflet/-/leaflet-1.7.1.tgz#10d684916edfe1bf41d688a3b97127c0322a2a19" + integrity sha512-/xwPEBidtg69Q3HlqPdU3DnrXQOvQU/CCHA1tcDQVzOwm91YMYaILjNp7L4Eaw5Z4sOYdbBz6koWyibppd8Zqw== + lilconfig@^2.0.3: version "2.0.4" resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082" @@ -1252,6 +1257,11 @@ safe-identifier@^0.4.2: resolved "https://registry.yarnpkg.com/safe-identifier/-/safe-identifier-0.4.2.tgz#cf6bfca31c2897c588092d1750d30ef501d59fcb" integrity sha512-6pNbSMW6OhAi9j+N8V+U715yBQsaWJ7eyEUaOrawX+isg5ZxhUlV1NipNtgaKHmFGiABwt+ZF04Ii+3Xjkg+8w== +screenfull@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/screenfull/-/screenfull-6.0.1.tgz#3b71e6f06b72d817a8d3be73c45ebe71fa8da1ce" + integrity sha512-yzQW+j4zMUBQC51xxWaoDYjxOtl8Kn+xvue3p6v/fv2pIi1jH4AldgVLU8TBfFVgH2x3VXlf3+YiA/AYIPlaew== + serialize-javascript@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-4.0.0.tgz#b525e1238489a5ecfc42afacc3fe99e666f4b1aa" From ce984348a0da93a4c8bf38d9498bc7d46b167b5a Mon Sep 17 00:00:00 2001 From: Martin McKeaveney Date: Wed, 9 Mar 2022 16:08:55 +0100 Subject: [PATCH 023/206] fixing enrichment of relationships in patch call --- packages/server/src/api/controllers/row/internal.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/server/src/api/controllers/row/internal.js b/packages/server/src/api/controllers/row/internal.js index 068a485a8a..9ddd6328b5 100644 --- a/packages/server/src/api/controllers/row/internal.js +++ b/packages/server/src/api/controllers/row/internal.js @@ -83,7 +83,11 @@ exports.patch = async ctx => { const isUserTable = tableId === InternalTables.USER_METADATA let oldRow try { - oldRow = await db.get(inputs._id) + let dbTable = await db.get(tableId) + oldRow = await outputProcessing( + dbTable, + await findRow(ctx, tableId, inputs._id) + ) } catch (err) { if (isUserTable) { // don't include the rev, it'll be the global rev From 792021616cd05b5ce1fbbaf96d934af5b1a0f6b0 Mon Sep 17 00:00:00 2001 From: Maurits Lourens Date: Wed, 9 Mar 2022 17:46:25 +0100 Subject: [PATCH 024/206] fix connection to firebase using service account --- packages/server/src/integrations/firebase.ts | 54 ++++++++++++++------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/packages/server/src/integrations/firebase.ts b/packages/server/src/integrations/firebase.ts index 8bcf198b7d..00a8e227eb 100644 --- a/packages/server/src/integrations/firebase.ts +++ b/packages/server/src/integrations/firebase.ts @@ -11,6 +11,7 @@ module Firebase { email: string privateKey: string projectId: string + serviceAccount?: string } const SCHEMA: Integration = { @@ -31,6 +32,10 @@ module Firebase { type: DatasourceFieldTypes.STRING, required: true, }, + serviceAccount: { + type: DatasourceFieldTypes.JSON, + required: false, + }, }, query: { create: { @@ -86,13 +91,24 @@ module Firebase { constructor(config: FirebaseConfig) { this.config = config - this.db = new Firestore({ - projectId: config.projectId, - credential: { - clientEmail: config.email, - privateKey: config.privateKey, - }, - }) + if (config.serviceAccount) { + const serviceAccount = JSON.parse(config.serviceAccount) + this.db = new Firestore({ + projectId: serviceAccount.project_id, + credentials: { + client_email: serviceAccount.client_email, + private_key: serviceAccount.private_key, + }, + }) + } else { + this.db = new Firestore({ + projectId: config.projectId, + credentials: { + client_email: config.email, + private_key: config.privateKey, + }, + }) + } } async create(query: { json: object; extra: { [key: string]: string } }) { @@ -104,17 +120,21 @@ module Firebase { } } - async read(query: { - field: string - opStr: WhereFilterOp - value: any - extra: { [key: string]: string } - }) { + async read(query: { json: object; extra: { [key: string]: string } }) { try { - const snapshot = await this.db - .collection(query.extra.collection) - .where(query.field, query.opStr, query.value) - .get() + let snapshot + const collectionRef = this.db.collection(query.extra.collection) + if (query.extra.field && query.extra.opStr && query.extra.queryValue) { + snapshot = await collectionRef + .where( + query.extra.field, + query.extra.opStr as WhereFilterOp, + query.extra.value + ) + .get() + } else { + snapshot = await collectionRef.get() + } const result: any[] = [] snapshot.forEach(doc => result.push(doc.data())) From b987dc345fc03aa415f4ee8101ed5be5687bf14d Mon Sep 17 00:00:00 2001 From: Maurits Lourens Date: Thu, 10 Mar 2022 00:23:36 +0100 Subject: [PATCH 025/206] finish up Firebase implementation --- packages/server/src/integrations/firebase.ts | 56 ++++++++++++++------ 1 file changed, 40 insertions(+), 16 deletions(-) diff --git a/packages/server/src/integrations/firebase.ts b/packages/server/src/integrations/firebase.ts index 00a8e227eb..503dae5c95 100644 --- a/packages/server/src/integrations/firebase.ts +++ b/packages/server/src/integrations/firebase.ts @@ -57,8 +57,13 @@ module Firebase { type: DatasourceFieldTypes.STRING, required: true, }, + filterField: { + displayName: "Filter field", + type: DatasourceFieldTypes.STRING, + required: false, + }, filter: { - displayName: "Filter query", + displayName: "Filter comparison", type: DatasourceFieldTypes.LIST, required: false, data: { @@ -77,8 +82,8 @@ module Firebase { ], }, }, - queryValue: { - displayName: "Query value", + filterValue: { + displayName: "Filter value", type: DatasourceFieldTypes.STRING, required: false, }, @@ -113,7 +118,12 @@ module Firebase { async create(query: { json: object; extra: { [key: string]: string } }) { try { - return await this.db.collection(query.extra.collection).add(query.json) + const documentReference = this.db + .collection(query.extra.collection) + .doc() + await documentReference.set({ ...query.json, id: documentReference.id }) + const snapshot = await documentReference.get() + return snapshot.data() } catch (err) { console.error("Error writing to Firestore", err) throw err @@ -124,12 +134,16 @@ module Firebase { try { let snapshot const collectionRef = this.db.collection(query.extra.collection) - if (query.extra.field && query.extra.opStr && query.extra.queryValue) { + if ( + query.extra.filterField && + query.extra.filter && + query.extra.filterValue + ) { snapshot = await collectionRef .where( - query.extra.field, - query.extra.opStr as WhereFilterOp, - query.extra.value + query.extra.filterField, + query.extra.filter as WhereFilterOp, + query.extra.filterValue ) .get() } else { @@ -146,27 +160,37 @@ module Firebase { } async update(query: { - id: string - json: object + json: Record extra: { [key: string]: string } }) { try { - return await this.db + await this.db .collection(query.extra.collection) - .doc(query.id) + .doc(query.json.id) .update(query.json) + + return ( + await this.db + .collection(query.extra.collection) + .doc(query.json.id) + .get() + ).data() } catch (err) { - console.error("Error writing to mongodb", err) + console.error("Error writing to firebase", err) throw err } } - async delete(query: { id: string; extra: { [key: string]: string } }) { + async delete(query: { + json: { id: string } + extra: { [key: string]: string } + }) { try { - return await this.db + await this.db .collection(query.extra.collection) - .doc(query.id) + .doc(query.json.id) .delete() + return true } catch (err) { console.error("Error writing to mongodb", err) throw err From a4fa08fc4d6de38f327d86bfbe9e306c09333ae4 Mon Sep 17 00:00:00 2001 From: Maurits Lourens Date: Thu, 10 Mar 2022 00:34:08 +0100 Subject: [PATCH 026/206] remove inkscape related data from svg --- .../DatasourceNavigator/icons/firebase.svg | 24 ------------------- 1 file changed, 24 deletions(-) diff --git a/packages/builder/src/components/backend/DatasourceNavigator/icons/firebase.svg b/packages/builder/src/components/backend/DatasourceNavigator/icons/firebase.svg index dc569606ad..583b90f5c3 100644 --- a/packages/builder/src/components/backend/DatasourceNavigator/icons/firebase.svg +++ b/packages/builder/src/components/backend/DatasourceNavigator/icons/firebase.svg @@ -3,32 +3,8 @@ viewBox="23 6 469 132" width="100" height="100" - version="1.1" - id="svg216" - sodipodi:docname="firebase.svg" - inkscape:version="1.1.2 (b8e25be8, 2022-02-05)" - xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape" - xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd" xmlns="http://www.w3.org/2000/svg" xmlns:svg="http://www.w3.org/2000/svg"> - Date: Thu, 10 Mar 2022 09:07:26 +0000 Subject: [PATCH 027/206] Corrected label for location option --- packages/client/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index c7f25f8270..51138f7a2d 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2497,7 +2497,7 @@ }, { "type": "boolean", - "label": "Enabled Location", + "label": "Enable Location", "key": "locationEnabled", "defaultValue": false }, From 0b7919cd3d8029a572e59e049af6b0fbdc2b7bb1 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 10 Mar 2022 10:18:03 +0000 Subject: [PATCH 028/206] Update date filtering to only use a between statement when 2 dates are provided --- packages/server/src/integrations/base/sql.ts | 23 ++++++++++++-------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/packages/server/src/integrations/base/sql.ts b/packages/server/src/integrations/base/sql.ts index ffa405f016..f4ebc02098 100644 --- a/packages/server/src/integrations/base/sql.ts +++ b/packages/server/src/integrations/base/sql.ts @@ -27,11 +27,8 @@ function parse(input: any) { if (typeof input !== "string") { return input } - if (input === MAX_ISO_DATE) { - return new Date(8640000000000000) - } - if (input === MIN_ISO_DATE) { - return new Date(-8640000000000000) + if (input === MAX_ISO_DATE || input === MIN_ISO_DATE) { + return null } if (isIsoDateString(input)) { return new Date(input) @@ -130,11 +127,19 @@ class InternalBuilder { } if (filters.range) { iterate(filters.range, (key, value) => { - if (!value.high || !value.low) { - return + if (value.low && value.high) { + // Use a between operator if we have 2 valid range values + const fnc = allOr ? "orWhereBetween" : "whereBetween" + query = query[fnc](key, [value.low, value.high]) + } else if (value.low) { + // Use just a single greater than operator if we only have a low + const fnc = allOr ? "orWhere" : "where" + query = query[fnc](key, ">", value.low) + } else if (value.high) { + // Use just a single less than operator if we only have a high + const fnc = allOr ? "orWhere" : "where" + query = query[fnc](key, "<", value.high) } - const fnc = allOr ? "orWhereBetween" : "whereBetween" - query = query[fnc](key, [value.low, value.high]) }) } if (filters.equal) { From 42cbdcd92354ca7fa24cc00c0c54ae9fd837f81b Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 10 Mar 2022 11:54:15 +0000 Subject: [PATCH 029/206] Add css print styles to ensure multi-page print is possible --- packages/client/src/components/ClientApp.svelte | 8 ++++++++ packages/client/src/components/app/Layout.svelte | 16 ++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte index e8ed7e9538..9a14f72ec8 100644 --- a/packages/client/src/components/ClientApp.svelte +++ b/packages/client/src/components/ClientApp.svelte @@ -226,4 +226,12 @@ border: 1px solid var(--spectrum-global-color-gray-300); border-radius: 4px; } + + @media print { + #spectrum-root, + #clip-root, + #app-root { + overflow: visible !important; + } + } diff --git a/packages/client/src/components/app/Layout.svelte b/packages/client/src/components/app/Layout.svelte index 600d295cba..a801ea4b46 100644 --- a/packages/client/src/components/app/Layout.svelte +++ b/packages/client/src/components/app/Layout.svelte @@ -427,4 +427,20 @@ height: var(--height); z-index: 998; } + + /* Print styles */ + @media print { + .layout, + .main-wrapper { + overflow: visible !important; + } + .nav-wrapper { + display: none !important; + } + .layout { + flex-direction: column !important; + justify-content: flex-start !important; + align-items: stretch !important; + } + } From c4a35021876e9e7b4fd8d838179b5042a0e8fccc Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Thu, 10 Mar 2022 11:56:31 +0000 Subject: [PATCH 030/206] Add comment for consistency --- packages/client/src/components/ClientApp.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/src/components/ClientApp.svelte b/packages/client/src/components/ClientApp.svelte index 9a14f72ec8..27767862ab 100644 --- a/packages/client/src/components/ClientApp.svelte +++ b/packages/client/src/components/ClientApp.svelte @@ -227,6 +227,7 @@ border-radius: 4px; } + /* Print styles */ @media print { #spectrum-root, #clip-root, From b196ca10af20c661a9648fef1f3fb914141a7299 Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 10 Mar 2022 12:48:49 +0000 Subject: [PATCH 031/206] Added the default location behaviour back in. Added in a Map Attribution field for users who alter their tile url. Some refactoring based on feedback --- packages/client/manifest.json | 14 +++- .../app/{ => embedded-map}/EmbeddedMap.svelte | 71 ++++++++++++------- .../{ => embedded-map}/EmbeddedMapControls.js | 0 packages/client/src/components/app/index.js | 2 +- 4 files changed, 61 insertions(+), 26 deletions(-) rename packages/client/src/components/app/{ => embedded-map}/EmbeddedMap.svelte (83%) rename packages/client/src/components/app/{ => embedded-map}/EmbeddedMapControls.js (100%) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 51138f7a2d..fff56f595d 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2499,7 +2499,7 @@ "type": "boolean", "label": "Enable Location", "key": "locationEnabled", - "defaultValue": false + "defaultValue": true }, { "type": "boolean", @@ -2535,6 +2535,18 @@ "label": "Tile URL", "key": "tileURL", "defaultValue": "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" + }, + { + "type": "text", + "label": "Default Location (lat,lng)", + "key": "defaultLocation", + "defaultValue": "51.5072,-0.1276" + }, + { + "type": "text", + "label": "Map Attribution", + "key": "mapAttribution", + "defaultValue": "OpenStreetMap contributors" } ] }, diff --git a/packages/client/src/components/app/EmbeddedMap.svelte b/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte similarity index 83% rename from packages/client/src/components/app/EmbeddedMap.svelte rename to packages/client/src/components/app/embedded-map/EmbeddedMap.svelte index 63b98ba651..642d22c009 100644 --- a/packages/client/src/components/app/EmbeddedMap.svelte +++ b/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte @@ -20,13 +20,16 @@ export let titleKey = null export let fullScreenEnabled = true export let locationEnabled = true + export let defaultLocation export let tileURL = "https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png" + export let mapAttribution const { styleable, notificationStore } = getContext("sdk") const component = getContext("component") const embeddedMapId = `${Helpers.uuid()}-wrapper` let cachedDeviceCoordinates + const fallbackCoordinates = [51.5072, -0.1276] //London let mapInstance let mapMarkerGroup = new L.FeatureGroup() @@ -38,6 +41,14 @@ ? 72 : Math.round(zoomLevel * (maxZoomLevel / 100)) + $: zoomControlUpdated(mapInstance, zoomEnabled) + $: locationControlUpdated(mapInstance, locationEnabled) + $: fullScreenControlUpdated(mapInstance, fullScreenEnabled) + $: updateMapDimensions( + mapInstance, + $component.styles.normal.width, + $component.styles.normal.height + ) $: addMapMarkers( mapInstance, dataProvider?.rows, @@ -47,16 +58,10 @@ ) $: if (typeof mapInstance === "object" && mapMarkers.length > 0) { mapInstance.setZoom(0) - mapInstance.fitBounds(mapMarkerGroup.getBounds()) + mapInstance.fitBounds(mapMarkerGroup.getBounds(), { + paddingTopLeft: [0, 24], + }) } - $: zoomControlUpdated(mapInstance, zoomEnabled) - $: locationControlUpdated(mapInstance, locationEnabled) - $: fullScreenControlUpdated(mapInstance, fullScreenEnabled) - $: updateMapDimensions( - mapInstance, - $component.styles.normal.width, - $component.styles.normal.height - ) const updateMapDimensions = mapInstance => { if (typeof mapInstance !== "object") { @@ -65,6 +70,37 @@ mapInstance.invalidateSize() } + let isValidLatitude = value => { + return !isNaN(value) && value > -90 && value < 90 + } + + let isValidLongitude = value => { + return !isNaN(value) && value > -180 && value < 180 + } + + const parseDefaultLocation = defaultLocation => { + if (typeof defaultLocation !== "string") { + return fallbackCoordinates + } + let defaultLocationParts = defaultLocation.split(",") + if (defaultLocationParts.length !== 2) { + return fallbackCoordinates + } + + let parsedDefaultLatitude = parseFloat(defaultLocationParts[0].trim()) + let parsedDefaultLongitude = parseFloat(defaultLocationParts[1].trim()) + + return isValidLatitude(parsedDefaultLatitude) === true && + isValidLongitude(parsedDefaultLongitude) === true + ? [parsedDefaultLatitude, parsedDefaultLongitude] + : fallbackCoordinates + } + + $: defaultCoordinates = + mapMarkers.length > 0 + ? parseDefaultLocation(defaultLocation) + : fallbackCoordinates + // Map Button Controls let locationControl = new LocationControl({ position: "bottomright", @@ -165,14 +201,6 @@ mapMarkerGroup.clearLayers() - let isValidLatitude = value => { - return !isNaN(value) && value > -90 && value < 90 - } - - let isValidLongitude = value => { - return !isNaN(value) && value > -180 && value < 180 - } - const validRows = rows.filter(row => { return isValidLatitude(row[latKey]) && isValidLongitude(row[lngKey]) }) @@ -203,14 +231,13 @@ } const initMap = () => { - const initCoords = [51.5072, -0.1276] + const initCoords = defaultCoordinates mapInstance = L.map(embeddedMapId, mapOptions) mapMarkerGroup.addTo(mapInstance) L.tileLayer(tileURL, { - attribution: - '© OpenStreetMap contributors', + attribution: "© " + mapAttribution, //No attribution, warning? zoom: adjustedZoomLevel, }).addTo(mapInstance) @@ -259,8 +286,4 @@ height: 100%; width: 100%; } - :global(.leaflet-tile) { - width: 258px !important; - height: 258px !important; - } diff --git a/packages/client/src/components/app/EmbeddedMapControls.js b/packages/client/src/components/app/embedded-map/EmbeddedMapControls.js similarity index 100% rename from packages/client/src/components/app/EmbeddedMapControls.js rename to packages/client/src/components/app/embedded-map/EmbeddedMapControls.js diff --git a/packages/client/src/components/app/index.js b/packages/client/src/components/app/index.js index e23659620b..db8cc43ef6 100644 --- a/packages/client/src/components/app/index.js +++ b/packages/client/src/components/app/index.js @@ -31,7 +31,7 @@ export { default as cardstat } from "./CardStat.svelte" export { default as spectrumcard } from "./SpectrumCard.svelte" export { default as tag } from "./Tag.svelte" export { default as markdownviewer } from "./MarkdownViewer.svelte" -export { default as embeddedmap } from "./EmbeddedMap.svelte" +export { default as embeddedmap } from "./embedded-map/EmbeddedMap.svelte" export * from "./charts" export * from "./forms" export * from "./table" From b5a36de5bd5f61385ac7e74fc2390aec7f03f0fe Mon Sep 17 00:00:00 2001 From: Dean Date: Thu, 10 Mar 2022 15:48:11 +0000 Subject: [PATCH 032/206] Added fix to accomodate a cleared attribution field --- .../client/src/components/app/embedded-map/EmbeddedMap.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte b/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte index 642d22c009..692735e9a9 100644 --- a/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte +++ b/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte @@ -237,7 +237,7 @@ mapMarkerGroup.addTo(mapInstance) L.tileLayer(tileURL, { - attribution: "© " + mapAttribution, //No attribution, warning? + attribution: "© " + mapAttribution || "", zoom: adjustedZoomLevel, }).addTo(mapInstance) From 39468c98c0d66e501f455c709d66039db72f8d99 Mon Sep 17 00:00:00 2001 From: Dean Date: Fri, 11 Mar 2022 11:06:05 +0000 Subject: [PATCH 033/206] Added html escaping package sanitize-html to prevent injection via the map attribution field --- .../src/components/app/embedded-map/EmbeddedMap.svelte | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte b/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte index 692735e9a9..b9fb89f8cb 100644 --- a/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte +++ b/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte @@ -1,5 +1,6 @@
@@ -300,7 +351,14 @@
{error}
{/if} -
+
+ + {#if candidateMarkerPosition} +
+ + +
+ {/if}
diff --git a/packages/client/src/stores/confirmation.js b/packages/client/src/stores/confirmation.js index 497b021b04..bb9a54386f 100644 --- a/packages/client/src/stores/confirmation.js +++ b/packages/client/src/stores/confirmation.js @@ -4,30 +4,36 @@ const initialState = { showConfirmation: false, title: null, text: null, - callback: null, + onConfirm: null, + onCancel: null, } const createConfirmationStore = () => { const store = writable(initialState) - const showConfirmation = (title, text, callback) => { + const showConfirmation = (title, text, onConfirm, onCancel) => { store.set({ showConfirmation: true, title, text, - callback, + onConfirm, + onCancel, }) } const confirm = async () => { const state = get(store) - if (!state.showConfirmation || !state.callback) { + if (!state.showConfirmation || !state.onConfirm) { return } store.set(initialState) - await state.callback() + await state.onConfirm() } const cancel = () => { + const state = get(store) store.set(initialState) + if (state.onCancel) { + state.onCancel() + } } return { diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index d880455af0..d9d4dced71 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -331,33 +331,36 @@ export const enrichButtonActions = (actions, context) => { // If this action is confirmable, show confirmation and await a // callback to execute further actions if (action.parameters?.confirm) { - const defaultText = confirmTextMap[action["##eventHandlerType"]] - const confirmText = action.parameters?.confirmText || defaultText - confirmationStore.actions.showConfirmation( - action["##eventHandlerType"], - confirmText, - async () => { - // When confirmed, execute this action immediately, - // then execute the rest of the actions in the chain - const result = await callback() - if (result !== false) { - // Generate a new total context to pass into the next enrichment - buttonContext.push(result) - const newContext = { ...context, actions: buttonContext } + return new Promise(resolve => { + const defaultText = confirmTextMap[action["##eventHandlerType"]] + const confirmText = action.parameters?.confirmText || defaultText + confirmationStore.actions.showConfirmation( + action["##eventHandlerType"], + confirmText, + async () => { + // When confirmed, execute this action immediately, + // then execute the rest of the actions in the chain + const result = await callback() + if (result !== false) { + // Generate a new total context to pass into the next enrichment + buttonContext.push(result) + const newContext = { ...context, actions: buttonContext } - // Enrich and call the next button action - const next = enrichButtonActions( - actions.slice(i + 1), - newContext - ) - await next() + // Enrich and call the next button action + const next = enrichButtonActions( + actions.slice(i + 1), + newContext + ) + resolve(await next()) + } else { + resolve(false) + } + }, + () => { + resolve(false) } - } - ) - - // Stop enriching actions when encountering a confirmable action, - // as the callback continues the action chain - return + ) + }) } // For non-confirmable actions, execute the handler immediately From 4cf9d8612a450e61d0baf26eccb6d354e81b3906 Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 16 Mar 2022 14:58:48 +0000 Subject: [PATCH 057/206] Update some settings text to be more consistent --- packages/client/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/client/manifest.json b/packages/client/manifest.json index 6c8ed8cbcc..ddea782ef0 100644 --- a/packages/client/manifest.json +++ b/packages/client/manifest.json @@ -2557,7 +2557,7 @@ }, { "type": "event", - "label": "On marker click", + "label": "On Click Marker", "key": "onClickMarker", "context": [ { @@ -2574,7 +2574,7 @@ }, { "type": "event", - "label": "On create marker", + "label": "On Create Marker", "key": "onCreateMarker", "dependsOn": "creationEnabled", "context": [ From d03dc54efd78751266f35aac1f59b0094c51b6ed Mon Sep 17 00:00:00 2001 From: Andrew Kingston Date: Wed, 16 Mar 2022 15:03:13 +0000 Subject: [PATCH 058/206] Update default height of map component --- .../client/src/components/app/embedded-map/EmbeddedMap.svelte | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte b/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte index b7eb516aa8..a6803ef5dd 100644 --- a/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte +++ b/packages/client/src/components/app/embedded-map/EmbeddedMap.svelte @@ -364,6 +364,7 @@ diff --git a/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte b/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte index a5045d8e9f..12282323ee 100644 --- a/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte +++ b/packages/builder/src/components/design/NavigationPanel/FrontendNavigatePane.svelte @@ -1,5 +1,5 @@ - -
-