Browse Source

Merge branch 'develop' into fix/gui-v2-form-view-email-me

pull/3364/head
Wing-Kam Wong 2 years ago
parent
commit
acd2423c1a
  1. 91
      .github/workflows/ci-cd-v2.yml
  2. 4
      packages/nc-gui-v2/assets/style.scss
  3. 2
      packages/nc-gui-v2/components.d.ts
  4. 12
      packages/nc-gui-v2/components/general/ColorPicker.vue
  5. 6
      packages/nc-gui-v2/components/smartsheet/Grid.vue
  6. 12
      packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue
  7. 14
      packages/nc-gui-v2/components/tabs/Smartsheet.vue
  8. 2
      packages/nc-gui-v2/components/webhook/Editor.vue
  9. 11
      packages/nc-gui-v2/composables/useLTARStore.ts
  10. 48
      packages/nc-gui-v2/composables/useProject.ts
  11. 35
      packages/nc-gui-v2/composables/useTheme/index.ts
  12. 7
      packages/nc-gui-v2/composables/useViewData.ts
  13. 2
      packages/nc-gui-v2/nuxt.config.ts
  14. 28
      packages/nc-gui-v2/package-lock.json
  15. 6
      packages/nc-gui-v2/package.json
  16. 170
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index.vue
  17. 1
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index.vue
  18. 17
      packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index/[type]/[title]/[[viewTitle]].vue
  19. 70
      packages/nc-gui-v2/pages/index/index/[id].vue
  20. 8
      packages/nc-gui-v2/pages/index/index/index.vue
  21. 3
      packages/nc-gui-v2/plugins/ant.ts
  22. 20
      packages/nc-gui-v2/scripts/updateNuxtRouting.js
  23. 11740
      packages/nocodb-sdk/package-lock.json
  24. 11
      packages/nocodb-sdk/package.json
  25. 2
      packages/nocodb/src/lib/meta/api/modelVisibilityApis.ts
  26. 14
      packages/nocodb/src/lib/meta/api/projectApis.ts
  27. 12
      packages/nocodb/src/lib/meta/helpers/extractProps.ts
  28. 2
      packages/nocodb/src/lib/models/Base.ts
  29. 2
      packages/nocodb/src/lib/models/FormViewColumn.ts
  30. 2
      packages/nocodb/src/lib/models/FormulaColumn.ts
  31. 2
      packages/nocodb/src/lib/models/GridViewColumn.ts
  32. 2
      packages/nocodb/src/lib/models/HookLog.ts
  33. 2
      packages/nocodb/src/lib/models/Project.ts
  34. 2
      packages/nocodb/src/lib/models/SyncSource.ts
  35. 2
      packages/nocodb/src/lib/models/User.ts
  36. 2
      packages/nocodb/src/lib/models/View.ts
  37. 1
      scripts/cypress-v2/integration/common/00_pre_configurations.js
  38. 14
      scripts/cypress-v2/integration/common/1c_sql_view.js
  39. 2
      scripts/cypress-v2/integration/common/3e_duration_column.js
  40. 28
      scripts/cypress-v2/integration/common/4a_table_view_grid_gallery_form.js
  41. 28
      scripts/cypress-v2/integration/common/4b_table_view_share.js
  42. 129
      scripts/cypress-v2/integration/common/4c_form_view_detailed.js
  43. 8
      scripts/cypress-v2/integration/common/4d_table_view_grid_locked.js
  44. 12
      scripts/cypress-v2/integration/common/4e_form_view_share.js
  45. 144
      scripts/cypress-v2/integration/common/4f_grid_view_share.js
  46. 252
      scripts/cypress-v2/integration/common/5a_user_role.js
  47. 12
      scripts/cypress-v2/integration/common/5b_preview_role.js
  48. 10
      scripts/cypress-v2/integration/common/7b_import_from_airtable.js
  49. 242
      scripts/cypress-v2/integration/common/9a_QuickTest.js
  50. 78
      scripts/cypress-v2/integration/spec/roleValidation.spec.js
  51. 2
      scripts/cypress-v2/integration/test/restRoles.js
  52. 5
      scripts/cypress-v2/integration/test/restViews.js
  53. 65
      scripts/cypress-v2/support/commands.js
  54. 22
      scripts/cypress-v2/support/page_objects/mainPage.js
  55. 11
      scripts/cypress-v2/support/page_objects/navigation.js

91
.github/workflows/ci-cd-v2.yml

@ -635,4 +635,93 @@ jobs:
# name: cypress-pg-restMisc-run-cache-snapshots # name: cypress-pg-restMisc-run-cache-snapshots
# path: scripts/cypress-v2/screenshots # path: scripts/cypress-v2/screenshots
# retention-days: 2 # retention-days: 2
# #
cy-quick-sqlite:
runs-on: ubuntu-20.04
steps:
- name: Setup Node
uses: actions/setup-node@v1
with:
node-version: 16.15.0
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Set env
run: echo "NODE_ENV=test" >> $GITHUB_ENV
- name: Cypress run
uses: cypress-io/github-action@v2
with:
start: |
cp ./scripts/cypress-v2/fixtures/quickTest/noco_0_91_7.db ./packages/nocodb/noco.db
npm run start:api:cache
npm run start:web-v2
docker-compose -f ./scripts/docker-compose-cypress.yml up -d
spec: "./scripts/cypress-v2/integration/test/quickTest.js"
wait-on: "http://localhost:8080"
wait-on-timeout: 1200
config-file: scripts/cypress-v2/cypress.json
- name: Upload screenshots
if: always()
uses: actions/upload-artifact@v2
with:
name: cy-quick-sqlite-snapshots
path: scripts/cypress-v2/screenshots
retention-days: 2
cy-quick-pg:
runs-on: ubuntu-20.04
steps:
- name: Setup Node
uses: actions/setup-node@v1
with:
node-version: 16.15.0
- name: Checkout
uses: actions/checkout@v2
with:
fetch-depth: 0
- name: Cache node modules
uses: actions/cache@v2
env:
cache-name: cache-node-modules
with:
# npm cache files are stored in `~/.npm` on Linux/macOS
path: ~/.npm
key: ${{ runner.os }}-build-${{ env.cache-name }}-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-build-${{ env.cache-name }}-
${{ runner.os }}-build-
${{ runner.os }}-
- name: Set env
run: echo "NODE_ENV=test" >> $GITHUB_ENV
- name: Cypress run
uses: cypress-io/github-action@v2
with:
start: |
docker-compose -f ./scripts/cypress-v2/docker-compose-pg-cy-quick.yml up -d
npm run start:api:cache:pg:cyquick
npm run start:web-v2
spec: "./scripts/cypress-v2/integration/test/quickTest.js"
wait-on: "http://localhost:8080"
wait-on-timeout: 1200
config-file: scripts/cypress-v2/cypress.json
- name: Upload screenshots
if: always()
uses: actions/upload-artifact@v2
with:
name: cy-quick-pg-snapshots
path: scripts/cypress-v2/screenshots
retention-days: 2

4
packages/nc-gui-v2/assets/style.scss

@ -214,10 +214,6 @@ a {
@apply !p-0 !rounded; @apply !p-0 !rounded;
} }
.ant-dropdown-menu-submenu-popup {
@apply scrollbar-thin-dull min-w-50 max-h-90vh overflow-auto !shadow !rounded;
}
.ant-tabs-dropdown-menu-title-content { .ant-tabs-dropdown-menu-title-content {
@apply flex items-center; @apply flex items-center;
} }

2
packages/nc-gui-v2/components.d.ts vendored

@ -170,6 +170,7 @@ declare module '@vue/runtime-core' {
MdiInformation: typeof import('~icons/mdi/information')['default'] MdiInformation: typeof import('~icons/mdi/information')['default']
MdiJson: typeof import('~icons/mdi/json')['default'] MdiJson: typeof import('~icons/mdi/json')['default']
MdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default'] MdiKeyboardReturn: typeof import('~icons/mdi/keyboard-return')['default']
MdiKeyChange: typeof import('~icons/mdi/key-change')['default']
MdiKeyPlus: typeof import('~icons/mdi/key-plus')['default'] MdiKeyPlus: typeof import('~icons/mdi/key-plus')['default']
MdiKeyStar: typeof import('~icons/mdi/key-star')['default'] MdiKeyStar: typeof import('~icons/mdi/key-star')['default']
MdiLink: typeof import('~icons/mdi/link')['default'] MdiLink: typeof import('~icons/mdi/link')['default']
@ -186,6 +187,7 @@ declare module '@vue/runtime-core' {
MdiMinusCircleOutline: typeof import('~icons/mdi/minus-circle-outline')['default'] MdiMinusCircleOutline: typeof import('~icons/mdi/minus-circle-outline')['default']
MdiMoonFull: typeof import('~icons/mdi/moon-full')['default'] MdiMoonFull: typeof import('~icons/mdi/moon-full')['default']
MdiNotebookCheckOutline: typeof import('~icons/mdi/notebook-check-outline')['default'] MdiNotebookCheckOutline: typeof import('~icons/mdi/notebook-check-outline')['default']
MdiNull: typeof import('~icons/mdi/null')['default']
MdiNumeric: typeof import('~icons/mdi/numeric')['default'] MdiNumeric: typeof import('~icons/mdi/numeric')['default']
MdiOpenInNew: typeof import('~icons/mdi/open-in-new')['default'] MdiOpenInNew: typeof import('~icons/mdi/open-in-new')['default']
MdiPencil: typeof import('~icons/mdi/pencil')['default'] MdiPencil: typeof import('~icons/mdi/pencil')['default']

12
packages/nc-gui-v2/components/general/ColorPicker.vue

@ -23,22 +23,22 @@ const emit = defineEmits(['update:modelValue'])
const vModel = computed({ const vModel = computed({
get: () => props.modelValue, get: () => props.modelValue,
set: (val) => { set: (val) => {
emit('update:modelValue', val.hex ? val.hex : val || null) emit('update:modelValue', val.hex8 ? val.hex8 : val || null)
}, },
}) })
const picked = ref<string | Record<string, any>>(props.modelValue || enumColor.light[0]) const picked = ref<string | Record<string, any>>(props.modelValue || enumColor.light[0])
const selectColor = (color: string | Record<string, any>) => { const selectColor = (color: string | Record<string, any>) => {
picked.value = typeof color === 'string' ? color : color.hex ? color.hex : color picked.value = typeof color === 'string' ? color : color.hex8 ? color.hex8 : color
vModel.value = typeof color === 'string' ? color : color.hex ? color.hex : color vModel.value = typeof color === 'string' ? color : color.hex8 ? color.hex8 : color
} }
const compare = (colorA: string, colorB: string) => colorA.toLowerCase() === colorB.toLowerCase() const compare = (colorA: string, colorB: string) => colorA.toLowerCase() === colorB.toLowerCase()
watch(picked, (n, _o) => { watch(picked, (n, _o) => {
if (!props.pickButton) { if (!props.pickButton) {
vModel.value = typeof n === 'string' ? n : n.hex ? n.hex : n vModel.value = typeof n === 'string' ? n : n.hex8 ? n.hex8 : n
} }
}) })
</script> </script>
@ -50,11 +50,11 @@ watch(picked, (n, _o) => {
v-for="(color, i) of colors.slice((colId - 1) * rowSize, colId * rowSize)" v-for="(color, i) of colors.slice((colId - 1) * rowSize, colId * rowSize)"
:key="`color-${colId}-${i}`" :key="`color-${colId}-${i}`"
class="color-selector" class="color-selector"
:class="compare(picked, color) ? 'selected' : ''" :class="compare(typeof picked === 'string' ? picked : picked.hex8, color) ? 'selected' : ''"
:style="{ 'background-color': `${color}` }" :style="{ 'background-color': `${color}` }"
@click="selectColor(color)" @click="selectColor(color)"
> >
{{ compare(picked, color) ? '&#10003;' : '' }} {{ compare(typeof picked === 'string' ? picked : picked.hex8, color) ? '&#10003;' : '' }}
</button> </button>
</div> </div>
<a-card v-if="props.advanced" class="w-full mt-2" :body-style="{ padding: '0px' }" :bordered="false"> <a-card v-if="props.advanced" class="w-full mt-2" :body-style="{ padding: '0px' }" :bordered="false">

6
packages/nc-gui-v2/components/smartsheet/Grid.vue

@ -79,6 +79,7 @@ const expandedFormRowState = ref<Record<string, any>>()
const visibleColLength = $computed(() => fields.value?.length) const visibleColLength = $computed(() => fields.value?.length)
const { const {
isLoading,
loadData, loadData,
paginationData, paginationData,
formattedData: data, formattedData: data,
@ -311,7 +312,10 @@ const onNavigate = (dir: NavigateDir) => {
<template> <template>
<div class="flex flex-col h-full min-h-0 w-full"> <div class="flex flex-col h-full min-h-0 w-full">
<div class="nc-grid-wrapper min-h-0 flex-1 scrollbar-thin-dull"> <div v-if="isLoading" class="flex items-center justify-center h-full w-full">
<a-spin size="large" />
</div>
<div v-else class="nc-grid-wrapper min-h-0 flex-1 scrollbar-thin-dull">
<a-dropdown v-model:visible="contextMenu" :trigger="['contextmenu']"> <a-dropdown v-model:visible="contextMenu" :trigger="['contextmenu']">
<table <table
ref="smartTable" ref="smartTable"

12
packages/nc-gui-v2/components/smartsheet/sidebar/MenuTop.vue

@ -24,7 +24,9 @@ import DlgViewDelete from '~/components/dlg/ViewDelete.vue'
interface Emits { interface Emits {
(event: 'openModal', data: { type: ViewTypes; title?: string; copyViewId?: string }): void (event: 'openModal', data: { type: ViewTypes; title?: string; copyViewId?: string }): void
(event: 'deleted'): void (event: 'deleted'): void
(event: 'sorted'): void (event: 'sorted'): void
} }
@ -186,8 +188,14 @@ function openDeleteDialog(view: Record<string, any>) {
closeDialog() closeDialog()
emits('deleted') emits('deleted')
// return to the default view if (activeView.value === view) {
activeView.value = views.value[0] // return to the default view
router.replace({
params: {
viewTitle: views.value[0].title,
},
})
}
}, },
}) })

14
packages/nc-gui-v2/components/tabs/Smartsheet.vue

@ -10,7 +10,6 @@ import {
MetaInj, MetaInj,
OpenNewRecordFormHookInj, OpenNewRecordFormHookInj,
ReloadViewDataHookInj, ReloadViewDataHookInj,
TabMetaInj,
computed, computed,
inject, inject,
provide, provide,
@ -18,12 +17,11 @@ import {
useMetas, useMetas,
useProvideSmartsheetStore, useProvideSmartsheetStore,
watch, watch,
watchEffect,
} from '#imports' } from '#imports'
import type { TabItem } from '~/composables' import type { TabItem } from '~/composables'
const { getMeta, metas } = useMetas() const { metas } = useMetas()
const activeView = ref() const activeView = ref()
@ -35,13 +33,8 @@ const tabMeta = inject(
TabMetaInj, TabMetaInj,
computed(() => ({} as TabItem)), computed(() => ({} as TabItem)),
) )
const meta = computed<TableType>(() => metas.value?.[tabMeta?.value?.id as string]) const meta = computed<TableType>(() => metas.value?.[tabMeta?.value?.id as string])
watchEffect(async () => {
await getMeta(tabMeta?.value?.id as string)
})
const reloadEventHook = createEventHook<void>() const reloadEventHook = createEventHook<void>()
const openNewRecordFormHook = createEventHook<void>() const openNewRecordFormHook = createEventHook<void>()
@ -52,7 +45,6 @@ provideSidebar({ storageKey: 'nc-right-sidebar' })
// todo: move to store // todo: move to store
provide(MetaInj, meta) provide(MetaInj, meta)
provide(TabMetaInj, tabMeta)
provide(ActiveViewInj, activeView) provide(ActiveViewInj, activeView)
provide(IsLockedInj, isLocked) provide(IsLockedInj, isLocked)
provide(ReloadViewDataHookInj, reloadEventHook) provide(ReloadViewDataHookInj, reloadEventHook)
@ -62,10 +54,6 @@ provide(IsFormInj, isForm)
const treeViewIsLockedInj = inject('TreeViewIsLockedInj', ref(false)) const treeViewIsLockedInj = inject('TreeViewIsLockedInj', ref(false))
watch(tabMeta, async (newTabMeta, oldTabMeta) => {
if (newTabMeta !== oldTabMeta && newTabMeta?.id) await getMeta(newTabMeta.id)
})
watch(isLocked, (nextValue) => (treeViewIsLockedInj.value = nextValue), { immediate: true }) watch(isLocked, (nextValue) => (treeViewIsLockedInj.value = nextValue), { immediate: true })
</script> </script>

2
packages/nc-gui-v2/components/webhook/Editor.vue

