mirror of https://github.com/nocodb/nocodb
Rohit T P
5 months ago
230 changed files with 9346 additions and 3985 deletions
@ -0,0 +1,55 @@ |
|||||||
|
name: Run BATS Tests |
||||||
|
|
||||||
|
on: |
||||||
|
push: |
||||||
|
paths: |
||||||
|
- 'docker-compose/setup-script/noco.sh' |
||||||
|
workflow_dispatch: |
||||||
|
|
||||||
|
jobs: |
||||||
|
prepare: |
||||||
|
runs-on: ubuntu-latest |
||||||
|
outputs: |
||||||
|
matrix: ${{ steps.set-matrix.outputs.matrix }} |
||||||
|
steps: |
||||||
|
- name: Checkout code |
||||||
|
uses: actions/checkout@v4 |
||||||
|
|
||||||
|
- name: Install jq |
||||||
|
run: | |
||||||
|
sudo apt-get update |
||||||
|
sudo apt-get install -y jq |
||||||
|
|
||||||
|
- name: Prepare matrix for test files |
||||||
|
id: set-matrix |
||||||
|
run: | |
||||||
|
BATS_FILES=$(find docker-compose/setup-script/tests -name '*.bats') |
||||||
|
MATRIX_JSON=$(echo $BATS_FILES | jq -Rsc 'split("\n") | map(select(. != ""))') |
||||||
|
echo "matrix=$MATRIX_JSON" >> $GITHUB_ENV |
||||||
|
|
||||||
|
test: |
||||||
|
needs: prepare |
||||||
|
runs-on: ubuntu-latest |
||||||
|
strategy: |
||||||
|
fail-fast: false |
||||||
|
matrix: |
||||||
|
test: ${{fromJson(env.matrix)}} |
||||||
|
steps: |
||||||
|
- name: Checkout repository |
||||||
|
uses: actions/checkout@v4 |
||||||
|
|
||||||
|
- name: Install BATS |
||||||
|
run: | |
||||||
|
sudo apt-get update |
||||||
|
sudo apt-get install -y bats expect |
||||||
|
|
||||||
|
- name: Get working directory |
||||||
|
run: | |
||||||
|
WORKING_DIR="$(pwd)/docker-compose/setup-script/tests" |
||||||
|
echo "WORKING_DIR=$WORKING_DIR" >> $GITHUB_ENV |
||||||
|
|
||||||
|
- name: Run BATS test |
||||||
|
run: bats ${{ matrix.test }} |
||||||
|
env: |
||||||
|
WORKING_DIR: ${{ env.WORKING_DIR }} |
||||||
|
SKIP_TARE_DOWN: true |
After Width: | Height: | Size: 377 B |
After Width: | Height: | Size: 597 B |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 732 B |
After Width: | Height: | Size: 257 B |
@ -0,0 +1,32 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
const { header, field, toggleSort } = defineProps<{ |
||||||
|
header: string |
||||||
|
activeSort: { field?: string; direction?: string } |
||||||
|
field: UsersSortType['field'] |
||||||
|
toggleSort: Function |
||||||
|
}>() |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<div class="flex items-center space-x-2 cursor-pointer text-gray-700" @click="toggleSort(field)"> |
||||||
|
<span> |
||||||
|
{{ header }} |
||||||
|
</span> |
||||||
|
<div class="flex flex-col"> |
||||||
|
<GeneralIcon |
||||||
|
icon="arrowDropUp" |
||||||
|
class="text-sm mb-[-10px] text-[16px]" |
||||||
|
:class="{ |
||||||
|
'text-primary': activeSort.field === field && activeSort.direction === 'asc', |
||||||
|
}" |
||||||
|
/> |
||||||
|
<GeneralIcon |
||||||
|
icon="arrowDropDown" |
||||||
|
class="text-sm text-[16px]" |
||||||
|
:class="{ |
||||||
|
'text-primary': activeSort.field === field && activeSort.direction === 'desc', |
||||||
|
}" |
||||||
|
/> |
||||||
|
</div> |
||||||
|
</div> |
||||||
|
</template> |
@ -1,79 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
import { iconMap } from '#imports' |
|
||||||
import type { UsersSortType } from '~/lib' |
|
||||||
|
|
||||||
const { field, direction, handleUserSort } = defineProps<{ |
|
||||||
field: UsersSortType['field'] |
|
||||||
direction?: UsersSortType['direction'] |
|
||||||
handleUserSort: Function |
|
||||||
}>() |
|
||||||
|
|
||||||
const isOpen = ref(false) |
|
||||||
|
|
||||||
const sortUserBy = (direction?: UsersSortType['direction']) => { |
|
||||||
handleUserSort({ |
|
||||||
field, |
|
||||||
direction, |
|
||||||
}) |
|
||||||
isOpen.value = false |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<a-dropdown |
|
||||||
v-model:visible="isOpen" |
|
||||||
:trigger="['click']" |
|
||||||
placement="bottomLeft" |
|
||||||
overlay-class-name="nc-user-menu-column-operations !border-1 rounded-lg !shadow-xl" |
|
||||||
@click.stop="isOpen = !isOpen" |
|
||||||
> |
|
||||||
<div> |
|
||||||
<GeneralIcon |
|
||||||
:icon="direction === 'asc' || direction === 'desc' ? 'sortDesc' : 'arrowDown'" |
|
||||||
class="text-grey h-full text-grey nc-user-menu-trigger cursor-pointer outline-0 mr-2 transition-none" |
|
||||||
:style="{ transform: direction === 'asc' ? 'rotate(180deg)' : undefined }" |
|
||||||
/> |
|
||||||
</div> |
|
||||||
<template #overlay> |
|
||||||
<NcMenu class="flex flex-col gap-1 border-gray-200 nc-user-menu-column-options"> |
|
||||||
<NcMenuItem @click="sortUserBy('asc')"> |
|
||||||
<div class="nc-column-insert-after nc-user-menu-item"> |
|
||||||
<component |
|
||||||
:is="iconMap.sortDesc" |
|
||||||
class="text-gray-700 !rotate-180 !w-4.25 !h-4.25" |
|
||||||
:style="{ |
|
||||||
transform: 'rotate(180deg)', |
|
||||||
}" |
|
||||||
/> |
|
||||||
|
|
||||||
<!-- Sort Ascending --> |
|
||||||
{{ $t('general.sortAsc') }} |
|
||||||
</div> |
|
||||||
</NcMenuItem> |
|
||||||
<NcMenuItem @click="sortUserBy('desc')"> |
|
||||||
<div class="nc-column-insert-before nc-user-menu-item"> |
|
||||||
<component :is="iconMap.sortDesc" class="text-gray-700 !w-4.25 !h-4.25 ml-0.5 mr-0.25" /> |
|
||||||
<!-- Sort Descending --> |
|
||||||
{{ $t('general.sortDesc') }} |
|
||||||
</div> |
|
||||||
</NcMenuItem> |
|
||||||
</NcMenu> |
|
||||||
</template> |
|
||||||
</a-dropdown> |
|
||||||
</template> |
|
||||||
|
|
||||||
<style scoped> |
|
||||||
.nc-user-menu-item { |
|
||||||
@apply flex items-center gap-2; |
|
||||||
} |
|
||||||
|
|
||||||
.nc-user-menu-column-options { |
|
||||||
.nc-icons { |
|
||||||
@apply !w-5 !h-5; |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
:deep(.ant-dropdown-menu-item) { |
|
||||||
@apply !hover:text-black text-gray-700; |
|
||||||
} |
|
||||||
</style> |
|
@ -0,0 +1,3 @@ |
|||||||
|
<template> |
||||||
|
<span></span> |
||||||
|
</template> |
@ -1,54 +0,0 @@ |
|||||||
<script lang="ts" setup> |
|
||||||
const props = defineProps<{ |
|
||||||
visible: boolean |
|
||||||
workspaceId: string |
|
||||||
}>() |
|
||||||
|
|
||||||
const emits = defineEmits(['update:visible']) |
|
||||||
const visible = useVModel(props, 'visible', emits) |
|
||||||
|
|
||||||
const workspaceStore = useWorkspace() |
|
||||||
|
|
||||||
const { deleteWorkspace: _deleteWorkspace, loadWorkspaces, navigateToWorkspace } = workspaceStore |
|
||||||
|
|
||||||
const { workspaces, workspacesList } = storeToRefs(workspaceStore) |
|
||||||
|
|
||||||
const { refreshCommandPalette } = useCommandPalette() |
|
||||||
|
|
||||||
const workspace = computed(() => workspaces.value.get(props.workspaceId)) |
|
||||||
|
|
||||||
const onDelete = async () => { |
|
||||||
if (!workspace.value) return |
|
||||||
|
|
||||||
try { |
|
||||||
await _deleteWorkspace(workspace.value.id!) |
|
||||||
await loadWorkspaces() |
|
||||||
|
|
||||||
if (!workspacesList.value?.[0]?.id) { |
|
||||||
return await navigateToWorkspace() |
|
||||||
} |
|
||||||
|
|
||||||
await navigateToWorkspace(workspacesList.value?.[0]?.id) |
|
||||||
} catch (e: any) { |
|
||||||
message.error(await extractSdkResponseErrorMsg(e)) |
|
||||||
} finally { |
|
||||||
refreshCommandPalette() |
|
||||||
} |
|
||||||
} |
|
||||||
</script> |
|
||||||
|
|
||||||
<template> |
|
||||||
<GeneralDeleteModal v-model:visible="visible" :entity-name="$t('objects.workspace')" :on-delete="onDelete"> |
|
||||||
<template #entity-preview> |
|
||||||
<div v-if="workspace" class="flex flex-row items-center py-2.25 px-2.75 bg-gray-50 rounded-lg text-gray-700 mb-4"> |
|
||||||
<GeneralIcon icon="workspace" /> |
|
||||||
<div |
|
||||||
class="capitalize text-ellipsis overflow-hidden select-none w-full pl-2.25" |
|
||||||
:style="{ wordBreak: 'keep-all', whiteSpace: 'nowrap', display: 'inline' }" |
|
||||||
> |
|
||||||
{{ workspace.title }} |
|
||||||
</div> |
|
||||||
</div> |
|
||||||
</template> |
|
||||||
</GeneralDeleteModal> |
|
||||||
</template> |
|
@ -0,0 +1,26 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import { useCopy } from '~/composables/useCopy' |
||||||
|
|
||||||
|
const props = defineProps<{ |
||||||
|
content?: string |
||||||
|
timeout?: number |
||||||
|
}>() |
||||||
|
|
||||||
|
const { copy } = useCopy() |
||||||
|
const copied = ref(false) |
||||||
|
|
||||||
|
const copyContent = async () => { |
||||||
|
await copy(props.content || '') |
||||||
|
copied.value = true |
||||||
|
setTimeout(() => { |
||||||
|
copied.value = false |
||||||
|
}, props.timeout || 2000) |
||||||
|
} |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<NcButton size="xsmall" type="text" @click="copyContent"> |
||||||
|
<MdiCheck v-if="copied" class="h-3.5" /> |
||||||
|
<component :is="iconMap.copy" v-else class="text-gray-800" /> |
||||||
|
</NcButton> |
||||||
|
</template> |
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,85 @@ |
|||||||
|
<script setup lang="ts"> |
||||||
|
import { ref } from 'vue' |
||||||
|
|
||||||
|
interface Props { |
||||||
|
isOpen: boolean |
||||||
|
} |
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), { |
||||||
|
isOpen: false, |
||||||
|
}) |
||||||
|
|
||||||
|
const emits = defineEmits(['update:isOpen']) |
||||||
|
|
||||||
|
const isOpen = useVModel(props, 'isOpen', emits) |
||||||
|
|
||||||
|
const ncLinksDropdownRef = ref<HTMLDivElement>() |
||||||
|
|
||||||
|
const randomClass = `link-records_${Math.floor(Math.random() * 99999)}` |
||||||
|
|
||||||
|
const addOrRemoveClass = (add: boolean = false) => { |
||||||
|
const dropdownRoot = ncLinksDropdownRef.value?.parentElement?.parentElement?.parentElement?.parentElement as HTMLElement |
||||||
|
if (dropdownRoot) { |
||||||
|
if (add) { |
||||||
|
dropdownRoot.classList.add('inset-0', 'nc-link-dropdown-root', `nc-root-${randomClass}`) |
||||||
|
} else { |
||||||
|
dropdownRoot.classList.remove('inset-0', 'nc-link-dropdown-root', `nc-root-${randomClass}`) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
watch( |
||||||
|
isOpen, |
||||||
|
(next) => { |
||||||
|
if (next) { |
||||||
|
onClickOutside(document.querySelector(`.${randomClass}`)! as HTMLDivElement, (e) => { |
||||||
|
const targetEl = e?.target as HTMLElement |
||||||
|
if (!targetEl?.classList.contains(`nc-root-${randomClass}`) || targetEl?.closest(`.nc-${randomClass}`)) { |
||||||
|
return |
||||||
|
} |
||||||
|
isOpen.value = false |
||||||
|
|
||||||
|
addOrRemoveClass(false) |
||||||
|
}) |
||||||
|
} else { |
||||||
|
addOrRemoveClass(false) |
||||||
|
} |
||||||
|
}, |
||||||
|
{ flush: 'post' }, |
||||||
|
) |
||||||
|
|
||||||
|
watch([ncLinksDropdownRef, isOpen], () => { |
||||||
|
if (!ncLinksDropdownRef.value) return |
||||||
|
|
||||||
|
if (isOpen.value) { |
||||||
|
addOrRemoveClass(true) |
||||||
|
} else { |
||||||
|
addOrRemoveClass(false) |
||||||
|
} |
||||||
|
}) |
||||||
|
</script> |
||||||
|
|
||||||
|
<template> |
||||||
|
<NcDropdown |
||||||
|
:visible="isOpen" |
||||||
|
placement="bottom" |
||||||
|
overlay-class-name="nc-links-dropdown !min-w-[540px]" |
||||||
|
:class="`.nc-${randomClass}`" |
||||||
|
> |
||||||
|
<slot /> |
||||||
|
<template #overlay> |
||||||
|
<div ref="ncLinksDropdownRef" class="h-[412px] w-[540px]" :class="`${randomClass}`"> |
||||||
|
<slot name="overlay" /> |
||||||
|
</div> |
||||||
|
</template> |
||||||
|
</NcDropdown> |
||||||
|
</template> |
||||||
|
|
||||||
|
<style lang="scss"> |
||||||
|
.nc-links-dropdown { |
||||||
|
z-index: 1000 !important; |
||||||
|
} |
||||||
|
.nc-link-dropdown-root { |
||||||
|
z-index: 1000; |
||||||
|
} |
||||||
|
</style> |
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in new issue