Browse Source

Merge branch 'develop' into feat/attachments

pull/4931/head
Wing-Kam Wong 2 years ago
parent
commit
e25fd8f0ab
  1. 14
      .github/workflows/release-nightly-dev.yml
  2. 1
      packages/nc-gui/components.d.ts
  3. 155
      packages/nc-gui/components/smartsheet/column/SelectOptions.vue
  4. 10
      packages/nc-gui/composables/useViewData.ts
  5. 19
      packages/noco-docs/content/en/setup-and-usages/account-settings.md
  6. 4
      packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts
  7. 17
      tests/playwright/pages/Dashboard/Grid/Column/SelectOptionColumn.ts
  8. 7
      tests/playwright/tests/columnSingleSelect.spec.ts

14
.github/workflows/release-nightly-dev.yml

@ -49,13 +49,13 @@ jobs:
NPM_TOKEN: "${{ secrets.NPM_TOKEN }}"
# Build executables and publish to GitHub
release-executables:
needs: [set-tag, release-npm]
uses: ./.github/workflows/release-timely-executables.yml
with:
tag: ${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.nightly_build_tag }}
secrets:
NC_GITHUB_TOKEN: "${{ secrets.NC_GITHUB_TOKEN }}"
# release-executables:
# needs: [set-tag, release-npm]
# uses: ./.github/workflows/release-timely-executables.yml
# with:
# tag: ${{ needs.set-tag.outputs.current_version }}-${{ needs.set-tag.outputs.nightly_build_tag }}
# secrets:
# NC_GITHUB_TOKEN: "${{ secrets.NC_GITHUB_TOKEN }}"
# Build docker image and push to docker hub
release-docker:

1
packages/nc-gui/components.d.ts vendored

