mirror of https://github.com/nocodb/nocodb
Raju Udava
2 years ago
committed by
GitHub
25 changed files with 2250 additions and 37 deletions
@ -0,0 +1,5 @@ |
|||||||
|
<template> |
||||||
|
<div class="w-full h-full !py-0 !px-2" style="height: 70vh"> |
||||||
|
<ErdView /> |
||||||
|
</div> |
||||||
|
</template> |
@ -0,0 +1,227 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import type { Edge, Node } from '@braks/vue-flow' |
||||||
|
import { Background, Controls, VueFlow, useVueFlow } from '@braks/vue-flow' |
||||||
|
import type { ColumnType, FormulaType, LinkToAnotherRecordType, LookupType, RollupType } from 'nocodb-sdk' |
||||||
|
import { UITypes } from 'nocodb-sdk' |
||||||
|
import dagre from 'dagre' |
||||||
|
import TableNode from './TableNode.vue' |
||||||
|
import RelationEdge from './RelationEdge.vue' |
||||||
|
|
||||||
|
interface Props { |
||||||
|
tables: any[] |
||||||
|
config: { |
||||||
|
showPkAndFk: boolean |
||||||
|
showViews: boolean |
||||||
|
showAllColumns: boolean |
||||||
|
singleTableMode: boolean |
||||||
|
showJunctionTableNames: boolean |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const { tables, config } = defineProps<Props>() |
||||||
|
|
||||||
|
const { metasWithIdAsKey } = useMetas() |
||||||
|
|
||||||
|
const { $destroy, fitView } = useVueFlow() |
||||||
|
|
||||||
|
const nodes = ref<Node[]>([]) |
||||||
|
const edges = ref<Edge[]>([]) |
||||||
|
|
||||||
|
let dagreGraph: dagre.graphlib.Graph |
||||||
|
|
||||||
|
const initDagre = () => { |
||||||
|
dagreGraph = new dagre.graphlib.Graph() |
||||||
|
dagreGraph.setDefaultEdgeLabel(() => ({})) |
||||||
|
dagreGraph.setGraph({ rankdir: 'LR' }) |
||||||
|
} |
||||||
|
|
||||||
|
const populateInitialNodes = () => { |
||||||
|
nodes.value = tables.flatMap((table) => { |
||||||
|
if (!table.id) return [] |
||||||
|
|
||||||
|
const columns = |
||||||
|
metasWithIdAsKey.value[table.id].columns?.filter( |
||||||
|
(col) => config.showAllColumns || (!config.showAllColumns && col.uidt === UITypes.LinkToAnotherRecord), |
||||||
|
) || [] |
||||||
|
|
||||||
|
dagreGraph.setNode(table.id, { width: 250, height: 50 * columns.length }) |
||||||
|
|
||||||
|
return [ |
||||||
|
{ |
||||||
|
id: table.id, |
||||||
|
data: { ...metasWithIdAsKey.value[table.id], showPkAndFk: config.showPkAndFk, showAllColumns: config.showAllColumns }, |
||||||
|
type: 'custom', |
||||||
|
position: { x: 0, y: 0 }, |
||||||
|
}, |
||||||
|
] |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const populateEdges = () => { |
||||||
|
const ltarColumns = tables.reduce<ColumnType[]>((acc, table) => { |
||||||
|
const meta = metasWithIdAsKey.value[table.id!] |
||||||
|
const columns = meta.columns?.filter( |
||||||
|
(column: ColumnType) => column.uidt === UITypes.LinkToAnotherRecord && column.system !== 1, |
||||||
|
) |
||||||
|
|
||||||
|
columns?.forEach((column: ColumnType) => { |
||||||
|
if ((column.colOptions as LinkToAnotherRecordType)?.type === 'hm') { |
||||||
|
acc.push(column) |
||||||
|
} |
||||||
|
|
||||||
|
if ((column.colOptions as LinkToAnotherRecordType).type === 'mm') { |
||||||
|
// Avoid duplicate mm connections |
||||||
|
const correspondingColumn = acc.find( |
||||||
|
(c) => |
||||||
|
(c.colOptions as LinkToAnotherRecordType | FormulaType | RollupType | LookupType).type === 'mm' && |
||||||
|
(c.colOptions as LinkToAnotherRecordType).fk_parent_column_id === |
||||||
|
(column.colOptions as LinkToAnotherRecordType).fk_child_column_id && |
||||||
|
(c.colOptions as LinkToAnotherRecordType).fk_child_column_id === |
||||||
|
(column.colOptions as LinkToAnotherRecordType).fk_parent_column_id, |
||||||
|
) |
||||||
|
if (!correspondingColumn) { |
||||||
|
acc.push(column) |
||||||
|
} |
||||||
|
} |
||||||
|
}) |
||||||
|
|
||||||
|
return acc |
||||||
|
}, [] as ColumnType[]) |
||||||
|
|
||||||
|
const edgeMMTableLabel = (modelId: string) => { |
||||||
|
const mmModel = metasWithIdAsKey.value[modelId] |
||||||
|
if (mmModel.title !== mmModel.table_name) { |
||||||
|
return `${mmModel.title} (${mmModel.table_name})` |
||||||
|
} |
||||||
|
return mmModel.title |
||||||
|
} |
||||||
|
|
||||||
|
edges.value = ltarColumns.map((column) => { |
||||||
|
const source = column.fk_model_id! |
||||||
|
const target = (column.colOptions as LinkToAnotherRecordType).fk_related_model_id! |
||||||
|
|
||||||
|
let sourceColumnId, targetColumnId |
||||||
|
let edgeLabel = '' |
||||||
|
|
||||||
|
if ((column.colOptions as LinkToAnotherRecordType).type === 'hm') { |
||||||
|
sourceColumnId = (column.colOptions as LinkToAnotherRecordType).fk_child_column_id |
||||||
|
targetColumnId = (column.colOptions as LinkToAnotherRecordType).fk_child_column_id |
||||||
|
} |
||||||
|
|
||||||
|
if ((column.colOptions as LinkToAnotherRecordType).type === 'mm') { |
||||||
|
sourceColumnId = (column.colOptions as LinkToAnotherRecordType).fk_parent_column_id |
||||||
|
targetColumnId = (column.colOptions as LinkToAnotherRecordType).fk_child_column_id |
||||||
|
edgeLabel = config.showJunctionTableNames |
||||||
|
? edgeMMTableLabel((column.colOptions as LinkToAnotherRecordType).fk_mm_model_id!) |
||||||
|
: '' |
||||||
|
} |
||||||
|
|
||||||
|
if (source !== target) dagreGraph.setEdge(source, target) |
||||||
|
|
||||||
|
return { |
||||||
|
id: `e-${sourceColumnId}-${source}-${targetColumnId}-${target}-#${edgeLabel}`, |
||||||
|
source: `${source}`, |
||||||
|
target: `${target}`, |
||||||
|
sourceHandle: `s-${sourceColumnId}-${source}`, |
||||||
|
targetHandle: `d-${targetColumnId}-${target}`, |
||||||
|
type: 'custom', |
||||||
|
data: { |
||||||
|
column, |
||||||
|
isSelfRelation: source === target && sourceColumnId === targetColumnId, |
||||||
|
label: edgeLabel, |
||||||
|
}, |
||||||
|
} |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const connectNonConnectedNodes = () => { |
||||||
|
const connectedNodes = new Set<string>() |
||||||
|
|
||||||
|
edges.value.forEach((edge) => { |
||||||
|
connectedNodes.add(edge.source) |
||||||
|
connectedNodes.add(edge.target) |
||||||
|
}) |
||||||
|
|
||||||
|
const nonConnectedNodes = tables.filter((table) => !connectedNodes.has(table.id!)) |
||||||
|
|
||||||
|
if (nonConnectedNodes.length === 0) return |
||||||
|
|
||||||
|
if (nonConnectedNodes.length === 1) { |
||||||
|
const firstTable = tables.find((table) => table.type === 'table' && table.id !== nonConnectedNodes[0].id) |
||||||
|
if (!firstTable) return |
||||||
|
|
||||||
|
dagreGraph.setEdge(nonConnectedNodes[0].id, firstTable.id) |
||||||
|
return |
||||||
|
} |
||||||
|
|
||||||
|
const firstNode = nonConnectedNodes[0] |
||||||
|
nonConnectedNodes.forEach((node, index) => { |
||||||
|
if (index === 0) return |
||||||
|
|
||||||
|
const source = firstNode.id |
||||||
|
const target = node.id |
||||||
|
|
||||||
|
dagreGraph.setEdge(source, target) |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const layoutNodes = () => { |
||||||
|
if (!config.singleTableMode) connectNonConnectedNodes() |
||||||
|
|
||||||
|
dagre.layout(dagreGraph) |
||||||
|
|
||||||
|
nodes.value = nodes.value.flatMap((node) => { |
||||||
|
const nodeWithPosition = dagreGraph.node(node.id) |
||||||
|
|
||||||
|
if (!nodeWithPosition) return [] |
||||||
|
|
||||||
|
return [{ ...node, position: { x: nodeWithPosition.x, y: nodeWithPosition.y } } as Node] |
||||||
|
}) |
||||||
|
} |
||||||
|
|
||||||
|
const init = () => { |
||||||
|
initDagre() |
||||||
|
populateInitialNodes() |
||||||
|
populateEdges() |
||||||
|
layoutNodes() |
||||||
|
|
||||||
|
setTimeout(() => fitView({ duration: 300 })) |
||||||
|
} |
||||||
|
|
||||||
|
init() |
||||||
|
|
||||||
|
onScopeDispose($destroy) |
||||||
|
|
||||||
|
watch([() => tables, () => config], init, { deep: true, flush: 'pre' }) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<VueFlow :nodes="nodes" :edges="edges" elevate-edges-on-select> |
||||||
|
<Controls class="!left-auto right-2 !top-3.5 !bottom-auto" :show-fit-view="false" :show-interactive="false" /> |
||||||
|
|
||||||
|
<template #node-custom="props"> |
||||||
|
<TableNode :data="props.data" /> |
||||||
|
</template> |
||||||
|
|
||||||
|
<template #edge-custom="props"> |
||||||
|
<RelationEdge v-bind="props" /> |
||||||
|
</template> |
||||||
|
|
||||||
|
<Background /> |
||||||
|
|
||||||
|
<div |
||||||
|
v-if="!config.singleTableMode" |
||||||
|
class="absolute bottom-0 right-0 flex flex-col text-xs bg-white px-2 py-1 border-1 rounded-md border-gray-200 z-50 nc-erd-histogram" |
||||||
|
style="font-size: 0.6rem" |
||||||
|
> |
||||||
|
<div class="flex flex-row items-center space-x-1 border-b-1 pb-1 border-gray-100"> |
||||||
|
<MdiTableLarge class="text-primary" /> |
||||||
|
<div>{{ $t('objects.table') }}</div> |
||||||
|
</div> |
||||||
|
<div class="flex flex-row items-center space-x-1 pt-1"> |
||||||
|
<MdiEyeCircleOutline class="text-primary" /> |
||||||
|
<div>{{ $t('objects.sqlVIew') }}</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</VueFlow> |
||||||
|
</template> |
@ -0,0 +1,161 @@ |
|||||||
|
<script setup> |
||||||
|
import { EdgeText, getBezierPath, getEdgeCenter } from '@braks/vue-flow' |
||||||
|
import { computed } from 'vue' |
||||||
|
|
||||||
|
const props = defineProps({ |
||||||
|
id: { |
||||||
|
type: String, |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
sourceX: { |
||||||
|
type: Number, |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
sourceY: { |
||||||
|
type: Number, |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
targetX: { |
||||||
|
type: Number, |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
targetY: { |
||||||
|
type: Number, |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
sourcePosition: { |
||||||
|
type: String, |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
targetPosition: { |
||||||
|
type: String, |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
data: { |
||||||
|
type: Object, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
markerEnd: { |
||||||
|
type: String, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
style: { |
||||||
|
type: Object, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
sourceHandleId: { |
||||||
|
type: String, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
targetHandleId: { |
||||||
|
type: String, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
}) |
||||||
|
|
||||||
|
const data = toRef(props, 'data') |
||||||
|
|
||||||
|
const isManyToMany = computed(() => data.value.column?.colOptions?.type === 'mm') |
||||||
|
|
||||||
|
const edgePath = computed(() => { |
||||||
|
if (data.value.isSelfRelation) { |
||||||
|
const { sourceX, sourceY, targetX, targetY } = props |
||||||
|
const radiusX = (sourceX - targetX) * 0.6 |
||||||
|
const radiusY = 50 |
||||||
|
return `M ${sourceX} ${sourceY} A ${radiusX} ${radiusY} 0 1 0 ${targetX} ${targetY}` |
||||||
|
} |
||||||
|
|
||||||
|
return getBezierPath({ |
||||||
|
sourceX: props.sourceX, |
||||||
|
sourceY: props.sourceY, |
||||||
|
sourcePosition: props.sourcePosition, |
||||||
|
targetX: props.targetX, |
||||||
|
targetY: props.targetY, |
||||||
|
targetPosition: props.targetPosition, |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
const center = computed(() => |
||||||
|
getEdgeCenter({ |
||||||
|
sourceX: props.sourceX, |
||||||
|
sourceY: props.sourceY, |
||||||
|
targetX: props.targetX, |
||||||
|
targetY: props.targetY, |
||||||
|
}), |
||||||
|
) |
||||||
|
</script> |
||||||
|
|
||||||
|
<script> |
||||||
|
export default { |
||||||
|
inheritAttrs: false, |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<path |
||||||
|
:id="id" |
||||||
|
:style="style" |
||||||
|
class="path-wrapper p-4 hover:cursor-pointer" |
||||||
|
:stroke-width="8" |
||||||
|
fill="none" |
||||||
|
:d="edgePath" |
||||||
|
:marker-end="markerEnd" |
||||||
|
/> |
||||||
|
<path |
||||||
|
:id="id" |
||||||
|
:style="style" |
||||||
|
class="path stroke-gray-500 hover:stroke-green-500 hover:cursor-pointer" |
||||||
|
:stroke-width="1.5" |
||||||
|
fill="none" |
||||||
|
:d="edgePath" |
||||||
|
:marker-end="markerEnd" |
||||||
|
/> |
||||||
|
|
||||||
|
<EdgeText |
||||||
|
v-if="data.label?.length > 0" |
||||||
|
:class="`nc-erd-table-label-${data.label.toLowerCase().replace(' ', '-').replace('\(', '').replace(')', '')}`" |
||||||
|
:x="center[0]" |
||||||
|
:y="center[1]" |
||||||
|
:label="data.label" |
||||||
|
:label-style="{ fill: 'white' }" |
||||||
|
:label-show-bg="true" |
||||||
|
:label-bg-style="{ fill: '#10b981' }" |
||||||
|
:label-bg-padding="[2, 4]" |
||||||
|
:label-bg-border-radius="2" |
||||||
|
/> |
||||||
|
|
||||||
|
<rect |
||||||
|
class="nc-erd-edge-rect" |
||||||
|
:x="sourceX" |
||||||
|
:y="sourceY - 4" |
||||||
|
width="8" |
||||||
|
height="8" |
||||||
|
fill="#fff" |
||||||
|
stroke="#6F3381" |
||||||
|
:stroke-width="1.5" |
||||||
|
:transform="`rotate(45,${sourceX + 2},${sourceY - 4})`" |
||||||
|
/> |
||||||
|
<rect |
||||||
|
v-if="isManyToMany" |
||||||
|
class="nc-erd-edge-rect" |
||||||
|
:x="targetX" |
||||||
|
:y="targetY - 4" |
||||||
|
width="8" |
||||||
|
height="8" |
||||||
|
fill="#fff" |
||||||
|
stroke="#6F3381" |
||||||
|
:stroke-width="1.5" |
||||||
|
:transform="`rotate(45,${targetX + 2},${targetY - 4})`" |
||||||
|
/> |
||||||
|
<circle v-else class="nc-erd-edge-circle" :cx="targetX" :cy="targetY" fill="#fff" :r="5" stroke="#6F3381" :stroke-width="1.5" /> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.path-wrapper:hover + .path { |
||||||
|
@apply stroke-green-500; |
||||||
|
stroke-width: 2; |
||||||
|
} |
||||||
|
.path:hover { |
||||||
|
stroke-width: 2; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,121 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import type { NodeProps } from '@braks/vue-flow' |
||||||
|
import { Handle, Position } from '@braks/vue-flow' |
||||||
|
import type { ColumnType, TableType } from 'nocodb-sdk' |
||||||
|
import { UITypes, isVirtualCol } from 'nocodb-sdk' |
||||||
|
import type { Ref } from 'vue' |
||||||
|
|
||||||
|
interface Props extends NodeProps { |
||||||
|
data: TableType & { showPkAndFk: boolean; showAllColumns: boolean } |
||||||
|
} |
||||||
|
|
||||||
|
const props = defineProps<Props>() |
||||||
|
|
||||||
|
const { data } = toRefs(props) |
||||||
|
|
||||||
|
provide(MetaInj, data as Ref<TableType>) |
||||||
|
|
||||||
|
const { $e } = useNuxtApp() |
||||||
|
|
||||||
|
const columns = computed(() => { |
||||||
|
// Hide hm ltar created for `mm` relations |
||||||
|
return data.value.columns?.filter((col) => !(col.uidt === UITypes.LinkToAnotherRecord && col.system === 1)) |
||||||
|
}) |
||||||
|
|
||||||
|
const pkAndFkColumns = computed(() => { |
||||||
|
return columns.value |
||||||
|
?.filter(() => data.value.showPkAndFk && data.value.showAllColumns) |
||||||
|
.filter((col) => col.pk || col.uidt === UITypes.ForeignKey) |
||||||
|
}) |
||||||
|
|
||||||
|
const nonPkColumns = computed(() => { |
||||||
|
return columns.value |
||||||
|
?.filter( |
||||||
|
(col: ColumnType) => data.value.showAllColumns || (!data.value.showAllColumns && col.uidt === UITypes.LinkToAnotherRecord), |
||||||
|
) |
||||||
|
.filter((col: ColumnType) => !col.pk && col.uidt !== UITypes.ForeignKey) |
||||||
|
}) |
||||||
|
|
||||||
|
const relatedColumnId = (col: Record<string, any>) => |
||||||
|
col.colOptions.type === 'mm' ? col.colOptions.fk_parent_column_id : col.colOptions.fk_child_column_id |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div |
||||||
|
class="h-full flex flex-col min-w-16 bg-gray-50 rounded-lg border-1 nc-erd-table-node" |
||||||
|
:class="`nc-erd-table-node-${data.table_name}`" |
||||||
|
@click="$e('c:erd:node-click')" |
||||||
|
> |
||||||
|
<GeneralTooltip modifier-key="Alt"> |
||||||
|
<template #title> {{ data.table_name }} </template> |
||||||
|
<div |
||||||
|
class="text-gray-600 text-md py-2 border-b-1 border-gray-200 rounded-t-lg w-full pr-3 pl-2 bg-gray-100 font-semibold flex flex-row items-center" |
||||||
|
> |
||||||
|
<MdiTableLarge v-if="data.type === 'table'" class="text-primary" /> |
||||||
|
<MdiEyeCircleOutline v-else class="text-primary" /> |
||||||
|
<div class="flex pl-1.5"> |
||||||
|
{{ data.title }} |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</GeneralTooltip> |
||||||
|
<div> |
||||||
|
<div |
||||||
|
v-for="col in pkAndFkColumns" |
||||||
|
:key="col.title" |
||||||
|
class="w-full border-b-1 py-2 border-gray-100 keys" |
||||||
|
:class="`nc-erd-table-node-${data.table_name}-column-${col.column_name}`" |
||||||
|
> |
||||||
|
<SmartsheetHeaderCell v-if="col" :column="col" :hide-menu="true" /> |
||||||
|
</div> |
||||||
|
|
||||||
|
<div class="w-full mb-1"></div> |
||||||
|
|
||||||
|
<div v-for="(col, index) in nonPkColumns" :key="col.title"> |
||||||
|
<div |
||||||
|
class="w-full h-full flex items-center min-w-32 border-gray-100 py-2 px-1" |
||||||
|
:class="index + 1 === nonPkColumns.length ? 'rounded-b-lg' : 'border-b-1'" |
||||||
|
> |
||||||
|
<div |
||||||
|
v-if="col.uidt === UITypes.LinkToAnotherRecord" |
||||||
|
class="flex relative w-full" |
||||||
|
:class="`nc-erd-table-node-${data.table_name}-column-${col.title?.toLowerCase().replace(' ', '_')}`" |
||||||
|
> |
||||||
|
<Handle |
||||||
|
:id="`s-${relatedColumnId(col)}-${data.id}`" |
||||||
|
class="-right-4 opacity-0" |
||||||
|
type="source" |
||||||
|
:position="Position.Right" |
||||||
|
/> |
||||||
|
|
||||||
|
<Handle |
||||||
|
:id="`d-${relatedColumnId(col)}-${data.id}`" |
||||||
|
class="-left-1 opacity-0" |
||||||
|
type="target" |
||||||
|
:position="Position.Left" |
||||||
|
/> |
||||||
|
<SmartsheetHeaderVirtualCell :column="col" :hide-menu="true" /> |
||||||
|
</div> |
||||||
|
<SmartsheetHeaderVirtualCell |
||||||
|
v-else-if="isVirtualCol(col)" |
||||||
|
:column="col" |
||||||
|
:hide-menu="true" |
||||||
|
:class="`nc-erd-table-node-${data.table_name}-column-${col.column_name}`" |
||||||
|
/> |
||||||
|
|
||||||
|
<SmartsheetHeaderCell |
||||||
|
v-else |
||||||
|
:column="col" |
||||||
|
:hide-menu="true" |
||||||
|
:class="`nc-erd-table-node-${data.table_name}-column-${col.column_name}`" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped lang="scss"> |
||||||
|
.keys { |
||||||
|
background-color: #f6f6f6; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,162 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import type { LinkToAnotherRecordType, TableType } from 'nocodb-sdk' |
||||||
|
import { UITypes } from 'nocodb-sdk' |
||||||
|
|
||||||
|
const { table } = defineProps<{ table?: TableType }>() |
||||||
|
|
||||||
|
const { includeM2M } = useGlobal() |
||||||
|
|
||||||
|
const { tables: projectTables } = useProject() |
||||||
|
const tables = ref<TableType[]>([]) |
||||||
|
const { metas, getMeta } = useMetas() |
||||||
|
|
||||||
|
let isLoading = $ref(true) |
||||||
|
const showAdvancedOptions = ref(false) |
||||||
|
|
||||||
|
const config = ref({ |
||||||
|
showPkAndFk: true, |
||||||
|
showViews: false, |
||||||
|
showAllColumns: true, |
||||||
|
singleTableMode: !!table, |
||||||
|
showMMTables: false, |
||||||
|
showJunctionTableNames: false, |
||||||
|
}) |
||||||
|
|
||||||
|
const loadMetaOfTablesNotInMetas = async (localTables: TableType[]) => { |
||||||
|
await Promise.all( |
||||||
|
localTables |
||||||
|
.filter((table) => !metas.value[table.id!]) |
||||||
|
.map(async (table) => { |
||||||
|
await getMeta(table.id!) |
||||||
|
}), |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
const populateTables = async () => { |
||||||
|
let localTables: TableType[] = [] |
||||||
|
if (table) { |
||||||
|
// if table is provided only get the table and its related tables |
||||||
|
localTables = projectTables.value.filter( |
||||||
|
(t) => |
||||||
|
t.id === table.id || |
||||||
|
table.columns?.find( |
||||||
|
(column) => |
||||||
|
column.uidt === UITypes.LinkToAnotherRecord && |
||||||
|
(column.colOptions as LinkToAnotherRecordType)?.fk_related_model_id === t.id, |
||||||
|
), |
||||||
|
) |
||||||
|
} else { |
||||||
|
localTables = projectTables.value |
||||||
|
} |
||||||
|
|
||||||
|
await loadMetaOfTablesNotInMetas(localTables) |
||||||
|
|
||||||
|
tables.value = localTables |
||||||
|
.filter( |
||||||
|
(t) => |
||||||
|
// todo: table type is missing mm property in type definition |
||||||
|
config.value.showMMTables || |
||||||
|
(!config.value.showMMTables && !t.mm) || |
||||||
|
// Show mm table if it's the selected table |
||||||
|
t.id === table?.id, |
||||||
|
) |
||||||
|
.filter((t) => (!config.value.showViews && t.type !== 'view') || config.value.showViews) |
||||||
|
|
||||||
|
isLoading = false |
||||||
|
} |
||||||
|
|
||||||
|
watch( |
||||||
|
[config, metas], |
||||||
|
async () => { |
||||||
|
await populateTables() |
||||||
|
}, |
||||||
|
{ |
||||||
|
deep: true, |
||||||
|
}, |
||||||
|
) |
||||||
|
|
||||||
|
watch( |
||||||
|
[projectTables], |
||||||
|
async () => { |
||||||
|
await populateTables() |
||||||
|
}, |
||||||
|
{ immediate: true }, |
||||||
|
) |
||||||
|
|
||||||
|
watch( |
||||||
|
() => config.value.showAllColumns, |
||||||
|
() => { |
||||||
|
config.value.showPkAndFk = config.value.showAllColumns |
||||||
|
}, |
||||||
|
) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div |
||||||
|
class="w-full" |
||||||
|
style="height: inherit" |
||||||
|
:class="{ |
||||||
|
'nc-erd-vue-flow': !config.singleTableMode, |
||||||
|
'nc-erd-vue-flow-single-table': config.singleTableMode, |
||||||
|
}" |
||||||
|
> |
||||||
|
<div v-if="isLoading" class="h-full w-full flex flex-col justify-center items-center"> |
||||||
|
<div class="flex flex-row justify-center"> |
||||||
|
<a-spin size="large" /> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
<div v-else class="relative h-full"> |
||||||
|
<ErdFlow :tables="tables" :config="config" /> |
||||||
|
|
||||||
|
<div |
||||||
|
class="absolute top-2 right-10 flex-col bg-white py-2 px-4 border-1 border-gray-100 rounded-md z-50 space-y-1 nc-erd-context-menu z-50" |
||||||
|
> |
||||||
|
<div class="flex flex-row items-center"> |
||||||
|
<a-checkbox |
||||||
|
v-model:checked="config.showAllColumns" |
||||||
|
v-e="['c:erd:showAllColumns']" |
||||||
|
class="nc-erd-showColumns-checkbox" |
||||||
|
/> |
||||||
|
<span |
||||||
|
class="ml-2 select-none nc-erd-showColumns-label" |
||||||
|
style="font-size: 0.65rem" |
||||||
|
@dblclick="showAdvancedOptions = true" |
||||||
|
> |
||||||
|
{{ $t('activity.erd.showColumns') }} |
||||||
|
</span> |
||||||
|
</div> |
||||||
|
<div class="flex flex-row items-center"> |
||||||
|
<a-checkbox |
||||||
|
v-model:checked="config.showPkAndFk" |
||||||
|
v-e="['c:erd:showPkAndFk']" |
||||||
|
class="nc-erd-showPkAndFk-checkbox" |
||||||
|
:class="{ |
||||||
|
'nc-erd-showPkAndFk-checkbox-enabled': config.showAllColumns, |
||||||
|
'nc-erd-showPkAndFk-checkbox-disabled': !config.showAllColumns, |
||||||
|
'nc-erd-showPkAndFk-checkbox-checked': config.showPkAndFk, |
||||||
|
'nc-erd-showPkAndFk-checkbox-unchecked': !config.showPkAndFk, |
||||||
|
}" |
||||||
|
:disabled="!config.showAllColumns" |
||||||
|
/> |
||||||
|
<span class="ml-2 select-none text-[0.65rem]">{{ $t('activity.erd.showPkAndFk') }}</span> |
||||||
|
</div> |
||||||
|
<div v-if="!table" class="flex flex-row items-center"> |
||||||
|
<a-checkbox v-model:checked="config.showViews" v-e="['c:erd:showViews']" class="nc-erd-showViews-checkbox" /> |
||||||
|
<span class="ml-2 select-none text-[0.65rem]">{{ $t('activity.erd.showSqlViews') }}</span> |
||||||
|
</div> |
||||||
|
<div v-if="!table && showAdvancedOptions && includeM2M" class="flex flex-row items-center"> |
||||||
|
<a-checkbox v-model:checked="config.showMMTables" v-e="['c:erd:showMMTables']" class="nc-erd-showMMTables-checkbox" /> |
||||||
|
<span class="ml-2 select-none text-[0.65rem]">{{ $t('activity.erd.showMMTables') }}</span> |
||||||
|
</div> |
||||||
|
<div v-if="showAdvancedOptions && includeM2M" class="flex flex-row items-center"> |
||||||
|
<a-checkbox |
||||||
|
v-model:checked="config.showJunctionTableNames" |
||||||
|
v-e="['c:erd:showJunctionTableNames']" |
||||||
|
class="nc-erd-showJunctionTableNames-checkbox" |
||||||
|
/> |
||||||
|
<span class="ml-2 select-none text-[0.65rem]">{{ $t('activity.erd.showJunctionTableNames') }}</span> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
@ -0,0 +1,42 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
const props = defineProps<Props>() |
||||||
|
|
||||||
|
const emits = defineEmits(['update:modelValue']) |
||||||
|
|
||||||
|
const meta = inject(MetaInj) |
||||||
|
|
||||||
|
interface Props { |
||||||
|
modelValue: boolean |
||||||
|
} |
||||||
|
|
||||||
|
const vModel = useVModel(props, 'modelValue', emits) |
||||||
|
|
||||||
|
const selectedView = inject(ActiveViewInj) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<a-modal |
||||||
|
v-model:visible="vModel" |
||||||
|
size="small" |
||||||
|
:footer="null" |
||||||
|
width="max(900px,60vw)" |
||||||
|
:closable="false" |
||||||
|
wrap-class-name="erd-single-table-modal" |
||||||
|
transition-name="fade" |
||||||
|
> |
||||||
|
<div class="flex flex-row justify-between w-full items-center mb-1"> |
||||||
|
<a-typography-title class="ml-4 select-none" type="secondary" :level="5"> |
||||||
|
{{ `${$t('title.erdView')}: ${selectedView?.title}` }} |
||||||
|
</a-typography-title> |
||||||
|
|
||||||
|
<a-button type="text" class="!rounded-md border-none -mt-1.5 -mr-1" @click="vModel = false"> |
||||||
|
<template #icon> |
||||||
|
<MdiClose class="cursor-pointer mt-1 nc-modal-close" /> |
||||||
|
</template> |
||||||
|
</a-button> |
||||||
|
</div> |
||||||
|
<div class="w-full h-full !py-0 !px-2" style="height: 70vh"> |
||||||
|
<ErdView :table="meta" /> |
||||||
|
</div> |
||||||
|
</a-modal> |
||||||
|
</template> |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,373 @@ |
|||||||
|
import { mainPage, settingsPage } from "../../support/page_objects/mainPage"; |
||||||
|
import {loginPage, projectsPage} from "../../support/page_objects/navigation"; |
||||||
|
import { isTestSuiteActive, sakilaSqlViews, sakilaTables } from "../../support/page_objects/projectConstants"; |
||||||
|
|
||||||
|
export const genTest = (apiType, dbType) => { |
||||||
|
if (!isTestSuiteActive(apiType, dbType)) return; |
||||||
|
|
||||||
|
describe(`${apiType.toUpperCase()} ERD`, () => { |
||||||
|
// before(() => {
|
||||||
|
// loginPage.loginAndOpenProject(apiType, dbType);
|
||||||
|
// cy.openTableTab("Country", 25);
|
||||||
|
// cy.saveLocalStorage();
|
||||||
|
// });
|
||||||
|
|
||||||
|
beforeEach(() => { |
||||||
|
cy.restoreLocalStorage(); |
||||||
|
}) |
||||||
|
|
||||||
|
afterEach(() => { |
||||||
|
cy.saveLocalStorage(); |
||||||
|
}) |
||||||
|
|
||||||
|
after(() => { |
||||||
|
cy.restoreLocalStorage(); |
||||||
|
cy.closeTableTab("Country"); |
||||||
|
cy.saveLocalStorage(); |
||||||
|
}); |
||||||
|
|
||||||
|
// Test cases
|
||||||
|
|
||||||
|
it(`Enable MM setting Open Table ERD`, () => { |
||||||
|
cy.openTableTab("Country", 25); |
||||||
|
mainPage.toggleShowMMSetting(); |
||||||
|
|
||||||
|
mainPage.openErdTab(); |
||||||
|
mainPage.closeMetaTab(); |
||||||
|
}); |
||||||
|
|
||||||
|
it(`Verify ERD Context menu in all table view`, () => { |
||||||
|
mainPage.openErdTab(); |
||||||
|
cy.get('.nc-erd-context-menu').should('be.visible'); |
||||||
|
cy.get('.nc-erd-context-menu').get('.nc-erd-histogram').should('be.visible'); |
||||||
|
cy.get('.nc-erd-context-menu').find('.ant-checkbox').should('have.length', 3); |
||||||
|
cy.get('.nc-erd-context-menu').find('.ant-checkbox').eq(0).should('have.class', 'ant-checkbox-checked'); |
||||||
|
cy.get('.nc-erd-context-menu').find('.ant-checkbox').eq(1).should('have.class', 'ant-checkbox-checked'); |
||||||
|
cy.get('.nc-erd-context-menu').find('.ant-checkbox').eq(2).should('not.have.class', 'ant-checkbox-checked'); |
||||||
|
|
||||||
|
cy.get('.nc-erd-context-menu').find('.nc-erd-showColumns-label').dblclick(); |
||||||
|
cy.get('.nc-erd-context-menu').find('.ant-checkbox').should('have.length', 5); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Verify ERD of all tables view and verify columns of actor and payment with default config", () => { |
||||||
|
cy.get('.nc-erd-vue-flow').find('.nc-erd-table-node').should('have.length', 12) |
||||||
|
cy.get('.nc-erd-vue-flow').find('.vue-flow__edge').should('have.length', 14) |
||||||
|
cy.get('.nc-erd-vue-flow').find('.nc-erd-edge-circle').should('have.length', 11) |
||||||
|
cy.get('.nc-erd-vue-flow').find('.nc-erd-edge-rect').should('have.length', 17) |
||||||
|
|
||||||
|
for(const tableName of sakilaTables) { |
||||||
|
cy.get('.nc-erd-vue-flow').find(`.nc-erd-table-node-${tableName}`).should('exist'); |
||||||
|
} |
||||||
|
|
||||||
|
// Actor table
|
||||||
|
[ |
||||||
|
'actor_id', |
||||||
|
'first_name', |
||||||
|
'last_name', |
||||||
|
'last_update', |
||||||
|
'film_list' |
||||||
|
].forEach((colTitle) => { |
||||||
|
cy.get('.nc-erd-vue-flow').find(`.nc-erd-table-node-actor`).find(`.nc-erd-table-node-actor-column-${colTitle}`).should('exist'); |
||||||
|
}); |
||||||
|
|
||||||
|
// Payment table
|
||||||
|
[ |
||||||
|
'payment_id', |
||||||
|
'customer_id', |
||||||
|
'staff_id', |
||||||
|
'rental_id', |
||||||
|
'amount', |
||||||
|
'payment_date', |
||||||
|
'last_update', |
||||||
|
'customer', |
||||||
|
'rental', |
||||||
|
'staff' |
||||||
|
].forEach((colTitle) => { |
||||||
|
cy.get('.nc-erd-vue-flow').find(`.nc-erd-table-node-payment`).find(`.nc-erd-table-node-payment-column-${colTitle}`).should('exist'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Verify ERD of all tables view and verify columns of actor and payment with default config with showAllColumn disabled", () => { |
||||||
|
cy.get('.nc-erd-context-menu').get('.nc-erd-showColumns-checkbox').click(); |
||||||
|
cy.get('.nc-erd-showPkAndFk-checkbox-disabled').should('exist'); |
||||||
|
cy.get('.nc-erd-showPkAndFk-checkbox-unchecked').should('exist'); |
||||||
|
|
||||||
|
// Actor table
|
||||||
|
[ |
||||||
|
'film_list' |
||||||
|
].forEach((colTitle) => { |
||||||
|
cy.get('.nc-erd-vue-flow').find(`.nc-erd-table-node-actor`).find(`.nc-erd-table-node-actor-column-${colTitle}`).should('exist'); |
||||||
|
}); |
||||||
|
|
||||||
|
// Payment table
|
||||||
|
[ |
||||||
|
'customer', |
||||||
|
'rental', |
||||||
|
'staff' |
||||||
|
].forEach((colTitle) => { |
||||||
|
cy.get('.nc-erd-vue-flow').find(`.nc-erd-table-node-payment`).find(`.nc-erd-table-node-payment-column-${colTitle}`).should('exist'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Verify ERD of all tables view and verify columns of actor and payment with default config with showPkAndFk disabled", () => { |
||||||
|
// enable showAllColumn
|
||||||
|
cy.get('.nc-erd-context-menu').get('.nc-erd-showColumns-checkbox').click(); |
||||||
|
cy.get('.nc-erd-context-menu').get('.nc-erd-showPkAndFk-checkbox').click(); |
||||||
|
|
||||||
|
// Actor table
|
||||||
|
[ |
||||||
|
'last_name', |
||||||
|
'last_update', |
||||||
|
'film_list' |
||||||
|
].forEach((colTitle) => { |
||||||
|
cy.get('.nc-erd-vue-flow').find(`.nc-erd-table-node-actor`).find(`.nc-erd-table-node-actor-column-${colTitle}`).should('exist'); |
||||||
|
}); |
||||||
|
|
||||||
|
// Payment table
|
||||||
|
[ |
||||||
|
'amount', |
||||||
|
'payment_date', |
||||||
|
'last_update', |
||||||
|
'customer', |
||||||
|
'rental', |
||||||
|
'staff' |
||||||
|
].forEach((colTitle) => { |
||||||
|
cy.get('.nc-erd-vue-flow').find(`.nc-erd-table-node-payment`).find(`.nc-erd-table-node-payment-column-${colTitle}`).should('exist'); |
||||||
|
}); |
||||||
|
}); |
||||||
|
|
||||||
|
it("Verify ERD of all tables view with sql grid on and verify columns of ActorInfo", () => { |
||||||
|
cy.get('.nc-erd-context-menu').get('.nc-erd-showViews-checkbox').click(); |
||||||
|
|
||||||
|
cy.get('.nc-erd-vue-flow').find('.nc-erd-table-node').should('have.length', 19) |
||||||
|
cy.get('.nc-erd-vue-flow').find('.vue-flow__edge').should('have.length', 14) |
||||||
|
cy.get('.nc-erd-vue-flow').find('.nc-erd-edge-circle').should('have.length', 11) |
||||||
|
cy.get('.nc-erd-vue-flow').find('.nc-erd-edge-rect').should('have.length', 17) |
||||||
|
|
||||||
|
for(const tableName of sakilaTables) { |
||||||
|
cy.get('.nc-erd-vue-flow').find(`.nc-erd-table-node-${tableName}`).should('exist'); |
||||||
|
} |
||||||
|
|
||||||
|
for(const tableName of sakilaSqlViews) { |
||||||
|
cy.get('.nc-erd-vue-flow').find(`.nc-erd-table-node-${tableName}`).should('exist'); |
||||||
|
} |
||||||
|
|
||||||
|
// ActorInfo SQL View
|
||||||
|
[ |
||||||
|
'actor_id', |
||||||
|
'first_name', |
||||||
|
'last_name', |
||||||
|
'film_info' |
||||||
|
].forEach((colTitle) => { |
||||||
|
cy.get('.nc-erd-vue-flow').find(`.nc-erd-table-node-actor_info`).find(`.nc-erd-table-node-actor_info-column-${colTitle}`).should('exist'); |
||||||
|
}) |
||||||
|
|
||||||
|
}); |
||||||
|
|
||||||
|
it("Verify show MM tables", () => { |
||||||
|
cy.get('.nc-erd-vue-flow').find(`.nc-erd-table-node-store`).should('not.exist'); |
||||||
|
// disable showViews
|
||||||
|
cy.get('.nc-erd-context-menu').get('.nc-erd-showViews-checkbox').click(); |
||||||
|
cy.get('.nc-erd-context-menu').get('.nc-erd-showMMTables-checkbox').click(); |
||||||
|
|
||||||
|
cy.get('.nc-erd-vue-flow').find('.nc-erd-table-node').should('have.length', 16) |
||||||
|
cy.get('.nc-erd-vue-flow').find('.vue-flow__edge').should('have.length', 26) |
||||||
|
cy.get('.nc-erd-vue-flow').find('.nc-erd-edge-circle').should('have.length', 22) |
||||||
|
cy.get('.nc-erd-vue-flow').find('.nc-erd-edge-rect').should('have.length', 30) |
||||||
|
|
||||||
|
// Check if store table is present
|
||||||
|
cy.get('.nc-erd-vue-flow').find(`.nc-erd-table-node-store`).should('exist'); |
||||||
|
}) |
||||||
|
|
||||||
|
it("Verify show junction table names", () => { |
||||||
|
// disable showViews
|
||||||
|
cy.get('.nc-erd-context-menu').get('.nc-erd-showJunctionTableNames-checkbox').click(); |
||||||
|
|
||||||
|
cy.get('.nc-erd-vue-flow').get('.nc-erd-table-label-filmactor-film_actor').should('exist'); |
||||||
|
mainPage.closeMetaTab(); |
||||||
|
}) |
||||||
|
|
||||||
|
it('Verify table ERD view of country', () => { |
||||||
|
mainPage.openTableErdView(); |
||||||
|
|
||||||
|
cy.get('.nc-erd-vue-flow-single-table').find('.nc-erd-table-node').should('have.length', 2) |
||||||
|
cy.get('.nc-erd-vue-flow-single-table').find('.vue-flow__edge').should('have.length', 1) |
||||||
|
cy.get('.nc-erd-vue-flow-single-table').find('.nc-erd-edge-circle').should('have.length', 1) |
||||||
|
cy.get('.nc-erd-vue-flow-single-table').find('.nc-erd-edge-rect').should('have.length', 1) |
||||||
|
|
||||||
|
const countryColumns = [ |
||||||
|
'country_id', |
||||||
|
'country', |
||||||
|
'last_update', |
||||||
|
'city_list' |
||||||
|
] |
||||||
|
|
||||||
|
// Country table
|
||||||
|
countryColumns.forEach((colTitle) => { |
||||||
|
cy.get('.nc-erd-vue-flow-single-table').find(`.nc-erd-table-node-country`).find(`.nc-erd-table-node-country-column-${colTitle}`).should('exist'); |
||||||
|
}); |
||||||
|
|
||||||
|
const cityColumns = [ |
||||||
|
'city_id', |
||||||
|
'city', |
||||||
|
'last_update', |
||||||
|
'country', |
||||||
|
'address_list' |
||||||
|
] |
||||||
|
|
||||||
|
// City table
|
||||||
|
cityColumns.forEach((colTitle) => { |
||||||
|
cy.get('.nc-erd-vue-flow-single-table').find(`.nc-erd-table-node-city`).find(`.nc-erd-table-node-city-column-${colTitle}`).should('exist'); |
||||||
|
}); |
||||||
|
}) |
||||||
|
|
||||||
|
it('Verify table ERD view of country showAllColumn disabled', () => { |
||||||
|
cy.get('.nc-erd-vue-flow-single-table').within(() => { |
||||||
|
cy.get('.nc-erd-context-menu').get('.nc-erd-showColumns-checkbox').click(); |
||||||
|
cy.get('.nc-erd-showPkAndFk-checkbox-disabled').should('exist'); |
||||||
|
cy.get('.nc-erd-showPkAndFk-checkbox-unchecked').should('exist'); |
||||||
|
|
||||||
|
const countryColumns = [ |
||||||
|
'city_list' |
||||||
|
] |
||||||
|
|
||||||
|
// Country table
|
||||||
|
countryColumns.forEach((colTitle) => { |
||||||
|
cy.get(`.nc-erd-table-node-country`).find(`.nc-erd-table-node-country-column-${colTitle}`).should('exist'); |
||||||
|
}); |
||||||
|
|
||||||
|
const cityColumns = [ |
||||||
|
'country', |
||||||
|
'address_list' |
||||||
|
] |
||||||
|
|
||||||
|
// City table
|
||||||
|
cityColumns.forEach((colTitle) => { |
||||||
|
cy.get(`.nc-erd-table-node-city`).find(`.nc-erd-table-node-city-column-${colTitle}`).should('exist'); |
||||||
|
}); |
||||||
|
|
||||||
|
cy.get('.nc-erd-context-menu').get('.nc-erd-showColumns-checkbox').click(); |
||||||
|
}) |
||||||
|
}) |
||||||
|
|
||||||
|
it('Verify table ERD view of country show PK AND FK disabled', () => { |
||||||
|
cy.get('.nc-erd-vue-flow-single-table').within(() => { |
||||||
|
cy.get('.nc-erd-context-menu').get('.nc-erd-showPkAndFk-checkbox').click(); |
||||||
|
|
||||||
|
const countryColumns = [ |
||||||
|
'country', |
||||||
|
'last_update', |
||||||
|
'city_list' |
||||||
|
] |
||||||
|
|
||||||
|
// Country table
|
||||||
|
countryColumns.forEach((colTitle) => { |
||||||
|
cy.get(`.nc-erd-table-node-country`).find(`.nc-erd-table-node-country-column-${colTitle}`).should('exist'); |
||||||
|
}); |
||||||
|
|
||||||
|
const cityColumns = [ |
||||||
|
'city', |
||||||
|
'last_update', |
||||||
|
'country', |
||||||
|
'address_list' |
||||||
|
] |
||||||
|
|
||||||
|
// City table
|
||||||
|
cityColumns.forEach((colTitle) => { |
||||||
|
cy.get(`.nc-erd-table-node-city`).find(`.nc-erd-table-node-city-column-${colTitle}`).should('exist'); |
||||||
|
}); |
||||||
|
|
||||||
|
cy.get('.nc-erd-context-menu').get('.nc-erd-showPkAndFk-checkbox').click(); |
||||||
|
|
||||||
|
}) |
||||||
|
cy.getActiveModal().find('.nc-modal-close').click({ force: true }); |
||||||
|
}) |
||||||
|
|
||||||
|
it('create column and check if the change is in the schema', () => { |
||||||
|
mainPage.addColumn('test_column', 'country') |
||||||
|
|
||||||
|
// table view
|
||||||
|
mainPage.openTableErdView(); |
||||||
|
cy.get('.nc-erd-vue-flow-single-table').within(() => { |
||||||
|
cy.get('.nc-erd-table-node-country').find('.nc-erd-table-node-country-column-test_column').should('exist'); |
||||||
|
}) |
||||||
|
cy.getActiveModal().find('.nc-modal-close').click({ force: true }); |
||||||
|
|
||||||
|
// All table view
|
||||||
|
mainPage.openErdTab(); |
||||||
|
cy.get('.nc-erd-vue-flow').within(() => { |
||||||
|
cy.get('.nc-erd-table-node-country').find('.nc-erd-table-node-country-column-test_column').should('exist'); |
||||||
|
}) |
||||||
|
mainPage.closeMetaTab(); |
||||||
|
|
||||||
|
|
||||||
|
mainPage.deleteColumn('test_column') |
||||||
|
|
||||||
|
// table view
|
||||||
|
mainPage.openTableErdView(); |
||||||
|
cy.get('.nc-erd-vue-flow-single-table').within(() => { |
||||||
|
cy.get('.nc-erd-table-node-country').find('.nc-erd-table-node-country-column-test_column').should('not.exist'); |
||||||
|
}) |
||||||
|
cy.getActiveModal().find('.nc-modal-close').click({ force: true }); |
||||||
|
|
||||||
|
// All table view
|
||||||
|
mainPage.openErdTab(); |
||||||
|
cy.get('.nc-erd-vue-flow').within(() => { |
||||||
|
cy.get('.nc-erd-table-node-country').find('.nc-erd-table-node-country-column-test_column').should('not.exist'); |
||||||
|
}) |
||||||
|
mainPage.closeMetaTab(); |
||||||
|
}) |
||||||
|
|
||||||
|
it('Create table should reflected in ERD', () => { |
||||||
|
cy.createTable('new') |
||||||
|
|
||||||
|
mainPage.openErdTab(); |
||||||
|
cy.get('.nc-erd-vue-flow').within(() => { |
||||||
|
cy.get('.nc-erd-table-node-new').should('exist'); |
||||||
|
}) |
||||||
|
mainPage.closeMetaTab(); |
||||||
|
|
||||||
|
cy.deleteTable('new') |
||||||
|
|
||||||
|
mainPage.openErdTab(); |
||||||
|
cy.get('.nc-erd-vue-flow').within(() => { |
||||||
|
cy.get('.nc-erd-table-node-new').should('not.exist'); |
||||||
|
}) |
||||||
|
mainPage.closeMetaTab(); |
||||||
|
}) |
||||||
|
|
||||||
|
it(`Disable MM setting Open Table ERD and check easter egg should not work`, () => { |
||||||
|
mainPage.toggleShowMMSetting(); |
||||||
|
|
||||||
|
mainPage.openErdTab(); |
||||||
|
cy.get('.nc-erd-vue-flow').within(() => { |
||||||
|
cy.get('.nc-erd-context-menu').find('.nc-erd-showColumns-label').dblclick(); |
||||||
|
cy.get('.nc-erd-context-menu').find('.ant-checkbox').should('have.length', 3); |
||||||
|
}) |
||||||
|
|
||||||
|
mainPage.closeMetaTab(); |
||||||
|
}); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
/** |
||||||
|
* @copyright Copyright (c) 2021, Xgene Cloud Ltd |
||||||
|
* |
||||||
|
* @author Raju Udava <sivadstala@gmail.com> |
||||||
|
* |
||||||
|
* @license GNU AGPL version 3 or any later version |
||||||
|
* |
||||||
|
* This program is free software: you can redistribute it and/or modify |
||||||
|
* it under the terms of the GNU Affero General Public License as |
||||||
|
* published by the Free Software Foundation, either version 3 of the |
||||||
|
* License, or (at your option) any later version. |
||||||
|
* |
||||||
|
* This program is distributed in the hope that it will be useful, |
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of |
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
||||||
|
* GNU Affero General Public License for more details. |
||||||
|
* |
||||||
|
* You should have received a copy of the GNU Affero General Public License |
||||||
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
|
* |
||||||
|
*/ |
Loading…
Reference in new issue