Merge pull request #15499 from Budibase/feature/pre-empt-data-source-deletion
Data section resource deletion warning
This commit is contained in:
commit
b4086e1187
|
@ -83,11 +83,15 @@ export function isViewId(id: string): boolean {
|
|||
/**
|
||||
* Check if a given ID is that of a datasource or datasource plus.
|
||||
*/
|
||||
export const isDatasourceId = (id: string): boolean => {
|
||||
export function isDatasourceId(id: string): boolean {
|
||||
// this covers both datasources and datasource plus
|
||||
return !!id && id.startsWith(`${DocumentType.DATASOURCE}${SEPARATOR}`)
|
||||
}
|
||||
|
||||
export function isQueryId(id: string): boolean {
|
||||
return !!id && id.startsWith(`${DocumentType.QUERY}${SEPARATOR}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets parameters for retrieving workspaces.
|
||||
*/
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
<script lang="ts">
|
||||
import "@spectrum-css/textfield/dist/index-vars.css"
|
||||
import { createEventDispatcher, onMount, tick } from "svelte"
|
||||
import type { UIEvent } from "@budibase/types"
|
||||
|
||||
export let value = null
|
||||
export let value: string | null = null
|
||||
export let placeholder: string | undefined = undefined
|
||||
export let type = "text"
|
||||
export let disabled = false
|
||||
|
@ -11,7 +12,7 @@
|
|||
export let updateOnChange = true
|
||||
export let quiet = false
|
||||
export let align: "left" | "right" | "center" | undefined = undefined
|
||||
export let autofocus = false
|
||||
export let autofocus: boolean | null = false
|
||||
export let autocomplete: boolean | undefined
|
||||
|
||||
const dispatch = createEventDispatcher()
|
||||
|
@ -24,7 +25,7 @@
|
|||
return
|
||||
}
|
||||
if (type === "number") {
|
||||
const float = parseFloat(newValue)
|
||||
const float = parseFloat(newValue as string)
|
||||
newValue = isNaN(float) ? null : float
|
||||
}
|
||||
dispatch("change", newValue)
|
||||
|
@ -37,31 +38,31 @@
|
|||
focus = true
|
||||
}
|
||||
|
||||
const onBlur = (event: any) => {
|
||||
const onBlur = (event: UIEvent) => {
|
||||
if (readonly || disabled) {
|
||||
return
|
||||
}
|
||||
focus = false
|
||||
updateValue(event.target.value)
|
||||
updateValue(event?.target?.value)
|
||||
}
|
||||
|
||||
const onInput = (event: any) => {
|
||||
const onInput = (event: UIEvent) => {
|
||||
if (readonly || !updateOnChange || disabled) {
|
||||
return
|
||||
}
|
||||
updateValue(event.target.value)
|
||||
updateValue(event.target?.value)
|
||||
}
|
||||
|
||||
const updateValueOnEnter = (event: any) => {
|
||||
const updateValueOnEnter = (event: UIEvent) => {
|
||||
if (readonly || disabled) {
|
||||
return
|
||||
}
|
||||
if (event.key === "Enter") {
|
||||
updateValue(event.target.value)
|
||||
updateValue(event.target?.value)
|
||||
}
|
||||
}
|
||||
|
||||
const getInputMode = (type: any) => {
|
||||
const getInputMode = (type: string) => {
|
||||
if (type === "bigint") {
|
||||
return "numeric"
|
||||
}
|
||||
|
@ -77,7 +78,7 @@
|
|||
|
||||
onMount(async () => {
|
||||
if (disabled) return
|
||||
focus = autofocus
|
||||
focus = autofocus || false
|
||||
if (focus) {
|
||||
await tick()
|
||||
field.focus()
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
import IntegrationIcon from "@/components/backend/DatasourceNavigator/IntegrationIcon.svelte"
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import UpdateDatasourceModal from "@/components/backend/DatasourceNavigator/modals/UpdateDatasourceModal.svelte"
|
||||
import DeleteConfirmationModal from "./DeleteConfirmationModal.svelte"
|
||||
import DeleteDataConfirmModal from "@/components/backend/modals/DeleteDataConfirmationModal.svelte"
|
||||
|
||||
export let datasource
|
||||
|
||||
|
@ -71,7 +71,10 @@
|
|||
{/if}
|
||||
</NavItem>
|
||||
<UpdateDatasourceModal {datasource} bind:this={editModal} />
|
||||
<DeleteConfirmationModal {datasource} bind:this={deleteConfirmationModal} />
|
||||
<DeleteDataConfirmModal
|
||||
source={datasource}
|
||||
bind:this={deleteConfirmationModal}
|
||||
/>
|
||||
|
||||
<style>
|
||||
.datasource-icon {
|
||||
|
|
|
@ -1,37 +0,0 @@
|
|||
<script>
|
||||
import { goto } from "@roxi/routify"
|
||||
import { datasources } from "@/stores/builder"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
import ConfirmDialog from "@/components/common/ConfirmDialog.svelte"
|
||||
|
||||
export let datasource
|
||||
|
||||
let confirmDeleteDialog
|
||||
|
||||
export const show = () => {
|
||||
confirmDeleteDialog.show()
|
||||
}
|
||||
|
||||
async function deleteDatasource() {
|
||||
try {
|
||||
const isSelected = datasource.selected || datasource.containsSelected
|
||||
await datasources.delete(datasource)
|
||||
notifications.success("Datasource deleted")
|
||||
if (isSelected) {
|
||||
$goto("./datasource")
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting datasource")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
okText="Delete Datasource"
|
||||
onOk={deleteDatasource}
|
||||
title="Confirm Deletion"
|
||||
>
|
||||
Are you sure you wish to delete the datasource
|
||||
<i>{datasource.name}</i>? This action cannot be undone.
|
||||
</ConfirmDialog>
|
|
@ -6,19 +6,18 @@
|
|||
} from "@/helpers/data/utils"
|
||||
import { goto as gotoStore, isActive } from "@roxi/routify"
|
||||
import {
|
||||
datasources,
|
||||
queries,
|
||||
userSelectedResourceMap,
|
||||
contextMenuStore,
|
||||
} from "@/stores/builder"
|
||||
import NavItem from "@/components/common/NavItem.svelte"
|
||||
import ConfirmDialog from "@/components/common/ConfirmDialog.svelte"
|
||||
import DeleteDataConfirmModal from "@/components/backend/modals/DeleteDataConfirmationModal.svelte"
|
||||
import { notifications, Icon } from "@budibase/bbui"
|
||||
|
||||
export let datasource
|
||||
export let query
|
||||
|
||||
let confirmDeleteDialog
|
||||
let confirmDeleteModal
|
||||
|
||||
// goto won't work in the context menu callback if the store is called directly
|
||||
$: goto = $gotoStore
|
||||
|
@ -31,7 +30,7 @@
|
|||
keyBind: null,
|
||||
visible: true,
|
||||
disabled: false,
|
||||
callback: confirmDeleteDialog.show,
|
||||
callback: confirmDeleteModal.show,
|
||||
},
|
||||
{
|
||||
icon: "Duplicate",
|
||||
|
@ -51,20 +50,6 @@
|
|||
]
|
||||
}
|
||||
|
||||
async function deleteQuery() {
|
||||
try {
|
||||
// Go back to the datasource if we are deleting the active query
|
||||
if ($queries.selectedQueryId === query._id) {
|
||||
goto(`./datasource/${query.datasourceId}`)
|
||||
}
|
||||
await queries.delete(query)
|
||||
await datasources.fetch()
|
||||
notifications.success("Query deleted")
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting query")
|
||||
}
|
||||
}
|
||||
|
||||
const openContextMenu = e => {
|
||||
e.preventDefault()
|
||||
e.stopPropagation()
|
||||
|
@ -90,14 +75,7 @@
|
|||
<Icon size="S" hoverable name="MoreSmallList" on:click={openContextMenu} />
|
||||
</NavItem>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
okText="Delete Query"
|
||||
onOk={deleteQuery}
|
||||
title="Confirm Deletion"
|
||||
>
|
||||
Are you sure you wish to delete this query? This action cannot be undone.
|
||||
</ConfirmDialog>
|
||||
<DeleteDataConfirmModal source={query} bind:this={confirmDeleteModal} />
|
||||
|
||||
<style>
|
||||
</style>
|
||||
|
|
|
@ -1,175 +0,0 @@
|
|||
<script>
|
||||
import { goto, params } from "@roxi/routify"
|
||||
import { appStore, tables, datasources, screenStore } from "@/stores/builder"
|
||||
import { InlineAlert, Link, Input, notifications } from "@budibase/bbui"
|
||||
import ConfirmDialog from "@/components/common/ConfirmDialog.svelte"
|
||||
import { DB_TYPE_EXTERNAL } from "@/constants/backend"
|
||||
|
||||
export let table
|
||||
|
||||
let confirmDeleteDialog
|
||||
|
||||
let screensPossiblyAffected = []
|
||||
let viewsMessage = ""
|
||||
let deleteTableName
|
||||
|
||||
const getViewsMessage = () => {
|
||||
const views = Object.values(table?.views ?? [])
|
||||
if (views.length < 1) {
|
||||
return ""
|
||||
}
|
||||
if (views.length === 1) {
|
||||
return ", including 1 view"
|
||||
}
|
||||
|
||||
return `, including ${views.length} views`
|
||||
}
|
||||
|
||||
export const show = () => {
|
||||
viewsMessage = getViewsMessage()
|
||||
screensPossiblyAffected = $screenStore.screens
|
||||
.filter(
|
||||
screen => screen.autoTableId === table._id && screen.routing?.route
|
||||
)
|
||||
.map(screen => ({
|
||||
text: screen.routing.route,
|
||||
url: `/builder/app/${$appStore.appId}/design/${screen._id}`,
|
||||
}))
|
||||
|
||||
confirmDeleteDialog.show()
|
||||
}
|
||||
|
||||
async function deleteTable() {
|
||||
const isSelected = $params.tableId === table._id
|
||||
try {
|
||||
await tables.delete(table)
|
||||
|
||||
if (table.sourceType === DB_TYPE_EXTERNAL) {
|
||||
await datasources.fetch()
|
||||
}
|
||||
notifications.success("Table deleted")
|
||||
if (isSelected) {
|
||||
$goto(`./datasource/${table.datasourceId}`)
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error(`Error deleting table - ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
function hideDeleteDialog() {
|
||||
deleteTableName = ""
|
||||
}
|
||||
|
||||
const autofillTableName = () => {
|
||||
deleteTableName = table.name
|
||||
}
|
||||
</script>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
okText="Delete Table"
|
||||
onOk={deleteTable}
|
||||
onCancel={hideDeleteDialog}
|
||||
title="Confirm Deletion"
|
||||
disabled={deleteTableName !== table.name}
|
||||
>
|
||||
<div class="content">
|
||||
<p class="firstWarning">
|
||||
Are you sure you wish to delete the table
|
||||
<span class="tableNameLine">
|
||||
<!-- svelte-ignore a11y-click-events-have-key-events -->
|
||||
<!-- svelte-ignore a11y-no-static-element-interactions -->
|
||||
<b on:click={autofillTableName} class="tableName">{table.name}</b>
|
||||
<span>?</span>
|
||||
</span>
|
||||
</p>
|
||||
|
||||
<p class="secondWarning">All table data will be deleted{viewsMessage}.</p>
|
||||
<p class="thirdWarning">This action <b>cannot be undone</b>.</p>
|
||||
|
||||
{#if screensPossiblyAffected.length > 0}
|
||||
<div class="affectedScreens">
|
||||
<InlineAlert
|
||||
header="The following screens were originally generated from this table and may no longer function as expected"
|
||||
>
|
||||
<ul class="affectedScreensList">
|
||||
{#each screensPossiblyAffected as item}
|
||||
<li>
|
||||
<Link quiet overBackground target="_blank" href={item.url}
|
||||
>{item.text}</Link
|
||||
>
|
||||
</li>
|
||||
{/each}
|
||||
</ul>
|
||||
</InlineAlert>
|
||||
</div>
|
||||
{/if}
|
||||
<p class="fourthWarning">Please enter the table name below to confirm.</p>
|
||||
<Input bind:value={deleteTableName} placeholder={table.name} />
|
||||
</div>
|
||||
</ConfirmDialog>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
margin-top: 0;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.firstWarning {
|
||||
margin: 0 0 12px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.tableNameLine {
|
||||
display: inline-flex;
|
||||
max-width: 100%;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.tableName {
|
||||
flex-grow: 1;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.secondWarning {
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.thirdWarning {
|
||||
margin: 0 0 12px;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.affectedScreens {
|
||||
margin: 18px 0;
|
||||
max-width: 100%;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.affectedScreens :global(.spectrum-InLineAlert) {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.affectedScreensList {
|
||||
padding: 0;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.affectedScreensList li {
|
||||
display: block;
|
||||
max-width: 100%;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.fourthWarning {
|
||||
margin: 12px 0 6px;
|
||||
max-width: 100%;
|
||||
}
|
||||
</style>
|
|
@ -8,7 +8,7 @@
|
|||
import NavItem from "@/components/common/NavItem.svelte"
|
||||
import { isActive } from "@roxi/routify"
|
||||
import EditModal from "./EditModal.svelte"
|
||||
import DeleteConfirmationModal from "./DeleteConfirmationModal.svelte"
|
||||
import DeleteConfirmationModal from "../../modals/DeleteDataConfirmationModal.svelte"
|
||||
import { Icon } from "@budibase/bbui"
|
||||
import { DB_TYPE_EXTERNAL } from "@/constants/backend"
|
||||
|
||||
|
@ -65,4 +65,4 @@
|
|||
{/if}
|
||||
</NavItem>
|
||||
<EditModal {table} bind:this={editModal} />
|
||||
<DeleteConfirmationModal {table} bind:this={deleteConfirmationModal} />
|
||||
<DeleteConfirmationModal source={table} bind:this={deleteConfirmationModal} />
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
<script lang="ts">
|
||||
import { Link, notifications } from "@budibase/bbui"
|
||||
import {
|
||||
appStore,
|
||||
datasources,
|
||||
queries,
|
||||
screenStore,
|
||||
tables,
|
||||
views,
|
||||
viewsV2,
|
||||
} from "@/stores/builder"
|
||||
import ConfirmDialog from "@/components/common/ConfirmDialog.svelte"
|
||||
import { helpers, utils } from "@budibase/shared-core"
|
||||
import { SourceType } from "@budibase/types"
|
||||
import { goto, params } from "@roxi/routify"
|
||||
import { DB_TYPE_EXTERNAL } from "@/constants/backend"
|
||||
import { get } from "svelte/store"
|
||||
import type { Table, ViewV2, View, Datasource, Query } from "@budibase/types"
|
||||
|
||||
export let source: Table | ViewV2 | Datasource | Query | undefined
|
||||
|
||||
let confirmDeleteDialog: any
|
||||
let affectedScreens: { text: string; url: string }[] = []
|
||||
let sourceType: SourceType | undefined = undefined
|
||||
|
||||
const getDatasourceQueries = () => {
|
||||
if (sourceType !== SourceType.DATASOURCE) {
|
||||
return ""
|
||||
}
|
||||
const sourceId = getSourceID()
|
||||
const queryList = get(queries).list.filter(
|
||||
query => query.datasourceId === sourceId
|
||||
)
|
||||
return queryList
|
||||
}
|
||||
|
||||
function getSourceID(): string {
|
||||
if (!source) {
|
||||
throw new Error("No data source provided.")
|
||||
}
|
||||
if ("id" in source) {
|
||||
return source.id
|
||||
}
|
||||
return source._id!
|
||||
}
|
||||
|
||||
export const show = async () => {
|
||||
const usage = await screenStore.usageInScreens(getSourceID())
|
||||
affectedScreens = processScreens(usage.screens)
|
||||
sourceType = usage.sourceType
|
||||
confirmDeleteDialog.show()
|
||||
}
|
||||
|
||||
function processScreens(
|
||||
screens: { url: string; _id: string }[]
|
||||
): { text: string; url: string }[] {
|
||||
return screens.map(({ url, _id }) => ({
|
||||
text: url,
|
||||
url: `/builder/app/${$appStore.appId}/design/${_id}`,
|
||||
}))
|
||||
}
|
||||
|
||||
function hideDeleteDialog() {
|
||||
sourceType = undefined
|
||||
}
|
||||
|
||||
async function deleteTable(table: Table & { datasourceId?: string }) {
|
||||
const isSelected = $params.tableId === table._id
|
||||
try {
|
||||
await tables.delete({
|
||||
_id: table._id!,
|
||||
_rev: table._rev!,
|
||||
})
|
||||
|
||||
if (table.sourceType === DB_TYPE_EXTERNAL) {
|
||||
await datasources.fetch()
|
||||
}
|
||||
notifications.success("Table deleted")
|
||||
if (isSelected) {
|
||||
$goto(`./datasource/${table.datasourceId}`)
|
||||
}
|
||||
} catch (error: any) {
|
||||
notifications.error(`Error deleting table - ${error.message}`)
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteView(view: ViewV2 | View) {
|
||||
try {
|
||||
if (helpers.views.isV2(view)) {
|
||||
await viewsV2.delete(view as ViewV2)
|
||||
} else {
|
||||
await views.delete(view as View)
|
||||
}
|
||||
notifications.success("View deleted")
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting view")
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteDatasource(datasource: Datasource) {
|
||||
try {
|
||||
await datasources.delete(datasource)
|
||||
notifications.success("Datasource deleted")
|
||||
const isSelected =
|
||||
get(datasources).selectedDatasourceId === datasource._id
|
||||
if (isSelected) {
|
||||
$goto("./datasource")
|
||||
}
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting datasource")
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteQuery(query: Query) {
|
||||
try {
|
||||
// Go back to the datasource if we are deleting the active query
|
||||
if ($queries.selectedQueryId === query._id) {
|
||||
$goto(`./datasource/${query.datasourceId}`)
|
||||
}
|
||||
await queries.delete(query)
|
||||
await datasources.fetch()
|
||||
notifications.success("Query deleted")
|
||||
} catch (error) {
|
||||
notifications.error("Error deleting query")
|
||||
}
|
||||
}
|
||||
|
||||
async function deleteSource() {
|
||||
if (!source || !sourceType) {
|
||||
throw new Error("Unable to delete - no data source found.")
|
||||
}
|
||||
|
||||
switch (sourceType) {
|
||||
case SourceType.TABLE:
|
||||
return await deleteTable(source as Table)
|
||||
case SourceType.VIEW:
|
||||
return await deleteView(source as ViewV2)
|
||||
case SourceType.QUERY:
|
||||
return await deleteQuery(source as Query)
|
||||
case SourceType.DATASOURCE:
|
||||
return await deleteDatasource(source as Datasource)
|
||||
default:
|
||||
utils.unreachable(sourceType)
|
||||
}
|
||||
}
|
||||
|
||||
function buildMessage(sourceType: string) {
|
||||
if (!source) {
|
||||
return ""
|
||||
}
|
||||
const screenCount = affectedScreens.length
|
||||
let message = `Removing ${source?.name} `
|
||||
let initialLength = message.length
|
||||
if (sourceType === SourceType.TABLE) {
|
||||
const views = "views" in source ? Object.values(source?.views ?? []) : []
|
||||
message += `will delete its data${
|
||||
views.length
|
||||
? `${screenCount ? "," : " and"} views (${views.length})`
|
||||
: ""
|
||||
}`
|
||||
} else if (sourceType === SourceType.DATASOURCE) {
|
||||
const queryList = getDatasourceQueries()
|
||||
if (queryList.length) {
|
||||
message += `will delete its queries (${queryList.length})`
|
||||
}
|
||||
}
|
||||
if (screenCount) {
|
||||
message +=
|
||||
initialLength !== message.length
|
||||
? ", and break connected screens:"
|
||||
: "will break connected screens:"
|
||||
} else {
|
||||
message += "."
|
||||
}
|
||||
return message.length !== initialLength ? message : ""
|
||||
}
|
||||
</script>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
okText="Delete"
|
||||
onOk={deleteSource}
|
||||
onCancel={hideDeleteDialog}
|
||||
title={`Are you sure you want to delete this ${sourceType}?`}
|
||||
>
|
||||
<div class="content">
|
||||
{#if sourceType}
|
||||
<p class="warning">
|
||||
{buildMessage(sourceType)}
|
||||
{#if affectedScreens.length > 0}
|
||||
<span class="screens">
|
||||
{#each affectedScreens as item, idx}
|
||||
<Link overBackground target="_blank" href={item.url}
|
||||
>{item.text}{idx !== affectedScreens.length - 1
|
||||
? ","
|
||||
: ""}</Link
|
||||
>
|
||||
{/each}
|
||||
</span>
|
||||
{/if}
|
||||
</p>
|
||||
{/if}
|
||||
<p class="warning">
|
||||
<b>This action cannot be undone.</b>
|
||||
</p>
|
||||
</div>
|
||||
</ConfirmDialog>
|
||||
|
||||
<style>
|
||||
.content {
|
||||
margin-top: 0;
|
||||
max-width: 320px;
|
||||
}
|
||||
|
||||
.warning {
|
||||
margin: 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.screens {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding-bottom: var(--spacing-l);
|
||||
gap: var(--spacing-xs);
|
||||
}
|
||||
</style>
|
|
@ -8,7 +8,7 @@
|
|||
export let onOk = undefined
|
||||
export let onCancel = undefined
|
||||
export let warning = true
|
||||
export let disabled
|
||||
export let disabled = false
|
||||
|
||||
let modal
|
||||
|
||||
|
|
|
@ -1,35 +0,0 @@
|
|||
<script>
|
||||
import { views, viewsV2 } from "@/stores/builder"
|
||||
import ConfirmDialog from "@/components/common/ConfirmDialog.svelte"
|
||||
import { notifications } from "@budibase/bbui"
|
||||
|
||||
export let view
|
||||
|
||||
let confirmDeleteDialog
|
||||
|
||||
export const show = () => {
|
||||
confirmDeleteDialog.show()
|
||||
}
|
||||
|
||||
async function deleteView() {
|
||||
try {
|
||||
if (view.version === 2) {
|
||||
await viewsV2.delete(view)
|
||||
} else {
|
||||
await views.delete(view)
|
||||
}
|
||||
notifications.success("View deleted")
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
notifications.error("Error deleting view")
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<ConfirmDialog
|
||||
bind:this={confirmDeleteDialog}
|
||||
body={`Are you sure you wish to delete the view '${view.name}'? Your data will be deleted and this action cannot be undone.`}
|
||||
okText="Delete View"
|
||||
onOk={deleteView}
|
||||
title="Confirm Deletion"
|
||||
/>
|
|
@ -10,9 +10,8 @@
|
|||
import { Icon, ActionButton, ActionMenu, MenuItem } from "@budibase/bbui"
|
||||
import { params, url } from "@roxi/routify"
|
||||
import EditViewModal from "./EditViewModal.svelte"
|
||||
import DeleteViewModal from "./DeleteViewModal.svelte"
|
||||
import EditTableModal from "@/components/backend/TableNavigator/TableNavItem/EditModal.svelte"
|
||||
import DeleteTableModal from "@/components/backend/TableNavigator/TableNavItem/DeleteConfirmationModal.svelte"
|
||||
import DeleteConfirmationModal from "@/components/backend/modals/DeleteDataConfirmationModal.svelte"
|
||||
import { UserAvatars } from "@budibase/frontend-core"
|
||||
import { DB_TYPE_EXTERNAL } from "@/constants/backend"
|
||||
import { TableNames } from "@/constants"
|
||||
|
@ -314,12 +313,12 @@
|
|||
|
||||
{#if table && tableEditable}
|
||||
<EditTableModal {table} bind:this={editTableModal} />
|
||||
<DeleteTableModal {table} bind:this={deleteTableModal} />
|
||||
<DeleteConfirmationModal source={table} bind:this={deleteTableModal} />
|
||||
{/if}
|
||||
|
||||
{#if editableView}
|
||||
<EditViewModal view={editableView} bind:this={editViewModal} />
|
||||
<DeleteViewModal view={editableView} bind:this={deleteViewModal} />
|
||||
<DeleteConfirmationModal source={editableView} bind:this={deleteViewModal} />
|
||||
{/if}
|
||||
|
||||
<style>
|
||||
|
|
|
@ -500,6 +500,13 @@ export class ScreenStore extends BudiStore<ScreenState> {
|
|||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides a list of screens that are used by a given source ID (table, view, datasource, query)
|
||||
*/
|
||||
async usageInScreens(sourceId: string) {
|
||||
return API.usageInScreens(sourceId)
|
||||
}
|
||||
}
|
||||
|
||||
export const screenStore = new ScreenStore()
|
||||
|
|
|
@ -2,12 +2,14 @@ import {
|
|||
DeleteScreenResponse,
|
||||
SaveScreenRequest,
|
||||
SaveScreenResponse,
|
||||
UsageInScreensResponse,
|
||||
} from "@budibase/types"
|
||||
import { BaseAPIClient } from "./types"
|
||||
|
||||
export interface ScreenEndpoints {
|
||||
saveScreen: (screen: SaveScreenRequest) => Promise<SaveScreenResponse>
|
||||
deleteScreen: (id: string, rev: string) => Promise<DeleteScreenResponse>
|
||||
usageInScreens: (sourceId: string) => Promise<UsageInScreensResponse>
|
||||
}
|
||||
|
||||
export const buildScreenEndpoints = (API: BaseAPIClient): ScreenEndpoints => ({
|
||||
|
@ -32,4 +34,10 @@ export const buildScreenEndpoints = (API: BaseAPIClient): ScreenEndpoints => ({
|
|||
url: `/api/screens/${id}/${rev}`,
|
||||
})
|
||||
},
|
||||
|
||||
usageInScreens: async sourceId => {
|
||||
return await API.post({
|
||||
url: `/api/screens/usage/${sourceId}`,
|
||||
})
|
||||
},
|
||||
})
|
||||
|
|
|
@ -1,34 +1,30 @@
|
|||
import { getScreenParams, generateScreenID, DocumentType } from "../../db/utils"
|
||||
import { DocumentType, generateScreenID } from "../../db/utils"
|
||||
import {
|
||||
events,
|
||||
context,
|
||||
tenancy,
|
||||
db as dbCore,
|
||||
events,
|
||||
roles,
|
||||
tenancy,
|
||||
} from "@budibase/backend-core"
|
||||
import { updateAppPackage } from "./application"
|
||||
import {
|
||||
Plugin,
|
||||
ScreenProps,
|
||||
Screen,
|
||||
UserCtx,
|
||||
DeleteScreenResponse,
|
||||
FetchScreenResponse,
|
||||
Plugin,
|
||||
SaveScreenRequest,
|
||||
SaveScreenResponse,
|
||||
DeleteScreenResponse,
|
||||
Screen,
|
||||
ScreenProps,
|
||||
ScreenUsage,
|
||||
UsageInScreensResponse,
|
||||
UserCtx,
|
||||
} from "@budibase/types"
|
||||
import { builderSocket } from "../../websockets"
|
||||
import sdk from "../../sdk"
|
||||
import { sdk as sharedSdk } from "@budibase/shared-core"
|
||||
|
||||
export async function fetch(ctx: UserCtx<void, FetchScreenResponse>) {
|
||||
const db = context.getAppDB()
|
||||
|
||||
const screens = (
|
||||
await db.allDocs(
|
||||
getScreenParams(null, {
|
||||
include_docs: true,
|
||||
})
|
||||
)
|
||||
).rows.map((el: any) => el.doc)
|
||||
const screens = await sdk.screens.fetch()
|
||||
|
||||
const roleId = ctx.user?.role?._id as string
|
||||
if (!roleId) {
|
||||
|
@ -140,3 +136,23 @@ function findPlugins(component: ScreenProps, foundPlugins: string[]) {
|
|||
}
|
||||
component._children.forEach(child => findPlugins(child, foundPlugins))
|
||||
}
|
||||
|
||||
export async function usage(ctx: UserCtx<void, UsageInScreensResponse>) {
|
||||
const sourceId = ctx.params.sourceId
|
||||
const sourceType = sdk.common.getSourceType(sourceId)
|
||||
const allScreens = await sdk.screens.fetch()
|
||||
const response: ScreenUsage[] = []
|
||||
for (let screen of allScreens) {
|
||||
const found = sharedSdk.screens.findInSettings(screen, sourceId)
|
||||
if (found.length !== 0) {
|
||||
response.push({
|
||||
url: screen.routing.route,
|
||||
_id: screen._id!,
|
||||
})
|
||||
}
|
||||
}
|
||||
ctx.body = {
|
||||
sourceType,
|
||||
screens: response,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,5 +19,10 @@ router
|
|||
authorized(permissions.BUILDER),
|
||||
controller.destroy
|
||||
)
|
||||
.post(
|
||||
"/api/screens/usage/:sourceId",
|
||||
authorized(permissions.BUILDER),
|
||||
controller.usage
|
||||
)
|
||||
|
||||
export default router
|
||||
|
|
|
@ -1,9 +1,24 @@
|
|||
import { checkBuilderEndpoint } from "./utilities/TestFunctions"
|
||||
import * as setup from "./utilities"
|
||||
import { events, roles } from "@budibase/backend-core"
|
||||
import { Screen, Role, BuiltinPermissionID } from "@budibase/types"
|
||||
import {
|
||||
Screen,
|
||||
Role,
|
||||
BuiltinPermissionID,
|
||||
SourceType,
|
||||
UsageInScreensResponse,
|
||||
} from "@budibase/types"
|
||||
|
||||
const { basicScreen } = setup.structures
|
||||
const {
|
||||
basicScreen,
|
||||
createTableScreen,
|
||||
createViewScreen,
|
||||
createQueryScreen,
|
||||
basicTable,
|
||||
viewV2,
|
||||
basicQuery,
|
||||
basicDatasource,
|
||||
} = setup.structures
|
||||
|
||||
describe("/screens", () => {
|
||||
let config = setup.getConfig()
|
||||
|
@ -18,7 +33,7 @@ describe("/screens", () => {
|
|||
|
||||
describe("fetch", () => {
|
||||
it("should be able to create a layout", async () => {
|
||||
const screens = await config.api.screen.list({ status: 200 })
|
||||
const screens = await config.api.screen.list()
|
||||
expect(screens.length).toEqual(1)
|
||||
expect(screens.some(s => s._id === screen._id)).toEqual(true)
|
||||
})
|
||||
|
@ -52,28 +67,22 @@ describe("/screens", () => {
|
|||
inherits: [role1._id!, role2._id!],
|
||||
permissionId: BuiltinPermissionID.WRITE,
|
||||
})
|
||||
screen1 = await config.api.screen.save(
|
||||
{
|
||||
...basicScreen(),
|
||||
routing: {
|
||||
roleId: role1._id!,
|
||||
route: "/foo",
|
||||
homeScreen: false,
|
||||
},
|
||||
screen1 = await config.api.screen.save({
|
||||
...basicScreen(),
|
||||
routing: {
|
||||
roleId: role1._id!,
|
||||
route: "/foo",
|
||||
homeScreen: false,
|
||||
},
|
||||
{ status: 200 }
|
||||
)
|
||||
screen2 = await config.api.screen.save(
|
||||
{
|
||||
...basicScreen(),
|
||||
routing: {
|
||||
roleId: role2._id!,
|
||||
route: "/bar",
|
||||
homeScreen: false,
|
||||
},
|
||||
})
|
||||
screen2 = await config.api.screen.save({
|
||||
...basicScreen(),
|
||||
routing: {
|
||||
roleId: role2._id!,
|
||||
route: "/bar",
|
||||
homeScreen: false,
|
||||
},
|
||||
{ status: 200 }
|
||||
)
|
||||
})
|
||||
// get into prod app
|
||||
await config.publish()
|
||||
})
|
||||
|
@ -81,10 +90,7 @@ describe("/screens", () => {
|
|||
async function checkScreens(roleId: string, screenIds: string[]) {
|
||||
await config.loginAsRole(roleId, async () => {
|
||||
const res = await config.api.application.getDefinition(
|
||||
config.prodAppId!,
|
||||
{
|
||||
status: 200,
|
||||
}
|
||||
config.getProdAppId()
|
||||
)
|
||||
expect(res.screens.length).toEqual(screenIds.length)
|
||||
expect(res.screens.map(s => s._id).sort()).toEqual(screenIds.sort())
|
||||
|
@ -114,10 +120,7 @@ describe("/screens", () => {
|
|||
},
|
||||
async () => {
|
||||
const res = await config.api.application.getDefinition(
|
||||
config.prodAppId!,
|
||||
{
|
||||
status: 200,
|
||||
}
|
||||
config.prodAppId!
|
||||
)
|
||||
const screenIds = [screen._id!, screen1._id!]
|
||||
expect(res.screens.length).toEqual(screenIds.length)
|
||||
|
@ -134,9 +137,7 @@ describe("/screens", () => {
|
|||
|
||||
it("should be able to create a screen", async () => {
|
||||
const screen = basicScreen()
|
||||
const responseScreen = await config.api.screen.save(screen, {
|
||||
status: 200,
|
||||
})
|
||||
const responseScreen = await config.api.screen.save(screen)
|
||||
|
||||
expect(responseScreen._rev).toBeDefined()
|
||||
expect(responseScreen.name).toEqual(screen.name)
|
||||
|
@ -145,13 +146,13 @@ describe("/screens", () => {
|
|||
|
||||
it("should be able to update a screen", async () => {
|
||||
const screen = basicScreen()
|
||||
let responseScreen = await config.api.screen.save(screen, { status: 200 })
|
||||
let responseScreen = await config.api.screen.save(screen)
|
||||
screen._id = responseScreen._id
|
||||
screen._rev = responseScreen._rev
|
||||
screen.name = "edit"
|
||||
jest.clearAllMocks()
|
||||
|
||||
responseScreen = await config.api.screen.save(screen, { status: 200 })
|
||||
responseScreen = await config.api.screen.save(screen)
|
||||
|
||||
expect(responseScreen._rev).toBeDefined()
|
||||
expect(responseScreen.name).toEqual(screen.name)
|
||||
|
@ -171,8 +172,7 @@ describe("/screens", () => {
|
|||
it("should be able to delete the screen", async () => {
|
||||
const response = await config.api.screen.destroy(
|
||||
screen._id!,
|
||||
screen._rev!,
|
||||
{ status: 200 }
|
||||
screen._rev!
|
||||
)
|
||||
expect(response.message).toBeDefined()
|
||||
expect(events.screen.deleted).toHaveBeenCalledTimes(1)
|
||||
|
@ -186,4 +186,57 @@ describe("/screens", () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("usage", () => {
|
||||
beforeEach(async () => {
|
||||
await config.init()
|
||||
await config.api.screen.save(basicScreen())
|
||||
})
|
||||
|
||||
function confirmScreen(usage: UsageInScreensResponse, screen: Screen) {
|
||||
expect(usage.screens.length).toEqual(1)
|
||||
expect(usage.screens[0].url).toEqual(screen.routing.route)
|
||||
expect(usage.screens[0]._id).toEqual(screen._id!)
|
||||
}
|
||||
|
||||
it("should find table usage", async () => {
|
||||
const table = await config.api.table.save(basicTable())
|
||||
const screen = await config.api.screen.save(
|
||||
createTableScreen("BudibaseDB", table)
|
||||
)
|
||||
const usage = await config.api.screen.usage(table._id!)
|
||||
expect(usage.sourceType).toEqual(SourceType.TABLE)
|
||||
confirmScreen(usage, screen)
|
||||
})
|
||||
|
||||
it("should find view usage", async () => {
|
||||
const table = await config.api.table.save(basicTable())
|
||||
const view = await config.api.viewV2.create(
|
||||
viewV2.createRequest(table._id!),
|
||||
{ status: 201 }
|
||||
)
|
||||
const screen = await config.api.screen.save(
|
||||
createViewScreen("BudibaseDB", view)
|
||||
)
|
||||
const usage = await config.api.screen.usage(view.id)
|
||||
expect(usage.sourceType).toEqual(SourceType.VIEW)
|
||||
confirmScreen(usage, screen)
|
||||
})
|
||||
|
||||
it("should find datasource/query usage", async () => {
|
||||
const datasource = await config.api.datasource.create(
|
||||
basicDatasource().datasource
|
||||
)
|
||||
const query = await config.api.query.save(basicQuery(datasource._id!))
|
||||
const screen = await config.api.screen.save(
|
||||
createQueryScreen(datasource._id!, query)
|
||||
)
|
||||
const dsUsage = await config.api.screen.usage(datasource._id!)
|
||||
expect(dsUsage.sourceType).toEqual(SourceType.DATASOURCE)
|
||||
confirmScreen(dsUsage, screen)
|
||||
const queryUsage = await config.api.screen.usage(query._id!)
|
||||
expect(queryUsage.sourceType).toEqual(SourceType.QUERY)
|
||||
confirmScreen(queryUsage, screen)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { roles } from "@budibase/backend-core"
|
||||
import { BASE_LAYOUT_PROP_IDS } from "./layouts"
|
||||
import { Screen } from "@budibase/types"
|
||||
import { Screen, Table, Query, ViewV2, Component } from "@budibase/types"
|
||||
|
||||
export function createHomeScreen(
|
||||
config: {
|
||||
|
@ -53,3 +53,177 @@ export function createHomeScreen(
|
|||
name: "home-screen",
|
||||
}
|
||||
}
|
||||
|
||||
function heading(text: string): Component {
|
||||
return {
|
||||
_id: "c1bff24cd821e41d18c894ac77a80ef99",
|
||||
_component: "@budibase/standard-components/heading",
|
||||
_styles: {
|
||||
normal: {},
|
||||
hover: {},
|
||||
active: {},
|
||||
selected: {},
|
||||
},
|
||||
_instanceName: "Table heading",
|
||||
_children: [],
|
||||
text,
|
||||
}
|
||||
}
|
||||
|
||||
export function createTableScreen(
|
||||
datasourceName: string,
|
||||
table: Table
|
||||
): Screen {
|
||||
return {
|
||||
props: {
|
||||
_id: "cad0a0904cacd4678a2ac094e293db1a5",
|
||||
_component: "@budibase/standard-components/container",
|
||||
_styles: {
|
||||
normal: {},
|
||||
hover: {},
|
||||
active: {},
|
||||
selected: {},
|
||||
},
|
||||
_children: [
|
||||
heading("table"),
|
||||
{
|
||||
_id: "ca6304be2079147bb9933092c4f8ce6fa",
|
||||
_component: "@budibase/standard-components/gridblock",
|
||||
_styles: {
|
||||
normal: {},
|
||||
hover: {},
|
||||
active: {},
|
||||
selected: {},
|
||||
},
|
||||
_instanceName: "table - Table",
|
||||
_children: [],
|
||||
table: {
|
||||
label: table.name,
|
||||
tableId: table._id!,
|
||||
type: "table",
|
||||
datasourceName,
|
||||
},
|
||||
},
|
||||
],
|
||||
_instanceName: "table - List",
|
||||
layout: "grid",
|
||||
direction: "column",
|
||||
hAlign: "stretch",
|
||||
vAlign: "top",
|
||||
size: "grow",
|
||||
gap: "M",
|
||||
},
|
||||
routing: {
|
||||
route: "/table",
|
||||
roleId: "ADMIN",
|
||||
homeScreen: false,
|
||||
},
|
||||
name: "screen-id",
|
||||
}
|
||||
}
|
||||
|
||||
export function createViewScreen(datasourceName: string, view: ViewV2): Screen {
|
||||
return {
|
||||
props: {
|
||||
_id: "cc359092bbd6c4e10b57827155edb7872",
|
||||
_component: "@budibase/standard-components/container",
|
||||
_styles: {
|
||||
normal: {},
|
||||
hover: {},
|
||||
active: {},
|
||||
selected: {},
|
||||
},
|
||||
_children: [
|
||||
heading("view"),
|
||||
{
|
||||
_id: "ccb4a9e3734794864b5c65b012a0bdc5a",
|
||||
_component: "@budibase/standard-components/gridblock",
|
||||
_styles: {
|
||||
normal: {},
|
||||
hover: {},
|
||||
active: {},
|
||||
selected: {},
|
||||
},
|
||||
_instanceName: "view - Table",
|
||||
_children: [],
|
||||
table: {
|
||||
...view,
|
||||
name: view.name,
|
||||
tableId: view.tableId,
|
||||
id: view.id,
|
||||
label: view.name,
|
||||
type: "viewV2",
|
||||
},
|
||||
},
|
||||
],
|
||||
_instanceName: "view - List",
|
||||
layout: "grid",
|
||||
direction: "column",
|
||||
hAlign: "stretch",
|
||||
vAlign: "top",
|
||||
size: "grow",
|
||||
gap: "M",
|
||||
},
|
||||
routing: {
|
||||
route: "/view",
|
||||
roleId: "ADMIN",
|
||||
homeScreen: false,
|
||||
},
|
||||
name: "view-id",
|
||||
}
|
||||
}
|
||||
|
||||
export function createQueryScreen(datasourceId: string, query: Query): Screen {
|
||||
return {
|
||||
props: {
|
||||
_id: "cc59b217aed264939a6c5249eee39cb25",
|
||||
_component: "@budibase/standard-components/container",
|
||||
_styles: {
|
||||
normal: {},
|
||||
hover: {},
|
||||
active: {},
|
||||
selected: {},
|
||||
},
|
||||
_children: [
|
||||
{
|
||||
_id: "c33a4a6e3cb5343158a08625c06b5cd7c",
|
||||
_component: "@budibase/standard-components/gridblock",
|
||||
_styles: {
|
||||
normal: {},
|
||||
hover: {},
|
||||
active: {},
|
||||
},
|
||||
_instanceName: "New Table",
|
||||
table: {
|
||||
...query,
|
||||
label: query.name,
|
||||
_id: query._id!,
|
||||
name: query.name,
|
||||
datasourceId: datasourceId,
|
||||
type: "query",
|
||||
},
|
||||
initialSortOrder: "Ascending",
|
||||
allowAddRows: true,
|
||||
allowEditRows: true,
|
||||
allowDeleteRows: true,
|
||||
stripeRows: false,
|
||||
quiet: false,
|
||||
columns: null,
|
||||
},
|
||||
],
|
||||
_instanceName: "Blank screen",
|
||||
layout: "grid",
|
||||
direction: "column",
|
||||
hAlign: "stretch",
|
||||
vAlign: "top",
|
||||
size: "grow",
|
||||
gap: "M",
|
||||
},
|
||||
routing: {
|
||||
route: "/query",
|
||||
roleId: "BASIC",
|
||||
homeScreen: false,
|
||||
},
|
||||
name: "screen-id",
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
export * from "./utils"
|
|
@ -0,0 +1,15 @@
|
|||
import { SourceType } from "@budibase/types"
|
||||
import { docIds } from "@budibase/backend-core"
|
||||
|
||||
export function getSourceType(sourceId: string): SourceType {
|
||||
if (docIds.isTableId(sourceId)) {
|
||||
return SourceType.TABLE
|
||||
} else if (docIds.isViewId(sourceId)) {
|
||||
return SourceType.VIEW
|
||||
} else if (docIds.isDatasourceId(sourceId)) {
|
||||
return SourceType.DATASOURCE
|
||||
} else if (docIds.isQueryId(sourceId)) {
|
||||
return SourceType.QUERY
|
||||
}
|
||||
throw new Error(`Unknown source type for source "${sourceId}"`)
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
export * from "./screens"
|
|
@ -0,0 +1,15 @@
|
|||
import { getScreenParams } from "../../../db/utils"
|
||||
import { context } from "@budibase/backend-core"
|
||||
import { Screen } from "@budibase/types"
|
||||
|
||||
export async function fetch(): Promise<Screen[]> {
|
||||
const db = context.getAppDB()
|
||||
|
||||
return (
|
||||
await db.allDocs<Screen>(
|
||||
getScreenParams(null, {
|
||||
include_docs: true,
|
||||
})
|
||||
)
|
||||
).rows.map(el => el.doc!)
|
||||
}
|
|
@ -11,6 +11,10 @@ export function isExternal(opts: { table?: Table; tableId?: string }): boolean {
|
|||
return false
|
||||
}
|
||||
|
||||
export function isInternal(opts: { table?: Table; tableId?: string }): boolean {
|
||||
return !isExternal(opts)
|
||||
}
|
||||
|
||||
export function isTable(table: any): table is Table {
|
||||
return table._id && docIds.isTableId(table._id)
|
||||
}
|
||||
|
|
|
@ -81,6 +81,14 @@ export function isView(view: any): view is ViewV2 {
|
|||
return view.id && docIds.isViewId(view.id) && view.version === 2
|
||||
}
|
||||
|
||||
export function isInternal(viewId: string) {
|
||||
if (!docIds.isViewId(viewId)) {
|
||||
return false
|
||||
}
|
||||
const { tableId } = utils.extractViewInfoFromID(viewId)
|
||||
return !isExternalTableID(tableId)
|
||||
}
|
||||
|
||||
function guardDuplicateCalculationFields(view: Omit<ViewV2, "id" | "version">) {
|
||||
const seen: Record<string, Record<CalculationType, boolean>> = {}
|
||||
const calculationFields = helpers.views.calculationFields(view)
|
||||
|
|
|
@ -11,6 +11,8 @@ import { default as plugins } from "./plugins"
|
|||
import * as views from "./app/views"
|
||||
import * as permissions from "./app/permissions"
|
||||
import * as rowActions from "./app/rowActions"
|
||||
import * as screens from "./app/screens"
|
||||
import * as common from "./app/common"
|
||||
|
||||
const sdk = {
|
||||
backups,
|
||||
|
@ -22,10 +24,12 @@ const sdk = {
|
|||
datasources,
|
||||
queries,
|
||||
plugins,
|
||||
screens,
|
||||
views,
|
||||
permissions,
|
||||
links,
|
||||
rowActions,
|
||||
common,
|
||||
}
|
||||
|
||||
// default export for TS
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { Screen } from "@budibase/types"
|
||||
import { Screen, UsageInScreensResponse } from "@budibase/types"
|
||||
import { Expectations, TestAPI } from "./base"
|
||||
|
||||
export class ScreenAPI extends TestAPI {
|
||||
|
@ -28,4 +28,16 @@ export class ScreenAPI extends TestAPI {
|
|||
}
|
||||
)
|
||||
}
|
||||
|
||||
usage = async (
|
||||
sourceId: string,
|
||||
expectations?: Expectations
|
||||
): Promise<UsageInScreensResponse> => {
|
||||
return this._post<UsageInScreensResponse>(
|
||||
`/api/screens/usage/${sourceId}`,
|
||||
{
|
||||
expectations,
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,6 +39,11 @@ import {
|
|||
import { LoopInput } from "../../definitions/automations"
|
||||
import { merge } from "lodash"
|
||||
import { generator } from "@budibase/backend-core/tests"
|
||||
export {
|
||||
createTableScreen,
|
||||
createQueryScreen,
|
||||
createViewScreen,
|
||||
} from "../../constants/screens"
|
||||
|
||||
const { BUILTIN_ROLE_IDS } = roles
|
||||
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export * as applications from "./applications"
|
||||
export * as automations from "./automations"
|
||||
export * as users from "./users"
|
||||
export * as screens from "./screens"
|
||||
|
|
|
@ -0,0 +1,23 @@
|
|||
import { Screen, Component } from "@budibase/types"
|
||||
|
||||
export function findInSettings(screen: Screen, toFind: string) {
|
||||
const foundIn: { setting: string; value: string }[] = []
|
||||
function recurse(props: Component, parentKey = "") {
|
||||
for (const [key, value] of Object.entries(props)) {
|
||||
if (!value) {
|
||||
continue
|
||||
}
|
||||
if (typeof value === "string" && value.includes(toFind)) {
|
||||
foundIn.push({
|
||||
setting: parentKey ? `${parentKey}.${key}` : key,
|
||||
value: value,
|
||||
})
|
||||
} else if (typeof value === "object") {
|
||||
recurse(value, key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
recurse(screen.props)
|
||||
return foundIn
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
import { ScreenRoutingJson, Screen } from "../../../documents"
|
||||
import { ScreenRoutingJson, Screen, SourceType } from "../../../documents"
|
||||
|
||||
export interface FetchScreenRoutingResponse {
|
||||
routes: ScreenRoutingJson
|
||||
|
@ -15,3 +15,13 @@ export interface SaveScreenResponse extends Screen {}
|
|||
export interface DeleteScreenResponse {
|
||||
message: string
|
||||
}
|
||||
|
||||
export interface ScreenUsage {
|
||||
url: string
|
||||
_id: string
|
||||
}
|
||||
|
||||
export interface UsageInScreensResponse {
|
||||
sourceType: SourceType
|
||||
screens: ScreenUsage[]
|
||||
}
|
||||
|
|
|
@ -57,3 +57,10 @@ export interface RestConfig {
|
|||
}
|
||||
dynamicVariables?: DynamicVariable[]
|
||||
}
|
||||
|
||||
export enum SourceType {
|
||||
DATASOURCE = "datasource",
|
||||
QUERY = "query",
|
||||
TABLE = "table",
|
||||
VIEW = "view",
|
||||
}
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
export interface UIEvent extends Omit<Event, "target"> {
|
||||
currentTarget: EventTarget & HTMLInputElement
|
||||
key?: string
|
||||
target?: any
|
||||
}
|
|
@ -3,3 +3,4 @@ export * from "./bindings"
|
|||
export * from "./components"
|
||||
export * from "./dataFetch"
|
||||
export * from "./datasource"
|
||||
export * from "./common"
|
||||
|
|
Loading…
Reference in New Issue