mirror of https://github.com/nocodb/nocodb
46 changed files with 643 additions and 37 deletions
After Width: | Height: | Size: 925 B |
After Width: | Height: | Size: 586 B |
After Width: | Height: | Size: 437 B |
After Width: | Height: | Size: 757 B |
@ -0,0 +1,24 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
const props = defineProps<{ |
||||||
|
value?: string | number | null |
||||||
|
lines?: number |
||||||
|
}>() |
||||||
|
|
||||||
|
const wrapper = ref() |
||||||
|
|
||||||
|
const key = ref(0) |
||||||
|
|
||||||
|
onMounted(() => { |
||||||
|
const observer = new ResizeObserver(() => { |
||||||
|
key.value++ |
||||||
|
}) |
||||||
|
|
||||||
|
observer.observe(wrapper.value) |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div ref="wrapper"> |
||||||
|
<text-clamp :key="key" class="w-full h-full break-all" :text="props.value || ''" :max-lines="props.lines" /> |
||||||
|
</div> |
||||||
|
</template> |
@ -0,0 +1,30 @@ |
|||||||
|
<script lang="ts" setup> |
||||||
|
import { Panel, PanelPosition } from '@vue-flow/additional-components' |
||||||
|
import type { ERDConfig } from './utils' |
||||||
|
import MiFullscreen from '~icons/material-symbols/fullscreen' |
||||||
|
import MiFullscreenExit from '~icons/material-symbols/fullscreen-exit' |
||||||
|
|
||||||
|
const props = defineProps<{ |
||||||
|
config: ERDConfig |
||||||
|
}>() |
||||||
|
|
||||||
|
const emit = defineEmits(['toggleFullScreen']) |
||||||
|
|
||||||
|
const { config } = toRefs(props) |
||||||
|
|
||||||
|
const toggleFullScreen = () => { |
||||||
|
emit('toggleFullScreen') |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<Panel |
||||||
|
class="text-xs bg-white border-1 rounded-md p-0.5 border-gray-50 z-50 nc-erd-histogram cursor-pointer hover:bg-gray-100" |
||||||
|
:position="PanelPosition.TopLeft" |
||||||
|
> |
||||||
|
<div class="flex"> |
||||||
|
<MiFullscreenExit v-if="config.isFullScreen" class="h-5 w-5" @click="toggleFullScreen" /> |
||||||
|
<MiFullscreen v-else class="h-5 w-5" @click="toggleFullScreen" /> |
||||||
|
</div> |
||||||
|
</Panel> |
||||||
|
</template> |
@ -0,0 +1,87 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import type { GridType } from 'nocodb-sdk' |
||||||
|
import { ActiveViewInj, IsLockedInj, inject, ref, useMenuCloseOnEsc } from '#imports' |
||||||
|
|
||||||
|
const { isSharedBase } = useProject() |
||||||
|
|
||||||
|
const view = inject(ActiveViewInj, ref()) |
||||||
|
|
||||||
|
const isPublic = inject(IsPublicInj, ref(false)) |
||||||
|
|
||||||
|
const isLocked = inject(IsLockedInj, ref(false)) |
||||||
|
|
||||||
|
const { $api } = useNuxtApp() |
||||||
|
|
||||||
|
const open = ref(false) |
||||||
|
|
||||||
|
const updateRowHeight = async (rh: number) => { |
||||||
|
if (view.value?.id) { |
||||||
|
if (rh === (view.value.view as GridType).row_height) return |
||||||
|
try { |
||||||
|
if (!isPublic.value && !isSharedBase.value) { |
||||||
|
await $api.dbView.gridUpdate(view.value.id, { |
||||||
|
row_height: rh, |
||||||
|
}) |
||||||
|
|
||||||
|
message.success('View updated successfully!') |
||||||
|
} |
||||||
|
|
||||||
|
;(view.value.view as GridType).row_height = rh |
||||||
|
|
||||||
|
open.value = false |
||||||
|
} catch (e) { |
||||||
|
message.error('There was an error while updating view!') |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
useMenuCloseOnEsc(open) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<a-dropdown v-model:visible="open" offset-y class="" :trigger="['click']" overlay-class-name="nc-dropdown-height-menu"> |
||||||
|
<div> |
||||||
|
<a-button v-e="['c:row-height']" class="nc-height-menu-btn nc-toolbar-btn" :disabled="isLocked"> |
||||||
|
<div class="flex items-center gap-1"> |
||||||
|
<RiLineHeight /> |
||||||
|
|
||||||
|
<!-- Row Height --> |
||||||
|
<MdiMenuDown class="text-grey" /> |
||||||
|
</div> |
||||||
|
</a-button> |
||||||
|
</div> |
||||||
|
<template #overlay> |
||||||
|
<div class="w-full bg-gray-50 shadow-lg menu-filter-dropdown !border" data-testid="nc-height-menu"> |
||||||
|
<div class="text-gray-500 !text-xs px-4 py-2">Select a row height</div> |
||||||
|
<div class="flex flex-col w-full text-sm" @click.stop> |
||||||
|
<div class="nc-row-height-option" @click="updateRowHeight(0)"> |
||||||
|
<NcIconsRowHeightShort class="nc-row-height-icon" /> |
||||||
|
Short |
||||||
|
</div> |
||||||
|
<div class="nc-row-height-option" @click="updateRowHeight(1)"> |
||||||
|
<NcIconsRowHeightMedium class="nc-row-height-icon" /> |
||||||
|
Medium |
||||||
|
</div> |
||||||
|
<div class="nc-row-height-option" @click="updateRowHeight(2)"> |
||||||
|
<NcIconsRowHeightTall class="nc-row-height-icon" /> |
||||||
|
Tall |
||||||
|
</div> |
||||||
|
<div class="nc-row-height-option" @click="updateRowHeight(3)"> |
||||||
|
<NcIconsRowHeightExtraTall class="nc-row-height-icon" /> |
||||||
|
Extra |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</a-dropdown> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style scoped> |
||||||
|
.nc-row-height-option { |
||||||
|
@apply flex items-center py-1 px-2 justify-start hover:bg-gray-200 cursor-pointer; |
||||||
|
} |
||||||
|
|
||||||
|
.nc-row-height-icon { |
||||||
|
@apply text-gray-600 mx-4 text-base; |
||||||
|
} |
||||||
|
</style> |
@ -0,0 +1,6 @@ |
|||||||
|
import TextClamp from 'vue3-text-clamp' |
||||||
|
import { defineNuxtPlugin } from 'nuxt/app' |
||||||
|
|
||||||
|
export default defineNuxtPlugin((nuxtApp) => { |
||||||
|
nuxtApp.vueApp.use(TextClamp) |
||||||
|
}) |
@ -0,0 +1,16 @@ |
|||||||
|
import { Knex } from 'knex'; |
||||||
|
import { MetaTable } from '../../utils/globals'; |
||||||
|
|
||||||
|
const up = async (knex: Knex) => { |
||||||
|
await knex.schema.alterTable(MetaTable.GRID_VIEW, (table) => { |
||||||
|
table.integer('row_height'); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
const down = async (knex) => { |
||||||
|
await knex.schema.alterTable(MetaTable.GRID_VIEW, (table) => { |
||||||
|
table.dropColumns('row_height'); |
||||||
|
}); |
||||||
|
}; |
||||||
|
|
||||||
|
export { up, down }; |
@ -0,0 +1,35 @@ |
|||||||
|
import BasePage from '../Base'; |
||||||
|
import { AccountPage } from './index'; |
||||||
|
|
||||||
|
export class AccountLicensePage extends BasePage { |
||||||
|
private accountPage: AccountPage; |
||||||
|
|
||||||
|
constructor(accountPage: AccountPage) { |
||||||
|
super(accountPage.rootPage); |
||||||
|
this.accountPage = accountPage; |
||||||
|
} |
||||||
|
|
||||||
|
async goto() { |
||||||
|
await this.rootPage.goto('/#/account/license', { waitUntil: 'networkidle' }); |
||||||
|
} |
||||||
|
|
||||||
|
async waitUntilContentLoads() { |
||||||
|
return this.rootPage.waitForResponse(resp => resp.url().includes('api/v1/license') && resp.status() === 200); |
||||||
|
} |
||||||
|
|
||||||
|
// License TextBox
|
||||||
|
get() { |
||||||
|
return this.accountPage.get().locator(`textarea[placeholder="License key"]`); |
||||||
|
} |
||||||
|
|
||||||
|
// Save button
|
||||||
|
getSaveButton() { |
||||||
|
return this.accountPage.get().locator(`button.ant-btn-primary:has-text("Save license key")`); |
||||||
|
} |
||||||
|
|
||||||
|
async saveLicenseKey(licenseKey: string) { |
||||||
|
await this.get().fill(licenseKey); |
||||||
|
await this.getSaveButton().click(); |
||||||
|
await this.verifyToast({ message: 'License key updated' }); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,26 @@ |
|||||||
|
import BasePage from '../../Base'; |
||||||
|
import { GridPage } from './index'; |
||||||
|
|
||||||
|
export class RowPageObject extends BasePage { |
||||||
|
readonly grid: GridPage; |
||||||
|
|
||||||
|
constructor(grid: GridPage) { |
||||||
|
super(grid.rootPage); |
||||||
|
this.grid = grid; |
||||||
|
} |
||||||
|
|
||||||
|
get() { |
||||||
|
return this.rootPage.locator('tr.nc-grid-row'); |
||||||
|
} |
||||||
|
|
||||||
|
async getRecord(index: number) { |
||||||
|
return this.get().nth(index); |
||||||
|
} |
||||||
|
|
||||||
|
// style="height: 3rem;"
|
||||||
|
async getRecordHeight(index: number) { |
||||||
|
const record = await this.getRecord(index); |
||||||
|
const style = await record.getAttribute('style'); |
||||||
|
return style.split(':')[1].split(';')[0].trim(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,19 @@ |
|||||||
|
import BasePage from '../../../Base'; |
||||||
|
import { ToolbarPage } from './index'; |
||||||
|
|
||||||
|
export class RowHeight extends BasePage { |
||||||
|
readonly toolbar: ToolbarPage; |
||||||
|
|
||||||
|
constructor(toolbar: ToolbarPage) { |
||||||
|
super(toolbar.rootPage); |
||||||
|
this.toolbar = toolbar; |
||||||
|
} |
||||||
|
|
||||||
|
get() { |
||||||
|
return this.rootPage.locator(`[data-testid="nc-height-menu"]`); |
||||||
|
} |
||||||
|
|
||||||
|
click({ title }: { title: string }) { |
||||||
|
return this.get().locator(`.nc-row-height-option:has-text("${title}")`).click(); |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,29 @@ |
|||||||
|
import { test } from '@playwright/test'; |
||||||
|
import { AccountPage } from '../pages/Account'; |
||||||
|
import setup from '../setup'; |
||||||
|
import { AccountLicensePage } from '../pages/Account/License'; |
||||||
|
import { DashboardPage } from '../pages/Dashboard'; |
||||||
|
|
||||||
|
test.describe('Enterprise License', () => { |
||||||
|
// @ts-ignore
|
||||||
|
let dashboard: DashboardPage; |
||||||
|
// @ts-ignore
|
||||||
|
let accountLicensePage: AccountLicensePage, accountPage: AccountPage, context: any; |
||||||
|
|
||||||
|
test.beforeEach(async ({ page }) => { |
||||||
|
context = await setup({ page }); |
||||||
|
accountPage = new AccountPage(page); |
||||||
|
accountLicensePage = new AccountLicensePage(accountPage); |
||||||
|
dashboard = new DashboardPage(page, context.project); |
||||||
|
}); |
||||||
|
|
||||||
|
test('Update license key & verify if enterprise features enabled', async () => { |
||||||
|
test.slow(); |
||||||
|
await accountLicensePage.goto(); |
||||||
|
await accountLicensePage.saveLicenseKey('1234567890'); |
||||||
|
|
||||||
|
await dashboard.goto(); |
||||||
|
// presence of snowflake icon indicates enterprise features are enabled
|
||||||
|
await dashboard.treeView.quickImport({ title: 'Snowflake' }); |
||||||
|
}); |
||||||
|
}); |
Loading…
Reference in new issue