@ -475,7 +475,7 @@ onMounted(async () => {
<a-row v-if="hook.notification.type === 'URL'" class="mb-5" type="flex" :gutter="[16, 0]"> <a-row v-if="hook.notification.type === 'URL'" class="mb-5" type="flex" :gutter="[16, 0]">
<a-col :span="6"> <a-col :span="6">
<a-select v-model:value="hook.notification.payload.method" size="large"> <a-select v-model:value="hook.notification.payload.method" size="large" class="nc-select-hook-url-method">
<a-select-option v-for="(method, i) in methodList" :key="i" :value="method.title">{{ method.title }}</a-select-option> <a-select-option v-for="(method, i) in methodList" :key="i" :value="method.title">{{ method.title }}</a-select-option>
</a-select> </a-select>
</a-col> </a-col>

11
packages/nc-gui-v2/composables/useLTARStore.ts

@ -47,8 +47,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
const colOptions = $computed(() => column?.value.colOptions as LinkToAnotherRecordType) const colOptions = $computed(() => column?.value.colOptions as LinkToAnotherRecordType)
const { sharedView } = useSharedView() as Record<string, any> const { sharedView } = useSharedView() as Record<string, any>
const projectId = project?.value?.id || sharedView.value?.view?.project_id
const projectId = project?.value?.id || sharedView.value.view.project_id
// getters // getters
const meta = computed(() => metas?.value?.[column?.value?.fk_model_id as string]) const meta = computed(() => metas?.value?.[column?.value?.fk_model_id as string])
@ -131,7 +130,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
meta.value.id, meta.value.id,
rowId.value, rowId.value,
colOptions.type as 'mm' | 'hm', colOptions.type as 'mm' | 'hm',
column?.value?.title, encodeURIComponent(column?.value?.title),
{ {
limit: String(childrenExcludedListPagination.size), limit: String(childrenExcludedListPagination.size),
offset: String(childrenExcludedListPagination.size * (childrenExcludedListPagination.page - 1)), offset: String(childrenExcludedListPagination.size * (childrenExcludedListPagination.page - 1)),
@ -157,7 +156,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
meta.value.id, meta.value.id,
rowId.value, rowId.value,
colOptions.type as 'mm' | 'hm', colOptions.type as 'mm' | 'hm',
column?.value?.title, encodeURIComponent(column?.value?.title),
{ {
limit: String(childrenListPagination.size), limit: String(childrenListPagination.size),
offset: String(childrenListPagination.size * (childrenListPagination.page - 1)), offset: String(childrenListPagination.size * (childrenListPagination.page - 1)),
@ -213,7 +212,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
meta.value.title, meta.value.title,
rowId.value, rowId.value,
colOptions.type as 'mm' | 'hm', colOptions.type as 'mm' | 'hm',
column?.value?.title, encodeURIComponent(column?.value?.title),
getRelatedTableRowId(row) as string, getRelatedTableRowId(row) as string,
) )
} catch (e: any) { } catch (e: any) {
@ -248,7 +247,7 @@ const [useProvideLTARStore, useLTARStore] = useInjectionState(
meta.value.title as string, meta.value.title as string,
rowId.value, rowId.value,
colOptions.type as 'mm' | 'hm', colOptions.type as 'mm' | 'hm',
column?.value?.title, encodeURIComponent(column?.value?.title),
getRelatedTableRowId(row) as string, getRelatedTableRowId(row) as string,
) )
await loadChildrenList() await loadChildrenList()

48
packages/nc-gui-v2/composables/useProject.ts

@ -4,6 +4,7 @@ import type { MaybeRef } from '@vueuse/core'
import { useNuxtApp, useRoute, useState } from '#app' import { useNuxtApp, useRoute, useState } from '#app'
import type { ProjectMetaInfo } from '~/lib' import type { ProjectMetaInfo } from '~/lib'
import { USER_PROJECT_ROLES } from '~/lib' import { USER_PROJECT_ROLES } from '~/lib'
import type { ThemeConfig } from '@/composables/useTheme'
export function useProject(projectId?: MaybeRef<string>) { export function useProject(projectId?: MaybeRef<string>) {
const projectRoles = useState<Record<string, boolean>>(USER_PROJECT_ROLES, () => ({})) const projectRoles = useState<Record<string, boolean>>(USER_PROJECT_ROLES, () => ({}))
@ -14,9 +15,11 @@ export function useProject(projectId?: MaybeRef<string>) {
const tables = useState<TableType[]>('tables', () => [] as TableType[]) const tables = useState<TableType[]>('tables', () => [] as TableType[])
const route = useRoute() const route = useRoute()
const { includeM2M } = useGlobal() const { includeM2M } = useGlobal()
const { setTheme } = useTheme()
const projectMetaInfo = useState<ProjectMetaInfo | undefined>('projectMetaInfo') const projectMetaInfo = useState<ProjectMetaInfo | undefined>('projectMetaInfo')
// todo: refactor path param name and variable name // todo: refactor path param name and variable name
const projectType = $computed(() => route.params.projectType as string) const projectType = $computed(() => route.params.projectType as string)
const isLoaded = ref(false)
const projectBaseType = $computed(() => project.value?.bases?.[0]?.type || '') const projectBaseType = $computed(() => project.value?.bases?.[0]?.type || '')
const isMysql = computed(() => ['mysql', 'mysql2'].includes(projectBaseType)) const isMysql = computed(() => ['mysql', 'mysql2'].includes(projectBaseType))
@ -27,6 +30,14 @@ export function useProject(projectId?: MaybeRef<string>) {
) )
const isSharedBase = computed(() => projectType === 'base') const isSharedBase = computed(() => projectType === 'base')
const projectMeta = computed(() => {
try {
return typeof project.value.meta === 'string' ? JSON.parse(project.value.meta) : project.value.meta
} catch (e) {
return {}
}
})
async function loadProjectMetaInfo(force?: boolean) { async function loadProjectMetaInfo(force?: boolean) {
if (!projectMetaInfo.value || force) { if (!projectMetaInfo.value || force) {
const data = await $api.project.metaGet(project.value.id!, {}, {}) const data = await $api.project.metaGet(project.value.id!, {}, {})
@ -71,16 +82,51 @@ export function useProject(projectId?: MaybeRef<string>) {
} else { } else {
_projectId = route.params.projectId as string _projectId = route.params.projectId as string
} }
isLoaded.value = true
project.value = await $api.project.read(_projectId!) project.value = await $api.project.read(_projectId!)
await loadProjectRoles() await loadProjectRoles()
await loadTables() await loadTables()
setTheme(projectMeta.value?.theme)
} }
async function updateProject(data: Partial<ProjectType>) {
if (unref(projectId)) {
_projectId = unref(projectId)!
} else if (projectType === 'base') {
return
} else {
_projectId = route.params.projectId as string
}
await $api.project.update(_projectId, data)
}
async function saveTheme(theme: Partial<ThemeConfig>) {
await updateProject({
meta: JSON.stringify({
...projectMeta.value,
theme,
}),
})
setTheme(theme)
}
onScopeDispose(() => {
if (isLoaded.value === true) {
project.value = {}
tables.value = []
projectMetaInfo.value = undefined
projectRoles.value = {}
setTheme({})
}
})
return { return {
project, project,
tables, tables,
loadProjectRoles, loadProjectRoles,
loadProject, loadProject,
updateProject,
loadTables, loadTables,
isMysql, isMysql,
isMssql, isMssql,
@ -89,5 +135,7 @@ export function useProject(projectId?: MaybeRef<string>) {
isSharedBase, isSharedBase,
loadProjectMetaInfo, loadProjectMetaInfo,
projectMetaInfo, projectMetaInfo,
projectMeta,
saveTheme,
} }
} }

35
packages/nc-gui-v2/composables/useTheme/index.ts

@ -1,9 +1,9 @@
import { ConfigProvider } from 'ant-design-vue' import { ConfigProvider } from 'ant-design-vue'
import type { Theme as AntTheme } from 'ant-design-vue/es/config-provider' import type { Theme as AntTheme } from 'ant-design-vue/es/config-provider'
import { useStorage } from '@vueuse/core' import tinycolor from 'tinycolor2'
import { NOCO, hexToRGB, themeV2Colors, useCssVar, useInjectionState } from '#imports' import { hexToRGB, themeV2Colors, useCssVar, useInjectionState } from '#imports'
interface ThemeConfig extends AntTheme { export interface ThemeConfig extends AntTheme {
primaryColor: string primaryColor: string
accentColor: string accentColor: string
} }
@ -13,29 +13,32 @@ const [setup, use] = useInjectionState((config?: Partial<ThemeConfig>) => {
const accentColor = useCssVar('--color-accent', typeof document !== 'undefined' ? document.documentElement : null) const accentColor = useCssVar('--color-accent', typeof document !== 'undefined' ? document.documentElement : null)
/** current theme config */ /** current theme config */
const currentTheme = useStorage<ThemeConfig>( const currentTheme = ref({
`${NOCO}db-theme`, primaryColor: themeV2Colors['royal-blue'].DEFAULT,
{ accentColor: themeV2Colors.pink['500'],
primaryColor: themeV2Colors['royal-blue'].DEFAULT, })
accentColor: themeV2Colors.pink['500'],
},
localStorage,
{ mergeDefaults: true },
)
/** set initial config */ /** set initial config */
setTheme(config ?? currentTheme.value) setTheme(config ?? currentTheme.value)
/** set theme (persists in localstorage) */ /** set theme (persists in localstorage) */
function setTheme(theme: Partial<ThemeConfig>) { function setTheme(theme: Partial<ThemeConfig>) {
const themePrimary = theme?.primaryColor ? tinycolor(theme.primaryColor) : tinycolor(themeV2Colors['royal-blue'].DEFAULT)
const themeAccent = theme?.accentColor ? tinycolor(theme.accentColor) : tinycolor(themeV2Colors.pink['500'])
// convert hex colors to rgb values // convert hex colors to rgb values
if (theme.primaryColor) primaryColor.value = hexToRGB(theme.primaryColor) primaryColor.value = themePrimary.isValid()
if (theme.accentColor) accentColor.value = hexToRGB(theme.accentColor) ? hexToRGB(themePrimary.toHex8String())
: hexToRGB(themeV2Colors['royal-blue'].DEFAULT)
accentColor.value = themeAccent.isValid() ? hexToRGB(themeAccent.toHex8String()) : hexToRGB(themeV2Colors.pink['500'])
currentTheme.value = theme as ThemeConfig currentTheme.value = {
primaryColor: themePrimary.toHex8String().toUpperCase(),
accentColor: themeAccent.toHex8String().toUpperCase(),
}
ConfigProvider.config({ ConfigProvider.config({
theme, theme: currentTheme.value,
}) })
} }

7
packages/nc-gui-v2/composables/useViewData.ts

@ -8,6 +8,7 @@ import {
extractPkFromRow, extractPkFromRow,
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
getHTMLEncodedText, getHTMLEncodedText,
useApi,
useProject, useProject,
useUIPermission, useUIPermission,
} from '#imports' } from '#imports'
@ -38,6 +39,7 @@ export function useViewData(
throw new Error('Table meta is not available') throw new Error('Table meta is not available')
} }
const { api, isLoading, error } = useApi()
const _paginationData = ref<PaginatedType>({ page: 1, pageSize: 25 }) const _paginationData = ref<PaginatedType>({ page: 1, pageSize: 25 })
const aggCommentCount = ref<{ row_id: string; count: number }[]>([]) const aggCommentCount = ref<{ row_id: string; count: number }[]>([])
const galleryData = ref<GalleryType>() const galleryData = ref<GalleryType>()
@ -114,9 +116,8 @@ export function useViewData(
const loadData = async (params: Parameters<Api<any>['dbViewRow']['list']>[4] = {}) => { const loadData = async (params: Parameters<Api<any>['dbViewRow']['list']>[4] = {}) => {
if ((!project?.value?.id || !meta?.value?.id || !viewMeta?.value?.id) && !isPublic.value) return if ((!project?.value?.id || !meta?.value?.id || !viewMeta?.value?.id) && !isPublic.value) return
const response = !isPublic.value const response = !isPublic.value
? await $api.dbViewRow.list('noco', project.value.id!, meta.value.id!, viewMeta!.value.id, { ? await api.dbViewRow.list('noco', project.value.id!, meta.value.id!, viewMeta!.value.id, {
...params, ...params,
...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }), ...(isUIAllowed('sortSync') ? {} : { sortArrJson: JSON.stringify(sorts.value) }),
...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }), ...(isUIAllowed('filterSync') ? {} : { filterArrJson: JSON.stringify(nestedFilters.value) }),
@ -351,6 +352,8 @@ export function useViewData(
} }
return { return {
error,
isLoading,
loadData, loadData,
paginationData, paginationData,
queryParams, queryParams,

2
packages/nc-gui-v2/nuxt.config.ts

@ -13,7 +13,7 @@ export default defineNuxtConfig({
ssr: false, ssr: false,
app: { app: {
baseURL: '/dashboard/', baseURL: process.env.NODE_ENV === 'production' ? '.' : undefined,
}, },
css: [ css: [
'virtual:windi.css', 'virtual:windi.css',

28
packages/nc-gui-v2/package-lock.json generated

@ -4,6 +4,7 @@
"requires": true, "requires": true,
"packages": { "packages": {
"": { "": {
"hasInstallScript": true,
"dependencies": { "dependencies": {
"@ckpack/vue-color": "^1.2.0", "@ckpack/vue-color": "^1.2.0",
"@vuelidate/core": "^2.0.0-alpha.44", "@vuelidate/core": "^2.0.0-alpha.44",
@ -23,6 +24,7 @@
"papaparse": "^5.3.2", "papaparse": "^5.3.2",
"socket.io-client": "^4.5.1", "socket.io-client": "^4.5.1",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"tinycolor2": "^1.4.2",
"unique-names-generator": "^4.7.1", "unique-names-generator": "^4.7.1",
"vue-dompurify-html": "^3.0.0", "vue-dompurify-html": "^3.0.0",
"vue-github-button": "^3.0.3", "vue-github-button": "^3.0.3",
@ -50,6 +52,7 @@
"@types/file-saver": "^2.0.5", "@types/file-saver": "^2.0.5",
"@types/papaparse": "^5.3.2", "@types/papaparse": "^5.3.2",
"@types/sortablejs": "^1.13.0", "@types/sortablejs": "^1.13.0",
"@types/tinycolor2": "^1.4.3",
"@vitest/ui": "^0.18.0", "@vitest/ui": "^0.18.0",
"@vue/compiler-sfc": "^3.2.37", "@vue/compiler-sfc": "^3.2.37",
"@vue/test-utils": "^2.0.2", "@vue/test-utils": "^2.0.2",
@ -2446,6 +2449,12 @@
"integrity": "sha512-C3064MH72iEfeGCYEGCt7FCxXoAXaMPG0QPnstcxvPmbl54erpISu06d++FY37Smja64iWy5L8wOyHHBghWbJQ==", "integrity": "sha512-C3064MH72iEfeGCYEGCt7FCxXoAXaMPG0QPnstcxvPmbl54erpISu06d++FY37Smja64iWy5L8wOyHHBghWbJQ==",
"dev": true "dev": true
}, },
"node_modules/@types/tinycolor2": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.3.tgz",
"integrity": "sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ==",
"dev": true
},
"node_modules/@types/tough-cookie": { "node_modules/@types/tough-cookie": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz",
@ -13677,6 +13686,14 @@
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==", "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==",
"dev": true "dev": true
}, },
"node_modules/tinycolor2": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz",
"integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==",
"engines": {
"node": "*"
}
},
"node_modules/tinypool": { "node_modules/tinypool": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.2.2.tgz", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.2.2.tgz",
@ -17077,6 +17094,12 @@
"integrity": "sha512-C3064MH72iEfeGCYEGCt7FCxXoAXaMPG0QPnstcxvPmbl54erpISu06d++FY37Smja64iWy5L8wOyHHBghWbJQ==", "integrity": "sha512-C3064MH72iEfeGCYEGCt7FCxXoAXaMPG0QPnstcxvPmbl54erpISu06d++FY37Smja64iWy5L8wOyHHBghWbJQ==",
"dev": true "dev": true
}, },
"@types/tinycolor2": {
"version": "1.4.3",
"resolved": "https://registry.npmjs.org/@types/tinycolor2/-/tinycolor2-1.4.3.tgz",
"integrity": "sha512-Kf1w9NE5HEgGxCRyIcRXR/ZYtDv0V8FVPtYHwLxl0O+maGX0erE77pQlD0gpP+/KByMZ87mOA79SjifhSB3PjQ==",
"dev": true
},
"@types/tough-cookie": { "@types/tough-cookie": {
"version": "4.0.2", "version": "4.0.2",
"resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz", "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.2.tgz",
@ -25393,6 +25416,11 @@
"integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==", "integrity": "sha512-1Uhn/aqw5C6RI4KejVeTg6mIS7IqxnLJ8Mv2tV5rTc0qWobay7pDUz6Wi392Cnc8ak1H0F2cjoRzb2/AW4+Fvg==",
"dev": true "dev": true
}, },
"tinycolor2": {
"version": "1.4.2",
"resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz",
"integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA=="
},
"tinypool": { "tinypool": {
"version": "0.2.2", "version": "0.2.2",
"resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.2.2.tgz", "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.2.2.tgz",

6
packages/nc-gui-v2/package.json

@ -10,8 +10,8 @@
"test:ui": "vitest -c test/vite.config.ts --ui", "test:ui": "vitest -c test/vite.config.ts --ui",
"coverage": "vitest -c test/vite.config.ts run --coverage", "coverage": "vitest -c test/vite.config.ts run --coverage",
"build:copy": "npm run generate; rm -rf ../nc-lib-gui-v2/lib/dist/; rsync -rvzh ./dist/ ../nc-lib-gui-v2/lib/dist/", "build:copy": "npm run generate; rm -rf ../nc-lib-gui-v2/lib/dist/; rsync -rvzh ./dist/ ../nc-lib-gui-v2/lib/dist/",
"build:copy:publish": "npm run generate; rm -rf ../nc-lib-gui-v2/lib/dist/; rsync -rvzh ./dist/ ../nc-lib-gui-v2/lib/dist/; npm publish ../nc-lib-gui-v2" "build:copy:publish": "npm run generate; rm -rf ../nc-lib-gui-v2/lib/dist/; rsync -rvzh ./dist/ ../nc-lib-gui-v2/lib/dist/; npm publish ../nc-lib-gui-v2",
"postinstall": "node scripts/updateNuxtRouting.js"
}, },
"dependencies": { "dependencies": {
"@ckpack/vue-color": "^1.2.0", "@ckpack/vue-color": "^1.2.0",
@ -32,6 +32,7 @@
"papaparse": "^5.3.2", "papaparse": "^5.3.2",
"socket.io-client": "^4.5.1", "socket.io-client": "^4.5.1",
"sortablejs": "^1.15.0", "sortablejs": "^1.15.0",
"tinycolor2": "^1.4.2",
"unique-names-generator": "^4.7.1", "unique-names-generator": "^4.7.1",
"vue-dompurify-html": "^3.0.0", "vue-dompurify-html": "^3.0.0",
"vue-github-button": "^3.0.3", "vue-github-button": "^3.0.3",
@ -59,6 +60,7 @@
"@types/file-saver": "^2.0.5", "@types/file-saver": "^2.0.5",
"@types/papaparse": "^5.3.2", "@types/papaparse": "^5.3.2",
"@types/sortablejs": "^1.13.0", "@types/sortablejs": "^1.13.0",
"@types/tinycolor2": "^1.4.3",
"@vitest/ui": "^0.18.0", "@vitest/ui": "^0.18.0",
"@vue/compiler-sfc": "^3.2.37", "@vue/compiler-sfc": "^3.2.37",
"@vue/test-utils": "^2.0.2", "@vue/test-utils": "^2.0.2",

170
packages/nc-gui-v2/pages/[projectType]/[projectId]/index.vue

@ -1,9 +1,11 @@
<script setup lang="ts"> <script setup lang="ts">
import { Chrome } from '@ckpack/vue-color'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import { Chrome } from '@ckpack/vue-color'
import tinycolor from 'tinycolor2'
import { import {
computed, computed,
definePageMeta, definePageMeta,
enumColor,
navigateTo, navigateTo,
onKeyStroke, onKeyStroke,
openLink, openLink,
@ -29,7 +31,7 @@ const route = useRoute()
const { appInfo, token, signOut, signedIn, user } = useGlobal() const { appInfo, token, signOut, signedIn, user } = useGlobal()
const { project, loadProject, loadTables, isSharedBase, loadProjectMetaInfo, projectMetaInfo } = useProject() const { project, loadProject, loadTables, isSharedBase, loadProjectMetaInfo, projectMetaInfo, saveTheme } = useProject()
const { addTab, clearTabs } = useTabs() const { addTab, clearTabs } = useTabs()
@ -53,22 +55,9 @@ const dropdownOpen = ref(false)
/** Sidebar ref */ /** Sidebar ref */
const sidebar = ref() const sidebar = ref()
const pickedColor = ref<any>('#ffffff')
let pickerActive = $ref<boolean | 'primary' | 'accent'>(false)
const email = computed(() => user.value?.email ?? '---') const email = computed(() => user.value?.email ?? '---')
const { setTheme, theme } = useTheme() const { theme } = useTheme()
watch(pickedColor, (nextColor) => {
if (pickerActive && nextColor.hex) {
setTheme({
primaryColor: pickerActive === 'primary' ? nextColor.hex : theme.value.primaryColor,
accentColor: pickerActive === 'accent' ? nextColor.hex : theme.value.accentColor,
})
}
})
const logout = () => { const logout = () => {
signOut() signOut()
@ -94,6 +83,31 @@ await loadProject()
await loadTables() await loadTables()
const themePrimaryColor = ref<any>(theme.value.primaryColor)
const themeAccentColor = ref<any>(theme.value.accentColor)
// Chrome provides object so if custom picker used we only edit primary otherwise use analogous as accent
watch(themePrimaryColor, (nextColor) => {
const hexColor = nextColor.hex ? nextColor.hex : nextColor
const tcolor = tinycolor(hexColor)
if (tcolor) {
const analogous = tcolor.complement()
saveTheme({
primaryColor: hexColor,
accentColor: nextColor.hex ? theme.value.accentColor : analogous.toHexString(),
})
}
})
watch(themeAccentColor, (nextColor) => {
const hexColor = nextColor.hex ? nextColor.hex : nextColor
saveTheme({
primaryColor: theme.value.primaryColor,
accentColor: hexColor,
})
})
if (!route.params.type && isUIAllowed('teamAndAuth')) { if (!route.params.type && isUIAllowed('teamAndAuth')) {
addTab({ type: TabType.AUTH, title: 'Team & Auth' }) addTab({ type: TabType.AUTH, title: 'Team & Auth' })
} }
@ -125,22 +139,6 @@ const copyAuthToken = async () => {
message.error(e.message) message.error(e.message)
} }
} }
const openColorPicker = (type: 'primary' | 'accent') => {
if (!pickerActive || pickerActive !== type) {
pickedColor.value = type === 'primary' ? theme.value.primaryColor : theme.value.accentColor
pickerActive = type
} else {
pickerActive = false
}
}
const onMenuClose = (visible: boolean) => {
if (!visible) {
pickedColor.value = '#ffffff'
pickerActive = false
}
}
</script> </script>
<template> <template>
@ -188,7 +186,7 @@ const onMenuClose = (visible: boolean) => {
</template> </template>
</div> </div>
<a-dropdown v-else class="h-full min-w-0 flex-1" :trigger="['click']" placement="bottom" @visible-change="onMenuClose"> <a-dropdown v-else class="h-full min-w-0 flex-1" :trigger="['click']" placement="bottom">
<div <div
:style="{ width: isOpen ? 'calc(100% - 40px) pr-2' : '100%' }" :style="{ width: isOpen ? 'calc(100% - 40px) pr-2' : '100%' }"
:class="[isOpen ? '' : 'justify-center']" :class="[isOpen ? '' : 'justify-center']"
@ -272,6 +270,63 @@ const onMenuClose = (visible: boolean) => {
</div> </div>
</a-menu-item> </a-menu-item>
<template v-if="isUIAllowed('projectTheme')">
<a-sub-menu key="theme">
<template #title>
<div class="nc-project-menu-item group">
<ClarityImageLine class="group-hover:text-accent" />
Project Theme
<div class="flex-1" />
<MaterialSymbolsChevronRightRounded
class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400"
/>
</div>
</template>
<template #expandIcon></template>
<GeneralColorPicker v-model="themePrimaryColor" :colors="enumColor.dark" :row-size="5" :advanced="false" />
<a-sub-menu key="theme-2">
<template #title>
<div class="nc-project-menu-item group">
Custom Theme
<div class="flex-1" />
<MaterialSymbolsChevronRightRounded
class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400"
/>
</div>
</template>
<template #expandIcon></template>
<a-sub-menu key="pick-primary">
<template #title>
<div class="nc-project-menu-item group">
<ClarityColorPickerSolid class="group-hover:text-accent" />
Primary Color
</div>
</template>
<template #expandIcon></template>
<Chrome v-model="themePrimaryColor" />
</a-sub-menu>
<a-sub-menu key="pick-accent">
<template #title>
<div class="nc-project-menu-item group">
<ClarityColorPickerSolid class="group-hover:text-accent" />
Accent Color
</div>
</template>
<template #expandIcon></template>
<Chrome v-model="themeAccentColor" />
</a-sub-menu>
</a-sub-menu>
</a-sub-menu>
</template>
<a-menu-divider /> <a-menu-divider />
<a-sub-menu v-if="isUIAllowed('previewAs')" key="preview-as"> <a-sub-menu v-if="isUIAllowed('previewAs')" key="preview-as">
@ -293,7 +348,11 @@ const onMenuClose = (visible: boolean) => {
<GeneralPreviewAs /> <GeneralPreviewAs />
</a-sub-menu> </a-sub-menu>
<a-sub-menu key="language" class="lang-menu scrollbar-thin-dull min-w-50 max-h-90vh overflow-auto !py-0"> <a-sub-menu
key="language"
class="lang-menu !py-0"
popup-class-name="scrollbar-thin-dull min-w-50 max-h-90vh !overflow-auto"
>
<template #title> <template #title>
<div class="nc-project-menu-item group"> <div class="nc-project-menu-item group">
<MaterialSymbolsTranslate class="group-hover:text-accent nc-language" /> <MaterialSymbolsTranslate class="group-hover:text-accent nc-language" />
@ -338,54 +397,13 @@ const onMenuClose = (visible: boolean) => {
<div v-t="['a:navbar:user:sign-out']" class="nc-project-menu-item group" @click="logout"> <div v-t="['a:navbar:user:sign-out']" class="nc-project-menu-item group" @click="logout">
<MdiLogout class="group-hover:(!text-accent)" />&nbsp; <MdiLogout class="group-hover:(!text-accent)" />&nbsp;
<span class="prose-sm"> <span class="prose-sm nc-user-menu-signout">
{{ $t('general.signOut') }} {{ $t('general.signOut') }}
</span> </span>
</div> </div>
</a-menu-item> </a-menu-item>
</a-sub-menu> </a-sub-menu>
</template> </template>
<a-menu-divider />
<a-sub-menu>
<template #title>
<div class="nc-project-menu-item group">
<ClarityImageLine class="group-hover:text-accent" />
Theme
<div class="flex-1" />
<MaterialSymbolsChevronRightRounded
class="transform group-hover:(scale-115 text-accent) text-xl text-gray-400"
/>
</div>
</template>
<template #expandIcon></template>
<a-menu-item>
<div class="nc-project-menu-item group" @click.stop="openColorPicker('primary')">
<ClarityColorPickerSolid class="group-hover:text-accent" />
Primary Color
</div>
</a-menu-item>
<a-menu-item>
<div class="nc-project-menu-item group" @click.stop="openColorPicker('accent')">
<ClarityColorPickerSolid class="group-hover:text-accent" />
Accent Color
</div>
</a-menu-item>
</a-sub-menu>
<Chrome
v-if="pickerActive"
v-model="pickedColor"
class="z-99 absolute right-[-225px]"
@click.stop
@blur="onMenuClose(false)"
/>
</a-menu-item-group> </a-menu-item-group>
</a-menu> </a-menu>
</template> </template>

1
packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index.vue

@ -92,6 +92,7 @@ function onEdit(targetKey: number, action: 'add' | 'remove' | string) {
.ant-tabs-nav-add { .ant-tabs-nav-add {
@apply !hidden; @apply !hidden;
} }
.ant-tabs-nav-more { .ant-tabs-nav-more {
@apply text-white; @apply text-white;
} }

17
packages/nc-gui-v2/pages/[projectType]/[projectId]/index/index/[type]/[title]/[[viewTitle]].vue

@ -1,11 +1,18 @@
<script> <script setup lang="ts">
export default { const { getMeta } = useMetas()
name: 'Index', const route = useRoute()
} const loading = ref(true)
getMeta(route.params.title as string, true).finally(() => {
loading.value = false
})
</script> </script>
<template> <template>
<TabsSmartsheet /> <div v-if="loading" class="flex items-center justify-center h-full w-full">
<a-spin size="large" />
</div>
<TabsSmartsheet v-else />
</template> </template>
<style scoped></style> <style scoped></style>

70
packages/nc-gui-v2/pages/index/index/[id].vue

@ -1,7 +1,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import type { Form } from 'ant-design-vue' import type { Form } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk'
import { message } from 'ant-design-vue' import { message } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk'
import tinycolor from 'tinycolor2'
import { import {
extractSdkResponseErrorMsg, extractSdkResponseErrorMsg,
navigateTo, navigateTo,
@ -15,12 +16,16 @@ import {
useSidebar, useSidebar,
} from '#imports' } from '#imports'
const { api, isLoading } = useApi() const { isLoading } = useApi()
useSidebar({ hasSidebar: false }) useSidebar({ hasSidebar: false })
const route = useRoute() const route = useRoute()
const { project, loadProject, updateProject } = useProject(route.params.id as string)
await loadProject()
const nameValidationRules = [ const nameValidationRules = [
{ {
required: true, required: true,
@ -31,22 +36,15 @@ const nameValidationRules = [
const form = ref<typeof Form>() const form = ref<typeof Form>()
const formState = reactive({ const formState = reactive<Partial<ProjectType>>({
title: '', title: '',
color: '#FFFFFF00',
}) })
const getProject = async () => {
try {
const result: ProjectType = await api.project.read(route.params.id as string)
formState.title = result.title as string
} catch (e: any) {
message.error(await extractSdkResponseErrorMsg(e))
}
}
const renameProject = async () => { const renameProject = async () => {
formState.color = formState.color === '#FFFFFF00' ? '' : formState.color
try { try {
await api.project.update(route.params.id as string, formState) await updateProject(formState)
navigateTo(`/nc/${route.params.id}`) navigateTo(`/nc/${route.params.id}`)
} catch (e: any) { } catch (e: any) {
@ -56,6 +54,8 @@ const renameProject = async () => {
// select and focus title field on load // select and focus title field on load
onMounted(async () => { onMounted(async () => {
formState.title = project.value.title as string
formState.color = project.value.color && tinycolor(project.value.color).isValid() ? project.value.color : '#FFFFFF00'
await nextTick(() => { await nextTick(() => {
// todo: replace setTimeout and follow better approach // todo: replace setTimeout and follow better approach
setTimeout(() => { setTimeout(() => {
@ -67,8 +67,6 @@ onMounted(async () => {
}, 500) }, 500)
}) })
}) })
await getProject()
</script> </script>
<template> <template>
@ -100,6 +98,27 @@ await getProject()
<a-input v-model:value="formState.title" name="title" class="nc-metadb-project-name" /> <a-input v-model:value="formState.title" name="title" class="nc-metadb-project-name" />
</a-form-item> </a-form-item>
<div class="flex items-center">
<span>Project color: </span>
<a-menu class="!border-0 !m-0 !p-0">
<a-sub-menu key="project-color">
<template #title>
<button type="button" class="color-selector" :style="{ 'background-color': formState.color }">
<MdiNull v-if="formState.color === '#FFFFFF00'" />
</button>
</template>
<template #expandIcon></template>
<GeneralColorPicker v-model="formState.color" name="color" class="nc-metadb-project-color" />
</a-sub-menu>
</a-menu>
<MdiClose
v-show="formState.color !== '#FFFFFF00'"
class="cursor-pointer"
:style="{ color: 'red' }"
@click="formState.color = '#FFFFFF00'"
/>
</div>
<div class="text-center"> <div class="text-center">
<button type="submit" class="submit"> <button type="submit" class="submit">
<span class="flex items-center gap-2"> <span class="flex items-center gap-2">
@ -112,7 +131,7 @@ await getProject()
</div> </div>
</template> </template>
<style lang="scss"> <style lang="scss" scoped>
.update-project { .update-project {
.ant-input-affix-wrapper, .ant-input-affix-wrapper,
.ant-input { .ant-input {
@ -137,4 +156,23 @@ await getProject()
} }
} }
} }
:deep(.ant-menu-submenu-title) {
@apply !p-0 !mx-2;
}
.color-selector {
position: relative;
height: 32px;
width: 32px;
border-radius: 5px;
-webkit-text-stroke-width: 1px;
-webkit-text-stroke-color: white;
@apply flex text-gray-500 border-4 items-center justify-center;
}
.color-selector:hover {
filter: brightness(90%);
-webkit-filter: brightness(90%);
}
</style> </style>

8
packages/nc-gui-v2/pages/index/index/index.vue

@ -158,9 +158,13 @@ await loadProjects()
> >
<!-- Title --> <!-- Title -->
<a-table-column key="title" :title="$t('general.title')" data-index="title"> <a-table-column key="title" :title="$t('general.title')" data-index="title">
<template #default="{ text }"> <template #default="{ text, record }">
<div <div
class="capitalize color-transition group-hover:text-primary !w-[400px] overflow-hidden overflow-ellipsis whitespace-nowrap" class="capitalize color-transition group-hover:text-primary !w-[400px] h-full overflow-hidden overflow-ellipsis whitespace-nowrap pl-2"
:class="{ 'border-l-4': record.color }"
:style="{
'border-color': record.color,
}"
> >
{{ text }} {{ text }}
</div> </div>

3
packages/nc-gui-v2/plugins/ant.ts

@ -1,6 +1,7 @@
import { Menu as AntMenu } from 'ant-design-vue' import { Menu as AntMenu, Modal as AntModal } from 'ant-design-vue'
import { defineNuxtPlugin } from '#imports' import { defineNuxtPlugin } from '#imports'
export default defineNuxtPlugin((nuxtApp) => { export default defineNuxtPlugin((nuxtApp) => {
nuxtApp.vueApp.component(AntMenu.name, AntMenu) nuxtApp.vueApp.component(AntMenu.name, AntMenu)
nuxtApp.vueApp.component(AntModal.name, AntModal)
}) })

20
packages/nc-gui-v2/scripts/updateNuxtRouting.js

@ -0,0 +1,20 @@
/** A temporary solution to enable hash based routing until
* nuxt-team merges - https://github.com/nuxt/framework/pull/6980
*/
const fs = require('fs')
const path = require('path')
const filePath = path.join(__dirname, '..', 'node_modules', 'nuxt', 'dist', 'pages', 'runtime', 'router.mjs')
/** Read file content to be updated */
const content = fs.readFileSync(filePath, 'utf8')
/** Replace `createWebHistory` with `createWebHashHistory` */
const updatedContent = content.replace(
/createRouter(\s*,\s*)createWebHistory(\s*,\s*)createMemoryHistory/,
`createRouter$1createWebHashHistory as createWebHistory$2createMemoryHistory`,
)
/** Update file content with updated code */
fs.writeFileSync(filePath, updatedContent, 'utf8')

11740
packages/nocodb-sdk/package-lock.json generated

File diff suppressed because it is too large Load Diff

11
packages/nocodb-sdk/package.json

@ -9,6 +9,7 @@
"license": "MIT", "license": "MIT",
"keywords": [], "keywords": [],
"scripts": { "scripts": {
"preinstall": "npx npm-force-resolutions",
"build": "npm run generate:sdk && run-p build:*", "build": "npm run generate:sdk && run-p build:*",
"build:main": "tsc -p tsconfig.json", "build:main": "tsc -p tsconfig.json",
"build:module": "tsc -p tsconfig.module.json", "build:module": "tsc -p tsconfig.module.json",
@ -37,8 +38,8 @@
"version": "standard-version", "version": "standard-version",
"reset-hard": "git clean -dfx && git reset --hard && npm i", "reset-hard": "git clean -dfx && git reset --hard && npm i",
"prepare-release": "run-s reset-hard test cov:check doc:html version doc:publish", "prepare-release": "run-s reset-hard test cov:check doc:html version doc:publish",
"generate:sdk": "npx swagger-typescript-api -r -p ../../scripts/sdk/swagger.json -o ./src/lib/ --axios --unwrap-response-data --module-name-first-tag --type-suffix=Type --templates ../../scripts/sdk/templates", "generate:sdk": "swagger-typescript-api -r -p ../../scripts/sdk/swagger.json -o ./src/lib/ --axios --unwrap-response-data --module-name-first-tag --type-suffix=Type --templates ../../scripts/sdk/templates",
"generate:sdk:default": "npx swagger-typescript-api -r -p ../../scripts/sdk/swagger.json -o ./src/lib/ --name Api2.ts --unwrap-response-data --module-name-first-tag --type-suffix=Type --templates ../../scripts/sdk/templates" "generate:sdk:default": "swagger-typescript-api -r -p ../../scripts/sdk/swagger.json -o ./src/lib/ --name Api2.ts --unwrap-response-data --module-name-first-tag --type-suffix=Type --templates ../../scripts/sdk/templates"
}, },
"engines": { "engines": {
"node": ">=10" "node": ">=10"
@ -68,10 +69,14 @@
"open-cli": "^6.0.1", "open-cli": "^6.0.1",
"prettier": "^2.1.1", "prettier": "^2.1.1",
"standard-version": "^9.0.0", "standard-version": "^9.0.0",
"swagger-typescript-api": "^10.0.1",
"ts-node": "^9.0.0", "ts-node": "^9.0.0",
"typedoc": "^0.22.17", "typedoc": "^0.22.17",
"typescript": "^4.0.2" "typescript": "^4.0.2"
}, },
"resolutions": {
"typescript": "4.7.4"
},
"files": [ "files": [
"build/main", "build/main",
"build/module", "build/module",
@ -107,4 +112,4 @@
"**/*.spec.js" "**/*.spec.js"
] ]
} }
} }

2
packages/nocodb/src/lib/meta/api/modelVisibilityApis.ts

@ -115,7 +115,7 @@ router.get(
await xcVisibilityMetaGet( await xcVisibilityMetaGet(
req.params.projectId, req.params.projectId,
null, null,
req.query.includeM2M req.query.includeM2M === true || req.query.includeM2M === 'true'
) )
); );
}, 'modelVisibilityList') }, 'modelVisibilityList')

14
packages/nocodb/src/lib/meta/api/projectApis.ts

@ -23,6 +23,7 @@ import getColumnUiType from '../helpers/getColumnUiType';
import mapDefaultPrimaryValue from '../helpers/mapDefaultPrimaryValue'; import mapDefaultPrimaryValue from '../helpers/mapDefaultPrimaryValue';
import { extractAndGenerateManyToManyRelations } from './metaDiffApis'; import { extractAndGenerateManyToManyRelations } from './metaDiffApis';
import { metaApiMetrics } from '../helpers/apiMetrics'; import { metaApiMetrics } from '../helpers/apiMetrics';
import { extractPropsAndSanitize } from '../helpers/extractProps';
const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4); const nanoid = customAlphabet('1234567890abcdefghijklmnopqrstuvwxyz_', 4);
@ -46,12 +47,15 @@ export async function projectUpdate(
req: Request<any, any, any>, req: Request<any, any, any>,
res: Response<ProjectListType> res: Response<ProjectListType>
) { ) {
// only support updating title at this moment const project = await Project.getWithInfo(req.params.projectId);
const data: any = {
title: DOMPurify.sanitize(req?.body?.title), const data: Partial<Project> = extractPropsAndSanitize(req?.body, [
}; 'title',
'meta',
'color',
]);
if (await Project.getByTitle(data.title)) { if (data?.title && project.title !== data.title && await Project.getByTitle(data.title)) {
NcError.badRequest('Project title already in use'); NcError.badRequest('Project title already in use');
} }

12
packages/nocodb/src/lib/meta/helpers/extractProps.ts

@ -1,7 +1,17 @@
export default function extractProps<T>(body: T, props: string[]): Partial<T> { import DOMPurify from 'isomorphic-dompurify';
export function extractProps<T>(body: T, props: string[]): Partial<T> {
// todo: throw error if no props found // todo: throw error if no props found
return props.reduce((o, key) => { return props.reduce((o, key) => {
if (key in body) o[key] = body[key]; if (key in body) o[key] = body[key];
return o; return o;
}, {}); }, {});
} }
export function extractPropsAndSanitize<T>(body: T, props: string[]): Partial<T> {
// todo: throw error if no props found
return props.reduce((o, key) => {
if (key in body) o[key] = body[key] === '' ? null : DOMPurify.sanitize(body[key]);
return o;
}, {});
}

2
packages/nocodb/src/lib/models/Base.ts

@ -10,7 +10,7 @@ import Model from './Model';
import { BaseType } from 'nocodb-sdk'; import { BaseType } from 'nocodb-sdk';
import NocoCache from '../cache/NocoCache'; import NocoCache from '../cache/NocoCache';
import CryptoJS from 'crypto-js'; import CryptoJS from 'crypto-js';
import extractProps from '../meta/helpers/extractProps'; import { extractProps } from '../meta/helpers/extractProps';
// todo: hide credentials // todo: hide credentials
export default class Base implements BaseType { export default class Base implements BaseType {

2
packages/nocodb/src/lib/models/FormViewColumn.ts

@ -3,7 +3,7 @@ import { CacheGetType, CacheScope, MetaTable } from '../utils/globals';
import { FormColumnType } from 'nocodb-sdk'; import { FormColumnType } from 'nocodb-sdk';
import View from './View'; import View from './View';
import NocoCache from '../cache/NocoCache'; import NocoCache from '../cache/NocoCache';
import extractProps from '../meta/helpers/extractProps'; import { extractProps } from '../meta/helpers/extractProps';
export default class FormViewColumn implements FormColumnType { export default class FormViewColumn implements FormColumnType {
id?: string; id?: string;

2
packages/nocodb/src/lib/models/FormulaColumn.ts

@ -1,7 +1,7 @@
import Noco from '../Noco'; import Noco from '../Noco';
import { CacheGetType, CacheScope, MetaTable } from '../utils/globals'; import { CacheGetType, CacheScope, MetaTable } from '../utils/globals';
import NocoCache from '../cache/NocoCache'; import NocoCache from '../cache/NocoCache';
import extractProps from '../meta/helpers/extractProps'; import { extractProps } from '../meta/helpers/extractProps';
export default class FormulaColumn { export default class FormulaColumn {
formula: string; formula: string;

2
packages/nocodb/src/lib/models/GridViewColumn.ts

@ -1,7 +1,7 @@
import Noco from '../Noco'; import Noco from '../Noco';
import { CacheGetType, CacheScope, MetaTable } from '../utils/globals'; import { CacheGetType, CacheScope, MetaTable } from '../utils/globals';
import { GridColumnType } from 'nocodb-sdk'; import { GridColumnType } from 'nocodb-sdk';
import extractProps from '../meta/helpers/extractProps'; import { extractProps } from '../meta/helpers/extractProps';
import View from './View'; import View from './View';
import NocoCache from '../cache/NocoCache'; import NocoCache from '../cache/NocoCache';

2
packages/nocodb/src/lib/models/HookLog.ts

@ -1,6 +1,6 @@
import { MetaTable } from '../utils/globals'; import { MetaTable } from '../utils/globals';
import Noco from '../Noco'; import Noco from '../Noco';
import extractProps from '../meta/helpers/extractProps'; import { extractProps } from '../meta/helpers/extractProps';
import Hook from './Hook'; import Hook from './Hook';
import { HookLogType } from 'nocodb-sdk'; import { HookLogType } from 'nocodb-sdk';

2
packages/nocodb/src/lib/models/Project.ts

@ -7,7 +7,7 @@ import {
CacheScope, CacheScope,
MetaTable, MetaTable,
} from '../utils/globals'; } from '../utils/globals';
import extractProps from '../meta/helpers/extractProps'; import { extractProps } from '../meta/helpers/extractProps';
import NocoCache from '../cache/NocoCache'; import NocoCache from '../cache/NocoCache';
export default class Project implements ProjectType { export default class Project implements ProjectType {

2
packages/nocodb/src/lib/models/SyncSource.ts

@ -1,6 +1,6 @@
import Noco from '../Noco'; import Noco from '../Noco';
import { MetaTable } from '../utils/globals'; import { MetaTable } from '../utils/globals';
import extractProps from '../meta/helpers/extractProps'; import { extractProps } from '../meta/helpers/extractProps';
import User from './User'; import User from './User';
export default class SyncSource { export default class SyncSource {

2
packages/nocodb/src/lib/models/User.ts

@ -1,7 +1,7 @@
import { UserType } from 'nocodb-sdk'; import { UserType } from 'nocodb-sdk';
import { CacheGetType, CacheScope, MetaTable } from '../utils/globals'; import { CacheGetType, CacheScope, MetaTable } from '../utils/globals';
import Noco from '../Noco'; import Noco from '../Noco';
import extractProps from '../meta/helpers/extractProps'; import { extractProps } from '../meta/helpers/extractProps';
import NocoCache from '../cache/NocoCache'; import NocoCache from '../cache/NocoCache';
export default class User implements UserType { export default class User implements UserType {
id: string; id: string;

2
packages/nocodb/src/lib/models/View.ts

@ -18,7 +18,7 @@ import GalleryViewColumn from './GalleryViewColumn';
import FormViewColumn from './FormViewColumn'; import FormViewColumn from './FormViewColumn';
import Column from './Column'; import Column from './Column';
import NocoCache from '../cache/NocoCache'; import NocoCache from '../cache/NocoCache';
import extractProps from '../meta/helpers/extractProps'; import { extractProps } from '../meta/helpers/extractProps';
const { v4: uuidv4 } = require('uuid'); const { v4: uuidv4 } = require('uuid');
export default class View implements ViewType { export default class View implements ViewType {

1
scripts/cypress-v2/integration/common/00_pre_configurations.js

@ -164,6 +164,7 @@ export const genTest = (apiType, dbType) => {
it("Admin SignUp", () => { it("Admin SignUp", () => {
cy.task("log", "This will be output to the terminal"); cy.task("log", "This will be output to the terminal");
cy.saveLocalStorage();
loginPage.signUp(roles.owner.credentials); loginPage.signUp(roles.owner.credentials);
}); });

14
scripts/cypress-v2/integration/common/1c_sql_view.js

@ -138,21 +138,21 @@ export const genTest = (apiType, dbType) => {
} }
}); });
it(`SQL View List`, () => { it.skip(`SQL View List`, () => {
// confirm if other views exist // confirm if other views exist
// //
cy.openViewsTab("CustomerList", 25); cy.openViewsTab("CustomerList", 25);
cy.closeViewsTab("CustomerList"); cy.closeViewsTab("CustomerList");
// cy.openViewsTab("FilmList", 25); cy.openViewsTab("FilmList", 25);
// cy.closeViewsTab("FilmList"); cy.closeViewsTab("FilmList");
// cy.openViewsTab("SalesByFilmCategory", 16); cy.openViewsTab("SalesByFilmCategory", 16);
// cy.closeViewsTab("SalesByFilmCategory"); cy.closeViewsTab("SalesByFilmCategory");
if (!isXcdb()) { if (!isXcdb()) {
// cy.openViewsTab("NicerButSlowerFilmList", 25); cy.openViewsTab("NicerButSlowerFilmList", 25);
// cy.closeViewsTab("NicerButSlowerFilmList"); cy.closeViewsTab("NicerButSlowerFilmList");
// SalesByStore && StaffList contain no entries. Hence marking row count to 0 // SalesByStore && StaffList contain no entries. Hence marking row count to 0
cy.openViewsTab("SalesByStore", 0); cy.openViewsTab("SalesByStore", 0);

2
scripts/cypress-v2/integration/common/3e_duration_column.js

@ -18,7 +18,6 @@ export const genTest = (apiType, dbType) => {
// Run once before test- create table // Run once before test- create table
// //
before(() => { before(() => {
cy.fileHook();
mainPage.tabReset(); mainPage.tabReset();
// // kludge: wait for page load to finish // // kludge: wait for page load to finish
@ -31,7 +30,6 @@ export const genTest = (apiType, dbType) => {
}); });
beforeEach(() => { beforeEach(() => {
cy.fileHook();
}); });
after(() => { after(() => {

28
scripts/cypress-v2/integration/common/4a_table_view_grid_gallery_form.js

@ -1,8 +1,6 @@
import { mainPage } from "../../support/page_objects/mainPage"; import { mainPage } from "../../support/page_objects/mainPage";
import { isTestSuiteActive } from "../../support/page_objects/projectConstants"; import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
// let viewTypeString = ["", "Form", "Gallery", "Grid"];
function capitalizeFirstLetter(string) { function capitalizeFirstLetter(string) {
return string.charAt(0).toUpperCase() + string.slice(1); return string.charAt(0).toUpperCase() + string.slice(1);
} }
@ -16,24 +14,17 @@ export const genTest = (apiType, dbType) => {
// Run once before test- create project (rest/graphql) // Run once before test- create project (rest/graphql)
// //
before(() => { before(() => {
cy.fileHook();
mainPage.tabReset(); mainPage.tabReset();
// // kludge: wait for page load to finish
// cy.wait(1000);
// // close team & auth tab
// cy.get('button.ant-tabs-tab-remove').should('exist').click();
// cy.wait(1000);
// open a table to work on views // open a table to work on views
// //
cy.openTableTab("Country", 25); cy.openTableTab("Country", 25);
// toggle right navbar (open)
cy.get('.nc-toggle-right-navbar').should('exist').click(); cy.get('.nc-toggle-right-navbar').should('exist').click();
}); });
beforeEach(() => { beforeEach(() => {
cy.fileHook();
}); });
after(() => { after(() => {
@ -49,7 +40,6 @@ export const genTest = (apiType, dbType) => {
cy.get(`.nc-create-${viewType}-view`).click(); cy.get(`.nc-create-${viewType}-view`).click();
// Pop up window, click Submit (accepting default name for view) // Pop up window, click Submit (accepting default name for view)
// cy.getActiveModal().find("button:contains(Submit)").click();
cy.getActiveModal().find(".ant-btn-primary").click(); cy.getActiveModal().find(".ant-btn-primary").click();
cy.toastWait("View created successfully"); cy.toastWait("View created successfully");
@ -64,10 +54,6 @@ export const genTest = (apiType, dbType) => {
it(`Edit ${viewType} view name`, () => { it(`Edit ${viewType} view name`, () => {
// click on edit-icon (becomes visible on hovering mouse) // click on edit-icon (becomes visible on hovering mouse)
// cy.get(".nc-view-edit-icon").last().click({
// force: true,
// timeout: 1000,
// });
cy.get(`.nc-${viewType}-view-item`).last().dblclick(); cy.get(`.nc-${viewType}-view-item`).last().dblclick();
// feed new name // feed new name
@ -89,15 +75,11 @@ export const genTest = (apiType, dbType) => {
// number of view entries should be 2 before we delete // number of view entries should be 2 before we delete
cy.get(".nc-view-item").its("length").should("eq", 2); cy.get(".nc-view-item").its("length").should("eq", 2);
cy.get(`.nc-${viewType}-view-item`).last().click();
cy.wait(3000);
// click on delete icon (becomes visible on hovering mouse) // click on delete icon (becomes visible on hovering mouse)
cy.get(`.nc-${viewType}-view-item`).last().trigger("mouseover").then(() => { cy.get(".nc-view-delete-icon").click({ force: true });
cy.get(".nc-view-delete-icon").should('exist').click({force: true}); cy.wait(300)
cy.getActiveModal().find(".ant-btn-dangerous").click(); cy.getActiveModal().find('.ant-btn-dangerous').click();
cy.toastWait("View deleted successfully"); cy.toastWait("View deleted successfully");
})
// kludge: right navbar closes abruptly. force it open again // kludge: right navbar closes abruptly. force it open again
window.localStorage.setItem('nc-right-sidebar', '{"isOpen":true,"hasSidebar":true}') window.localStorage.setItem('nc-right-sidebar', '{"isOpen":true,"hasSidebar":true}')

28
scripts/cypress-v2/integration/common/4b_table_view_share.js

@ -1,5 +1,6 @@
import { mainPage } from "../../support/page_objects/mainPage"; import { mainPage } from "../../support/page_objects/mainPage";
import { isTestSuiteActive } from "../../support/page_objects/projectConstants"; import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
import {loginPage} from "../../support/page_objects/navigation";
let storedURL = ""; let storedURL = "";
let linkText = ""; let linkText = "";
@ -35,32 +36,20 @@ export const genTest = (apiType, dbType) => {
// Run once before test- create project (rest/graphql) // Run once before test- create project (rest/graphql)
// //
before(() => { before(() => {
cy.fileHook();
mainPage.tabReset(); mainPage.tabReset();
// // kludge: wait for page load to finish
// cy.wait(1000);
// // close team & auth tab
// cy.get('button.ant-tabs-tab-remove').should('exist').click();
// cy.wait(1000);
cy.openTableTab("City", 25); cy.openTableTab("City", 25);
// store base URL- to re-visit and delete form view later // store base URL- to re-visit and delete form view later
cy.url().then((url) => { cy.url().then((url) => {
storedURL = url; storedURL = url;
}); });
generateLinkWithPwd(); generateLinkWithPwd();
}); });
beforeEach(() => { beforeEach(() => {
cy.fileHook();
cy.restoreLocalStorage();
}); });
afterEach(() => { afterEach(() => {
cy.saveLocalStorage();
}); });
it("Share view with incorrect password", () => { it("Share view with incorrect password", () => {
@ -89,17 +78,22 @@ export const genTest = (apiType, dbType) => {
cy.getActiveModal().find('button:contains("Unlock")').click(); cy.getActiveModal().find('button:contains("Unlock")').click();
// if pwd is incorrect, active modal requesting to feed in password again will persist // if pwd is incorrect, active modal requesting to feed in password again will persist
cy.getActiveModal().find('button:contains("Unlock")').should('not.exist'); // cy.getActiveModal().find('button:contains("Unlock")').should('not.exist');
cy.get(".ant-modal-content:visible").should("not.exist")
// Verify Download as CSV is here // Verify Download as CSV is here
mainPage.downloadCsv().should("exist"); mainPage.downloadCsv().should("exist");
mainPage.downloadExcel().should("exist"); mainPage.downloadExcel().should("exist");
}); });
it("Delete view", () => { it("Delete view", () => {
cy.visit(storedURL, { // issue with restore local storage- need to refresh page to get new URL
baseUrl: null,
}); loginPage.loginAndOpenProject(apiType, dbType);
// cy.restoreLocalStorage();
// cy.visit(storedURL, { baseUrl: null });
cy.openTableTab("City", 25);
// wait for page load to complete // wait for page load to complete
cy.get(".nc-grid-row").should("have.length", 25); cy.get(".nc-grid-row").should("have.length", 25);

129
scripts/cypress-v2/integration/common/4c_form_view_detailed.js

@ -1,5 +1,6 @@
import { isTestSuiteActive } from "../../support/page_objects/projectConstants"; import { isTestSuiteActive } from "../../support/page_objects/projectConstants";
import { mainPage, settingsPage } from "../../support/page_objects/mainPage"; import { mainPage, settingsPage } from "../../support/page_objects/mainPage";
import {loginPage} from "../../support/page_objects/navigation";
let formViewURL; let formViewURL;
@ -11,14 +12,24 @@ function verifyFormDrawerFieldLocation(fieldName, position) {
} }
function verifyFormDrawerHideObjectCount(count) { function verifyFormDrawerHideObjectCount(count) {
cy.get(".nc-form") if(count) {
.find(".nc-field-remove-icon") cy.get(".nc-form")
.its("length") .find(".nc-field-remove-icon")
.should("eq", count); .its("length")
.should("eq", count);
} else {
cy.get(".nc-form")
.find(".nc-field-remove-icon")
.should("not.exist");
}
} }
function verifyFormMenuDrawerCardCount(cardCount) { function verifyFormMenuDrawerCardCount(cardCount) {
cy.get('.nc-form-left-drawer').find('.ant-card').should('have.length', cardCount); if(cardCount) {
cy.get('.nc-form-left-drawer').find('.ant-card').should('have.length', cardCount);
} else {
cy.get('.nc-form-left-drawer').find('.ant-card').should('not.exist');
}
} }
function validateFormHeader() { function validateFormHeader() {
@ -47,14 +58,14 @@ export const genTest = (apiType, dbType) => {
// Run once before test- create project (rest/graphql) // Run once before test- create project (rest/graphql)
// //
before(() => { before(() => {
cy.fileHook();
mainPage.tabReset(); mainPage.tabReset();
// loginPage.loginAndOpenProject(apiType, dbType);
// // kludge: wait for page load to finish // kludge: wait for page load to finish
// cy.wait(1000); cy.wait(2000);
// // close team & auth tab // close team & auth tab
// cy.get('button.ant-tabs-tab-remove').should('exist').click(); cy.get('button.ant-tabs-tab-remove').should('exist').click();
// cy.wait(1000); cy.wait(1000);
// open a table to work on views // open a table to work on views
// //
@ -63,12 +74,11 @@ export const genTest = (apiType, dbType) => {
}); });
beforeEach(() => { beforeEach(() => {
cy.fileHook(); // fix me!
cy.restoreLocalStorage(); window.localStorage.setItem('nc-right-sidebar', '{"isOpen":true,"hasSidebar":true}')
}); });
afterEach(() => { afterEach(() => {
cy.saveLocalStorage();
}); });
after(() => { after(() => {
@ -103,6 +113,7 @@ export const genTest = (apiType, dbType) => {
cy.get(".nc-form-drag-LastUpdate").drag( cy.get(".nc-form-drag-LastUpdate").drag(
".nc-form-drag-Country" ".nc-form-drag-Country"
); );
cy.wait(1000);
// Verify if order is: LastUpdate, Country, Country => City // Verify if order is: LastUpdate, Country, Country => City
verifyFormDrawerFieldLocation("LastUpdate", 0); verifyFormDrawerFieldLocation("LastUpdate", 0);
@ -197,8 +208,8 @@ export const genTest = (apiType, dbType) => {
// fill up mandatory fields // fill up mandatory fields
cy.get(".nc-form-input-Country").type("_abc"); cy.get(".nc-form-input-Country").type("_abc");
cy.get(".nc-form-input-LastUpdate").click(); cy.get(".nc-form-input-LastUpdate").click();
cy.getActiveModal().find("button").contains("19").click(); cy.get(".ant-picker-now-btn:visible").contains("Now").click();
cy.getActiveModal().find("button").contains("OK").click(); cy.get(".ant-btn-primary:visible").contains("Ok").click();
// default message, no update // default message, no update
@ -214,18 +225,15 @@ export const genTest = (apiType, dbType) => {
it(`Validate ${viewType}: Submit default, with valid Show message entry`, () => { it(`Validate ${viewType}: Submit default, with valid Show message entry`, () => {
// clicking again on view name shows blank still. work around- toggling between two views // clicking again on view name shows blank still. work around- toggling between two views
// cy.get(`.nc-view-item.nc-grid-view-item`)
// .contains("Country")
// .click();
cy.get(`.nc-view-item.nc-${viewType}-view-item`) cy.get(`.nc-view-item.nc-${viewType}-view-item`)
.contains("Form-1") .contains("Form-1")
.click(); .click();
// fill up mandatory fields // fill up mandatory fields
cy.get(".nc-form-input-Country").type("_abc"); cy.get(".nc-form-input-Country").should('exist').type("_abc");
cy.get(".nc-form-input-LastUpdate").click(); cy.get(".nc-form-input-LastUpdate").click();
cy.getActiveModal().find("button").contains("19").click(); cy.get(".ant-picker-now-btn:visible").contains("Now").click();
cy.getActiveModal().find("button").contains("OK").click(); cy.get(".ant-btn-primary:visible").contains("Ok").click();
// add message // add message
cy.get("textarea.nc-form-after-submit-msg") cy.get("textarea.nc-form-after-submit-msg")
@ -240,9 +248,6 @@ export const genTest = (apiType, dbType) => {
it(`Validate ${viewType}: Submit default, Enable checkbox "Submit another form`, () => { it(`Validate ${viewType}: Submit default, Enable checkbox "Submit another form`, () => {
// clicking again on view name shows blank still. work around- toggling between two views // clicking again on view name shows blank still. work around- toggling between two views
// cy.get(`.nc-view-item.nc-grid-view-item`)
// .contains("Country")
// .click();
cy.get(`.nc-view-item.nc-${viewType}-view-item`) cy.get(`.nc-view-item.nc-${viewType}-view-item`)
.contains("Form-1") .contains("Form-1")
.click(); .click();
@ -250,8 +255,8 @@ export const genTest = (apiType, dbType) => {
// fill up mandatory fields // fill up mandatory fields
cy.get(".nc-form-input-Country").type("_abc"); cy.get(".nc-form-input-Country").type("_abc");
cy.get(".nc-form-input-LastUpdate").click(); cy.get(".nc-form-input-LastUpdate").click();
cy.getActiveModal().find("button").contains("19").click(); cy.get(".ant-picker-now-btn:visible").contains("Now").click();
cy.getActiveModal().find("button").contains("OK").click(); cy.get(".ant-btn-primary:visible").contains("Ok").click();
// enable "Submit another form" check box // enable "Submit another form" check box
cy.get("button.nc-form-checkbox-submit-another-form").click(); cy.get("button.nc-form-checkbox-submit-another-form").click();
@ -274,8 +279,8 @@ export const genTest = (apiType, dbType) => {
cy.get(".nc-form-input-Country").type("_abc"); cy.get(".nc-form-input-Country").type("_abc");
cy.get(".nc-form-input-LastUpdate").click(); cy.get(".nc-form-input-LastUpdate").click();
cy.getActiveModal().find("button").contains("19").click(); cy.get(".ant-picker-now-btn:visible").contains("Now").click();
cy.getActiveModal().find("button").contains("OK").click(); cy.get(".ant-btn-primary:visible").contains("Ok").click();
// enable "New form after 5 seconds" button // enable "New form after 5 seconds" button
cy.get("button.nc-form-checkbox-submit-another-form") cy.get("button.nc-form-checkbox-submit-another-form")
@ -303,10 +308,11 @@ export const genTest = (apiType, dbType) => {
.click(); .click();
// validate if form has appeared again // validate if form has appeared again
cy.wait(1000);
validateFormHeader(); validateFormHeader();
cy.get(".nc-form-remove-all").click();
cy.get("button.nc-form-checkbox-send-email") cy.get(".nc-form-checkbox-send-email").click();
.click();
// validate if toaster pops up requesting to activate SMTP // validate if toaster pops up requesting to activate SMTP
cy.toastWait( cy.toastWait(
"Please activate SMTP plugin in App store for enabling email notification" "Please activate SMTP plugin in App store for enabling email notification"
@ -335,13 +341,17 @@ export const genTest = (apiType, dbType) => {
// validate if form has appeared again // validate if form has appeared again
validateFormHeader(); validateFormHeader();
cy.get("button.nc-form-checkbox-send-email") cy.get(".nc-form-checkbox-send-email")
.click(); .click();
cy.toastWait(
"Please activate SMTP plugin in App store for enabling email notification"
);
settingsPage.openMenu(settingsPage.APPSTORE) settingsPage.openMenu(settingsPage.APPSTORE)
mainPage.resetSMTP(); mainPage.resetSMTP();
cy.wait(3000); cy.wait(300);
cy.openTableTab("Country", 25); cy.openTableTab("Country", 25);
}); });
@ -350,30 +360,22 @@ export const genTest = (apiType, dbType) => {
cy.get(`.nc-view-item.nc-${viewType}-view-item`) cy.get(`.nc-view-item.nc-${viewType}-view-item`)
.contains("Form-1") .contains("Form-1")
.click(); .click();
cy.get(".nc-form-add-all").click();
cy.wait(3000); cy.wait(300);
// validate if form has appeared again // validate if form has appeared again
validateFormHeader(); validateFormHeader();
cy.get(".nc-form-input-LastUpdate").should("exist"); cy.get(".nc-form-input-LastUpdate").should("exist");
// remove "LastUpdate field" // remove "LastUpdate field"
cy.get(".nc-form").find(".nc-field-remove-icon").eq(2).click(); cy.get(".nc-form").find(".nc-field-remove-icon").eq(1).click();
cy.get(".nc-form-input-LastUpdate").should("not.exist"); cy.get(".nc-form-input-LastUpdate").should("not.exist");
// cy.get(".col-md-4")
// .find(".pointer.item")
// .contains("LastUpdate")
// .should("exist");
// add it back
// cy.get(".col-md-4")
// .find(".pointer.item")
// .contains("LastUpdate")
// .click();
cy.get('.nc-form-left-drawer').find('.ant-card').contains('LastUpdate').should('exist').click(); cy.get('.nc-form-left-drawer').find('.ant-card').contains('LastUpdate').should('exist').click();
cy.get(".nc-form-input-LastUpdate").should("exist"); cy.get(".nc-form-input-LastUpdate").should("exist");
cy.wait(3000); cy.wait(300);
}); });
it(`Validate ${viewType}: URL verification`, () => { it(`Validate ${viewType}: URL verification`, () => {
@ -391,27 +393,33 @@ export const genTest = (apiType, dbType) => {
formViewURL = url; formViewURL = url;
}); });
cy.wait(3000); // cy.saveLocalStorage();
cy.wait(300);
}); });
it(`Validate ${viewType}: URL validation after re-access`, () => { it.skip(`Validate ${viewType}: URL validation after re-access`, () => {
// visit URL // visit URL
cy.log(formViewURL); cy.log(formViewURL);
// cy.restoreLocalStorage();
cy.visit(formViewURL, { cy.visit(formViewURL, {
baseUrl: null, baseUrl: null,
}); });
cy.wait(5000);
// New form appeared? Header & description should exist // New form appeared? Header & description should exist
validateFormHeader(); validateFormHeader();
}); });
it(`Delete ${viewType} view`, () => { it(`Delete ${viewType} view`, () => {
// cy.restoreLocalStorage();
// number of view entries should be 2 before we delete // number of view entries should be 2 before we delete
cy.get(".nc-view-item").its("length").should("eq", 2); cy.get(".nc-view-item").its("length").should("eq", 2);
// click on delete icon (becomes visible on hovering mouse) // click on delete icon (becomes visible on hovering mouse)
cy.get(".nc-view-delete-icon").click({ force: true }); cy.get(".nc-view-delete-icon").click({ force: true });
cy.wait(1000)
cy.getActiveModal().find('.ant-btn-dangerous').click();
cy.toastWait("View deleted successfully"); cy.toastWait("View deleted successfully");
// confirm if the number of veiw entries is reduced by 1 // confirm if the number of veiw entries is reduced by 1
@ -420,8 +428,6 @@ export const genTest = (apiType, dbType) => {
// clean up newly added rows into Country table operations // clean up newly added rows into Country table operations
// this auto verifies successfull addition of rows to table as well // this auto verifies successfull addition of rows to table as well
mainPage.getPagination(5).click(); mainPage.getPagination(5).click();
// kludge: flicker on load
// cy.wait(3000)
cy.get(".nc-grid-row").should("have.length", 13); cy.get(".nc-grid-row").should("have.length", 13);
cy.get(".ant-checkbox").should('exist').eq(10).click({ force: true }); cy.get(".ant-checkbox").should('exist').eq(10).click({ force: true });
@ -433,27 +439,6 @@ export const genTest = (apiType, dbType) => {
cy.getActiveMenu() cy.getActiveMenu()
.contains("Delete Selected Rows") .contains("Delete Selected Rows")
.click({ force: true }); .click({ force: true });
// mainPage
// .getRow(10)
// .find(".mdi-checkbox-blank-outline")
// .click({ force: true });
// mainPage
// .getRow(11)
// .find(".mdi-checkbox-blank-outline")
// .click({ force: true });
// mainPage
// .getRow(12)
// .find(".mdi-checkbox-blank-outline")
// .click({ force: true });
// mainPage
// .getRow(13)
// .find(".mdi-checkbox-blank-outline")
// .click({ force: true });
//
// mainPage.getCell("Country", 10).rightclick();
// cy.getActiveMenu().contains("Delete Selected Row").click();
// cy.toastWait('Deleted selected rows successfully')
}); });
}; };

8
scripts/cypress-v2/integration/common/4d_table_view_grid_locked.js

@ -8,22 +8,14 @@ export const genTest = (apiType, dbType) => {
// Run once before test- create project (rest/graphql) // Run once before test- create project (rest/graphql)
// //
before(() => { before(() => {
cy.fileHook();
mainPage.tabReset(); mainPage.tabReset();
// // kludge: wait for page load to finish
// cy.wait(1000);
// // close team & auth tab
// cy.get('button.ant-tabs-tab-remove').should('exist').click();
// cy.wait(1000);
// open a table to work on views // open a table to work on views
// //
cy.openTableTab("Country", 25); cy.openTableTab("Country", 25);
}); });
beforeEach(() => { beforeEach(() => {
cy.fileHook();
}); });
after(() => { after(() => {

12
scripts/cypress-v2/integration/common/4e_form_view_share.js

@ -12,7 +12,6 @@ export const genTest = (apiType, dbType) => {
// Run once before test- create project (rest/graphql) // Run once before test- create project (rest/graphql)
// //
before(() => { before(() => {
cy.fileHook();
mainPage.tabReset(); mainPage.tabReset();
// open a table to work on views // open a table to work on views
// //
@ -29,7 +28,6 @@ export const genTest = (apiType, dbType) => {
}); });
beforeEach(() => { beforeEach(() => {
cy.fileHook();
cy.restoreLocalStorage(); cy.restoreLocalStorage();
}); });
@ -107,12 +105,12 @@ export const genTest = (apiType, dbType) => {
.contains("Form-1") .contains("Form-1")
.click(); .click();
mainPage.shareView().click({ force: true }); cy.wait(2000);
mainPage.shareView().click();
cy.wait(5000);
// copy link text, visit URL // copy link text, visit URL
cy.getActiveModal() cy.getActiveModal()
.should('exist')
.find(".share-link-box") .find(".share-link-box")
.contains("/nc/form/", { timeout: 10000 }) .contains("/nc/form/", { timeout: 10000 })
.should("exist") .should("exist")
@ -199,13 +197,15 @@ export const genTest = (apiType, dbType) => {
cy.visit(storedURL, { cy.visit(storedURL, {
baseUrl: null, baseUrl: null,
}); });
cy.wait(5000); cy.wait(2000);
// number of view entries should be 2 before we delete // number of view entries should be 2 before we delete
cy.get(".nc-view-item").its("length").should("eq", 2); cy.get(".nc-view-item").its("length").should("eq", 2);
// click on delete icon (becomes visible on hovering mouse) // click on delete icon (becomes visible on hovering mouse)
cy.get(".nc-view-delete-icon").click({ force: true }); cy.get(".nc-view-delete-icon").click({ force: true });
cy.wait(1000);
cy.getActiveModal().find('.ant-btn-dangerous').should('exist').click();
cy.toastWait("View deleted successfully"); cy.toastWait("View deleted successfully");
// confirm if the number of veiw entries is reduced by 1 // confirm if the number of veiw entries is reduced by 1

144
scripts/cypress-v2/integration/common/4f_grid_view_share.js

@ -15,11 +15,6 @@ export const genTest = (apiType, dbType) => {
if (!isTestSuiteActive(apiType, dbType)) return; if (!isTestSuiteActive(apiType, dbType)) return;
const generateViewLink = (viewName) => { const generateViewLink = (viewName) => {
// click on share view
// cy.get(".v-navigation-drawer__content > .container")
// .find(".v-list > .v-list-item")
// .contains("Share View")
// .click();
mainPage.shareView().click(); mainPage.shareView().click();
cy.wait(1000); cy.wait(1000);
@ -45,7 +40,6 @@ export const genTest = (apiType, dbType) => {
// Run once before test- create project (rest/graphql) // Run once before test- create project (rest/graphql)
// //
before(() => { before(() => {
cy.fileHook();
mainPage.tabReset(); mainPage.tabReset();
// // kludge: wait for page load to finish // // kludge: wait for page load to finish
@ -63,7 +57,6 @@ export const genTest = (apiType, dbType) => {
}); });
beforeEach(() => { beforeEach(() => {
cy.fileHook();
cy.restoreLocalStorage(); cy.restoreLocalStorage();
}); });
@ -90,6 +83,7 @@ export const genTest = (apiType, dbType) => {
// store base URL- to re-visit and delete form view later // store base URL- to re-visit and delete form view later
cy.url().then((url) => { cy.url().then((url) => {
storedURL = url; storedURL = url;
cy.saveLocalStorage();
}); });
}); });
@ -110,16 +104,8 @@ export const genTest = (apiType, dbType) => {
generateViewLink("combined"); generateViewLink("combined");
// verify if only one link exists in table // verify if only one link exists in table
// cy.get(".v-navigation-drawer__content > .container")
// .find(".v-list > .v-list-item")
// .contains("Share View")
// .parent()
// .find("button.mdi-dots-vertical")
// .click();
mainPage.shareViewList().click(); mainPage.shareViewList().click();
// cy.getActiveMenu().find(".v-list-item").contains("Views List").click();
cy.get('th:contains("View Link")').should("exist"); cy.get('th:contains("View Link")').should("exist");
cy.get('th:contains("View Link")') cy.get('th:contains("View Link")')
@ -131,9 +117,6 @@ export const genTest = (apiType, dbType) => {
.should("eq", 1) .should("eq", 1)
.then(() => { .then(() => {
cy.get('button.ant-modal-close:visible').click(); cy.get('button.ant-modal-close:visible').click();
// cy.get(".v-overlay--active > .v-overlay__scrim").click({
// force: true,
// });
}); });
}); });
@ -192,7 +175,6 @@ export const genTest = (apiType, dbType) => {
for (let j = 0; j < 4; j++) { for (let j = 0; j < 4; j++) {
expect(strCol[j]).to.be.equal(retCol[j]); expect(strCol[j]).to.be.equal(retCol[j]);
} }
// expect(retrievedRecords[i]).to.be.equal(storedRecords[i])
} }
}; };
@ -291,27 +273,27 @@ export const genTest = (apiType, dbType) => {
.find(".nc-icon.nc-action-icon.nc-plus") .find(".nc-icon.nc-action-icon.nc-plus")
.should("not.exist"); .should("not.exist");
// to be fixed mainPage
// mainPage .getCell("Customer List", 3)
// .getCell("Customer List", 3) .click()
// .click() .find(".nc-icon.nc-action-icon.nc-arrow-expand")
// .find(".nc-icon.nc-action-icon.nc-arrow-expand") .click({ force: true });
// .click();
// reload button
// cy.getActiveModal().find(".nc-icon.nc-reload").should("exist"); cy.getActiveModal().find(".nc-icon").should("exist");
// cy.getActiveModal() cy.getActiveModal()
// .find("button") .find("button")
// .contains("Link to") .contains("Link to")
// .should("not.exist"); .should("not.exist");
// cy.getActiveModal() cy.getActiveModal()
// .find(".ant-card") .find(".ant-card")
// .contains("2") .contains("2")
// .should("exist"); .should("exist");
// cy.getActiveModal() cy.getActiveModal()
// .find(".ant-card") .find(".ant-card")
// .find("button") .find("button")
// .should("not.exist"); .should("not.exist");
// cy.get('button.ant-modal-close').click(); cy.get('button.ant-modal-close').click();
}); });
it(`Share GRID view : Virtual column validation > belongs to`, () => { it(`Share GRID view : Virtual column validation > belongs to`, () => {
@ -322,17 +304,16 @@ export const genTest = (apiType, dbType) => {
.find(".nc-icon.nc-unlink-icon") .find(".nc-icon.nc-unlink-icon")
.should("not.exist"); .should("not.exist");
// to be fixed mainPage
// mainPage .getCell("City", 1)
// .getCell("City", 1) .click()
// .click() .find(".nc-icon.nc-action-icon.nc-arrow-expand")
// .find(".nc-icon.nc-action-icon.nc-arrow-expand") .should("not.exist");
// .should("not.exist"); mainPage
// mainPage .getCell("City", 1)
// .getCell("City", 1) .find(".chips")
// .find(".chips") .contains("Kanchrapara")
// .contains("Kanchrapara") .should("exist");
// .should("exist");
}); });
it(`Share GRID view : Virtual column validation > many to many`, () => { it(`Share GRID view : Virtual column validation > many to many`, () => {
@ -348,23 +329,24 @@ export const genTest = (apiType, dbType) => {
.find(".nc-icon.nc-action-icon.nc-plus") .find(".nc-icon.nc-action-icon.nc-plus")
.should("not.exist"); .should("not.exist");
// to be fixed mainPage
// mainPage .getCell("Staff List", 1)
// .getCell("Staff List", 1) .click()
// .click() .find(".nc-icon.nc-action-icon.nc-arrow-expand")
// .find(".nc-icon.nc-action-icon.nc-arrow-expand") .click({ force: true });
// .click();
// reload button
// cy.getActiveModal().find(".nc-icon.nc-reload").should("exist"); cy.getActiveModal().find(".nc-icon").should("exist");
// cy.getActiveModal() cy.getActiveModal()
// .find("button") .find("button")
// .contains("Link to") .contains("Link to")
// .should("not.exist"); .should("not.exist");
// cy.get("body").type("{esc}"); cy.get('button.ant-modal-close:visible').last().click();
}); });
it(`Delete ${viewType.toUpperCase()} view`, () => { it(`Delete ${viewType.toUpperCase()} view`, () => {
// go back to base page // go back to base page
cy.restoreLocalStorage();
cy.visit(storedURL, { cy.visit(storedURL, {
baseUrl: null, baseUrl: null,
}); });
@ -389,23 +371,15 @@ export const genTest = (apiType, dbType) => {
describe(`${apiType.toUpperCase()} api - Grid view/ row-column update verification`, () => { describe(`${apiType.toUpperCase()} api - Grid view/ row-column update verification`, () => {
before(() => { before(() => {
cy.fileHook();
// // kludge: wait for page load to finish
// cy.wait(1000);
// // close team & auth tab
// cy.get('button.ant-tabs-tab-remove').should('exist').click();
// cy.wait(1000);
cy.restoreLocalStorage();
// Address table has belongs to, has many & many-to-many // Address table has belongs to, has many & many-to-many
cy.openTableTab("Country", 25); cy.openTableTab("Country", 25);
mainPage.toggleRightSidebar();
cy.saveLocalStorage();
// store base URL- to re-visit and delete form view later // store base URL- to re-visit and delete form view later
cy.url().then((url) => { cy.url().then((url) => {
storedURL = url; storedURL = url;
cy.saveLocalStorage();
generateViewLink("rowColUpdate"); generateViewLink("rowColUpdate");
}); });
}); });
@ -425,13 +399,6 @@ export const genTest = (apiType, dbType) => {
// wait for page rendering to complete // wait for page rendering to complete
cy.get(".nc-grid-row").should("have.length", 10); cy.get(".nc-grid-row").should("have.length", 10);
// mainPage
// .getRow(10)
// .find(".mdi-checkbox-blank-outline")
// .click({ force: true });
// mainPage.getCell("Country", 10).rightclick();
// cy.getActiveMenu().contains("Delete Selected Row").click();
mainPage.getCell("Country", 10).rightclick(); mainPage.getCell("Country", 10).rightclick();
cy.getActiveMenu() cy.getActiveMenu()
.find('.ant-dropdown-menu-item:contains("Delete Row")') .find('.ant-dropdown-menu-item:contains("Delete Row")')
@ -440,14 +407,6 @@ export const genTest = (apiType, dbType) => {
// delete column // delete column
mainPage.deleteColumn("dummy"); mainPage.deleteColumn("dummy");
// cy.get(`th:contains('dummy') .nc-ui-dt-dropdown`)
// .trigger("mouseover")
// .click();
// cy.get(".nc-column-delete").click();
// cy.get("button:contains(Confirm)").click();
//
// cy.toastWait("Update table successful");
mainPage.deleteCreatedViews(); mainPage.deleteCreatedViews();
// close table // close table
@ -457,12 +416,6 @@ export const genTest = (apiType, dbType) => {
it(`Generate default Shared GRID view URL`, () => { it(`Generate default Shared GRID view URL`, () => {
// add row // add row
cy.get(".nc-add-new-row-btn").click(); cy.get(".nc-add-new-row-btn").click();
// cy.get("#data-table-form-Country > input")
// .first()
// .click()
// .type("a");
// cy.contains("Save row").filter("button").click({ force: true });
// cy.toastWait("updated successfully");
cy.get(".nc-expand-col-Country").find(".nc-cell > input") cy.get(".nc-expand-col-Country").find(".nc-cell > input")
.should("exist") .should("exist")
.first() .first()
@ -488,7 +441,6 @@ export const genTest = (apiType, dbType) => {
}); });
cy.wait(5000); cy.wait(5000);
//5
// wait for public view page to load! // wait for public view page to load!
// wait for page rendering to complete // wait for page rendering to complete
cy.get(".nc-grid-row").should("have.length", 25); cy.get(".nc-grid-row").should("have.length", 25);

252
scripts/cypress-v2/integration/common/5a_user_role.js

@ -23,13 +23,32 @@ export const genTest = (apiType, dbType) => {
describe("Static user creations (different roles)", () => { describe("Static user creations (different roles)", () => {
before(() => { before(() => {
cy.fileHook();
mainPage.tabReset(); mainPage.tabReset();
// kludge: wait for page load to finish
cy.wait(4000);
// close team & auth tab
cy.get('button.ant-tabs-tab-remove').should('exist').click();
cy.wait(1000);
settingsPage.openMenu(settingsPage.TEAM_N_AUTH) settingsPage.openMenu(settingsPage.TEAM_N_AUTH)
cy.saveLocalStorage();
}); });
beforeEach(() => { beforeEach(() => {
cy.fileHook(); cy.restoreLocalStorage();
});
after(() => {
// sign out
cy.visit(`/`);
cy.wait(5000);
cy.get('.nc-menu-accounts').should('exist').click();
cy.getActiveMenu().find('.ant-dropdown-menu-item').eq(1).click();
cy.wait(5000);
cy.get('button:contains("SIGN")').should('exist')
}); });
const addUser = (user) => { const addUser = (user) => {
@ -38,20 +57,26 @@ export const genTest = (apiType, dbType) => {
// for subsequent projects, they will be required to just add to this project // for subsequent projects, they will be required to just add to this project
// using ROW count to identify if its former or latter scenario // using ROW count to identify if its former or latter scenario
// 5 users (owner, creator, editor, viewer, commenter) = 5 // 5 users (owner, creator, editor, viewer, commenter) = 5
cy.get(`.nc-user-row`).then((obj) => { // cy.get(`.nc-user-row`).then((obj) => {
cy.log(obj.length); // cy.log(obj.length);
if (obj.length == 5) { // if (obj.length == 5) {
mainPage.addExistingUserToProject( // mainPage.addExistingUserToProject(
user.credentials.username, // user.credentials.username,
user.name // user.name
); // );
} else { // } else {
mainPage.addNewUserToProject( // mainPage.addNewUserToProject(
user.credentials, // user.credentials,
user.name // user.name
); // );
} // }
}); // });
cy.get(`.nc-user-row`).should('exist')
mainPage.addNewUserToProject(
user.credentials,
user.name
);
}); });
}; };
@ -60,7 +85,7 @@ export const genTest = (apiType, dbType) => {
addUser(roles.commenter); addUser(roles.commenter);
addUser(roles.viewer); addUser(roles.viewer);
// Access contrl list- configuration // Access control list- configuration
// //
it(`Access control list- configuration`, () => { it(`Access control list- configuration`, () => {
mainPage.closeMetaTab(); mainPage.closeMetaTab();
@ -96,69 +121,77 @@ export const genTest = (apiType, dbType) => {
const roleValidation = (roleType) => { const roleValidation = (roleType) => {
describe(`User role validation`, () => { describe(`User role validation`, () => {
before(() => { before(() => {
cy.fileHook(); // cy.restoreLocalStorage();
cy.visit(mainPage.roleURL[roleType])
cy.wait(5000);
cy.get('button:contains("SIGN UP")').should('exist')
cy.get('input[type="text"]', { timeout: 20000 }).type(
roles[roleType].credentials.username
);
cy.get('input[type="password"]').type(roles[roleType].credentials.password);
cy.get('button:contains("SIGN UP")').click();
cy.wait(3000);
cy.get('.nc-project-page-title').contains("My Projects").should("be.visible");
if (dbType === "xcdb") {
if ("rest" == apiType)
projectsPage.openProject(
staticProjects.sampleREST.basic.name
);
else
projectsPage.openProject(
staticProjects.sampleGQL.basic.name
);
} else if (dbType === "mysql") {
if ("rest" == apiType)
projectsPage.openProject(
staticProjects.externalREST.basic.name
);
else
projectsPage.openProject(
staticProjects.externalGQL.basic.name
);
} else if (dbType === "postgres") {
if ("rest" == apiType)
projectsPage.openProject(
staticProjects.pgExternalREST.basic.name
);
else
projectsPage.openProject(
staticProjects.pgExternalGQL.basic.name
);
}
if (roleType === "creator") {
// kludge: wait for page load to finish
// close team & auth tab
cy.wait(2000);
cy.get('button.ant-tabs-tab-remove').should('exist').click();
cy.wait(1000);
}
cy.saveLocalStorage();
}) })
beforeEach(() => { beforeEach(() => {
cy.fileHook(); cy.restoreLocalStorage();
}); });
if (roleType != "owner") { after(() => {
it(`[${roles[roleType].name}] SignIn, Open project`, () => { // sign out
cy.log(mainPage.roleURL[roleType]); cy.visit(`/`);
cy.visit(mainPage.roleURL[roleType], { cy.wait(5000);
baseUrl: null, cy.get('.nc-menu-accounts').should('exist').click();
}); cy.getActiveMenu().find('.ant-dropdown-menu-item').eq(1).click();
cy.wait(5000);
cy.wait(5000);
// Redirected to new URL, feed details cy.get('button:contains("SIGN")').should('exist')
// });
cy.get('input[type="text"]')
.should("exist")
.type(roles[roleType].credentials.username);
cy.get('input[type="password"]').type(
roles[roleType].credentials.password
);
cy.get('button:contains("SIGN")').click();
// cy.url({ timeout: 6000 }).should("contain", "#/project");
cy.get('nc-project-page-title').contains("My Projects").should("be.visible");
if (dbType === "xcdb") {
if ("rest" == apiType)
projectsPage.openProject(
staticProjects.sampleREST.basic.name
);
else
projectsPage.openProject(
staticProjects.sampleGQL.basic.name
);
} else if (dbType === "mysql") {
if ("rest" == apiType)
projectsPage.openProject(
staticProjects.externalREST.basic.name
);
else
projectsPage.openProject(
staticProjects.externalGQL.basic.name
);
} else if (dbType === "postgres") {
if ("rest" == apiType)
projectsPage.openProject(
staticProjects.pgExternalREST.basic.name
);
else
projectsPage.openProject(
staticProjects.pgExternalGQL.basic.name
);
}
if (roleType != "creator") {
cy.closeTableTab("Actor");
}
});
}
/////////////////////////////////////////////////////// ///////////////////////////////////////////////////////
// Test suite // Test suite
@ -205,45 +238,44 @@ export const genTest = (apiType, dbType) => {
_viewMenu(roleType, "userRole"); _viewMenu(roleType, "userRole");
}); });
it(`[${roles[roleType].name}] Top Right Menu bar`, () => {
// Share button is conditional
// Rest are static/ mandatory
//
_topRightMenu(roleType, "userRole");
});
it(`[${roles[roleType].name}] Download files`, () => { it(`[${roles[roleType].name}] Download files`, () => {
// viewer & commenter doesn't contain hideField option in ncv2
// #ID, City, LastUpdate, City => Address, Country <= City, + // to be fixed
mainPage.hideField("LastUpdate"); if(roleType === 'commenter' || roleType === 'viewer') {}
else {
const verifyCsv = (retrievedRecords) => { // viewer & commenter doesn't contain hideField option in ncv2
// expected output, statically configured // #ID, City, LastUpdate, City => Address, Country <= City, +
let storedRecords = [ mainPage.hideField("LastUpdate");
`City,Address List,Country`,
`A Corua (La Corua),939 Probolinggo Loop,Spain`, const verifyCsv = (retrievedRecords) => {
`Abha,733 Mandaluyong Place,Saudi Arabia`, // expected output, statically configured
`Abu Dhabi,535 Ahmadnagar Manor,United Arab Emirates`, let storedRecords = [
`Acua,1789 Saint-Denis Parkway,Mexico`, `City,Address List,Country`,
]; `A Corua (La Corua),939 Probolinggo Loop,Spain`,
`Abha,733 Mandaluyong Place,Saudi Arabia`,
// skip if xcdb `Abu Dhabi,535 Ahmadnagar Manor,United Arab Emirates`,
if (!isXcdb()) { `Acua,1789 Saint-Denis Parkway,Mexico`,
for (let i = 0; i < storedRecords.length; i++) { ];
// cy.log(retrievedRecords[i])
expect(retrievedRecords[i]).to.be.equal( // skip if xcdb
storedRecords[i] if (!isXcdb()) {
); for (let i = 0; i < storedRecords.length; i++) {
// cy.log(retrievedRecords[i])
expect(retrievedRecords[i]).to.be.equal(
storedRecords[i]
);
}
} }
} };
};
// download & verify // download & verify
mainPage.downloadAndVerifyCsv( mainPage.downloadAndVerifyCsv(
`City_exported_1.csv`, `City_exported_1.csv`,
verifyCsv verifyCsv,
); roleType
mainPage.unhideField("LastUpdate"); );
mainPage.unhideField("LastUpdate");
}
}); });
}); });
}; };

12
scripts/cypress-v2/integration/common/5b_preview_role.js

@ -29,20 +29,23 @@ export const genTest = (apiType, dbType, roleType) => {
describe("Role preview validations", () => { describe("Role preview validations", () => {
// Sign in/ open project // Sign in/ open project
before(() => { before(() => {
cy.fileHook();
loginPage.loginAndOpenProject(apiType, dbType); loginPage.loginAndOpenProject(apiType, dbType);
cy.openTableTab("City", 25); cy.openTableTab("City", 25);
cy.wait(3000);
settingsPage.openProjectMenu(); settingsPage.openProjectMenu();
cy.getActiveMenu().find(`[data-submenu-id="preview-as"]`).should('exist').click() cy.getActiveMenu().find(`[data-submenu-id="preview-as"]`).should('exist').click()
cy.wait(1000) cy.wait(1000)
cy.get('.ant-dropdown-menu-submenu').eq(3).find(`[data-menu-id="editor"]`).should('exist').click() cy.get('.ant-dropdown-menu-submenu').eq(4).find(`[data-menu-id="editor"]`).should('exist').click()
cy.wait(10000) cy.wait(10000)
cy.saveLocalStorage();
}); });
beforeEach(() => { beforeEach(() => {
cy.fileHook(); cy.restoreLocalStorage();
}); });
after(() => { after(() => {
@ -93,6 +96,9 @@ export const genTest = (apiType, dbType, roleType) => {
.find(`[type="radio"][value="${roleType}"]`) .find(`[type="radio"][value="${roleType}"]`)
.should('exist') .should('exist')
.click(); .click();
cy.wait(5000)
cy.saveLocalStorage();
}); });
it(`Role preview: ${roleType}: Advance settings`, () => { it(`Role preview: ${roleType}: Advance settings`, () => {

10
scripts/cypress-v2/integration/common/7b_import_from_airtable.js

@ -13,7 +13,6 @@ export const genTest = (apiType, dbType) => {
describe(`Import from airtable`, () => { describe(`Import from airtable`, () => {
before(() => { before(() => {
cy.fileHook();
apiKey = Cypress.env("airtable").apiKey; apiKey = Cypress.env("airtable").apiKey;
sharedBase = Cypress.env("airtable").sharedBase; sharedBase = Cypress.env("airtable").sharedBase;
@ -21,14 +20,17 @@ export const genTest = (apiType, dbType) => {
projectsPage.createProject({ dbType: "none", apiType: "REST", name: "importSample" }, {}) projectsPage.createProject({ dbType: "none", apiType: "REST", name: "importSample" }, {})
}); });
after(() => {}); after(() => {
cy.saveLocalStorage();
});
it("Import", () => { it("Import", () => {
cy.log(apiKey, sharedBase); cy.log(apiKey, sharedBase);
// trigger import // trigger import
cy.get(`[data-menu-id="addORImport"]`).click(); cy.get('.nc-add-new-table').should('exist').trigger('mouseover')
cy.getActivePopUp().contains("Airtable").should('exist').click(); cy.get('.nc-import-menu').should('exist').click()
cy.getActiveMenu().find('.ant-dropdown-menu-item').contains('Airtable').click()
cy.getActiveModal().find(".nc-input-api-key").should('exist').clear().type(apiKey) cy.getActiveModal().find(".nc-input-api-key").should('exist').clear().type(apiKey)
cy.getActiveModal().find(".nc-input-shared-base").should('exist').clear().type(sharedBase) cy.getActiveModal().find(".nc-input-shared-base").should('exist').clear().type(sharedBase)

242
scripts/cypress-v2/integration/common/9a_QuickTest.js

@ -39,30 +39,31 @@ let cn = [ "Name", "Notes", "Status", "Tags", "Done", "Date", "Phone",
"Actor", "Status (from Actor)", "RollUp", "Computation", "Producer" ] "Actor", "Status (from Actor)", "RollUp", "Computation", "Producer" ]
function openWebhook(index) { function openWebhook(index) {
cy.get(".nc-btn-webhook").should("exist").click(); cy.get('.nc-actions-menu-btn').should('exist').click();
cy.get(".nc-hook").eq(index).click({ force: true }); cy.getActiveMenu().find('.ant-dropdown-menu-title-content').contains('Webhooks').click()
cy.get(".nc-hook").eq(index).click();
} }
// to be invoked after open // to be invoked after open
function verifyWebhook(config) { function verifyWebhook(config) {
cy.get(".nc-text-field-hook-title") cy.get(".nc-text-field-hook-title").then(($element) => {
.find('input').then(($element) => {
expect($element[0].value).to.have.string(config.title) expect($element[0].value).to.have.string(config.title)
}) })
cy.get(".nc-text-field-hook-event") cy.get(".nc-text-field-hook-event")
.find('.v-select__selection') .find('.ant-select-selection-item')
.contains(config.event) .contains(config.event)
.should('exist') .should('exist')
cy.get(".nc-text-field-hook-notification-type") cy.get(".nc-select-hook-notification-type")
.find('.v-select__selection') .find('.ant-select-selection-item')
.contains(config.notification) .contains(config.notification)
.should('exist') .should('exist')
cy.get('.nc-select-hook-url-method') cy.get('.nc-select-hook-url-method')
.find('.v-select__selection') .find('.ant-select-selection-item')
.contains(config.type) .contains(config.type)
.should('exist') .should('exist')
cy.get(".nc-text-field-hook-url-path") cy.get(".nc-text-field-hook-url-path")
.find('input').then(($element) => { .then(($element) => {
expect($element[0].value).to.have.string(config.url) expect($element[0].value).to.have.string(config.url)
}) })
cy.get(".nc-icon-hook-navigate-left").click({force:true}) cy.get(".nc-icon-hook-navigate-left").click({force:true})
@ -80,26 +81,56 @@ export const genTest = (apiType, dbType, testMode) => {
} }
before(() => { before(() => {
cy.fileHook(); cy.restoreLocalStorage();
if( testMode === 'CY_QUICK') { if( testMode === 'CY_QUICK') {
// cy.task("copyFile") // cy.task("copyFile")
loginPage.signIn(roles.owner.credentials); loginPage.signIn(roles.owner.credentials);
projectsPage.openProject("sample"); projectsPage.openProject("sample");
// kludge: wait for page load to finish
cy.wait(2000);
// close team & auth tab
cy.get('button.ant-tabs-tab-remove').should('exist').click();
cy.wait(1000);
cy.saveLocalStorage();
} }
}); });
after(() => {}); beforeEach(() => {
cy.restoreLocalStorage();
})
// afterEach(() => {
// cy.saveLocalStorage();
// })
after(() => {
cy.restoreLocalStorage();
// sign out
cy.visit(`/`);
cy.wait(5000);
cy.get('.nc-menu-accounts').should('exist').click();
cy.getActiveMenu().find('.ant-dropdown-menu-item').eq(1).click();
cy.wait(5000);
cy.get('button:contains("SIGN")').should('exist')
});
it("Verify Schema", () => { it("Verify Schema", () => {
cy.openTableTab("Film", 3) cy.openTableTab("Film", 3)
// verify if all tables exist // verify if all tables exist
for(let i=0; i<tn.length; i++) for(let i=0; i<tn.length; i++) {
cy.get(".nc-project-tree").contains(tn[i]).should('exist') cy.get(`.nc-project-tree-tbl-${tn[i]}`).should('exist')
}
// for Film table, verify columns // for Film table, verify columns
for(let i=0; i<columnCount; i++) for(let i=0; i<columnCount; i++) {
cy.get(".nc-grid-header-row").find(`[data-col="${cn[i]}"]`).should('exist') cy.get(`th[data-title="${cn[i]}"]`).should("exist");
}
}); });
@ -114,20 +145,18 @@ export const genTest = (apiType, dbType, testMode) => {
// checkbox // checkbox
mainPage mainPage
.getCell("Done", cellIdx) .getCell("Done", cellIdx)
.find(".mdi-check-circle-outline") .find(".nc-cell-hover-show")
.should(records2.Done ? "exist" : "not.exist"); .should(records2.Done ? "not.exist" : "exist");
// date // date
// duration // duration
mainPage.getCell("Duration", cellIdx).find('input').then(($e) => { mainPage.getCell("Duration", cellIdx).contains(records2.Duration).should("exist");
expect($e[0].value).to.equal(records2.Duration)
})
// rating // rating
mainPage mainPage
.getCell("Rating", cellIdx) .getCell("Rating", cellIdx)
.find("button.mdi-star") .find(".ant-rate-star-full")
.should("have.length", records2.Rating); .should("have.length", records2.Rating);
// verifying only one instance as its different for PG & SQLite // verifying only one instance as its different for PG & SQLite
@ -135,58 +164,34 @@ export const genTest = (apiType, dbType, testMode) => {
// for SQLite: its Actor1, Actor2 // for SQLite: its Actor1, Actor2
// LinkToAnotherRecord // LinkToAnotherRecord
mainPage.getCell("Actor", cellIdx).scrollIntoView(); mainPage.getCell("Actor", cellIdx).scrollIntoView();
cy.get( cy.get(`:nth-child(${cellIdx}) > [data-title="Actor"]`)
`:nth-child(${cellIdx}) > [data-col="Actor"] > .nc-virtual-cell > .v-lazy > .d-100 > .chips > :nth-child(1) > .v-chip__content > .name` .find('.chip')
) .eq(0)
.contains(records2.Actor[0]) .contains(records2.Actor[0])
.should("exist"); .should("exist");
// cy.get(
// `:nth-child(${cellIdx}) > [data-col="Actor"] > .nc-virtual-cell > .v-lazy > .d-100 > .chips > :nth-child(2) > .v-chip__content > .name`
// )
// .contains(records2.Actor[1])
// .should("exist");
// lookup // lookup
mainPage.getCell("Status (from Actor)", cellIdx).scrollIntoView(); mainPage.getCell("Status (from Actor)", cellIdx).scrollIntoView();
cy.get( cy.get(`:nth-child(${cellIdx}) > [data-title="Status (from Actor)"]`)
`:nth-child(${cellIdx}) > [data-col="Status (from Actor)"] > .nc-virtual-cell > .v-lazy > .d-flex > :nth-child(1) > .v-chip__content > div > .set-item` .find('.nc-cell')
) .eq(0)
.contains(records2["Status (from Actor)"][0]) .contains(records2["Status (from Actor)"][0])
.should("exist"); .should("exist");
// cy.get(
// `:nth-child(${cellIdx}) > [data-col="Status (from Actor)"] > .nc-virtual-cell > .v-lazy > .d-flex > :nth-child(2) > .v-chip__content > div > .set-item`
// )
// .contains(records2["Status (from Actor)"][1])
// .should("exist");
// rollup // rollup
if( testMode === 'CY_QUICK') { if( testMode === 'CY_QUICK') {
mainPage.getCell("RollUp", cellIdx).scrollIntoView(); mainPage.getCell("RollUp", cellIdx).scrollIntoView();
cy.get(`:nth-child(${cellIdx}) > [data-col="RollUp"] > .nc-virtual-cell`) mainPage.getCell("RollUp", cellIdx).contains(records2.RollUp).should("exist");
.contains(records2.RollUp)
.should("exist");
// formula // formula
mainPage.getCell("Computation", cellIdx).scrollIntoView(); mainPage.getCell("Computation", cellIdx).scrollIntoView();
cy.get( mainPage.getCell("Computation", cellIdx).contains(records2.Computation).should("exist");
`:nth-child(${cellIdx}) > [data-col="Computation"] > .nc-virtual-cell`
)
.contains(records2.Computation)
.should("exist");
// ltar hm relation // ltar hm relation
mainPage.getCell("Producer", cellIdx).scrollIntoView(); mainPage.getCell("Producer", cellIdx).scrollIntoView();
cy.get( mainPage.getCell("Producer", cellIdx).find('.chip').eq(0).contains(records2.Producer[0]).should('exist')
`:nth-child(${cellIdx}) > [data-col="Producer"] > .nc-virtual-cell > .v-lazy > .d-100 > .chips > :nth-child(1) > .v-chip__content > .name` mainPage.getCell("Producer", cellIdx).find('.chip').eq(1).contains(records2.Producer[1]).should('exist')
)
.contains(records2.Producer[0])
.should("exist");
cy.get(
`:nth-child(${cellIdx}) > [data-col="Producer"] > .nc-virtual-cell > .v-lazy > .d-100 > .chips > :nth-child(2) > .v-chip__content > .name`
)
.contains(records2.Producer[1])
.should("exist");
} }
cy.closeTableTab("Film"); cy.closeTableTab("Film");
@ -194,55 +199,80 @@ export const genTest = (apiType, dbType, testMode) => {
it("Verify Views & Shared base", () => { it("Verify Views & Shared base", () => {
cy.openTableTab("Film", 3); cy.openTableTab("Film", 3);
cy.get('.nc-form-view-item').eq(0) mainPage.toggleRightSidebar();
.click({ force: true }) cy.get('.nc-form-view-item:visible')
.should('exist')
.eq(0)
.click({ force: true })
// Header & description should exist // Header & description should exist
// cy.get(".nc-form")
// .find('[placeholder="Form Title"]')
// .contains("FormTitle")
// .should("exist");
// cy.get(".nc-form")
// .find('[placeholder="Add form description"]')
// .contains("FormDescription")
// .should("exist");
cy.get(".nc-form").should("exist");
cy.get(".nc-form") cy.get(".nc-form")
.find('[placeholder="Form Title"]') .find('[placeholder="Form Title"]')
.contains("FormTitle") .should("exist").then(($el) => {
.should("exist"); cy.log($el)
expect($el.val()).to.equal("FormTitle");
})
cy.get(".nc-form") cy.get(".nc-form")
.find('[placeholder="Add form description"]') .find('[placeholder="Add form description"]')
.contains("FormDescription") .should("exist").then(($el) => {
.should("exist"); cy.log($el)
expect($el.val()).to.equal("FormDescription");
})
// modified column name & help text // modified column name & help text
cy.get(".nc-field-wrapper").eq(0) cy.get(".nc-editable").eq(0)
.find('.nc-field-labels') .find('.name')
.contains("DisplayName") .contains("DisplayName")
.should('exist') .should('exist')
cy.get(".nc-field-wrapper").eq(0) cy.get(".nc-editable").eq(0)
.find('.nc-hint') .find('.text-gray-500')
.contains('HelpText') .contains('HelpText')
.should('exist') .should('exist')
cy.get(".nc-field-wrapper").eq(1) cy.get(".nc-editable").eq(1)
.find('.nc-field-labels') .find('.name')
.contains("Email") .contains("Email")
.should('exist') .should('exist')
// add message // add message
cy.get(".nc-form > .mx-auto") cy.get("textarea.nc-form-after-submit-msg").then(($element) => {
.find("textarea").then(($element) => { expect($element[0].value).to.have.string("Thank you for submitting the form!")
expect($element[0].value).to.have.string("Thank you for submitting the form!") })
}) // cy.get(".nc-form > .mx-auto")
// .find("textarea").then(($element) => {
// submit another form button // expect($element[0].value).to.have.string("Thank you for submitting the form!")
cy.get(".nc-form > .mx-auto") // })
.find('[type="checkbox"]')
.eq(0) cy.get("button.nc-form-checkbox-submit-another-form.ant-switch-checked").should('exist')
.should('be.checked') cy.get("button.nc-form-checkbox-show-blank-form.ant-switch-checked").should('exist')
// "New form after 5 seconds" button cy.get("button.nc-form-checkbox-send-email.ant-switch-checked").should('not.exist')
cy.get(".nc-form > .mx-auto")
.find('[type="checkbox"]') // // submit another form button
.eq(1) // cy.get(".nc-form > .mx-auto")
.should('be.checked') // .find('[type="checkbox"]')
// email me // .eq(0)
cy.get(".nc-form > .mx-auto") // .should('be.checked')
.find('[type="checkbox"]') // // "New form after 5 seconds" button
.eq(2) // cy.get(".nc-form > .mx-auto")
.should('not.be.checked') // .find('[type="checkbox"]')
// .eq(1)
// .should('be.checked')
// // email me
// cy.get(".nc-form > .mx-auto")
// .find('[type="checkbox"]')
// .eq(2)
// .should('not.be.checked')
cy.closeTableTab("Film"); cy.closeTableTab("Film");
}); });
@ -296,29 +326,34 @@ export const genTest = (apiType, dbType, testMode) => {
mainPage.getPagination(">").click(); mainPage.getPagination(">").click();
mainPage mainPage
.getPagination(2) .getPagination(2)
.should("have.class", "v-pagination__item--active"); .should("have.class", "ant-pagination-item-active");
// verify < pagination option // verify < pagination option
mainPage.getPagination("<").click(); mainPage.getPagination("<").click();
mainPage mainPage
.getPagination(1) .getPagination(1)
.should("have.class", "v-pagination__item--active"); .should("have.class", "ant-pagination-item-active");
cy.closeTableTab("Actor"); cy.closeTableTab("Actor");
}); });
it("Verify Fields, Filter & Sort", () => { it("Verify Fields, Filter & Sort", () => {
cy.openTableTab("Actor", 25); cy.openTableTab("Actor", 25);
mainPage.toggleRightSidebar();
cy.get(".nc-grid-view-item").eq(1).click() cy.get(".nc-grid-view-item").eq(1).click()
cy.get(".nc-grid-header-cell").contains('Name').should("be.visible"); cy.wait(3000)
cy.get(".nc-grid-header-cell").contains('Notes').should("be.visible");
// fix me! cy.get(".nc-grid-header").find(`th[data-title="Name"]`).should("be.visible");
if(testMode !== 'AT_IMPORT') cy.get(".nc-grid-header-cell").contains('Attachments').should("not.be.visible"); cy.get(".nc-grid-header").find(`th[data-title="Notes"]`).should("be.visible");
cy.get(".nc-grid-header-cell").contains('Status').should("be.visible"); cy.get(".nc-grid-header").find(`th[data-title="Attachments"]`).should("not.exist");
cy.get(".nc-grid-header-cell").contains('Film').should("be.visible"); cy.get(".nc-grid-header").find(`th[data-title="Status"]`).should("be.visible");
cy.get(".nc-grid-header").find(`th[data-title="Film"]`).should("be.visible");
cy.wait(2000);
cy.get(".nc-fields-menu-btn").click()
cy.get(".nc-fields-menu-btn").click();
cy.getActiveMenu().find(`[type="checkbox"]`).eq(0).should('be.checked') cy.getActiveMenu().find(`[type="checkbox"]`).eq(0).should('be.checked')
cy.getActiveMenu().find(`[type="checkbox"]`).eq(1).should('be.checked') cy.getActiveMenu().find(`[type="checkbox"]`).eq(1).should('be.checked')
cy.getActiveMenu().find(`[type="checkbox"]`).eq(2).should('not.be.checked') cy.getActiveMenu().find(`[type="checkbox"]`).eq(2).should('not.be.checked')
@ -358,15 +393,18 @@ export const genTest = (apiType, dbType, testMode) => {
if( testMode === 'CY_QUICK') { if( testMode === 'CY_QUICK') {
cy.openTableTab("Producer", 3) cy.openTableTab("Producer", 3)
mainPage.toggleRightSidebar();
cy.get('.nc-grid-view-item').should('have.length', 4) cy.get('.nc-grid-view-item').should('have.length', 4)
cy.get('.nc-form-view-item').should('have.length', 4) cy.get('.nc-form-view-item').should('have.length', 4)
cy.get('.nc-gallery-view-item').should('have.length', 3) cy.get('.nc-gallery-view-item').should('have.length', 3)
// LinkToAnotherRecord hm relation // LinkToAnotherRecord hm relation
mainPage.getCell("FilmRead", 1).scrollIntoView(); mainPage.getCell("FilmRead", 1).scrollIntoView();
cy.get( cy.get('[data-title="FilmRead"] > .h-full > .nc-virtual-cell > .w-full > .chips > .chip > .name')
':nth-child(1) > [data-col="FilmRead"] > .nc-virtual-cell > .v-lazy > .d-100 > .chips > :nth-child(1) > .v-chip__content > .name' // cy.get(
) // ':nth-child(1) > [data-col="FilmRead"] > .nc-virtual-cell > .v-lazy > .d-100 > .chips > :nth-child(1) > .v-chip__content > .name'
// )
.contains('Movie-1') .contains('Movie-1')
.should("exist"); .should("exist");
@ -374,7 +412,7 @@ export const genTest = (apiType, dbType, testMode) => {
} }
}) })
it("Delete Project", () => { it.skip("Delete Project", () => {
if( testMode === 'AT_IMPORT') { if( testMode === 'AT_IMPORT') {
mainPage.toolBarTopLeft(mainPage.HOME).click({force:true}) mainPage.toolBarTopLeft(mainPage.HOME).click({force:true})
cy.get(`.mdi-delete-outline`, { cy.get(`.mdi-delete-outline`, {

78
scripts/cypress-v2/integration/spec/roleValidation.spec.js

@ -49,12 +49,12 @@ export function _advSettings(roleType, mode) {
export function _editSchema(roleType, mode) { export function _editSchema(roleType, mode) {
let columnName = "City"; let columnName = "City";
let validationString = let validationString =
true == roles[roleType].validations.editSchema ? "exist" : "not.exist"; true === roles[roleType].validations.editSchema ? "exist" : "not.exist";
cy.openTableTab(columnName, 25); cy.openTableTab(columnName, 25);
// create table // create table
cy.get(`.nc-add-import-btn`).should(validationString); cy.get(`.nc-add-new-table`).should(validationString);
// delete table option // delete table option
cy.get(`.nc-project-tree-tbl-City`).should("exist").rightclick(); cy.get(`.nc-project-tree-tbl-City`).should("exist").rightclick();
@ -64,7 +64,8 @@ export function _editSchema(roleType, mode) {
cy.getActiveMenu().find('[role="menuitem"]').contains("Delete").should("exist"); cy.getActiveMenu().find('[role="menuitem"]').contains("Delete").should("exist");
cy.getActiveMenu().find('[role="menuitem"]').contains("Rename").should("exist"); cy.getActiveMenu().find('[role="menuitem"]').contains("Rename").should("exist");
mainPage.getCell(columnName, 1).click(); // click on a cell to close table context menu
mainPage.getCell(columnName, 3).click();
} }
// add new column option // add new column option
@ -73,6 +74,15 @@ export function _editSchema(roleType, mode) {
// update column (edit/ delete menu) // update column (edit/ delete menu)
cy.get('.nc-ui-dt-dropdown').should(validationString) cy.get('.nc-ui-dt-dropdown').should(validationString)
if(validationString === "exist"){
cy.get('.nc-import-menu').should('exist').click();
cy.getActiveMenu().should('exist')
cy.getActiveMenu().find('.ant-dropdown-menu-item').contains('Airtable')
cy.getActiveMenu().find('.ant-dropdown-menu-item').contains('CSV file')
cy.getActiveMenu().find('.ant-dropdown-menu-item').contains('JSON file')
cy.getActiveMenu().find('.ant-dropdown-menu-item').contains('Microsoft Excel')
}
} }
export function _editData(roleType, mode) { export function _editData(roleType, mode) {
@ -82,20 +92,17 @@ export function _editData(roleType, mode) {
cy.openTableTab(columnName, 25); cy.openTableTab(columnName, 25);
// add row // add row button
cy.get('.nc-add-new-row-btn:visible').should(validationString); cy.get('.nc-add-new-row-btn:visible').should(validationString);
// add button at bottom of page
mainPage.getCell(columnName, 25).scrollIntoView(); mainPage.getCell(columnName, 25).scrollIntoView();
// cy.get('.nc-grid-add-new-cell').scrollIntoView();
cy.get('.nc-grid-add-new-cell:visible').should(validationString); cy.get('.nc-grid-add-new-cell:visible').should(validationString);
// update row option (right click) // update row option (right click)
// //
mainPage.getCell("City", 5).rightclick(); mainPage.getCell("City", 5).rightclick();
cy.wait(100);
cy.wait(1000);
cy.get(".ant-dropdown-content:visible").should(validationString); cy.get(".ant-dropdown-content:visible").should(validationString);
if (validationString === "exist") { if (validationString === "exist") {
@ -183,7 +190,7 @@ export function _editComment(roleType, mode) {
.click(); .click();
cy.getActiveDrawer().find(".nc-comment-box").should('exist').type("Comment-1{enter}"); cy.getActiveDrawer().find(".nc-comment-box").should('exist').type("Comment-1{enter}");
cy.toastWait('Comment added successfully') // cy.toastWait('Comment added successfully')
cy.getActiveDrawer().find(".nc-toggle-comments").click(); cy.getActiveDrawer().find(".nc-toggle-comments").click();
} }
@ -200,54 +207,61 @@ export function _editComment(roleType, mode) {
export function _viewMenu(roleType, mode) { export function _viewMenu(roleType, mode) {
let columnName = "City"; let columnName = "City";
// Download CSV, Excel // Lock, Download, Upload
let actionsMenuItemsCnt = 2; let menuWithSubmenuCount = 3;
cy.openTableTab(columnName, 25); // share view list, webhook
let menuWithoutSubmenuCount = 2;
cy.wait(1000); cy.openTableTab(columnName, 25);
// temporary!
cy.get('.nc-toggle-right-navbar').click(); cy.get('.nc-toggle-right-navbar').click();
cy.wait(1000); cy.wait(1000);
// hard-wire
// window.localStorage.setItem('nc-right-sidebar', '{"isOpen":true,"hasSidebar":true}')
let validationString = let validationString =
true === roles[roleType].validations.shareView ? "exist" : "not.exist"; true === roles[roleType].validations.shareView ? "exist" : "not.exist";
if (roleType === "owner" || roleType === "creator") { if (roleType === "editor") {
// Download CSV / Download XLSX / Upload CSV / Shared View List / Webhook // Download / Upload CSV
actionsMenuItemsCnt = 5; menuWithSubmenuCount = 2;
} else if (roleType == "editor") { menuWithoutSubmenuCount = 0
// Download CSV / Upload CSV / Download XLSX } else if (roleType === "commenter" || roleType === "viewer") {
actionsMenuItemsCnt = 2; // Download CSV & Download excel
menuWithSubmenuCount = 0;
menuWithoutSubmenuCount = 2
} }
// view list field (default GRID view) // view list field (default GRID view)
cy.get(`.nc-view-item`).should("exist"); cy.get(`.nc-view-item`).should("exist");
// view create option, exists only for owner/ creator // view create option, exists only for owner/ creator
cy.get(`.nc-create-1-view`).should(validationString); cy.get(`.nc-create-grid-view`).should(validationString);
cy.get(`.nc-create-2-view`).should(validationString); cy.get(`.nc-create-gallery-view`).should(validationString);
cy.get(`.nc-create-3-view`).should(validationString); cy.get(`.nc-create-form-view`).should(validationString);
// share view & automations, exists only for owner/creator
// cy.get(".nc-btn-share-view").should(validationString);
// cy.get(`.nc-webhook-btn`).should(validationString);
// share view permissions are role specific // share view permissions are role specific
// actions menu (more), only download csv should be visible for non-previlaged users // actions menu (more), only download csv should be visible for non-previlaged users
cy.get(".nc-actions-menu-btn").click(); cy.get(".nc-actions-menu-btn").click();
cy.getActiveMenu() cy.getActiveMenu()
.find('.nc-project-menu-item') .find('.ant-dropdown-menu-submenu:visible')
.should("have.length", actionsMenuItemsCnt); .should("have.length", menuWithSubmenuCount);
cy.getActiveMenu()
.find('.ant-dropdown-menu-item:visible')
.should("have.length", menuWithoutSubmenuCount);
// click again to close menu
cy.get(".nc-actions-menu-btn").click();
} }
export function _topRightMenu(roleType, mode) { export function _topRightMenu(roleType, mode) {
// kludge; download csv menu persists until clicked // kludge; download csv menu persists until clicked
let columnName = "City"; let columnName = "City";
cy.closeTableTab(columnName); // cy.closeTableTab(columnName);
cy.openTableTab(columnName, 25); // cy.openTableTab(columnName, 25);
let validationString = let validationString =
true == roles[roleType].validations.shareView ? "exist" : "not.exist"; true == roles[roleType].validations.shareView ? "exist" : "not.exist";

2
scripts/cypress-v2/integration/test/restRoles.js

@ -11,7 +11,7 @@ const nocoTestSuite = (apiType, dbType) => {
t01.genTest(apiType, dbType); t01.genTest(apiType, dbType);
t5a.genTest(apiType, dbType); t5a.genTest(apiType, dbType);
t5b.genTest(apiType, dbType); // t5b.genTest(apiType, dbType);
}; };
nocoTestSuite("rest", "mysql"); nocoTestSuite("rest", "mysql");

5
scripts/cypress-v2/integration/test/restViews.js

@ -14,11 +14,12 @@ const nocoTestSuite = (apiType, dbType) => {
setCurrentMode(apiType, dbType); setCurrentMode(apiType, dbType);
t01.genTest(apiType, dbType); t01.genTest(apiType, dbType);
// place plugin related activities at top
t4c.genTest(apiType, dbType);
t4a.genTest(apiType, dbType); t4a.genTest(apiType, dbType);
t4b.genTest(apiType, dbType); t4b.genTest(apiType, dbType);
t4c.genTest(apiType, dbType);
t4d.genTest(apiType, dbType); t4d.genTest(apiType, dbType);
t4e.genTest(apiType, dbType); // to be fixed t4e.genTest(apiType, dbType);
t4f.genTest(apiType, dbType); t4f.genTest(apiType, dbType);
}; };

65
scripts/cypress-v2/support/commands.js

@ -154,15 +154,19 @@ Cypress.Commands.add("refreshTableTab", () => {
// rc: row count. validate row count if rc!=0 // rc: row count. validate row count if rc!=0
Cypress.Commands.add("openTableTab", (tn, rc) => { Cypress.Commands.add("openTableTab", (tn, rc) => {
cy.task("log", `[openTableTab] ${tn} ${rc}`); cy.task("log", `[openTableTab] ${tn} ${rc}`);
cy.get(`.nc-project-tree-tbl-${tn}`, { timeout: 10000 }).should("exist")
cy.get(`.nc-project-tree-tbl-${tn}`)
.should("exist")
.first() .first()
.click({ force: true }); .click();
// kludge to make new tab active // kludge to make new tab active
cy.get('.ant-tabs-tab-btn') // cy.get('.ant-tabs-tab-btn')
.contains(tn) // .contains(tn)
.should('exist') // .should('exist')
.click({ force: true }); // .click();
cy.wait(3000);
cy.get('.xc-row-table.nc-grid').should('exist'); cy.get('.xc-row-table.nc-grid').should('exist');
// wait for page rendering to complete // wait for page rendering to complete
@ -227,14 +231,32 @@ Cypress.Commands.add("openOrCreateGqlProject", (_args) => {
}); });
let LOCAL_STORAGE_MEMORY = {}; let LOCAL_STORAGE_MEMORY = {};
let LOCAL_STORAGE_MEMORY_v2 = {};
Cypress.Commands.add("saveLocalStorage", (name) => {
if(name) {
cy.task('log', `[saveLocalStorage] ${name}`);
LOCAL_STORAGE_MEMORY_v2[name] = {}
Object.keys(localStorage).forEach((key) => {
LOCAL_STORAGE_MEMORY_v2[name][key] = localStorage[key];
});
return;
}
Cypress.Commands.add("saveLocalStorage", () => {
Object.keys(localStorage).forEach((key) => { Object.keys(localStorage).forEach((key) => {
LOCAL_STORAGE_MEMORY[key] = localStorage[key]; LOCAL_STORAGE_MEMORY[key] = localStorage[key];
}); });
}); });
Cypress.Commands.add("restoreLocalStorage", () => { Cypress.Commands.add("restoreLocalStorage", (name) => {
if(name) {
cy.task('log', `[restoreLocalStorage] ${name}`);
Object.keys(LOCAL_STORAGE_MEMORY_v2[name]).forEach((key) => {
localStorage.setItem(key, LOCAL_STORAGE_MEMORY_v2[name][key]);
});
return;
}
Object.keys(LOCAL_STORAGE_MEMORY).forEach((key) => { Object.keys(LOCAL_STORAGE_MEMORY).forEach((key) => {
localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]); localStorage.setItem(key, LOCAL_STORAGE_MEMORY[key]);
}); });
@ -246,6 +268,11 @@ Cypress.Commands.add("deleteLocalStorage", () => {
}); });
}); });
Cypress.Commands.add('printLocalStorage', () => {
cy.task('log', `[printLocalStorage]`);
cy.task('log', JSON.stringify(localStorage, null, 2));
})
Cypress.Commands.add("getActiveModal", () => { Cypress.Commands.add("getActiveModal", () => {
return cy.get(".ant-modal-content:visible").last() return cy.get(".ant-modal-content:visible").last()
}); });
@ -264,25 +291,29 @@ Cypress.Commands.add("getActiveSelection", () => {
Cypress.Commands.add("getActiveDrawer", () => { Cypress.Commands.add("getActiveDrawer", () => {
return cy.get(".ant-drawer-content:visible").last(); return cy.get(".ant-drawer-content:visible").last();
}); });
Cypress.Commands.add("createTable", (name) => {
// cy.get(".nc-btn-tbl-add").click();
// cy.get(`[data-menu-id="addORImport"]`).click(); Cypress.Commands.add("getActivePicker", () => {
// cy.getActivePopUp().contains("Add new table").should('exist').click(); return cy.get(".ant-picker-dropdown :visible").last();
});
Cypress.Commands.add("createTable", (name) => {
cy.task("log", `[createTableTab] ${name}`);
cy.wait(1000);
cy.get('.nc-add-new-table').should('exist').click(); cy.get('.nc-add-new-table').should('exist').click();
cy.wait(1000);
cy.getActiveModal().find(`input[type="text"]:visible`) cy.getActiveModal().find(`input[type="text"]:visible`)
.click() .click()
.clear() .clear()
.type(name) .type(name)
cy.getActiveModal().find("button").contains("Submit").click(); // submit button
cy.getActiveModal().find("button.ant-btn-primary:visible").click();
cy.wait(1000)
cy.get('.xc-row-table.nc-grid').should('exist'); cy.get('.xc-row-table.nc-grid').should('exist');
cy.get('.ant-tabs-tab-active > .ant-tabs-tab-btn').contains(name).should("exist"); // cy.get('.ant-tabs-tab-active > .ant-tabs-tab-btn').contains(name).should("exist");
cy.url().should("contain", `table/${name}`); cy.url().should("contain", `table/${name}`);
cy.get(`.nc-project-tree-tbl-${name}`).should("exist"); cy.get(`.nc-project-tree-tbl-${name}`).should("exist");
cy.wait(1000)
}); });
Cypress.Commands.add("deleteTable", (name, dbType) => { Cypress.Commands.add("deleteTable", (name, dbType) => {

22
scripts/cypress-v2/support/page_objects/mainPage.js

@ -238,10 +238,11 @@ export class _mainPage {
cy.getActiveModal().find('#form_item_from').should('exist').clear().type(from) cy.getActiveModal().find('#form_item_from').should('exist').clear().type(from)
cy.getActiveModal().find('#form_item_host').should('exist').clear().type(host) cy.getActiveModal().find('#form_item_host').should('exist').clear().type(host)
cy.getActiveModal().find('#form_item_port').should('exist').clear().type(port) cy.getActiveModal().find('#form_item_port').should('exist').clear().type(port)
cy.getActiveModal().find('#form_item_secure').should('exist').clear().type(secure) // cy.getActiveModal().find('#form_item_secure').should('exist').clear().type(secure)
cy.getActiveModal().find("button").contains("Save").click(); cy.getActiveModal().find("button").contains("Save").click();
cy.toastWait('Successfully installed and email notification will use SMTP configuration'); cy.toastWait('Successfully installed and email notification will use SMTP configuration');
settingsPage.closeMenu()
}; };
resetSMTP = () => { resetSMTP = () => {
@ -251,6 +252,7 @@ export class _mainPage {
cy.getActiveModal().find("button").contains("Confirm").click(); cy.getActiveModal().find("button").contains("Confirm").click();
cy.toastWait("Plugin uninstalled successfully"); cy.toastWait("Plugin uninstalled successfully");
settingsPage.closeMenu()
}; };
shareView = () => { shareView = () => {
@ -365,7 +367,7 @@ export class _mainPage {
// one of the row would contain seggregation header ('other views) // one of the row would contain seggregation header ('other views)
if (5 == $tableRow[0].childElementCount) { if (5 == $tableRow[0].childElementCount) {
cy.wrap($tableRow).find(".nc-icon").last().click(); cy.wrap($tableRow).find(".nc-icon").last().click();
cy.wait(1000); cy.wait(100);
} }
}) })
.then(() => { .then(() => {
@ -380,11 +382,17 @@ export class _mainPage {
// wait for a while & check in configured download folder for the intended file // wait for a while & check in configured download folder for the intended file
// if it exists, verify it against 'expectedRecords' passed in as parameter // if it exists, verify it against 'expectedRecords' passed in as parameter
// //
downloadAndVerifyCsv = (filename, verifyCsv) => { downloadAndVerifyCsv = (filename, verifyCsv, role) => {
cy.get(".nc-actions-menu-btn").click();
cy.getActiveMenu().find('.nc-project-menu-item').contains('Download').click(); if(role === 'commenter' || role === 'viewer') {
cy.wait(1000); cy.get(".nc-actions-menu-btn").click();
cy.get('.nc-project-menu-item').contains('Download as CSV').should('exist').click(); cy.getActiveMenu().find('.nc-project-menu-item').contains('Download as CSV').click();
} else {
cy.get(".nc-actions-menu-btn").click();
cy.getActiveMenu().find('.nc-project-menu-item').contains('Download').click();
cy.wait(1000);
cy.get('.nc-project-menu-item').contains('Download as CSV').should('exist').click();
}
cy.toastWait("Successfully exported all table data").then(() => { cy.toastWait("Successfully exported all table data").then(() => {
// download folder path, read from config file // download folder path, read from config file

11
scripts/cypress-v2/support/page_objects/navigation.js

@ -68,6 +68,9 @@ export class _loginPage {
// standard pre-project activity // standard pre-project activity
// //
loginAndOpenProject(apiType, dbType) { loginAndOpenProject(apiType, dbType) {
cy.restoreLocalStorage();
cy.wait(1000);
loginPage.signIn(roles.owner.credentials); loginPage.signIn(roles.owner.credentials);
if (dbType === "mysql") { if (dbType === "mysql") {
@ -77,6 +80,12 @@ export class _loginPage {
} else if (dbType === "postgres") { } else if (dbType === "postgres") {
projectsPage.openProject(staticProjects.pgExternalREST.basic.name); projectsPage.openProject(staticProjects.pgExternalREST.basic.name);
} }
// kludge: wait for page load to finish
cy.wait(2000);
// close team & auth tab
cy.get('button.ant-tabs-tab-remove').should('exist').click();
cy.wait(1000);
} }
} }
@ -128,6 +137,8 @@ export class _projectsPage {
cy.get(".nc-metadb-project-name").should("exist"); cy.get(".nc-metadb-project-name").should("exist");
cy.contains("button", "Create").should("exist"); cy.contains("button", "Create").should("exist");
cy.wait(1000)
// feed project name // feed project name
cy.get(".nc-metadb-project-name", { timeout: 20000 }).clear().type( cy.get(".nc-metadb-project-name", { timeout: 20000 }).clear().type(
projectName projectName

Loading…
Cancel
Save