Browse Source

Merge branch 'develop' into qr-prototyping-cleanedup

pr-4468-qr-code-extraction
Daniel Spaude 2 years ago
parent
commit
7f768b7b5e
No known key found for this signature in database
GPG Key ID: 654A3D1FA4F35FFE
  1. 9
      packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue
  2. 2
      packages/nc-gui/lang/ar.json
  3. 3
      packages/nc-gui/lang/bn_IN.json
  4. 2
      packages/nc-gui/lang/da.json
  5. 2
      packages/nc-gui/lang/de.json
  6. 2
      packages/nc-gui/lang/es.json
  7. 2
      packages/nc-gui/lang/fa.json
  8. 2
      packages/nc-gui/lang/fi.json
  9. 2
      packages/nc-gui/lang/fr.json
  10. 2
      packages/nc-gui/lang/he.json
  11. 2
      packages/nc-gui/lang/hi.json
  12. 2
      packages/nc-gui/lang/hr.json
  13. 2
      packages/nc-gui/lang/id.json
  14. 2
      packages/nc-gui/lang/it.json
  15. 2
      packages/nc-gui/lang/ja.json
  16. 2
      packages/nc-gui/lang/ko.json
  17. 2
      packages/nc-gui/lang/lv.json
  18. 2
      packages/nc-gui/lang/nl.json
  19. 2
      packages/nc-gui/lang/no.json
  20. 2
      packages/nc-gui/lang/pl.json
  21. 2
      packages/nc-gui/lang/pt.json
  22. 2
      packages/nc-gui/lang/pt_BR.json
  23. 2
      packages/nc-gui/lang/ru.json
  24. 2
      packages/nc-gui/lang/sl.json
  25. 2
      packages/nc-gui/lang/sv.json
  26. 2
      packages/nc-gui/lang/th.json
  27. 2
      packages/nc-gui/lang/tr.json
  28. 2
      packages/nc-gui/lang/uk.json
  29. 2
      packages/nc-gui/lang/vi.json
  30. 2
      packages/nc-gui/lang/zh-Hans.json
  31. 280
      packages/nc-gui/lang/zh-Hant.json
  32. 22
      packages/nc-gui/pages/index/index/[projectId].vue
  33. 19
      packages/nc-gui/pages/index/index/create.vue
  34. 5
      packages/nc-gui/pages/index/index/user.vue
  35. 51
      tests/playwright/pages/Account/ChangePassword.ts
  36. 3
      tests/playwright/pages/Account/Users.ts
  37. 10
      tests/playwright/pages/Account/index.ts
  38. 2
      tests/playwright/pages/Dashboard/Grid/index.ts
  39. 24
      tests/playwright/pages/Dashboard/Settings/Teams.ts
  40. 1
      tests/playwright/pages/Dashboard/TreeView.ts
  41. 1
      tests/playwright/pages/Dashboard/WebhookForm/index.ts
  42. 8
      tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts
  43. 25
      tests/playwright/pages/Dashboard/common/Toolbar/index.ts
  44. 32
      tests/playwright/pages/Dashboard/index.ts
  45. 50
      tests/playwright/pages/ProjectsPage/index.ts
  46. 2
      tests/playwright/tests/accountUserSettings.spec.ts
  47. 27
      tests/playwright/tests/authChangePassword.spec.ts
  48. 2
      tests/playwright/tests/metaSync.spec.ts
  49. 3
      tests/playwright/tests/toolbarOperations.spec.ts
  50. 7
      tests/playwright/tests/viewGridShare.spec.ts
  51. 9
      tests/playwright/tests/viewKanban.spec.ts

9
packages/nc-gui/components/tabs/auth/user-management/UsersModal.vue