@ -125,6 +125,7 @@ declare module '@vue/runtime-core' {
MdiArrowDownDropCircleOutline: typeof import('~icons/mdi/arrow-down-drop-circle-outline')['default']
MdiArrowExpand: typeof import('~icons/mdi/arrow-expand')['default']
MdiArrowLeftBold: typeof import('~icons/mdi/arrow-left-bold')['default']
MdiArrowULeftBottom: typeof import('~icons/mdi/arrow-u-left-bottom')['default']
MdiAt: typeof import('~icons/mdi/at')['default']
MdiBackburger: typeof import('~icons/mdi/backburger')['default']
MdiBookOpenOutline: typeof import('~icons/mdi/book-open-outline')['default']

155
packages/nc-gui/components/smartsheet/column/SelectOptions.vue

@ -3,6 +3,14 @@ import Draggable from 'vuedraggable'
import { UITypes } from 'nocodb-sdk'
import { IsKanbanInj, enumColor, onMounted, useColumnCreateStoreOrThrow, useVModel, watch } from '#imports'
interface Option {
color: string
title: string
id?: string
fk_colum_id?: string
order?: number
}
const props = defineProps<{
value: any
}>()
@ -13,7 +21,10 @@ const vModel = useVModel(props, 'value', emit)
const { setAdditionalValidations, validateInfos, isPg, isMysql } = useColumnCreateStoreOrThrow()
let options = $ref<any[]>([])
let options = $ref<Option[]>([])
let renderedOptions = $ref<(Option & { status?: 'remove' })[]>([])
let savedDefaultOption = $ref<Option | null>(null)
let savedCdf = $ref<string | null>(null)
const colorMenus = $ref<any>({})
@ -58,6 +69,9 @@ onMounted(() => {
}
}
options = vModel.value.colOptions.options
renderedOptions = [...options]
// Support for older options
for (const op of options.filter((el) => el.order === null)) {
op.title = op.title.replace(/^'/, '').replace(/'$/, '')
@ -87,13 +101,6 @@ const optionChanged = (changedId: string) => {
}
}
const optionDropped = (changedId: string) => {
if (changedId && changedId === defaultOption.value?.id) {
vModel.value.cdf = null
defaultOption.value = null
}
}
const getNextColor = () => {
let tempColor = colors[0]
if (options.length && options[options.length - 1].color) {
@ -108,13 +115,40 @@ const addNewOption = () => {
title: '',
color: getNextColor(),
}
renderedOptions.push(tempOption)
options.push(tempOption)
}
const removeOption = (index: number) => {
const optionId = options[index]?.id
options.splice(index, 1)
optionDropped(optionId)
const syncOptions = () => {
vModel.value.colOptions.options = renderedOptions.filter((op) => op.status !== 'remove')
}
const removeRenderedOption = (index: number) => {
renderedOptions[index].status = 'remove'
syncOptions()
const optionId = renderedOptions[index]?.id
if (optionId === defaultOption.value?.id) {
savedDefaultOption = { ...defaultOption.value }
savedCdf = vModel.value.cdf
defaultOption.value = null
vModel.value.cdf = null
}
}
const undoRemoveRenderedOption = (index: number) => {
renderedOptions[index].status = undefined
syncOptions()
const optionId = renderedOptions[index]?.id
if (optionId === savedDefaultOption?.id) {
defaultOption.value = { ...savedDefaultOption }
vModel.value.cdf = savedCdf
savedDefaultOption = null
savedCdf = null
}
}
// focus last created input
@ -128,47 +162,62 @@ watch(inputs, () => {
<template>
<div class="w-full">
<div class="max-h-[250px] overflow-x-auto scrollbar-thin-dull pr-3">
<Draggable :list="options" item-key="id" handle=".nc-child-draggable-icon">
<Draggable :list="renderedOptions" item-key="id" handle=".nc-child-draggable-icon" @change="syncOptions">
<template #item="{ element, index }">
<div class="flex py-1 items-center nc-select-option">
<MdiDragVertical
v-if="!isKanban"
small
class="nc-child-draggable-icon handle"
:data-testid="`select-option-column-handle-icon-${element.title}`"
/>
<a-dropdown
v-model:visible="colorMenus[index]"
:trigger="['click']"
overlay-class-name="nc-dropdown-select-color-options"
<div class="flex p-1 items-center nc-select-option">
<div
class="flex items-center w-full"
:data-testid="`select-column-option-${index}`"
:class="{ removed: element.status === 'remove' }"
>
<template #overlay>
<LazyGeneralColorPicker
v-model="element.color"
:pick-button="true"
@update:model-value="colorMenus[index] = false"
<MdiDragVertical
v-if="!isKanban"
small
class="nc-child-draggable-icon handle"
:data-testid="`select-option-column-handle-icon-${element.title}`"
/>
<a-dropdown
v-model:visible="colorMenus[index]"
:trigger="['click']"
overlay-class-name="nc-dropdown-select-color-options"
>
<template #overlay>
<LazyGeneralColorPicker
v-model="element.color"
:pick-button="true"
@update:model-value="colorMenus[index] = false"
/>
</template>
<MdiArrowDownDropCircle
class="mr-2 text-[1.5em] outline-0 hover:!text-[1.75em]"
:class="{ 'text-[1.75em]': colorMenus[index] }"
:style="{ color: element.color }"
/>
</template>
<MdiArrowDownDropCircle
class="mr-2 text-[1.5em] outline-0 hover:!text-[1.75em]"
:class="{ 'text-[1.75em]': colorMenus[index] }"
:style="{ color: element.color }"
</a-dropdown>
<a-input
ref="inputs"
v-model:value="element.title"
class="caption"
:data-testid="`select-column-option-input-${index}`"
:disabled="element.status === 'remove'"
@keydown.enter.prevent="element.title?.trim() && addNewOption()"
@change="optionChanged(element.id)"
/>
</a-dropdown>
<a-input
ref="inputs"
v-model:value="element.title"
class="caption"
:data-testid="`select-column-option-input-${index}`"
@keydown.enter.prevent="element.title?.trim() && addNewOption()"
@change="optionChanged(element.id)"
/>
</div>
<MdiClose
v-if="element.status !== 'remove'"
class="ml-2 hover:!text-black-500 text-gray-500 cursor-pointer"
:data-testid="`select-column-option-remove-${index}`"
@click="removeOption(index)"
@click="removeRenderedOption(index)"
/>
<MdiArrowULeftBottom
v-else
class="ml-2 hover:!text-black-500 text-gray-500 cursor-pointer"
:data-testid="`select-column-option-remove-undo-${index}`"
@click="undoRemoveRenderedOption(index)"
/>
</div>
</template>
@ -186,3 +235,19 @@ watch(inputs, () => {
</a-button>
</div>
</template>
<style scoped>
.removed {
position: relative;
}
.removed:after {
position: absolute;
left: 0;
top: 50%;
height: 1px;
background: #ccc;
content: '';
width: calc(100% + 5px);
display: block;
}
</style>

10
packages/nc-gui/composables/useViewData.ts

@ -49,7 +49,9 @@ export function useViewData(
const { getMeta } = useMetas()
const appInfoDefaultLimit = appInfo.defaultLimit || 25
const _paginationData = ref<PaginatedType>({ page: 1, pageSize: appInfoDefaultLimit })
const aggCommentCount = ref<{ row_id: string; count: number }[]>([])
const galleryData = ref<GalleryType>()
@ -64,7 +66,7 @@ export function useViewData(
const { project, isSharedBase } = useProject()
const { fetchSharedViewData, paginationData: sharedPaginationData } = useSharedView()
const { sharedView, fetchSharedViewData, paginationData: sharedPaginationData } = useSharedView()
const { $api, $e } = useNuxtApp()
@ -203,8 +205,10 @@ export function useViewData(
}
async function loadGalleryData() {
if (!viewMeta?.value?.id || isPublic.value) return
galleryData.value = await $api.dbView.galleryRead(viewMeta.value.id)
if (!viewMeta?.value?.id) return
galleryData.value = isPublic.value
? (sharedView.value?.view as GalleryType)
: await $api.dbView.galleryRead(viewMeta.value.id)
}
async function insertRow(

19
packages/noco-docs/content/en/setup-and-usages/account-settings.md

@ -27,14 +27,25 @@ If you are a super admin, you can also manage all user roles in organization lev
## User Management
Super-admin has new privelege to do user management at root-level.
Permissions within NocoDB are divided into two levels: Organisation level and Project level.
- `org-level-creator` - this user can create a new project and access any invited project.
- `org-level-viewer` - this user can't create a new project but they can access any invited project.
### Organisation Level Permissions:
- `Org Level Creator`: Allows users to create new projects and access invited projects.
- `Org Level Viewer`: Allows users to access invited projects but does not permit the creation of new projects.
![image](https://user-images.githubusercontent.com/35857179/203261168-5ba75f9c-476e-4fe7-ace4-f81051f42773.png)
### Project Level Permissions:
- `Owner`: The user who created the project. A project can have only one owner. The owner persists until the project exists and the role is non-transferable. The owner has access to carry out any operations within the project, including deleting it.
- `Creator`: Has access to carry out any operations within the project except deleting the project and removing the "Owner."
- `Editor`: Can modify data but cannot modify the schema (add/remove columns, tables, users, and such).
- `Commenter`: Can neither modify data nor schema, can only see data and can mark row-level comments.
- `Viewer`: Can only see data.
Additional access details for project level permissions can be found [here](https://docs.nocodb.com/setup-and-usages/team-and-auth#advanced-options--configurations).
Please note that the above-mentioned Project Level Permissions are additional to the already defined Organisation Level Permissions.
In addition to the previously defined permissions, NocoDB also includes the role of "Super Admin." The "Super Admin" is the first user to sign up on this NocoDB installation. An organisation can have only one "Super Admin" and this role is non-transferable. The "Super Admin" will have the equivalent permissions of an "Org Level Creator" and "Owner" for all projects within the organisation.
## Enable / Disable Signup
Signup without an invitation is disabled by default and can be managed from UI by a super admin.

4
packages/nocodb-sdk/src/lib/sqlUi/MssqlUi.ts

@ -384,7 +384,7 @@ export class MssqlUi {
return '';
case 'varchar':
return '';
return 255;
default:
return '';
@ -1095,7 +1095,7 @@ export class MssqlUi {
colProp.dt = 'varchar';
break;
case 'Date':
colProp.dt = 'varchar';
colProp.dt = 'date';
break;
case 'Year':

17
tests/playwright/pages/Dashboard/Grid/Column/SelectOptionColumn.ts

@ -1,5 +1,6 @@
import { ColumnPageObject } from '.';
import BasePage from '../../../Base';
import { expect } from '@playwright/test';
export class SelectOptionColumnPageObject extends BasePage {
readonly column: ColumnPageObject;
@ -49,6 +50,22 @@ export class SelectOptionColumnPageObject extends BasePage {
await this.column.get().locator(`svg[data-testid="select-column-option-remove-${index}"]`).click();
await expect(this.column.get().getByTestId(`select-column-option-${index}`)).toHaveClass(/removed/);
await this.column.save({ isUpdated: true });
}
async deleteOptionWithUndo({ columnTitle, index }: { index: number; columnTitle: string }) {
await this.column.openEdit({ title: columnTitle });
await this.column.get().locator(`svg[data-testid="select-column-option-remove-${index}"]`).click();
await expect(this.column.get().getByTestId(`select-column-option-${index}`)).toHaveClass(/removed/);
await this.column.get().locator(`svg[data-testid="select-column-option-remove-undo-${index}"]`).click();
await expect(this.column.get().getByTestId(`select-column-option-${index}`)).not.toHaveClass(/removed/);
await this.column.save({ isUpdated: true });
}

7
tests/playwright/tests/columnSingleSelect.spec.ts

@ -53,6 +53,13 @@ test.describe('Single select', () => {
await grid.cell.selectOption.select({ index: 0, columnHeader: 'SingleSelect', option: 'Option 3' });
await grid.cell.selectOption.verify({ index: 0, columnHeader: 'SingleSelect', option: 'Option 3' });
await grid.column.selectOption.deleteOptionWithUndo({ index: 0, columnTitle: 'SingleSelect' });
await grid.cell.selectOption.verifyOptions({
index: 0,
columnHeader: 'SingleSelect',
options: ['Option 1', 'Option 2', 'Option 3'],
});
await grid.column.selectOption.deleteOption({ index: 2, columnTitle: 'SingleSelect' });
await grid.cell.selectOption.verifyNoOptionsSelected({ index: 0, columnHeader: 'SingleSelect' });

Loading…
Cancel
Save