@ -150,11 +150,16 @@ const emailField = (inputEl: typeof Input) => {
wrap-class-name="nc-modal-invite-user-and-share-base"
@cancel="emit('closed')"
>
<div class="flex flex-col">
<div class="flex flex-col" data-testid="invite-user-and-share-base-modal">
<div class="flex flex-row justify-between items-center pb-1.5 mb-2 border-b-1 w-full">
<a-typography-title class="select-none" :level="4"> {{ $t('activity.share') }}: {{ project.title }} </a-typography-title>
<a-button type="text" class="!rounded-md mr-1 -mt-1.5" @click="emit('closed')">
<a-button
type="text"
class="!rounded-md mr-1 -mt-1.5"
data-testid="invite-user-and-share-base-modal-close-btn"
@click="emit('closed')"
>
<template #icon>
<MaterialSymbolsCloseRounded class="flex mx-auto" />
</template>

2
packages/nc-gui/lang/ar.json

@ -368,6 +368,8 @@
"setPrimary": "تعيين كقيمة أساسية",
"addRow": "إضافة صف جديد",
"saveRow": "حفظ الصف",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "إدراج صف جديد",
"deleteRow": "حذف الصف",
"deleteSelectedRow": "حذف الصفوف المحددة",

3
packages/nc-gui/lang/bn_IN.json

@ -368,6 +368,8 @@
"setPrimary": "পথমিক মন হিট করন",
"addRow": "নতন সিত করন",
"saveRow": "সিরকষণ করন",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "নতন সিন",
"deleteRow": "সিন",
"deleteSelectedRow": "নিিত সিিন",
@ -601,7 +603,6 @@
"tableDeleted": "Deleted table successfully",
"generatePublicShareableReadonlyBase": "Generate publicly shareable readonly base",
"deleteViewConfirmation": "Are you sure you want to delete this view?",
"deleteTokenConfirmation": "Are you sure you want to delete this token?",
"deleteTableConfirmation": "Do you want to delete the table",
"showM2mTables": "Show M2M Tables",
"deleteKanbanStackConfirmation": "Deleting this stack will also remove the select option `{stackToBeDeleted}` from the `{groupingField}`. The records will move to the uncategorized stack."

2
packages/nc-gui/lang/da.json

@ -368,6 +368,8 @@
"setPrimary": "Indstil som primær værdi",
"addRow": "Tilføj ny række",
"saveRow": "Gem ro",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Indsæt ny række",
"deleteRow": "DELETE ROW.",
"deleteSelectedRow": "Slet de valgte rækker",

2
packages/nc-gui/lang/de.json

@ -369,6 +369,8 @@
"setPrimary": "Als Primärwert festlegen",
"addRow": "Neue Zeile hinzufügen",
"saveRow": "Zeile speichern",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Neue Zeile einfügen",
"deleteRow": "Zeile löschen",
"deleteSelectedRow": "Ausgewählte Zeilen löschen",

2
packages/nc-gui/lang/es.json

@ -368,6 +368,8 @@
"setPrimary": "Establecido como clave primaria",
"addRow": "Añadir nueva fila",
"saveRow": "Grabar la fila",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Insertar nueva fila",
"deleteRow": "Borrar fila",
"deleteSelectedRow": "Eliminar filas seleccionadas",

2
packages/nc-gui/lang/fa.json

@ -368,6 +368,8 @@
"setPrimary": "تنظیم به عنوان مقدار اولیه",
"addRow": "اضافه کردن ردیف جدید",
"saveRow": "دخیره ردیف",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "وارد کردن ردیف جدید",
"deleteRow": "حذف ردیف جدید",
"deleteSelectedRow": "حذف ردیفهای انتخاب شده",

2
packages/nc-gui/lang/fi.json

@ -368,6 +368,8 @@
"setPrimary": "Aseta ensisijainen arvo",
"addRow": "Lisää uusi rivi",
"saveRow": "Tallenna rivi",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Lisää uusi rivi",
"deleteRow": "Poista rivi",
"deleteSelectedRow": "Poista valitut rivit",

2
packages/nc-gui/lang/fr.json

@ -368,6 +368,8 @@
"setPrimary": "Définir comme valeur primaire",
"addRow": "Ajouter une nouvelle ligne",
"saveRow": "Enregistrer la ligne",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Insérer une nouvelle ligne",
"deleteRow": "Supprimer la ligne",
"deleteSelectedRow": "Supprimer les lignes sélectionnées",

2
packages/nc-gui/lang/he.json

@ -368,6 +368,8 @@
"setPrimary": "להגדיר כערך ראשי",
"addRow": "הוסף שורה חדשה",
"saveRow": "שמור שורה",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "הכנס שורה חדשה",
"deleteRow": "מחק שורה",
"deleteSelectedRow": "מחק את השורות שנבחרו",

2
packages/nc-gui/lang/hi.json

@ -368,6 +368,8 @@
"setPrimary": "पथमिक मय कप मट कर",
"addRow": "नई पि",
"saveRow": "पि सह",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "नई पि",
"deleteRow": "पि हट",
"deleteSelectedRow": "चयनित पि हट",

2
packages/nc-gui/lang/hr.json

@ -368,6 +368,8 @@
"setPrimary": "Postavite kao primarnu vrijednost",
"addRow": "Dodaj novi red",
"saveRow": "Spremanje retka",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Umetnite novi red",
"deleteRow": "Brisanje retka",
"deleteSelectedRow": "Izbrišite odabrane retke",

2
packages/nc-gui/lang/id.json

@ -368,6 +368,8 @@
"setPrimary": "Tetapkan sebagai nilai utama",
"addRow": "Tambahkan baris baru",
"saveRow": "Hemat Baris",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Masukkan baris baru.",
"deleteRow": "Hapus Baris",
"deleteSelectedRow": "Hapus baris yang dipilih",

2
packages/nc-gui/lang/it.json

@ -368,6 +368,8 @@
"setPrimary": "Impostare come valore primario",
"addRow": "Aggiungi nuova riga",
"saveRow": "Salva riga",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Inserisci nuova riga",
"deleteRow": "Elimina riga.",
"deleteSelectedRow": "Elimina righe selezionate",

2
packages/nc-gui/lang/ja.json

@ -368,6 +368,8 @@
"setPrimary": "プライマリ値として設定",
"addRow": "行を追加",
"saveRow": "行を保存",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "行を挿入",
"deleteRow": "行を削除",
"deleteSelectedRow": "選択行を削除",

2
packages/nc-gui/lang/ko.json

@ -368,6 +368,8 @@
"setPrimary": "Primary value로 설정",
"addRow": "행 추가",
"saveRow": "행 저장",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "행 삽입",
"deleteRow": "행 삭제",
"deleteSelectedRow": "선택한 행 삭제",

2
packages/nc-gui/lang/lv.json

@ -368,6 +368,8 @@
"setPrimary": "Uzstādīt kā primāro atslēgu",
"addRow": "Pievienot ierakstu",
"saveRow": "Saglabāt ierakstu",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Pievienot jaunu ierakstu",
"deleteRow": "Dzēst ierakstu",
"deleteSelectedRow": "Dzēst izvēlētos ierakstus",

2
packages/nc-gui/lang/nl.json

@ -368,6 +368,8 @@
"setPrimary": "Instellen als primaire waarde",
"addRow": "Nieuwe rij toevoegen",
"saveRow": "Sla rij op",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Voeg nieuwe rij toe",
"deleteRow": "Verwijder rij",
"deleteSelectedRow": "Verwijder geselecteerde rijen",

2
packages/nc-gui/lang/no.json

@ -368,6 +368,8 @@
"setPrimary": "Sett som primærverdi",
"addRow": "Legg til ny rad",
"saveRow": "Lagre rad",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Sett inn ny rad",
"deleteRow": "Slett rad",
"deleteSelectedRow": "Slett utvalgte rader",

2
packages/nc-gui/lang/pl.json

@ -368,6 +368,8 @@
"setPrimary": "Ustaw jako wartość podstawowa",
"addRow": "Dodaj nowy rząd",
"saveRow": "Zapisz wiersz",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Wstaw nowy rząd",
"deleteRow": "Usuń rząd",
"deleteSelectedRow": "Usuń wybrane wiersze",

2
packages/nc-gui/lang/pt.json

@ -368,6 +368,8 @@
"setPrimary": "Definido como valor primário",
"addRow": "Adicionar nova linha",
"saveRow": "Salvar linha",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Insira a nova linha",
"deleteRow": "Excluir linha",
"deleteSelectedRow": "Excluir linhas selecionadas",

2
packages/nc-gui/lang/pt_BR.json

@ -368,6 +368,8 @@
"setPrimary": "Definido como valor primário",
"addRow": "Adicionar nova linha",
"saveRow": "Salvar linha",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Insira a nova linha",
"deleteRow": "Excluir linha",
"deleteSelectedRow": "Excluir linhas selecionadas",

2
packages/nc-gui/lang/ru.json

@ -368,6 +368,8 @@
"setPrimary": "Установить в качестве основного значения",
"addRow": "Добавить новую строку",
"saveRow": "Сохранить строку",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Вставить новый строк",
"deleteRow": "Удалить строку",
"deleteSelectedRow": "Удалить выбранные строки",

2
packages/nc-gui/lang/sl.json

@ -368,6 +368,8 @@
"setPrimary": "Kot primarna vrednost",
"addRow": "Dodaj novo vrstico",
"saveRow": "Shrani vrstico",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Vstavite novo vrstico",
"deleteRow": "Izbriši vrstico",
"deleteSelectedRow": "Izbrišite izbrane vrstice",

2
packages/nc-gui/lang/sv.json

@ -368,6 +368,8 @@
"setPrimary": "Ange som primärt värde",
"addRow": "Lägg till ny rad",
"saveRow": "Spara rad",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Infoga ny rad",
"deleteRow": "Radera raden",
"deleteSelectedRow": "Ta bort valda rader",

2
packages/nc-gui/lang/th.json

@ -368,6 +368,8 @@
"setPrimary": "ตงคาเปนคาปฐมภ",
"addRow": "เพมแถวใหม",
"saveRow": "บนทกแถว",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "แทรกแถวใหม",
"deleteRow": "ลบแถว",
"deleteSelectedRow": "ลบแถวทเลอก",

2
packages/nc-gui/lang/tr.json

@ -368,6 +368,8 @@
"setPrimary": "Birincil değer yap",
"addRow": "Yeni satır ekle",
"saveRow": "Satırı kaydet",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Yeni Satır Ekle",
"deleteRow": "Satırı Sil",
"deleteSelectedRow": "Seçilen Satırları Sil",

2
packages/nc-gui/lang/uk.json

@ -368,6 +368,8 @@
"setPrimary": "Встановлено як первинне значення",
"addRow": "Додати новий рядок",
"saveRow": "Рятувати рядок",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Вставте новий рядок",
"deleteRow": "Видалити рядок",
"deleteSelectedRow": "Видалити вибрані рядки",

2
packages/nc-gui/lang/vi.json

@ -368,6 +368,8 @@
"setPrimary": "Đặt dưới dạng giá trị chính",
"addRow": "Thêm hàng mới",
"saveRow": "Lưu hàng.",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "Chèn hàng mới",
"deleteRow": "Xóa hàng",
"deleteSelectedRow": "Xóa các hàng đã chọn",

2
packages/nc-gui/lang/zh-Hans.json

@ -368,6 +368,8 @@
"setPrimary": "设置为主要值",
"addRow": "添加新行",
"saveRow": "保存行",
"saveAndExit": "Save & Exit",
"saveAndStay": "Save & Stay",
"insertRow": "插入新行",
"deleteRow": "删除行",
"deleteSelectedRow": "删除所选行",

280
packages/nc-gui/lang/zh-Hant.json

@ -6,7 +6,7 @@
"close": "關閉",
"yes": "是",
"no": "否",
"ok": "OK",
"ok": "確認",
"and": "和",
"or": "或",
"add": "新增",
@ -59,23 +59,23 @@
"confirm": "確認",
"generate": "Generate",
"copy": "複製",
"misc": "其他",
"misc": "Miscellaneous",
"lock": "鎖定",
"unlock": "解鎖",
"credentials": "憑證",
"help": "幫助",
"questions": "問題",
"reachOut": "Reach out here",
"betaNote": "此功能還在測試中",
"moreInfo": "More information can be found here",
"logs": "Logs",
"betaNote": "此功能目前是 Beta 測試版",
"moreInfo": "更多資訊能在這裡找到",
"logs": "日誌",
"groupingField": "Grouping Field"
},
"objects": {
"project": "專案",
"projects": "全部專案",
"table": "資料表",
"tables": "全部資料表",
"project": "項目",
"projects": "項目",
"table": "表",
"tables": "表",
"field": "欄位",
"fields": "欄位",
"column": "列",
@ -86,8 +86,8 @@
"records": "記錄",
"webhook": "Webhook",
"webhooks": "Webhook",
"view": "檢視",
"views": "所有檢視",
"view": "檢視",
"views": "檢視",
"viewType": {
"grid": "網格",
"gallery": "圖庫",
@ -100,19 +100,19 @@
"role": "角色",
"roles": "角色",
"roleType": {
"owner": "有者",
"owner": "有者",
"creator": "創造者",
"editor": "編輯",
"commenter": "評論者",
"viewer": "檢視者",
"orgLevelCreator": "組織級建立者",
"orgLevelViewer": "組織級檢視者"
"orgLevelCreator": "組織級建立者",
"orgLevelViewer": "組織級檢視者"
},
"sqlVIew": "SQL View"
},
"datatype": {
"ID": "ID",
"ForeignKey": "外",
"ForeignKey": "外鑰匙",
"SingleLineText": "單行文本",
"LongText": "長篇文章",
"Attachment": "附件",
@ -134,8 +134,8 @@
"Rating": "評分",
"Formula": "公式",
"Rollup": "捲起",
"Count": "數",
"Lookup": "查找",
"Count": "數",
"Lookup": "抬頭",
"DateTime": "日期時間",
"CreateTime": "創建時間",
"LastModifiedTime": "最後修改時間",
@ -192,11 +192,11 @@
"teamAndSettings": "團隊 & 設定",
"apiDocs": "API 說明文件",
"importFromAirtable": "從 Airtable 匯入",
"generateToken": "Generate Token",
"APIsAndSupport": "APIs 與支援",
"generateToken": "產生 Token",
"APIsAndSupport": "APIs & Support",
"helpCenter": "幫助中心",
"swaggerDocumentation": "Swagger 文件",
"quickImportFrom": "Quick Import From",
"quickImportFrom": "快速匯入從",
"quickImport": "快速匯入",
"advancedSettings": "進階設定",
"codeSnippet": "程式碼片段"
@ -204,16 +204,16 @@
"labels": {
"createdBy": "Created By",
"notifyVia": "透過...通知",
"projName": "專案名",
"tableName": "資料表名稱",
"projName": "項目名",
"tableName": "表名稱",
"viewName": "查看名稱",
"viewLink": "查看鏈接",
"columnName": "欄位名稱",
"columnType": "欄位類型",
"columnName": "名稱",
"columnType": "類型",
"roleName": "角色名稱",
"roleDescription": "角色描述",
"databaseType": "數據庫類別",
"lengthValue": "長度 / 值",
"databaseType": "鍵入數據庫",
"lengthValue": "長度/值",
"dbType": "資料庫類型",
"sqliteFile": "SQLite 檔案",
"hostAddress": "主機位址",
@ -221,7 +221,7 @@
"username": "使用者名稱",
"password": "密碼",
"schemaName": "Schema 名稱",
"database": "資料庫",
"database": "數據庫",
"action": "行動",
"actions": "行動",
"operation": "操作",
@ -231,7 +231,7 @@
"authentication": "驗證",
"token": "權杖",
"where": "在哪裡",
"cache": "快取",
"cache": "緩存",
"chat": "聊天",
"email": "電子郵件",
"storage": "貯存",
@ -249,13 +249,13 @@
"requriedCa": "必填 - CA",
"requriedIdentity": "必填 - IDENTITY",
"inflection": {
"tableName": "屈折 - 資料表名稱",
"tableName": "屈折 - 表名稱",
"columnName": "屈折 - 欄位名稱"
},
"community": {
"starUs1": "在 Github 上",
"starUs2": "幫我們按讚",
"bookDemo": "預免費 Demo",
"bookDemo": "預免費 Demo",
"getAnswered": "解惑您的問題",
"joinDiscord": "加入 Discord",
"joinCommunity": "加入 NocoDB 社群",
@ -265,10 +265,10 @@
"docReference": "文件參考文獻",
"selectUserRole": "選擇使用者角色",
"childTable": "子表格",
"childColumn": "子欄",
"childColumn": "子欄",
"onUpdate": "更新",
"onDelete": "在刪除",
"account": "帳戶",
"account": "Account",
"language": "語言",
"primaryColor": "Primary Color",
"accentColor": "Accent Color",
@ -277,13 +277,13 @@
"apiKey": "API Key",
"sharedBase": "Shared Base",
"importData": "匯入資料",
"importSecondaryViews": "匯入 Secondary Views",
"importRollupColumns": "匯入 Rollup 欄位",
"importLookupColumns": "匯入 Lookup 欄位",
"importAttachmentColumns": "匯入 Attachment 欄位",
"importSecondaryViews": "Import Secondary Views",
"importRollupColumns": "Import Rollup Columns",
"importLookupColumns": "Import Lookup Columns",
"importAttachmentColumns": "Import Attachment Columns",
"importFormulaColumns": "Import Formula Columns",
"noData": "目前沒有資料",
"goToDashboard": "Go to Dashboard",
"noData": "沒有資料",
"goToDashboard": "前往儀表板",
"importing": "匯入中",
"flattenNested": "Flatten Nested",
"downloadAllowed": "允許下載",
@ -291,16 +291,16 @@
"primaryKey": "主鍵",
"hasMany": "has many",
"belongsTo": "belongs to",
"manyToMany": "have many to many relation",
"manyToMany": "有多對多關聯",
"extraConnectionParameters": "Extra connection parameters",
"commentsOnly": "Comments only",
"documentation": "文件",
"documentation": "Documentation",
"subscribeNewsletter": "Subscribe to our weekly newsletter",
"signUpWithGoogle": "使用 Google 帳號註冊",
"signInWithGoogle": "使用 Google 帳號登入",
"signUpWithGoogle": "Sign up with Google",
"signInWithGoogle": "Sign in with Google",
"agreeToTos": "By signing up, you agree to the Terms of Service",
"welcomeToNc": "Welcome to NocoDB!",
"inviteOnlySignup": "Allow signup only using invite url"
"inviteOnlySignup": "只接受使用邀請連結進行註冊"
},
"activity": {
"createProject": "建立專案",
@ -313,7 +313,7 @@
"deleteProject": "刪除專案",
"refreshProject": "重新整理專案",
"saveProject": "儲存專案",
"deleteKanbanStack": "刪除 stack?",
"deleteKanbanStack": "Delete stack?",
"createProjectExtended": {
"extDB": "連線至外部資料庫來建立",
"excel": "從 Excel 建立專案",
@ -332,10 +332,10 @@
"projInfo": "複製專案資訊",
"themes": "主題"
},
"sort": "排序",
"addSort": "加排序選項",
"sort": "種類",
"addSort": "加排序選項",
"filter": "篩選",
"addFilter": "加過濾器",
"addFilter": "加過濾器",
"share": "分享",
"shareBase": {
"disable": "禁用共享基礎",
@ -352,7 +352,7 @@
"deleteUser": "從專案中刪除使用者",
"resendInvite": "重新發送邀請電子郵件",
"copyInviteURL": "複製邀請連結",
"copyPasswordResetURL": "Copy password reset URL",
"copyPasswordResetURL": "複製重設密碼連結",
"newRole": "新角色",
"reloadRoles": "重新載入角色",
"nextPage": "下一頁",
@ -360,14 +360,16 @@
"nextRecord": "下一步記錄",
"previousRecord": "之前的紀錄",
"copyApiURL": "複製 API 網址",
"createTable": "建立資料表",
"createTable": "表創造",
"refreshTable": "表刷新",
"renameTable": "重命名資料表",
"deleteTable": "刪除資料表",
"addField": "將新欄位增加到此資料表",
"renameTable": "重命名",
"deleteTable": "刪除",
"addField": "將新字段添加到此表",
"setPrimary": "設置為主要值",
"addRow": "新增行",
"saveRow": "儲存行",
"saveAndExit": "儲存並結束",
"saveAndStay": "Save & Stay",
"insertRow": "插入新行",
"deleteRow": "刪除行",
"deleteSelectedRow": "刪除所選行",
@ -382,19 +384,19 @@
"clearMetadata": "清除中繼資料",
"exportToFile": "匯出為檔案",
"changePwd": "更改密碼",
"createView": "建立檢視",
"shareView": "分享檢視",
"createView": "建立檢視",
"shareView": "分享檢視",
"listSharedView": "共享視圖列表",
"ListView": "檢視表清單",
"copyView": "複製檢視",
"renameView": "重新命名檢視",
"deleteView": "刪除檢視",
"copyView": "複製檢視",
"renameView": "重新命名檢視",
"deleteView": "刪除檢視",
"createGrid": "創建網格視圖",
"createGallery": "創建畫廊視圖",
"createCalendar": "創建日曆視圖",
"createKanban": "創建尋呼視圖",
"createForm": "創建表單視圖",
"showSystemFields": "顯示系統欄位",
"showSystemFields": "顯示系統字段",
"copyUrl": "複製網址",
"openTab": "開啟新分頁",
"iFrame": "複製嵌入式 HTML 程式碼",
@ -412,9 +414,9 @@
"sponsorUs": "贊助我們",
"sendEmail": "傳送電子郵件",
"addUserToProject": "Add user to project",
"getApiSnippet": "Get API Snippet",
"clearCell": "Clear cell",
"addFilterGroup": "Add Filter Group",
"getApiSnippet": "取得 API 程式碼片段",
"clearCell": "清除儲存格",
"addFilterGroup": "增加過濾組",
"linkRecord": "Link record",
"addNewRecord": "Add new record",
"useConnectionUrl": "Use Connection URL",
@ -425,7 +427,7 @@
"showColumns": "顯示欄位",
"showPkAndFk": "顯示主鍵與外鍵",
"showSqlViews": "Show SQL Views",
"showMMTables": "Show Many to Many tables",
"showMMTables": "顯示多對多資料表",
"showJunctionTableNames": "Show Junction Table Names"
},
"kanban": {
@ -446,13 +448,13 @@
"dark": "它確實有黑色(^⇧b)",
"light": "它是黑色嗎?(^⇧b)"
},
"addTable": "建立資料表",
"inviteMore": "邀請更多使用者",
"addTable": "添加新表",
"inviteMore": "邀請更多用戶",
"toggleNavDraw": "切換導航抽屜",
"reloadApiToken": "重新載入 API 權杖",
"generateNewApiToken": "產生新 API 權杖",
"addRole": "添加新角色",
"reloadList": "重新載列表",
"reloadList": "重新載列表",
"metaSync": "同步中繼資料",
"sqlMigration": "重新加載遷移",
"updateRestart": "更新並重新啟動",
@ -468,15 +470,15 @@
"projName": "輸入專案名稱",
"password": {
"enter": "輸入密碼",
"current": "前密碼",
"current": "前密碼",
"new": "新密碼",
"save": "儲存密碼",
"confirm": "確認新密碼"
},
"searchProjectTree": "搜索專案樹",
"searchFields": "搜索欄位",
"searchColumn": "搜索 {search} 列",
"searchApps": "搜索應用程",
"searchProjectTree": "搜索",
"searchFields": "搜索字段",
"searchColumn": "搜索{search}列",
"searchApps": "搜索應用程",
"searchModels": "搜索模型",
"noItemsFound": "未找到任何項目",
"defaultValue": "預設值",
@ -487,13 +489,13 @@
"msg": {
"info": {
"roles": {
"orgCreator": "建立者可以建立專案與存取任何受邀請的專案.",
"orgViewer": "檢視者不可建立新專案但可以存取任何受邀請的專案."
"orgCreator": "建立者可以建立專案與存取任何受邀請的專案",
"orgViewer": "建立者不能建立專案但可以存取任何受邀請的專案"
},
"footerInfo": "每頁行駛",
"upload": "選擇檔案以上傳",
"upload_sub": "或拖放檔案",
"excelSupport": "支:.xls,.xlsx,.xlsm,.ods,.ots",
"excelSupport": "支:.xls,.xlsx,.xlsm,.ods,.ots",
"excelURL": "輸入 Excel 檔案 URL",
"csvURL": "輸入 CSV 檔案 URL",
"footMsg": "要解析為推斷數據類型的行數",
@ -506,10 +508,10 @@
"startProject": "你想啟動這個專案嗎?",
"restartProject": "你想重新啟動專案嗎?",
"deleteProject": "你想刪除這個專案嗎?",
"shareBasePrivate": "產生公開享的 Readonly Base",
"shareBasePrivate": "產生公開享的 Readonly Base",
"shareBasePublic": "網路上的任何人都可以查看",
"userInviteNoSMTP": "看起來你還沒有配置郵件!請複上面的邀請鏈接並將其發送給",
"dragDropHide": "在此處拖放欄位以隱藏",
"userInviteNoSMTP": "看起來你還沒有配置郵件!請複上面的邀請鏈接並將其發送給",
"dragDropHide": "在此處拖放字段以隱藏",
"formInput": "輸入表單輸入標籤",
"formHelpText": "添加一些幫助文本",
"onlyCreator": "僅建立者可見",
@ -520,10 +522,10 @@
"privateLinkAdditionalInfo": "具有私有連結的人只能看到此檢視表中可見的儲存格",
"afterFormSubmitted": "表格提交後",
"apiOptions": "存取專案方式",
"submitAnotherForm": "顯示 '提交另一個表格' 按鈕",
"submitAnotherForm": "顯示“提交另一個表格”按鈕",
"showBlankForm": "5 秒後顯示空白表格",
"emailForm": "發電子郵件給我",
"showSysFields": "顯示系統欄位",
"showSysFields": "顯示系統字段",
"filterAutoApply": "自動申請",
"showMessage": "顯示此消息",
"viewNotShared": "當前視圖不共享!",
@ -531,9 +533,9 @@
"collabView": "具有編輯權限或更高的合作者可以更改視圖配置。",
"lockedView": "沒有人可以編輯視圖配置,直到它被解鎖。",
"personalView": "只有您可以編輯視圖配置。默認情況下,其他合作者的個人視圖隱藏。",
"ownerDesc": "可以添加/刪除創建者。和完整編輯資料庫結構和欄位。",
"creatorDesc": "可以完全編輯資料庫結構和值。",
"editorDesc": "可以編輯記錄但無法更改資料庫/欄位的結構。",
"ownerDesc": "可以添加/刪除創建者。和完整編輯數據庫結構和字段。",
"creatorDesc": "可以完全編輯數據庫結構和值。",
"editorDesc": "可以編輯記錄但無法更改數據庫/字段的結構。",
"commenterDesc": "可以查看和評論記錄,但無法編輯任何內容",
"viewerDesc": "可以查看記錄但無法編輯任何內容",
"addUser": "新增使用者",
@ -551,7 +553,7 @@
},
"sponsor": {
"header": "你可以幫助我們!",
"message": "我們是一個小型團隊,全職打造 NocoDB 並且開源程式碼。我們相信像 NocoDB 這樣的工具應該在網際網路上自由提供給每位問題解決者。"
"message": "我們是一支小型團隊,全職工作,使Nocodb開放來源。我們相信一個像Nocodb這樣的工具應該在互聯網上的每個問題求解器上自由提供。"
},
"loginMsg": "登入 NocoDB",
"passwordRecovery": {
@ -577,7 +579,7 @@
"tablesMetadataInSync": "表元數據同步",
"addMultipleUsers": "您可以添加多個逗號(,)分隔的電子郵件",
"enterTableName": "輸入表名",
"addDefaultColumns": "建立預設欄位",
"addDefaultColumns": "添加默認列",
"tableNameInDb": "數據庫中保存的表名",
"airtable": {
"credentials": "Where to find this?"
@ -591,18 +593,18 @@
"copiedToClipboard": "複製到剪貼簿",
"requriedFieldsCantBeMoved": "Required field can't be moved",
"updateNotAllowedWithoutPK": "Update not allowed for table which doesn't have primary key",
"autoIncFieldNotEditable": "Auto increment field is not editable",
"editingPKnotSupported": "Editing primary key not supported",
"deletedCache": "Deleted cache successfully",
"autoIncFieldNotEditable": "自增欄位不可編輯",
"editingPKnotSupported": "不支援編輯主鍵",
"deletedCache": "刪除快取成功",
"cacheEmpty": "快取是空的",
"exportedCache": "Exported Cache Successfully",
"valueAlreadyInList": "This value is already in the list",
"valueAlreadyInList": "此值已在列表中",
"noColumnsToUpdate": "No columns to update",
"tableDeleted": "Deleted table successfully",
"tableDeleted": "刪除資料表成功",
"generatePublicShareableReadonlyBase": "Generate publicly shareable readonly base",
"deleteViewConfirmation": "Are you sure you want to delete this view?",
"deleteTableConfirmation": "Do you want to delete the table",
"showM2mTables": "顯示 M2M 資料表",
"deleteViewConfirmation": "是否確定要刪除此檢視?",
"deleteTableConfirmation": "你想刪除此資料表",
"showM2mTables": "顯示多對多資料表",
"deleteKanbanStackConfirmation": "Deleting this stack will also remove the select option `{stackToBeDeleted}` from the `{groupingField}`. The records will move to the uncategorized stack."
},
"error": {
@ -619,51 +621,51 @@
"passwdRequired": "密碼為必填",
"passwdLength": "您的密碼應至少有 8 個字元",
"passwdMismatch": "密碼不匹配",
"completeRuleSet": "At least 8 characters with one Uppercase, one number and one special character",
"atLeast8Char": "At least 8 characters",
"atLeastOneUppercase": "One Uppercase letter",
"atLeastOneNumber": "One Number",
"atLeastOneSpecialChar": "One special character",
"allowedSpecialCharList": "Allowed special character list"
"completeRuleSet": "密碼必須含有至少 8 個字元,其中有一個大寫字母、一個數字和一個特殊字元",
"atLeast8Char": "至少 8 個字元",
"atLeastOneUppercase": "一個大寫字母",
"atLeastOneNumber": "一個數字",
"atLeastOneSpecialChar": "一個特殊字元",
"allowedSpecialCharList": "允許特殊字元列表"
},
"invalidURL": "無效的 URL",
"internalError": "Some internal error occurred",
"invalidURL": "無效的連結",
"internalError": "發生內部錯誤",
"templateGeneratorNotFound": "Template Generator cannot be found!",
"fileUploadFailed": "檔案上傳失敗",
"fileUploadFailed": "上傳文件失敗",
"primaryColumnUpdateFailed": "Failed to update primary column",
"formDescriptionTooLong": "Data too long for Form Description",
"formDescriptionTooLong": "表單描述資料過長",
"columnsRequired": "Following columns are required",
"selectAtleastOneColumn": "At least one column has to be selected",
"selectAtleastOneColumn": "至少必須選擇一個欄位",
"columnDescriptionNotFound": "Cannot find the destination column for",
"duplicateMappingFound": "Duplicate mapping found, please remove one of the mapping",
"nullValueViolatesNotNull": "Null value violates not-null constraint",
"sourceHasInvalidNumbers": "Source data contains some invalid numbers",
"sourceHasInvalidBoolean": "Source data contains some invalid boolean values",
"invalidForm": "無效的表",
"nullValueViolatesNotNull": "Null 值違反不可為 Null 限制條件",
"sourceHasInvalidNumbers": "來源資料包含無效的數字",
"sourceHasInvalidBoolean": "來源資料包含無效的布林值",
"invalidForm": "無效的表",
"formValidationFailed": "Form validation failed",
"youHaveBeenSignedOut": "You have been signed out",
"youHaveBeenSignedOut": "您已登出",
"failedToLoadList": "Failed to load list",
"failedToLoadChildrenList": "Failed to load children list",
"deleteFailed": "Delete failed",
"deleteFailed": "刪除失敗",
"unlinkFailed": "Unlink failed",
"rowUpdateFailed": "Row update failed",
"deleteRowFailed": "Failed to delete row",
"rowUpdateFailed": "資料更新失敗",
"deleteRowFailed": "刪除資料失敗",
"setFormDataFailed": "Failed to set form data",
"formViewUpdateFailed": "Failed to update form view",
"tableNameRequired": "Table name is required",
"nameShouldStartWithAnAlphabetOr_": "Name should start with an alphabet or _",
"tableNameRequired": "資料表名稱必填",
"nameShouldStartWithAnAlphabetOr_": "名稱必須用 英文字母 或 _ 當開頭",
"followingCharactersAreNotAllowed": "Following characters are not allowed",
"columnNameRequired": "Column name is required",
"projectNameExceeds50Characters": "Project name exceeds 50 characters",
"projectNameCannotStartWithSpace": "Project name cannot start with space",
"requiredField": "Required field",
"columnNameRequired": "欄位名稱必填",
"projectNameExceeds50Characters": "專案名稱超過 50 個字元",
"projectNameCannotStartWithSpace": "專案名稱不能有空白開頭",
"requiredField": "必填欄位",
"ipNotAllowed": "IP not allowed",
"targetFileIsNotAnAcceptedFileType": "Target file is not an accepted file type",
"theAcceptedFileTypeIsCsv": "The accepted file type is .csv",
"theAcceptedFileTypesAreXlsXlsxXlsmOdsOts": "The accepted file types are .xls, .xlsx, .xlsm, .ods, .ots",
"parameterKeyCannotBeEmpty": "Parameter key 不可為空",
"parameterKeyCannotBeEmpty": "Parameter key cannot be empty",
"duplicateParameterKeysAreNotAllowed": "Duplicate parameter keys are not allowed",
"fieldRequired": "{value} 不可為空.",
"fieldRequired": "{value} cannot be empty.",
"projectNotAccessible": "Project not accessible"
},
"toast": {
@ -685,37 +687,37 @@
},
"success": {
"updatedUIACL": "Updated UI ACL for tables successfully",
"pluginUninstalled": "Plugin uninstalled successfully",
"pluginSettingsSaved": "Plugin settings saved successfully",
"pluginTested": "Successfully tested plugin settings",
"pluginUninstalled": "外掛移除安裝成功",
"pluginSettingsSaved": "外掛設定儲存成功",
"pluginTested": "外掛設定測試成功",
"tableRenamed": "資料表重新命名成功",
"viewDeleted": "View deleted successfully",
"viewDeleted": "檢視刪除成功",
"primaryColumnUpdated": "Successfully updated as primary column",
"tableDataExported": "Successfully exported all table data",
"updated": "Successfully updated",
"updated": "成功更新",
"sharedViewDeleted": "Deleted shared view successfully",
"userDeleted": "User deleted successfully",
"viewRenamed": "View renamed successfully",
"tokenGenerated": "Token generated successfully",
"tokenDeleted": "Token deleted successfully",
"userAddedToProject": "成功增加使用者到專案",
"userAdded": "成功增加使用者",
"userDeletedFromProject": "Successfully deleted user from project",
"inviteEmailSent": "Invite Email sent successfully",
"inviteURLCopied": "Invite URL 複製到剪貼簿",
"passwordResetURLCopied": "Password reset URL copied to 剪貼簿",
"shareableURLCopied": "Copied shareable base URL to 剪貼簿!",
"userDeleted": "使用者已成功删除",
"viewRenamed": "檢視重新命名成功",
"tokenGenerated": "Token 產生成功",
"tokenDeleted": "Token 刪除成功",
"userAddedToProject": "專案增加使用者成功",
"userAdded": "增加使用者成功",
"userDeletedFromProject": "專案移除使用者成功",
"inviteEmailSent": "邀請郵件發送成功",
"inviteURLCopied": "邀請連結已複製到剪貼簿",
"passwordResetURLCopied": "密碼重置連結已複製到剪貼簿",
"shareableURLCopied": "Copied shareable base URL to clipboard!",
"embeddableHTMLCodeCopied": "Copied embeddable HTML code!",
"userDetailsUpdated": "Successfully updated the user details",
"tableDataImported": "Successfully imported table data",
"userDetailsUpdated": "成功更新使用者資料",
"tableDataImported": "成功匯入資料表資料",
"webhookUpdated": "Webhook details updated successfully",
"webhookDeleted": "Hook deleted successfully",
"webhookTested": "Webhook tested successfully",
"columnUpdated": "欄位已更新",
"columnCreated": "欄位已建立",
"passwordChanged": "密碼變更成功. 請重新登入.",
"settingsSaved": "設定儲存成功",
"roleUpdated": "角色更新成功"
"passwordChanged": "密碼已更新,請重新登入。",
"settingsSaved": "設定已成功儲存",
"roleUpdated": "角色已成功更新"
}
}
}

22
packages/nc-gui/pages/index/index/[projectId].vue

@ -1,6 +1,7 @@
<script lang="ts" setup>
import type { Form } from 'ant-design-vue'
import type { ProjectType } from 'nocodb-sdk'
import type { VNodeRef } from '@vue/runtime-core'
import {
extractSdkResponseErrorMsg,
message,
@ -8,14 +9,13 @@ import {
projectTitleValidator,
reactive,
ref,
tryOnMounted,
useProject,
useRoute,
} from '#imports'
const route = useRoute()
const { project, loadProject, updateProject, isLoading, projectLoadedHook } = useProject()
const { loadProject, updateProject, isLoading } = useProject()
loadProject(false)
@ -43,21 +43,7 @@ const renameProject = async () => {
}
}
// select and focus title field on load
projectLoadedHook(async () => {
formState.title = project.value.title as string
tryOnMounted(() => {
// todo: replace setTimeout and follow better approach
setTimeout(() => {
const input = form.value?.$el?.querySelector('input[type=text]')
input.focus()
input.setSelectionRange(0, formState.title?.length)
}, 150)
})
})
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</script>
<template>
@ -89,7 +75,7 @@ projectLoadedHook(async () => {
@finish="renameProject"
>
<a-form-item :label="$t('labels.projName')" name="title" :rules="nameValidationRules">
<a-input v-model:value="formState.title" name="title" class="nc-metadb-project-name" />
<a-input :ref="focus" v-model:value="formState.title" name="title" class="nc-metadb-project-name" />
</a-form-item>
<div class="text-center">

19
packages/nc-gui/pages/index/index/create.vue

@ -1,11 +1,10 @@
<script lang="ts" setup>
import type { Form } from 'ant-design-vue'
import type { VNodeRef } from '@vue/runtime-core'
import {
extractSdkResponseErrorMsg,
message,
navigateTo,
nextTick,
onMounted,
projectTitleValidator,
reactive,
ref,
@ -47,19 +46,7 @@ const createProject = async () => {
}
}
// select and focus title field on load
onMounted(async () => {
await nextTick(() => {
// todo: replace setTimeout and follow better approach
setTimeout(() => {
const input = form.value?.$el?.querySelector('input[type=text]')
input.setSelectionRange(0, formState.title.length)
input.focus()
}, 500)
})
})
const focus: VNodeRef = (el) => (el as HTMLInputElement)?.focus()
</script>
<template>
@ -88,7 +75,7 @@ onMounted(async () => {
@finish="createProject"
>
<a-form-item :label="$t('labels.projName')" name="title" :rules="nameValidationRules" class="m-10">
<a-input v-model:value="formState.title" name="title" class="nc-metadb-project-name" />
<a-input :ref="focus" v-model:value="formState.title" name="title" class="nc-metadb-project-name" />
</a-form-item>
<div class="text-center">

5
packages/nc-gui/pages/index/index/user.vue

@ -68,7 +68,10 @@ const resetError = () => {
</script>
<template>
<div class="relative flex flex-col justify-center gap-2 w-full p-8 md:(bg-white rounded-lg border-1 border-gray-200 shadow)">
<div
class="relative flex flex-col justify-center gap-2 w-full p-8 md:(bg-white rounded-lg border-1 border-gray-200 shadow)"
data-testid="user-change-password"
>
<LazyGeneralNocoIcon class="color-transition hover:(ring ring-accent)" :animate="isLoading" />
<div

51
tests/playwright/pages/Account/ChangePassword.ts

@ -0,0 +1,51 @@
import { expect, Page } from '@playwright/test';
import BasePage from '../Base';
export class ChangePasswordPage extends BasePage {
constructor(rootPage: Page) {
super(rootPage);
}
get() {
return this.rootPage.getByTestId('nc-user-settings-form');
}
async changePassword({
oldPass,
newPass,
repeatPass,
networkValidation,
}: {
oldPass: string;
newPass: string;
repeatPass: string;
networkValidation?: boolean;
}) {
const currentPassword = this.get().locator('input[data-testid="nc-user-settings-form__current-password"]');
const newPassword = this.get().locator('input[data-testid="nc-user-settings-form__new-password"]');
const confirmPassword = this.get().locator('input[data-testid="nc-user-settings-form__new-password-repeat"]');
await currentPassword.fill(oldPass);
await newPassword.fill(newPass);
await confirmPassword.fill(repeatPass);
const submitChangePassword = this.get().locator('button[data-testid="nc-user-settings-form__submit"]').click();
if (networkValidation) {
await this.waitForResponse({
uiAction: submitChangePassword,
httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: 'api/v1/auth/password/change',
});
} else {
await submitChangePassword;
}
}
async verifyFormError({ error }: { error: string }) {
await expect(this.get().getByTestId('nc-user-settings-form__error')).toHaveText(error);
}
async verifyPasswordDontMatchError() {
await expect(this.rootPage.locator('.ant-form-item-explain-error')).toHaveText('Passwords do not match');
}
}

3
tests/playwright/pages/Account/Users.ts

@ -1,10 +1,12 @@
import { Locator } from '@playwright/test';
import BasePage from '../Base';
import { ChangePasswordPage } from './ChangePassword';
import { AccountPage } from './index';
export class AccountUsersPage extends BasePage {
readonly inviteUserBtn: Locator;
readonly inviteUserModal: Locator;
readonly changePasswordPage: ChangePasswordPage;
private accountPage: AccountPage;
constructor(accountPage: AccountPage) {
@ -12,6 +14,7 @@ export class AccountUsersPage extends BasePage {
this.accountPage = accountPage;
this.inviteUserBtn = this.get().locator(`[data-testid="nc-super-user-invite"]`);
this.inviteUserModal = accountPage.rootPage.locator(`.nc-modal-invite-user`);
this.changePasswordPage = new ChangePasswordPage(this.rootPage);
}
async goto() {

10
tests/playwright/pages/Account/index.ts

@ -1,9 +1,19 @@
import { Page } from '@playwright/test';
import BasePage from '../Base';
import { AccountSettingsPage } from './Settings';
import { AccountTokenPage } from './Token';
import { AccountUsersPage } from './Users';
export class AccountPage extends BasePage {
readonly settings: AccountSettingsPage;
readonly token: AccountTokenPage;
readonly users: AccountUsersPage;
constructor(page: Page) {
super(page);
this.settings = new AccountSettingsPage(this);
this.token = new AccountTokenPage(this);
this.users = new AccountUsersPage(this);
}
get() {

2
tests/playwright/pages/Dashboard/Grid/index.ts

@ -135,7 +135,7 @@ export class GridPage extends BasePage {
}
async deleteRow(index: number) {
await this.get().locator(`td[data-testid="cell-Title-${index}"]`).click({
await this.get().getByTestId(`cell-Title-${index}`).click({
button: 'right',
});

24
tests/playwright/pages/Dashboard/Settings/Teams.ts

@ -1,32 +1,31 @@
import { expect, Locator } from '@playwright/test';
import { Locator } from '@playwright/test';
import { SettingsPage } from '.';
import BasePage from '../../Base';
import { writeFileAsync } from 'xlsx';
import { ToolbarPage } from '../common/Toolbar';
export class TeamsPage extends BasePage {
private readonly settings: SettingsPage;
readonly inviteTeamBtn: Locator;
readonly inviteTeamModal: Locator;
private readonly inviteTeamBtn: Locator;
private readonly inviteTeamModal: Locator;
constructor(settings: SettingsPage) {
super(settings.rootPage);
this.settings = settings;
this.inviteTeamBtn = this.get().locator(`button:has-text("Invite Team")`);
this.inviteTeamModal = this.rootPage.locator(`.nc-modal-invite-user-and-share-base`);
this.inviteTeamModal = this.rootPage.getByTestId('invite-user-and-share-base-modal');
}
get() {
return this.settings.get().locator(`[data-testid="nc-settings-subtab-Users Management"]`);
return this.settings.get().getByTestId('nc-settings-subtab-Users Management');
}
// Prefixing to differentiate between emails created by the tests which are deleted after the test run
prefixEmail(email: string) {
const parallelId = process.env.TEST_PARALLEL_INDEX ?? '0';
return `nc_test_${parallelId}_${email}`;
}
getSharedBaseSubModal() {
return this.rootPage.locator(`[data-testid="nc-share-base-sub-modal"]`);
return this.rootPage.getByTestId('nc-share-base-sub-modal');
}
async invite({ email, role }: { email: string; role: string }) {
@ -44,8 +43,8 @@ export class TeamsPage extends BasePage {
}
async closeInvite() {
// two btn-icon-only in invite modal: close & copy url
await this.inviteTeamModal.locator(`button.ant-btn-icon-only:visible`).first().click();
// todo: Fix the case where there is ghost dom for previous modal
await this.inviteTeamModal.getByTestId('invite-user-and-share-base-modal-close-btn').last().click();
}
async inviteMore() {
@ -53,7 +52,7 @@ export class TeamsPage extends BasePage {
}
async toggleSharedBase({ toggle }: { toggle: boolean }) {
const toggleBtn = await this.getSharedBaseSubModal().locator(`.nc-disable-shared-base`);
const toggleBtn = this.getSharedBaseSubModal().locator(`.nc-disable-shared-base`);
const toggleBtnText = await toggleBtn.first().innerText();
const disabledBase = toggleBtnText.includes('Disable');
@ -76,8 +75,7 @@ export class TeamsPage extends BasePage {
}
async getSharedBaseUrl() {
const url = await this.getSharedBaseSubModal().locator(`.nc-url:visible`).innerText();
return url;
return await this.getSharedBaseSubModal().locator(`.nc-url:visible`).textContent();
}
async sharedBaseActions({ action }: { action: string }) {

1
tests/playwright/pages/Dashboard/TreeView.ts

@ -57,6 +57,7 @@ export class TreeViewPage extends BasePage {
responseJsonMatcher: json => json.title === title && json.type === 'table',
});
// Tab render is slow for playwright
await this.dashboard.waitForTabRender({ title });
}

1
tests/playwright/pages/Dashboard/WebhookForm/index.ts

@ -23,7 +23,6 @@ export class WebhookFormPage extends BasePage {
return this.dashboard.get().locator(`.nc-drawer-webhook-body`);
}
// todo: Removing opening webhook drawer logic as it belongs to `Toolbar` page
async create({ title, event, url = 'http://localhost:9090/hook' }: { title: string; event: string; url?: string }) {
await this.toolbar.clickActions();
await this.toolbar.actions.click('Webhooks');

8
tests/playwright/pages/Dashboard/common/Toolbar/Filter.ts

@ -40,11 +40,6 @@ export class ToolbarFilterPage extends BasePage {
value: string;
isLocallySaved: boolean;
}) {
await this.toolbar.clickFilter();
// todo: If the filter menu is open for the first time for the table, there can will be a api call which will re render the filter menu
await this.rootPage.waitForTimeout(1000);
await this.get().locator(`button:has-text("Add Filter")`).first().click();
await this.rootPage.locator('.nc-filter-field-select').last().click();
@ -82,9 +77,6 @@ export class ToolbarFilterPage extends BasePage {
requestUrlPathToMatch: isLocallySaved ? `/api/v1/db/public/` : `/api/v1/db/data/noco/`,
});
await this.toolbar.parent.dashboard.waitForLoaderToDisappear();
await this.toolbar.clickFilter();
await this.toolbar.parent.waitLoading();
}

25
tests/playwright/pages/Dashboard/common/Toolbar/index.ts

@ -69,13 +69,30 @@ export class ToolbarPage extends BasePage {
if (menuOpen) await this.sort.get().waitFor({ state: 'hidden' });
}
async clickFilter() {
async clickFilter({
// `networkValidation` is used to verify that api calls are made when the button is clicked
// which happens when the filter is opened for the first time
networkValidation,
}: { networkValidation?: boolean } = {}) {
const menuOpen = await this.filter.get().isVisible();
await this.get().locator(`button.nc-filter-menu-btn`).click();
const clickFilterAction = this.get().locator(`button.nc-filter-menu-btn`).click();
// Wait for the menu to close
if (menuOpen) await this.filter.get().waitFor({ state: 'hidden' });
if (menuOpen) {
await clickFilterAction;
await this.filter.get().waitFor({ state: 'hidden' });
} else {
if (networkValidation) {
// Since on opening filter menu, api is called to fetch filter options, and will rerender the menu
await this.waitForResponse({
uiAction: clickFilterAction,
requestUrlPathToMatch: '/api/v1/db',
httpMethodsToMatch: ['GET'],
});
} else {
await clickFilterAction;
}
}
}
async clickShareView() {

32
tests/playwright/pages/Dashboard/index.ts

@ -61,7 +61,7 @@ export class DashboardPage extends BasePage {
}
async gotoSettings() {
await this.rootPage.locator('[data-testid="nc-project-menu"]').click();
await this.rootPage.getByTestId('nc-project-menu').click();
await this.rootPage.locator('div.nc-project-menu-item:has-text(" Team & Settings")').click();
}
@ -79,9 +79,6 @@ export class DashboardPage extends BasePage {
}
async clickHome() {
// todo: Fast page transition breaks the vue router
await this.rootPage.waitForTimeout(2000);
await this.rootPage.getByTestId('nc-noco-brand-icon').click();
const projectsPage = new ProjectsPage(this.rootPage);
await projectsPage.waitToBeRendered();
@ -124,32 +121,9 @@ export class DashboardPage extends BasePage {
}
}
async openPasswordChangeModal() {
// open change password portal
await this.rootPage.locator('.nc-menu-accounts').click();
await this.rootPage
.locator('.nc-dropdown-user-accounts-menu')
.getByTestId('nc-menu-accounts__user-settings')
.click();
}
// todo: Move this to a seperate page
async changePassword({ oldPass, newPass, repeatPass }: { oldPass: string; newPass: string; repeatPass: string }) {
// change password
const currentPassword = this.rootPage.locator('input[data-testid="nc-user-settings-form__current-password"]');
const newPassword = this.rootPage.locator('input[data-testid="nc-user-settings-form__new-password"]');
const confirmPassword = this.rootPage.locator('input[data-testid="nc-user-settings-form__new-password-repeat"]');
await currentPassword.fill(oldPass);
await newPassword.fill(newPass);
await confirmPassword.fill(repeatPass);
await this.rootPage.locator('button[data-testid="nc-user-settings-form__submit"]').click();
}
async signOut() {
await this.rootPage.locator('[data-testid="nc-project-menu"]').click();
const projMenu = await this.rootPage.locator('.nc-dropdown-project-menu');
await this.rootPage.getByTestId('nc-project-menu').click();
const projMenu = this.rootPage.locator('.nc-dropdown-project-menu');
await projMenu.locator('[data-menu-id="account"]:visible').click();
await this.rootPage.locator('div.nc-project-menu-item:has-text("Sign Out"):visible').click();
await this.rootPage.locator('[data-testid="nc-form-signin"]:visible').waitFor();

50
tests/playwright/pages/ProjectsPage/index.ts

@ -28,7 +28,7 @@ export class ProjectsPage extends BasePage {
}) {
if (!withoutPrefix) name = this.prefixTitle(name);
await this.rootPage.locator('.nc-new-project-menu').click();
await this.get().locator('.nc-new-project-menu').click();
const createProjectMenu = await this.rootPage.locator('.nc-dropdown-create-project');
@ -38,20 +38,18 @@ export class ProjectsPage extends BasePage {
await createProjectMenu.locator(`.ant-dropdown-menu-title-content`).nth(1).click();
}
// todo: Fast page transition breaks the vue router
await this.rootPage.waitForTimeout(2000);
await this.rootPage.locator(`.nc-metadb-project-name`).waitFor();
await this.rootPage.locator(`input.nc-metadb-project-name`).fill(name);
await this.rootPage.waitForTimeout(2000);
await this.rootPage.locator(`button:has-text("Create")`).click({
delay: 2000,
const createProjectSubmitAction = this.rootPage.locator(`button:has-text("Create")`).click();
await this.waitForResponse({
uiAction: createProjectSubmitAction,
httpMethodsToMatch: ['POST'],
requestUrlPathToMatch: '/api/v1/db/meta/projects/',
});
// fix me! wait for page to be rendered completely
await this.rootPage.waitForTimeout(2000);
// wait for dashboard to render
await this.rootPage.locator('.nc-container').waitFor({ state: 'visible' });
}
async checkProjectCreateButton({ exists = true }) {
@ -91,9 +89,6 @@ export class ProjectsPage extends BasePage {
withoutPrefix?: boolean;
waitForAuthTab?: boolean;
}) {
// todo: Fast page transition breaks the vue router
await this.rootPage.waitForTimeout(2000);
if (!withoutPrefix) title = this.prefixTitle(title);
let project: any;
@ -138,7 +133,13 @@ export class ProjectsPage extends BasePage {
if (!withoutPrefix) title = this.prefixTitle(title);
await this.get().locator(`[data-testid="delete-project-${title}"]`).click();
await this.rootPage.locator(`button:has-text("Yes")`).click();
const deleteProjectAction = this.rootPage.locator(`button:has-text("Yes")`).click();
await this.waitForResponse({
uiAction: deleteProjectAction,
httpMethodsToMatch: ['DELETE'],
requestUrlPathToMatch: '/api/v1/db/meta/projects/',
});
await this.get().locator('.ant-table-row', { hasText: title }).waitFor({ state: 'hidden' });
}
@ -161,9 +162,6 @@ export class ProjectsPage extends BasePage {
});
await projRow.locator('.nc-action-btn').nth(0).click();
// todo: Fast page transition breaks the vue router
await this.rootPage.waitForTimeout(2000);
await project.locator('input.nc-metadb-project-name').fill(newTitle);
// press enter to save
const submitAction = project.locator('input.nc-metadb-project-name').press('Enter');
@ -172,9 +170,6 @@ export class ProjectsPage extends BasePage {
requestUrlPathToMatch: 'api/v1/db/meta/projects/',
httpMethodsToMatch: ['PATCH'],
});
// todo: vue navigation breaks if page changes very quickly
await this.rootPage.waitForTimeout(1000);
}
async openLanguageMenu() {
@ -187,10 +182,23 @@ export class ProjectsPage extends BasePage {
}
async verifyLanguage(param: { json: any }) {
const title = await this.rootPage.locator(`.nc-project-page-title`);
const title = this.rootPage.locator(`.nc-project-page-title`);
const menu = this.rootPage.locator(`.nc-new-project-menu`);
await expect(title).toHaveText(param.json.title.myProject);
await expect(menu).toHaveText(param.json.title.newProj);
await this.rootPage.locator(`[placeholder="${param.json.activity.searchProject}"]`).waitFor();
}
async openPasswordChangeModal() {
// open change password portal
await this.rootPage.locator('.nc-menu-accounts').click();
await this.rootPage
.locator('.nc-dropdown-user-accounts-menu')
.getByTestId('nc-menu-accounts__user-settings')
.click();
}
async waitForRender() {
await this.rootPage.locator('.nc-project-page-title:has-text("My Projects")').waitFor();
}
}

2
tests/playwright/tests/accountUserSettings.spec.ts

@ -13,7 +13,7 @@ test.describe('App settings', () => {
test.beforeEach(async ({ page }) => {
context = await setup({ page });
accountPage = new AccountPage(page);
accountSettingsPage = new AccountSettingsPage(accountPage);
accountSettingsPage = accountPage.settings;
});
test('Toggle invite only signup', async () => {

27
tests/playwright/tests/authChangePassword.spec.ts

@ -4,17 +4,24 @@ import setup from '../setup';
import { LoginPage } from '../pages/LoginPage';
import { SettingsPage, SettingTab } from '../pages/Dashboard/Settings';
import { SignupPage } from '../pages/SignupPage';
import { ProjectsPage } from '../pages/ProjectsPage';
import { AccountPage } from '../pages/Account';
test.describe('Auth', () => {
let context: any;
let dashboard: DashboardPage;
let settings: SettingsPage;
let context: any;
let signupPage: SignupPage;
let projectsPage: ProjectsPage;
let accountPage: AccountPage;
test.beforeEach(async ({ page }) => {
context = await setup({ page });
dashboard = new DashboardPage(page, context.project);
signupPage = new SignupPage(page);
projectsPage = new ProjectsPage(page);
accountPage = new AccountPage(page);
settings = dashboard.settings;
});
@ -37,31 +44,31 @@ test.describe('Auth', () => {
password: 'Password123.',
});
await dashboard.openPasswordChangeModal();
await projectsPage.openPasswordChangeModal();
// Existing active pass incorrect
await dashboard.changePassword({
await accountPage.users.changePasswordPage.changePassword({
oldPass: '123456789',
newPass: '123456789',
repeatPass: '123456789',
});
await dashboard.rootPage
.locator('[data-testid="nc-user-settings-form__error"]:has-text("Current password is wrong")')
.waitFor();
await accountPage.users.changePasswordPage.verifyFormError({ error: 'Current password is wrong' });
// New pass and repeat pass mismatch
await dashboard.changePassword({
await accountPage.users.changePasswordPage.changePassword({
oldPass: 'Password123.',
newPass: '123456789',
repeatPass: '987654321',
networkValidation: false,
});
await dashboard.rootPage.locator('.ant-form-item-explain-error:has-text("Passwords do not match")').waitFor();
await accountPage.users.changePasswordPage.verifyPasswordDontMatchError();
// All good
await dashboard.changePassword({
await accountPage.users.changePasswordPage.changePassword({
oldPass: 'Password123.',
newPass: 'NewPasswordConfigured',
repeatPass: 'NewPasswordConfigured',
networkValidation: true,
});
const loginPage = new LoginPage(page);
@ -69,6 +76,6 @@ test.describe('Auth', () => {
await loginPage.fillPassword('NewPasswordConfigured');
await loginPage.submit();
await page.locator('.nc-project-page-title:has-text("My Projects")').waitFor();
await projectsPage.waitForRender();
});
});

2
tests/playwright/tests/metaSync.spec.ts

@ -260,12 +260,14 @@ test.describe('Meta sync', () => {
isLocallySaved: false,
});
await dashboard.grid.toolbar.clickFilter();
await dashboard.grid.toolbar.filter.addNew({
columnTitle: 'Col1',
opType: '>=',
value: '5',
isLocallySaved: false,
});
await dashboard.grid.toolbar.clickFilter();
await dashboard.grid.verifyRowCount({ count: 5 });
});

3
tests/playwright/tests/toolbarOperations.spec.ts

@ -56,12 +56,15 @@ test.describe('Toolbar operations (GRID)', () => {
await validateFirstRow('Afghanistan');
// Filter column
await toolbar.clickFilter();
await toolbar.filter.addNew({
columnTitle: 'Country',
value: 'India',
opType: 'is equal',
isLocallySaved: false,
});
await toolbar.clickFilter();
await validateFirstRow('India');
// Reset filter

7
tests/playwright/tests/viewGridShare.spec.ts

@ -39,12 +39,14 @@ test.describe('Shared view', () => {
isLocallySaved: false,
});
// filter
await dashboard.grid.toolbar.clickFilter();
await dashboard.grid.toolbar.filter.addNew({
columnTitle: 'Address',
value: 'Ab',
opType: 'is like',
isLocallySaved: false,
});
await dashboard.grid.toolbar.clickFilter();
// share with password disabled, download enabled
await dashboard.grid.toolbar.clickShareView();
@ -106,12 +108,14 @@ test.describe('Shared view', () => {
});
if (isMysql(context)) {
await sharedPage.grid.toolbar.clickFilter();
await sharedPage.grid.toolbar.filter.addNew({
columnTitle: 'District',
value: 'Ta',
opType: 'is like',
isLocallySaved: true,
});
await sharedPage.grid.toolbar.clickFilter();
}
await sharedPage.grid.toolbar.fields.toggle({ title: 'LastUpdate', isLocallySaved: true });
expectedColumns[6].isVisible = false;
@ -191,12 +195,15 @@ test.describe('Shared view', () => {
title: 'New Column',
isVisible: true,
});
await sharedPage2.grid.toolbar.clickFilter();
await sharedPage2.grid.toolbar.filter.addNew({
columnTitle: 'Country',
value: 'New Country',
opType: 'is like',
isLocallySaved: true,
});
await sharedPage2.grid.toolbar.clickFilter();
await sharedPage2.grid.cell.verify({
index: 0,
columnHeader: 'Country',

9
tests/playwright/tests/viewKanban.spec.ts

@ -142,12 +142,17 @@ test.describe('View', () => {
});
// verify filter
await toolbar.clickFilter({
networkValidation: true,
});
await toolbar.filter.addNew({
columnTitle: 'Title',
opType: 'is like',
value: 'BA',
isLocallySaved: false,
});
await toolbar.clickFilter();
// verify card order
const order4 = [
['BAKED CLEOPATRA', 'BALLROOM MOCKINGBIRD'],
@ -188,12 +193,16 @@ test.describe('View', () => {
isAscending: false,
isLocallySaved: false,
});
await toolbar.clickFilter();
await toolbar.filter.addNew({
columnTitle: 'Title',
opType: 'is like',
value: 'BA',
isLocallySaved: false,
});
await toolbar.clickFilter();
await toolbar.fields.hideAll();
await toolbar.fields.toggle({ title: 'Title' });

Loading…
Cancel
Save