diff --git a/.eslintrc.json b/.eslintrc.json index ce4b07ca2f..9dab2f1a88 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -54,7 +54,10 @@ "ignoreRestSiblings": true } ], - "local-rules/no-budibase-imports": "error" + "no-redeclare": "off", + "@typescript-eslint/no-redeclare": "error", + // have to turn this off to allow function overloading in typescript + "no-dupe-class-members": "off" } }, { @@ -87,7 +90,9 @@ "jest/expect-expect": "off", // We do this in some tests where the behaviour of internal tables // differs to external, but the API is broadly the same - "jest/no-conditional-expect": "off" + "jest/no-conditional-expect": "off", + // have to turn this off to allow function overloading in typescript + "no-dupe-class-members": "off" } }, { diff --git a/.github/AUTHORS.md b/.github/AUTHORS.md index df346f3325..d31bb64987 100644 --- a/.github/AUTHORS.md +++ b/.github/AUTHORS.md @@ -8,4 +8,5 @@ Contributors * Andrew Kingston - [@aptkingston](https://github.com/aptkingston) * Michael Drury - [@mike12345567](https://github.com/mike12345567) * Peter Clement - [@PClmnt](https://github.com/PClmnt) -* Rory Powell - [@Rory-Powell](https://github.com/Rory-Powell) \ No newline at end of file +* Rory Powell - [@Rory-Powell](https://github.com/Rory-Powell) +* Michaël St-Georges [@CSLTech](https://github.com/CSLTech) \ No newline at end of file diff --git a/i18n/README.it.md b/i18n/README.it.md new file mode 100644 index 0000000000..8972fcdb18 --- /dev/null +++ b/i18n/README.it.md @@ -0,0 +1,213 @@ +

+ + Budibase + +

+

+ Budibase +

+ +

+ La piattaforma low-code che amerai utilizzare +

+

+ Budibase è una piattaforma low-code open source ed è il modo più semplice per creare strumenti interni che migliorano la produttività. +

+ +

+ 🤖 🎨 🚀 +

+
+ +

+ Budibase design ui +

+ +

+ + GitHub tutte le release + + + GitHub release (ordine cronologico) + + + Segui @budibase + + Codice di condotta + + + +

+ +

+ Inizia + · + Documentazione + · + Richieste di miglioramento + · + Segnala un bug + · + Supporto: Discussioni +

+ +

+## ✨ Funzionalità + +### Costruisci e distribuisci software reale +A differenza di altre piattaforme, con Budibase puoi costruire e distribuire applicazioni one-page. Le applicazioni Budibase sono altamente performanti e possono essere progettate in modo responsive, offrendo ai tuoi utenti un'esperienza eccezionale. +

+ +### Sorgente aperto ed estensibile +Budibase è software open source - sotto licenza GPL v3. Questo dovrebbe rassicurarti sul fatto che Budibase sarà sempre lì. Puoi anche codificare in Budibase o fare fork e apportare modifiche a tuo piacimento, rendendolo un'esperienza amichevole per gli sviluppatori. +

+ +### Importa dati o inizia da zero +Budibase può estrarre i suoi dati da diverse fonti, tra cui MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB o un'API REST. E a differenza di altre piattaforme, con Budibase puoi partire da zero e creare applicazioni aziendali senza alcuna fonte di dati. [Richiedi una nuova fonte di dati](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). + +

+ Dati Budibase +

+

+ +### Progetta e crea applicazioni utilizzando componenti predefiniti. + +Budibase è dotato di componenti predefiniti belli e potenti che puoi utilizzare come mattoni per costruire la tua interfaccia utente. Esporremo anche molte delle tue opzioni di stile CSS preferite in modo che tu possa esprimere una creatività maggiore. [Richiedi un nuovo componente](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). + +

+ Design Budibase +

+

+ +### Automatizza processi, integra altri strumenti e collegati a webhook +Risparmia tempo automatizzando processi manuali e flussi di lavoro. Che si tratti di connettersi a webhook o automatizzare email, basta dire a Budibase cosa fare e lasciarlo lavorare per te. Puoi facilmente [creare una nuova automazione per Budibase qui](https://github.com/Budibase/automations) o [Richiedere una nuova automazione](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). + +

+ Automazioni Budibase +

+

+ +### Integrazione con i tuoi strumenti preferiti +Budibase si integra con vari strumenti popolari, consentendoti di creare applicazioni che si adattano perfettamente alla tua stack tecnologica. + +

+ Integrazioni Budibase +

+

+ +### Paradiso degli amministratori +Budibase è progettato per crescere. Con Budibase, puoi auto-ospitarti sulla tua infrastruttura e gestire globalmente utenti, home, SMTP, applicazioni, gruppi, aspetto e altro ancora. Puoi anche fornire agli utenti/gruppi un portale delle applicazioni e affidare la gestione degli utenti al responsabile del gruppo. + +- Guarda il video promozionale: https://youtu.be/xoljVpty_Kw + +


+ +## 🏁 Inizio + + + +Implementa Budibase self-hosted nella tua infrastruttura esistente, utilizzando Docker, Kubernetes e Digital Ocean. +Oppure utilizza Budibase Cloud se non hai bisogno di auto-ospitare e desideri iniziare rapidamente. + +### [Inizia con Budibase](https://budibase.com) + + +

+ +## 🎓 Imparare Budibase + +La documentazione Budibase [è qui](https://docs.budibase.com). +
+ + +

+ +## 💬 Comunità + +Se hai domande o vuoi discutere con altri utenti di Budibase e unirti alla nostra comunità, vai su: [Discussioni Github](https://github.com/Budibase/budibase/discussions) + +


+ + +## ❗ Codice di condotta + +Budibase si impegna a offrire a tutti un'esperienza accogliente, diversificata e priva di molestie. Ci aspettiamo che tutti i membri della comunità Budibase rispettino i principi del nostro [**Codice di condotta**](https://github.com/Budibase/budibase/blob/HEAD/.github/CODE_OF_CONDUCT.md). Grazie per la tua attenzione. +
+ + +

+ + +## 🙌 Contribuire a Budibase + +Che tu stia aprendo un rapporto di bug o creando una Pull request, ogni contributo è apprezzato e benvenuto. Se stai pensando di implementare una nuova funzionalità o modificare l'API, crea prima un Issue. In questo modo possiamo assicurarci che il tuo lavoro non sia inutile. + +### Non sai da dove cominciare ? +Un buon punto di partenza per contribuire è qui: [Progetti in corso](https://github.com/Budibase/budibase/projects/22). + +### Come è organizzato il repo ? +Budibase è un monorepo gestito da lerna. Lerna gestisce la costruzione e la pubblicazione dei pacchetti di Budibase. Ecco, a grandi linee, i pacchetti che compongono Budibase. + +- [packages/builder](https://github.com/Budibase/budibase/tree/HEAD/packages/builder) - contiene il codice per l'applicazione svelte lato client di budibase builder. + +- [packages/client](https://github.com/Budibase/budibase/tree/HEAD/packages/client) - Un modulo che viene eseguito nel browser e che è responsabile della lettura delle definizioni JSON e della creazione di applicazioni web viventi da esse. + +- [packages/server](https://github.com/Budibase/budibase/tree/HEAD/packages/server) - Il server budibase. Questa applicazione Koa è responsabile del servizio del JS per le applicazioni builder e budibase, oltre a fornire l'API per l'interazione con il database e il filesystem. + +Per ulteriori informazioni, vedere [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md) + +

+ + +## 📝 Licenza + +Budibase è open source, con licenza [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html). Le librerie client e dei componenti sono con licenza [MPL](https://directory.fsf.org/wiki/License:MPL-2.0) - quindi le applicazioni che crei possono essere utilizzate con licenza come desideri. + +

+ +## ⭐ Stargazers nel tempo + +[![Stargazers nel tempo](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase) + +Se riscontri problemi tra gli aggiornamenti del builder, utilizza la seguente guida [qui](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md#troubleshooting) per pulire il tuo ambiente. + +

+ +## Contributeurs ✨ + +Grazie a queste meravigliose persone ([emoji key](https://allcontributors.org/docs/en/emoji-key)): + + + + + + + + + + + + + + + + + + + + + + + +

Martin McKeaveney

💻 📖 ⚠️ 🚇

Michael Drury

📖 💻 ⚠️ 🚇

Andrew Kingston

📖 💻 ⚠️ 🎨

Michael Shanks

📖 💻 ⚠️

Kevin Åberg Kultalahti

📖 💻 ⚠️

Joe

📖 💻 🖋 🎨

Rory Powell

💻 📖 ⚠️

Peter Clement

💻 📖 ⚠️

Conor Mack

💻 📖

Dominic Cave

💻 📖

Rabonaire

💻 📖 🐛

Alex Yeung

📖 💻

Sam Woods

📖 💻 🚇

ScooterSwope

💻 📖
+ + + + + +Questo progetto segue il [convenant del contribuente](https://github.com/Budibase/budibase/blob/master/CODE_OF_CONDUCT.md). Ogni contributo è il benvenuto! + + + + + diff --git a/i18n/README.por.md b/i18n/README.por.md new file mode 100644 index 0000000000..b8954dbe22 --- /dev/null +++ b/i18n/README.por.md @@ -0,0 +1,211 @@ +

+ + Budibase + +

+

+ Budibase +

+ +

+ A plataforma low-code que você vai adorar usar +

+

+ Budibase é uma plataforma low-code de código aberto e é a maneira mais fácil de criar ferramentas internas que melhoram a produtividade. +

+ +

+ 🤖 🎨 🚀 +

+
+ +

+ Budibase design ui +

+ +

+ + GitHub todos os releases + + + GitHub release (por ordem cronológica) + + + Siga @budibase + + Código de Conduta + + + +

+ +

+ Começar + · + Documentação + · + Solicitar melhorias + · + Reportar um bug + · + Suporte: Discussões +

+ +

+## ✨ Recursos + +### Construa e implante um software real +Ao contrário de outras plataformas, com o Budibase você constrói e implanta aplicativos de uma página. Os aplicativos Budibase são altamente performáticos e podem ser designados de forma responsiva, proporcionando uma experiência excepcional aos seus usuários. +

+ +### Código-fonte livre e extensível +Budibase é software livre - sob a licença GPL v3. Isso deve lhe dar confiança de que o Budibase estará sempre disponível. Você também pode codificar no Budibase ou bifurcá-lo e fazer alterações conforme desejar, tornando-o amigável para desenvolvedores. +

+ +### Importar dados ou começar do zero +Budibase pode extrair dados de várias fontes, incluindo MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB ou uma API REST. E ao contrário de outras plataformas, com o Budibase você pode começar do zero e criar aplicativos de negócios sem nenhuma fonte de dados. [Solicitar uma nova fonte de dados](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). + +

+ Dados Budibase +

+

+ +### Projetar e criar aplicativos usando componentes pré-definidos +O Budibase vem com componentes lindamente projetados e poderosos que você pode usar como blocos de construção para criar sua interface do usuário. Também oferecemos muitas das suas opções de estilo CSS favoritas para que você possa mostrar sua criatividade. [Solicitar um novo componente](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). + +

+ Design Budibase +

+

+ +### Automatizar processos, integrar outras ferramentas e conectar webhooks +Economize tempo automatizando processos manuais e fluxos de trabalho. Seja conectando-se a webhooks ou automatizando e-mails, basta dizer ao Budibase o que fazer e deixá-lo trabalhar para você. Você pode facilmente [criar uma nova automação para o Budibase aqui](https://github.com/Budibase/automations) ou [Solicitar uma nova automação](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). + +

+ Automações Budibase +

+

+ +### Integração com suas ferramentas favoritas +O Budibase se integra a várias ferramentas populares, permitindo que você crie aplicativos que se encaixam perfeitamente em sua pilha tecnológica. + +

+ Integrações Budibase +

+

+ +### Paraíso dos administradores +O Budibase é projetado para escalar. Com o Budibase, você pode se auto-hospedar em sua própria infraestrutura e gerenciar globalmente usuários, home, SMTP, aplicativos, grupos, aparência e muito mais. Você também pode fornecer aos usuários/grupos um portal de aplicativos e delegar o gerenciamento de usuários ao líder do grupo. + +- Assista ao vídeo promocional: https://youtu.be/xoljVpty_Kw + +


+ +## 🏁 Começar + + + +Implante o Budibase em auto-hospedagem em sua infraestrutura existente, usando Docker, Kubernetes e Digital Ocean. +Ou use o Budibase Cloud se você não precisar se auto-hospedar e quiser começar rapidamente. + +### [Começar com o Budibase](https://budibase.com) + + +

+ +## 🎓 Aprenda Budibase + +A documentação Budibase [está aqui](https://docs.budibase.com). +
+ + +

+ +## 💬 Comunidade + +Se você tiver alguma dúvida ou quiser conversar com outros usuários do Budibase e se juntar à nossa comunidade, visite [Discussões do Github](https://github.com/Budibase/budibase/discussions) + +


+ + +## ❗ Código de Conduta + +O Budibase está comprometido em oferecer a todos uma experiência acolhedora, diversificada e livre de assédio. Esperamos que todos os membros da comunidade Budibase sigam os princípios do nosso [**Código de Conduta**](https://github.com/Budibase/budibase/blob/HEAD/.github/CODE_OF_CONDUCT.md). Obrigado por ler. +
+ + +

+ + +## 🙌 Contribuindo para o Budibase + +Seja abrindo uma issue ou criando um pull request, toda contribuição é apreciada e bem-vinda. Se você está pensando em implementar uma nova funcionalidade ou alterar a API, por favor, crie primeiro uma Issue. Assim, podemos garantir que seu trabalho não seja em vão. + +### Não sabe por onde começar? +Um bom lugar para começar a contribuir é aqui: [Projetos em andamento](https://github.com/Budibase/budibase/projects/22). + +### Como o repositório está organizado? +O Budibase é um monorepo gerenciado pelo lerna. O Lerna cuida da construção e publicação dos pacotes do Budibase. Aqui estão, em alto nível, os pacotes que compõem o Budibase. + +- [packages/builder](https://github.com/Budibase/budibase/tree/HEAD/packages/builder) - contém o código para o aplicativo svelte do lado do cliente do budibase builder. + +- [packages/client](https://github.com/Budibase/budibase/tree/HEAD/packages/client) - Um módulo que roda no navegador e é responsável por ler definições JSON e criar aplicativos web dinâmicos a partir delas. + +- [packages/server](https://github.com/Budibase/budibase/tree/HEAD/packages/server) - O servidor budibase. Este aplicativo Koa é responsável por servir o JS para os aplicativos builder e budibase, bem como fornecer a API para interagir com o banco de dados e o sistema de arquivos. + +Para mais informações, veja [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md) + +

+ + +## 📝 Licença + +O Budibase é open source, sob a licença [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html). As bibliotecas do cliente e dos componentes estão licenciadas sob [MPL](https://directory.fsf.org/wiki/License:MPL-2.0) - para que os aplicativos que você cria possam ser usados sob licença como você desejar. + +

+ +## ⭐ Stargazers ao longo do tempo + +[![Stargazers ao longo do tempo](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase) + +Se você tiver problemas entre as atualizações do builder, por favor, use o guia a seguir [aqui](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md#troubleshooting) para limpar seu ambiente. + +

+ +## Contribuidores ✨ + +Agradecimentos a estas pessoas maravilhosas ([chave de emoji](https://allcontributors.org/docs/fr/emoji-key)): + + + + + + + + + + + + + + + + + + + + + + +

Martin McKeaveney

💻 📖 ⚠️ 🚇

Michael Drury

📖 💻 ⚠️ 🚇

Andrew Kingston

📖 💻 ⚠️ 🎨

Michael Shanks

📖 💻 ⚠️

Kevin Åberg Kultalahti

📖 💻 ⚠️

Joe

📖 💻 🖋 🎨

Rory Powell

💻 📖 ⚠️

Peter Clement

💻 📖 ⚠️

Conor Mack

💻 📖 ⚠️

Grays-world

💻

Sylvain Galand

💻 📖

John Mullins

💻

Jakeboyd

💻

Steve Bridle

💻
+ + + +

+ + +## Licença + +Distribuído sob a licença GPL v3.0. Veja `LICENSE` para mais informações. + +

diff --git a/i18n/README.ru.md b/i18n/README.ru.md new file mode 100644 index 0000000000..b235828a40 --- /dev/null +++ b/i18n/README.ru.md @@ -0,0 +1,207 @@ +

+ + Budibase + +

+

+ Budibase +

+ +

+ Низкокодовая платформа, которую вы полюбите использовать +

+

+ Budibase - это открытая низкокодовая платформа, которая представляет собой самый простой способ создания внутренних инструментов, повышающих производительность. +

+ +

+ 🤖 🎨 🚀 +

+
+ +

+ Budibase design ui +

+ +

+ + GitHub все релизы + + + GitHub релизы (в хронологическом порядке) + + + Подписаться на @budibase + + Кодекс поведения + + + +

+ +

+ Начать + · + Документация + · + Запросы на улучшения + · + Сообщить об ошибке + · + Поддержка: Обсуждения +

+ +

+## ✨ Функциональные возможности + +### Строим и развертываем настоящее программное обеспечение +В отличие от других платформ, с помощью Budibase вы создаете и развертываете одностраничные приложения. Приложения Budibase имеют высокую производительность и могут быть адаптированы для разных устройств, обеспечивая вашим пользователям удивительный опыт. +

+ +### Открытый и расширяемый исходный код +Budibase - это свободное программное обеспечение под лицензией GPL v3. Это должно вас уверить в том, что Budibase всегда будет здесь. Вы также можете писать код в Budibase или форкнуть его и вносить изменения по своему усмотрению, что сделает его дружелюбным для разработчиков. +

+ +### Импорт данных или начало с нуля +Budibase может получать данные из различных источников, включая MongoDB, CouchDB, PostgreSQL, MySQL, Airtable, S3, DynamoDB или REST API. И в отличие от других платформ, с помощью Budibase вы можете начать с нуля и создавать бизнес-приложения без каких-либо источников данных. [Запросить новый источник данных](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). + +

+ Budibase data +

+

+ +### Проектирование и создание приложений с использованием предварительно определенных компонентов. + +Budibase поставляется с красиво оформленными и мощными компонентами, которые вы можете использовать как строительные блоки для создания вашего пользовательского интерфейса. Мы также предоставляем множество ваших любимых опций стилей CSS, чтобы вы могли проявить больше креативности. [Запросить новый компонент](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). + +

+ Budibase design +

+

+ +### Автоматизация процессов, интеграция с другими инструментами и подключение к вебхукам +Экономьте время, автоматизируя ручные процессы и рабочие потоки. Будь то подключение к вебхукам или автоматизация отправки электронных писем, просто скажите Budibase, что он должен делать, и позвольте ему работать за вас. Вы можете легко [создать новую автоматизацию для Budibase здесь](https://github.com/Budibase/automations) или [Запросить новую автоматизацию](https://github.com/Budibase/budibase/discussions?discussions_q=category%3AIdeas). + +

+ Budibase automations +

+

+ +### Интеграция с вашими любимыми инструментами +Budibase интегрируется с рядом популярных инструментов, что позволяет вам создавать приложения, которые идеально вписываются в вашу технологическую стопку. + +

+ Budibase integrations +

+

+ +### Рай для админов +Budibase разработан для масштабирования. С Budibase вы можете самостоятельно размещать его на своей собственной инфраструктуре и глобально управлять пользователями, доменами, SMTP, приложениями, группами, внешним видом и многим другим. Вы также можете предоставить пользователям/группам портал приложений и поручить управление пользователями руководителю группы. + +- Смотрите промо-видео: https://youtu.be/xoljVpty_Kw + +


+ +## 🏁 Начало работы + + + +Разверните Budibase на своей собственной инфраструктуре с использованием Docker, Kubernetes и Digital Ocean. +Или используйте Budibase Cloud, если вам не нужно самостоятельно размещаться, и вы хотите быстро начать. + +### [Начать работу с Budibase](https://budibase.com) + + +

+ +## 🎓 Изучение Budibase + +Документация Budibase [здесь](https://docs.budibase.com). +
+ + +

+ +## 💬 Сообщество + +Если у вас есть вопросы или вы хотите обсудить что-то с другими пользователями Budibase и присоединиться к нашему сообществу, пожалуйста, перейдите по следующей ссылке: [Обсуждения на GitHub](https://github.com/Budibase/budibase/discussions) + +


+ + +## ❗ Кодекс поведения + +Budibase обязуется обеспечить каждому дружелюбный, разнообразный и безопасный опыт. Мы ожидаем, что все члены сообщества Budibase будут следовать принципам нашего [**Кодекса поведения**](https://github.com/Budibase/budibase/blob/HEAD/.github/CODE_OF_CONDUCT.md). Спасибо за внимание. +
+ + +

+ + +## 🙌 Вклад в Budibase + +Будь то открытие ошибки или создание запроса на включение изменений, любой вклад приветствуется и приветствуется. Если вы планируете реализовать новую функциональность или изменить API, сначала создайте Issue. Так мы сможем убедиться, что ваша работа не напрасна. + +### Не знаете, с чего начать? +Хорошее место для начала вклада - это здесь: [Текущие проекты](https://github.com/Budibase/budibase/projects/22). + +### Как организован репозиторий? +Budibase - это монорепозиторий, управляемый с помощью lerna. Lerna управляет сборкой и публикацией пакетов Budibase. Вот, в общих чертах, пакеты, из которых состоит Budibase. + +- [packages/builder](https://github.com/Budibase/budibase/tree/HEAD/packages/builder) - содержит код клиентского приложения Svelte для Budibase builder. + +- [packages/client](https://github.com/Budibase/budibase/tree/HEAD/packages/client) - Модуль, который запускается в браузере и отвечает за чтение JSON-определений и создание веб-приложений из них. + +- [packages/server](https://github.com/Budibase/budibase/tree/HEAD/packages/server) - Сервер Budibase. Это приложение Koa отвечает за предоставление JS для строителей и приложений Budibase, а также предоставляет API для взаимодействия с базой данных и файловой системой. + +Для получения дополнительной информации см. [CONTRIBUTING.md](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md) + +

+ + +## 📝 Лицензия + +Budibase является проектом с открытым исходным кодом, лицензированным по [GPL v3](https://www.gnu.org/licenses/gpl-3.0.en.html). Клиентские библиотеки и компоненты лицензируются по [MPL](https://directory.fsf.org/wiki/License:MPL-2.0), так что приложения, которые вы создаете, могут использоваться под любой лицензией, как вам угодно. + +

+ +## ⭐ Старгейзеры во времени + +[![Stargazers во времени](https://starchart.cc/Budibase/budibase.svg)](https://starchart.cc/Budibase/budibase) + +Если у вас возникли проблемы между обновлениями билдера, пожалуйста, используйте следующее руководство [здесь](https://github.com/Budibase/budibase/blob/HEAD/.github/CONTRIBUTING.md#troubleshooting), чтобы очистить ваше окружение. + +

+ +## Участники ✨ + +Благодарим этих замечательных людей ([ключи эмодзи](https://allcontributors.org/docs/ru/emoji-key)): + + + + + + + + + + + + + + + + + + + + + + + +

Martin McKeaveney

💻 📖 ⚠️ 🚇

Michael Drury

📖 💻 ⚠️ 🚇

Andrew Kingston

📖 💻 ⚠️ 🎨

Michael Shanks

📖 💻 ⚠️

Kevin Åberg Kultalahti

📖 💻 ⚠️

Joe

📓

Nico Kleynhans

🎨

Keith Lee

🎨

Ben-Shabs

📓

Reece King

🎨

Gunjan Chhabra

📓

Stavros Liaskos

🎨

theshu8

📓

Kleebster

📓
+ + + + +

diff --git a/lerna.json b/lerna.json index 16dc73aa30..cba15492eb 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "2.25.0", + "version": "2.26.4", "npmClient": "yarn", "packages": [ "packages/*", diff --git a/packages/backend-core/package.json b/packages/backend-core/package.json index 030fec8728..ff35ccee22 100644 --- a/packages/backend-core/package.json +++ b/packages/backend-core/package.json @@ -73,7 +73,6 @@ "chance": "1.1.8", "ioredis-mock": "8.9.0", "jest": "29.7.0", - "jest-environment-node": "29.7.0", "jest-serial-runner": "1.2.1", "pino-pretty": "10.0.0", "pouchdb-adapter-memory": "7.2.2", diff --git a/packages/backend-core/src/cache/user.ts b/packages/backend-core/src/cache/user.ts index ecfa20f99e..d319c5dcb6 100644 --- a/packages/backend-core/src/cache/user.ts +++ b/packages/backend-core/src/cache/user.ts @@ -69,7 +69,7 @@ async function populateUsersFromDB( export async function getUser( userId: string, tenantId?: string, - populateUser?: any + populateUser?: (userId: string, tenantId: string) => Promise ) { if (!populateUser) { populateUser = populateFromDB @@ -83,7 +83,7 @@ export async function getUser( } const client = await redis.getUserClient() // try cache - let user = await client.get(userId) + let user: User = await client.get(userId) if (!user) { user = await populateUser(userId, tenantId) await client.store(userId, user, EXPIRY_SECONDS) diff --git a/packages/backend-core/src/context/types.ts b/packages/backend-core/src/context/types.ts index f297d3089f..6694320978 100644 --- a/packages/backend-core/src/context/types.ts +++ b/packages/backend-core/src/context/types.ts @@ -1,4 +1,6 @@ import { IdentityContext, Snippet, VM } from "@budibase/types" +import { OAuth2Client } from "google-auth-library" +import { GoogleSpreadsheet } from "google-spreadsheet" // keep this out of Budibase types, don't want to expose context info export type ContextMap = { @@ -12,4 +14,8 @@ export type ContextMap = { vm?: VM cleanup?: (() => void | Promise)[] snippets?: Snippet[] + googleSheets?: { + oauthClient: OAuth2Client + clients: Record + } } diff --git a/packages/backend-core/src/db/Replication.ts b/packages/backend-core/src/db/Replication.ts index 735c2fa86e..617269df10 100644 --- a/packages/backend-core/src/db/Replication.ts +++ b/packages/backend-core/src/db/Replication.ts @@ -1,14 +1,31 @@ import PouchDB from "pouchdb" import { getPouchDB, closePouchDB } from "./couch" -import { DocumentType } from "../constants" +import { DocumentType } from "@budibase/types" + +enum ReplicationDirection { + TO_PRODUCTION = "toProduction", + TO_DEV = "toDev", +} class Replication { source: PouchDB.Database target: PouchDB.Database + direction: ReplicationDirection | undefined constructor({ source, target }: { source: string; target: string }) { this.source = getPouchDB(source) this.target = getPouchDB(target) + if ( + source.startsWith(DocumentType.APP_DEV) && + target.startsWith(DocumentType.APP) + ) { + this.direction = ReplicationDirection.TO_PRODUCTION + } else if ( + source.startsWith(DocumentType.APP) && + target.startsWith(DocumentType.APP_DEV) + ) { + this.direction = ReplicationDirection.TO_DEV + } } async close() { @@ -40,12 +57,18 @@ class Replication { } const filter = opts.filter + const direction = this.direction + const toDev = direction === ReplicationDirection.TO_DEV delete opts.filter return { ...opts, filter: (doc: any, params: any) => { - if (doc._id && doc._id.startsWith(DocumentType.AUTOMATION_LOG)) { + // don't sync design documents + if (toDev && doc._id?.startsWith("_design")) { + return false + } + if (doc._id?.startsWith(DocumentType.AUTOMATION_LOG)) { return false } if (doc._id === DocumentType.APP_METADATA) { diff --git a/packages/backend-core/src/db/couch/DatabaseImpl.ts b/packages/backend-core/src/db/couch/DatabaseImpl.ts index ef351f7d4d..8194d1aabf 100644 --- a/packages/backend-core/src/db/couch/DatabaseImpl.ts +++ b/packages/backend-core/src/db/couch/DatabaseImpl.ts @@ -12,6 +12,7 @@ import { isDocument, RowResponse, RowValue, + SQLiteDefinition, SqlQueryBinding, } from "@budibase/types" import { getCouchInfo } from "./connections" @@ -21,6 +22,8 @@ import { ReadStream, WriteStream } from "fs" import { newid } from "../../docIds/newid" import { SQLITE_DESIGN_DOC_ID } from "../../constants" import { DDInstrumentedDatabase } from "../instrumentation" +import { checkSlashesInUrl } from "../../helpers" +import env from "../../environment" const DATABASE_NOT_FOUND = "Database does not exist." @@ -281,25 +284,61 @@ export class DatabaseImpl implements Database { }) } + async _sqlQuery( + url: string, + method: "POST" | "GET", + body?: Record + ): Promise { + url = checkSlashesInUrl(`${this.couchInfo.sqlUrl}/${url}`) + const args: { url: string; method: string; cookie: string; body?: any } = { + url, + method, + cookie: this.couchInfo.cookie, + } + if (body) { + args.body = body + } + return this.performCall(() => { + return async () => { + const response = await directCouchUrlCall(args) + const json = await response.json() + if (response.status > 300) { + throw json + } + return json as T + } + }) + } + async sql( sql: string, parameters?: SqlQueryBinding ): Promise { const dbName = this.name const url = `/${dbName}/${SQLITE_DESIGN_DOC_ID}` - const response = await directCouchUrlCall({ - url: `${this.couchInfo.sqlUrl}/${url}`, - method: "POST", - cookie: this.couchInfo.cookie, - body: { - query: sql, - args: parameters, - }, + return await this._sqlQuery(url, "POST", { + query: sql, + args: parameters, }) - if (response.status > 300) { - throw new Error(await response.text()) + } + + // checks design document is accurate (cleans up tables) + // this will check the design document and remove anything from + // disk which is not supposed to be there + async sqlDiskCleanup(): Promise { + const dbName = this.name + const url = `/${dbName}/_cleanup` + return await this._sqlQuery(url, "POST") + } + + // removes a document from sqlite + async sqlPurgeDocument(docIds: string[] | string): Promise { + if (!Array.isArray(docIds)) { + docIds = [docIds] } - return (await response.json()) as T[] + const dbName = this.name + const url = `/${dbName}/_purge` + return await this._sqlQuery(url, "POST", { docs: docIds }) } async query( @@ -314,6 +353,17 @@ export class DatabaseImpl implements Database { async destroy() { try { + if (env.SQS_SEARCH_ENABLE) { + // delete the design document, then run the cleanup operation + try { + const definition = await this.get( + SQLITE_DESIGN_DOC_ID + ) + await this.remove(SQLITE_DESIGN_DOC_ID, definition._rev) + } finally { + await this.sqlDiskCleanup() + } + } return await this.nano().db.destroy(this.name) } catch (err: any) { // didn't exist, don't worry diff --git a/packages/backend-core/src/db/couch/utils.ts b/packages/backend-core/src/db/couch/utils.ts index 005b02a896..270d953320 100644 --- a/packages/backend-core/src/db/couch/utils.ts +++ b/packages/backend-core/src/db/couch/utils.ts @@ -21,7 +21,7 @@ export async function directCouchUrlCall({ url: string cookie: string method: string - body?: any + body?: Record }) { const params: any = { method: method, diff --git a/packages/backend-core/src/db/instrumentation.ts b/packages/backend-core/src/db/instrumentation.ts index 32ba81ebd8..4e2b147ef3 100644 --- a/packages/backend-core/src/db/instrumentation.ts +++ b/packages/backend-core/src/db/instrumentation.ts @@ -56,12 +56,17 @@ export class DDInstrumentedDatabase implements Database { }) } + remove(idOrDoc: Document): Promise + remove(idOrDoc: string, rev?: string): Promise remove( - id: string | Document, - rev?: string | undefined + idOrDoc: string | Document, + rev?: string ): Promise { return tracer.trace("db.remove", span => { - span?.addTags({ db_name: this.name, doc_id: id }) + span?.addTags({ db_name: this.name, doc_id: idOrDoc }) + const isDocument = typeof idOrDoc === "object" + const id = isDocument ? idOrDoc._id! : idOrDoc + rev = isDocument ? idOrDoc._rev : rev return this.db.remove(id, rev) }) } @@ -160,4 +165,18 @@ export class DDInstrumentedDatabase implements Database { return this.db.sql(sql, parameters) }) } + + sqlPurgeDocument(docIds: string[] | string): Promise { + return tracer.trace("db.sqlPurgeDocument", span => { + span?.addTags({ db_name: this.name }) + return this.db.sqlPurgeDocument(docIds) + }) + } + + sqlDiskCleanup(): Promise { + return tracer.trace("db.sqlDiskCleanup", span => { + span?.addTags({ db_name: this.name }) + return this.db.sqlDiskCleanup() + }) + } } diff --git a/packages/backend-core/src/environment.ts b/packages/backend-core/src/environment.ts index 9ade81b9d7..20ff16739f 100644 --- a/packages/backend-core/src/environment.ts +++ b/packages/backend-core/src/environment.ts @@ -109,6 +109,7 @@ const environment = { API_ENCRYPTION_KEY: getAPIEncryptionKey(), COUCH_DB_URL: process.env.COUCH_DB_URL || "http://localhost:4005", COUCH_DB_SQL_URL: process.env.COUCH_DB_SQL_URL || "http://localhost:4006", + SQS_SEARCH_ENABLE: process.env.SQS_SEARCH_ENABLE, COUCH_DB_USERNAME: process.env.COUCH_DB_USER, COUCH_DB_PASSWORD: process.env.COUCH_DB_PASSWORD, GOOGLE_CLIENT_ID: process.env.GOOGLE_CLIENT_ID, diff --git a/packages/backend-core/src/middleware/authenticated.ts b/packages/backend-core/src/middleware/authenticated.ts index 69dba27c43..51cd4ec2af 100644 --- a/packages/backend-core/src/middleware/authenticated.ts +++ b/packages/backend-core/src/middleware/authenticated.ts @@ -13,7 +13,7 @@ import { getGlobalDB, doInTenant } from "../context" import { decrypt } from "../security/encryption" import * as identity from "../context/identity" import env from "../environment" -import { Ctx, EndpointMatcher, SessionCookie } from "@budibase/types" +import { Ctx, EndpointMatcher, SessionCookie, User } from "@budibase/types" import { InvalidAPIKeyError, ErrorCode } from "../errors" import tracer from "dd-trace" @@ -41,7 +41,10 @@ function finalise(ctx: any, opts: FinaliseOpts = {}) { ctx.version = opts.version } -async function checkApiKey(apiKey: string, populateUser?: Function) { +async function checkApiKey( + apiKey: string, + populateUser?: (userId: string, tenantId: string) => Promise +) { // check both the primary and the fallback internal api keys // this allows for rotation if (isValidInternalAPIKey(apiKey)) { @@ -128,6 +131,7 @@ export default function ( } else { user = await getUser(userId, session.tenantId) } + // @ts-ignore user.csrfToken = session.csrfToken if (session?.lastAccessedAt < timeMinusOneMinute()) { @@ -167,19 +171,25 @@ export default function ( authenticated = false } - if (user) { + const isUser = ( + user: any + ): user is User & { budibaseAccess?: string } => { + return user && user.email + } + + if (isUser(user)) { tracer.setUser({ - id: user?._id, - tenantId: user?.tenantId, - budibaseAccess: user?.budibaseAccess, - status: user?.status, + id: user._id!, + tenantId: user.tenantId, + budibaseAccess: user.budibaseAccess, + status: user.status, }) } // isAuthenticated is a function, so use a variable to be able to check authed state finalise(ctx, { authenticated, user, internal, version, publicEndpoint }) - if (user && user.email) { + if (isUser(user)) { return identity.doInUserContext(user, ctx, next) } else { return next() diff --git a/packages/backend-core/src/objectStore/utils.ts b/packages/backend-core/src/objectStore/utils.ts index 5b9c2e3646..30c2fefbf1 100644 --- a/packages/backend-core/src/objectStore/utils.ts +++ b/packages/backend-core/src/objectStore/utils.ts @@ -9,6 +9,9 @@ import { AutomationAttachmentContent, BucketedContent, } from "@budibase/types" +import stream from "stream" +import streamWeb from "node:stream/web" + /**************************************************** * NOTE: When adding a new bucket - name * * sure that S3 usages (like budibase-infra) * @@ -53,12 +56,10 @@ export const bucketTTLConfig = ( Rules: [lifecycleRule], } - const params = { + return { Bucket: bucketName, LifecycleConfiguration: lifecycleConfiguration, } - - return params } async function processUrlAttachment( @@ -69,9 +70,12 @@ async function processUrlAttachment( throw new Error(`Unexpected response ${response.statusText}`) } const fallbackFilename = path.basename(new URL(attachment.url).pathname) + if (!response.body) { + throw new Error("No response received for attachment") + } return { filename: attachment.filename || fallbackFilename, - content: response.body, + content: stream.Readable.fromWeb(response.body as streamWeb.ReadableStream), } } diff --git a/packages/backend-core/src/users/db.ts b/packages/backend-core/src/users/db.ts index f77c6385ba..37547573bd 100644 --- a/packages/backend-core/src/users/db.ts +++ b/packages/backend-core/src/users/db.ts @@ -492,7 +492,7 @@ export class UserDB { await platform.users.removeUser(dbUser) - await db.remove(userId, dbUser._rev) + await db.remove(userId, dbUser._rev!) const creatorsToDelete = (await isCreator(dbUser)) ? 1 : 0 await UserDB.quotas.removeUsers(1, creatorsToDelete) diff --git a/packages/bbui/src/Actions/position_dropdown.js b/packages/bbui/src/Actions/position_dropdown.js index 6c4fcab757..21635592d2 100644 --- a/packages/bbui/src/Actions/position_dropdown.js +++ b/packages/bbui/src/Actions/position_dropdown.js @@ -155,6 +155,8 @@ export default function positionDropdown(element, opts) { applyXStrategy(Strategies.StartToEnd) } else if (align === "left-outside") { applyXStrategy(Strategies.EndToStart) + } else if (align === "center") { + applyXStrategy(Strategies.MidPoint) } else { applyXStrategy(Strategies.StartToStart) } diff --git a/packages/bbui/src/Tabs/Tab.svelte b/packages/bbui/src/Tabs/Tab.svelte index f51ad96e05..627d7e525a 100644 --- a/packages/bbui/src/Tabs/Tab.svelte +++ b/packages/bbui/src/Tabs/Tab.svelte @@ -1,40 +1,28 @@ @@ -53,11 +56,12 @@
{#if icon} @@ -72,7 +76,8 @@ {/if} {title}
-{#if $selected.title === title} + +{#if isSelected} diff --git a/packages/builder/package.json b/packages/builder/package.json index f29ae3f7f2..a00936bdca 100644 --- a/packages/builder/package.json +++ b/packages/builder/package.json @@ -93,7 +93,6 @@ "identity-obj-proxy": "^3.0.0", "jest": "29.7.0", "jsdom": "^21.1.1", - "ncp": "^2.0.0", "svelte-jester": "^1.3.2", "vite": "^4.5.0", "vite-plugin-static-copy": "^0.17.0", diff --git a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte index 0cf0f6c740..879927343f 100644 --- a/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte +++ b/packages/builder/src/components/automation/SetupPanel/AutomationBlockSetup.svelte @@ -48,6 +48,7 @@ import { TriggerStepID, ActionStepID } from "constants/backend/automations" import { onMount } from "svelte" import { cloneDeep } from "lodash/fp" + import { FIELDS } from "constants/backend" export let block export let testData @@ -228,6 +229,10 @@ categoryName, bindingName ) => { + const field = Object.values(FIELDS).find( + field => field.type === value.type && field.subtype === value.subtype + ) + return { readableBinding: bindingName ? `${bindingName}.${name}` @@ -238,7 +243,7 @@ icon, category: categoryName, display: { - type: value.type, + type: field?.name || value.type, name, rank: isLoopBlock ? idx + 1 : idx - loopBlockCount, }, @@ -282,6 +287,7 @@ for (const key in table?.schema) { schema[key] = { type: table.schema[key].type, + subtype: table.schema[key].subtype, } } // remove the original binding @@ -368,6 +374,16 @@ return `${value.title || (key === "row" ? "Table" : key)} ${requiredSuffix}` } + function handleAttachmentParams(keyValueObj) { + let params = {} + if (keyValueObj?.length) { + for (let param of keyValueObj) { + params[param.url] = param.filename + } + } + return params + } + onMount(async () => { try { await environment.loadVariables() @@ -375,15 +391,6 @@ console.error(error) } }) - const handleAttachmentParams = keyValuObj => { - let params = {} - if (keyValuObj?.length) { - for (let param of keyValuObj) { - params[param.url] = param.filename - } - } - return params - }
diff --git a/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte b/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte index 9b4e5e36f6..1b52e35314 100644 --- a/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte +++ b/packages/builder/src/components/automation/SetupPanel/RowSelectorTypes.svelte @@ -25,21 +25,21 @@ return !!schema.constraints?.inclusion?.length } - const handleAttachmentParams = keyValuObj => { + function handleAttachmentParams(keyValueObj) { let params = {} if ( schema.type === FieldType.ATTACHMENT_SINGLE && - Object.keys(keyValuObj).length === 0 + Object.keys(keyValueObj).length === 0 ) { return [] } - if (!Array.isArray(keyValuObj)) { - keyValuObj = [keyValuObj] + if (!Array.isArray(keyValueObj) && keyValueObj) { + keyValueObj = [keyValueObj] } - if (keyValuObj.length) { - for (let param of keyValuObj) { + if (keyValueObj.length) { + for (let param of keyValueObj) { params[param.url] = param.filename } } diff --git a/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte b/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte index 1ec32cb3fd..4ff8ae994b 100644 --- a/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte +++ b/packages/builder/src/components/backend/DataTable/RowFieldControl.svelte @@ -14,9 +14,8 @@ import LinkedRowSelector from "components/common/LinkedRowSelector.svelte" import Editor from "../../integration/QueryEditor.svelte" - export let defaultValue export let meta - export let value = defaultValue || (meta.type === "boolean" ? false : "") + export let value export let readonly export let error diff --git a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte index 77229f3a17..26972ede16 100644 --- a/packages/builder/src/components/backend/DataTable/TableDataTable.svelte +++ b/packages/builder/src/components/backend/DataTable/TableDataTable.svelte @@ -1,5 +1,6 @@ - +{#if editableColumn} + +{/if} diff --git a/packages/builder/src/components/backend/Datasources/ConfigEditor/stores/validatedConfig.js b/packages/builder/src/components/backend/Datasources/ConfigEditor/stores/validatedConfig.js index 7b8b2c0975..edd39cc49f 100644 --- a/packages/builder/src/components/backend/Datasources/ConfigEditor/stores/validatedConfig.js +++ b/packages/builder/src/components/backend/Datasources/ConfigEditor/stores/validatedConfig.js @@ -86,8 +86,9 @@ export const createValidatedConfigStore = (integration, config) => { ([$configStore, $errorsStore, $selectedValidatorsStore]) => { const validatedConfig = [] + const allowedRestKeys = ["rejectUnauthorized", "downloadImages"] Object.entries(integration.datasource).forEach(([key, properties]) => { - if (integration.name === "REST" && key !== "rejectUnauthorized") { + if (integration.name === "REST" && !allowedRestKeys.includes(key)) { return } diff --git a/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte b/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte index b7fa243c07..318f459f46 100644 --- a/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte +++ b/packages/builder/src/components/backend/TableNavigator/ExistingTableDataImport.svelte @@ -59,13 +59,17 @@ value: FieldType.ATTACHMENTS, }, { - label: "User", + label: "Users", value: `${FieldType.BB_REFERENCE}${BBReferenceFieldSubType.USER}`, }, { label: "Users", value: `${FieldType.BB_REFERENCE}${BBReferenceFieldSubType.USERS}`, }, + { + label: "User", + value: `${FieldType.BB_REFERENCE_SINGLE}${BBReferenceFieldSubType.USER}`, + }, ] $: { diff --git a/packages/builder/src/components/common/Dropzone.spec.js b/packages/builder/src/components/common/Dropzone.spec.js new file mode 100644 index 0000000000..8bf202223b --- /dev/null +++ b/packages/builder/src/components/common/Dropzone.spec.js @@ -0,0 +1,50 @@ +import { it, expect, describe, vi } from "vitest" +import Dropzone from "./Dropzone.svelte" +import { render, fireEvent } from "@testing-library/svelte" +import { notifications } from "@budibase/bbui" +import { admin } from "stores/portal" + +vi.spyOn(notifications, "error").mockImplementation(() => {}) + +describe("Dropzone", () => { + let instance = null + + afterEach(() => { + vi.restoreAllMocks() + }) + + it("that the Dropzone is rendered", () => { + instance = render(Dropzone, {}) + expect(instance).toBeDefined() + }) + + it("Ensure the correct error message is shown when uploading the file in cloud", async () => { + admin.subscribe = vi.fn().mockImplementation(callback => { + callback({ cloud: true }) + return () => {} + }) + instance = render(Dropzone, { props: { fileSizeLimit: 1000000 } }) // 1MB + const fileInput = instance.getByLabelText("Select a file to upload") + const file = new File(["hello".repeat(2000000)], "hello.png", { + type: "image/png", + }) + await fireEvent.change(fileInput, { target: { files: [file] } }) + expect(notifications.error).toHaveBeenCalledWith( + "Files cannot exceed 1MB. Please try again with smaller files." + ) + }) + + it("Ensure the file size error message is not shown when running on self host", async () => { + admin.subscribe = vi.fn().mockImplementation(callback => { + callback({ cloud: false }) + return () => {} + }) + instance = render(Dropzone, { props: { fileSizeLimit: 1000000 } }) // 1MB + const fileInput = instance.getByLabelText("Select a file to upload") + const file = new File(["hello".repeat(2000000)], "hello.png", { + type: "image/png", + }) + await fireEvent.change(fileInput, { target: { files: [file] } }) + expect(notifications.error).not.toHaveBeenCalled() + }) +}) diff --git a/packages/builder/src/components/common/Dropzone.svelte b/packages/builder/src/components/common/Dropzone.svelte index a864e1d028..37569df0d5 100644 --- a/packages/builder/src/components/common/Dropzone.svelte +++ b/packages/builder/src/components/common/Dropzone.svelte @@ -1,9 +1,11 @@ @@ -14,12 +15,16 @@
-
- -
-
+ {#if !disabled} +
+ +
+
+ +
+ {:else} -
+ {/if}
diff --git a/packages/builder/src/components/common/FontAwesomeIcon.svelte b/packages/builder/src/components/common/FontAwesomeIcon.svelte index 16c065cfaa..0e10dfc86c 100644 --- a/packages/builder/src/components/common/FontAwesomeIcon.svelte +++ b/packages/builder/src/components/common/FontAwesomeIcon.svelte @@ -23,6 +23,7 @@ faQuestionCircle, faCircleCheck, faGear, + faRectangleList, } from "@fortawesome/free-solid-svg-icons" import { faGithub, faDiscord } from "@fortawesome/free-brands-svg-icons" @@ -37,6 +38,7 @@ faFileArrowUp, faChevronLeft, faCircleInfo, + faRectangleList, // -- Required for easyMDE use in the builder. faBold, diff --git a/packages/builder/src/components/common/HelpMenu.svelte b/packages/builder/src/components/common/HelpMenu.svelte index 63156676d2..0ce5ab9c8e 100644 --- a/packages/builder/src/components/common/HelpMenu.svelte +++ b/packages/builder/src/components/common/HelpMenu.svelte @@ -4,6 +4,7 @@ import { isEnabled, TENANT_FEATURE_FLAGS } from "helpers/featureFlags" import { licensing } from "stores/portal" import { isPremiumOrAbove } from "helpers/planTitle" + import { ChangelogURL } from "constants" $: premiumOrAboveLicense = isPremiumOrAbove($licensing?.license?.plan?.type) @@ -30,6 +31,13 @@ Help docs
+ +
+ +
+ Changelog +
+
+ import { Button, Label, Icon, Input, notifications } from "@budibase/bbui" + import { AppStatus } from "constants" + import { appStore, initialise } from "stores/builder" + import { appsStore } from "stores/portal" + import { API } from "api" + import { writable } from "svelte/store" + import { createValidationStore } from "helpers/validation/yup" + import * as appValidation from "helpers/validation/yup/app" + import EditableIcon from "components/common/EditableIcon.svelte" + import { isEqual } from "lodash" + import { createEventDispatcher } from "svelte" + + export let alignActions = "left" + + const values = writable({}) + const validation = createValidationStore() + const dispatch = createEventDispatcher() + + let updating = false + let edited = false + let initialised = false + + $: filteredApps = $appsStore.apps.filter(app => app.devId == $appStore.appId) + $: app = filteredApps.length ? filteredApps[0] : {} + $: appDeployed = app?.status === AppStatus.DEPLOYED + + $: appName = $appStore.name + $: appURL = $appStore.url + $: appIconName = $appStore.icon?.name + $: appIconColor = $appStore.icon?.color + + $: appMeta = { + name: appName, + url: appURL, + iconName: appIconName, + iconColor: appIconColor, + } + + const initForm = appMeta => { + edited = false + values.set({ + ...appMeta, + }) + + if (!initialised) { + setupValidation() + initialised = true + } + } + + const validate = (vals, appMeta) => { + const { url } = vals || {} + validation.check({ + ...vals, + url: url?.[0] === "/" ? url.substring(1, url.length) : url, + }) + edited = !isEqual(vals, appMeta) + } + + // On app/apps update, reset the state. + $: initForm(appMeta) + $: validate($values, appMeta) + + const resolveAppUrl = (template, name) => { + let parsedName + const resolvedName = resolveAppName(null, name) + parsedName = resolvedName ? resolvedName.toLowerCase() : "" + const parsedUrl = parsedName ? parsedName.replace(/\s+/g, "-") : "" + return encodeURI(parsedUrl) + } + + const nameToUrl = appName => { + let resolvedUrl = resolveAppUrl(null, appName) + tidyUrl(resolvedUrl) + } + + const resolveAppName = (template, name) => { + if (template && !name) { + return template.name + } + return name ? name.trim() : null + } + + const tidyUrl = url => { + if (url && !url.startsWith("/")) { + url = `/${url}` + } + $values.url = url === "" ? null : url + } + + const updateIcon = e => { + const { name, color } = e.detail + $values.iconColor = color + $values.iconName = name + } + + const setupValidation = async () => { + appValidation.name(validation, { + apps: $appsStore.apps, + currentApp: app, + }) + appValidation.url(validation, { + apps: $appsStore.apps, + currentApp: app, + }) + } + + async function updateApp() { + try { + await appsStore.save($appStore.appId, { + name: $values.name?.trim(), + url: $values.url?.trim(), + icon: { + name: $values.iconName, + color: $values.iconColor, + }, + }) + + await initialiseApp() + notifications.success("App update successful") + } catch (error) { + console.error(error) + notifications.error("Error updating app") + } + } + + const initialiseApp = async () => { + const applicationPkg = await API.fetchAppPackage($appStore.appId) + await initialise(applicationPkg) + } + + +
+
+
+ + ($validation.touched.name = true)} + on:change={nameToUrl($values.name)} + disabled={appDeployed} + /> +
+
+ + ($validation.touched.url = true)} + on:change={tidyUrl($values.url)} + placeholder={$values.url + ? $values.url + : `/${resolveAppUrl(null, $values.name)}`} + disabled={appDeployed} + /> +
+
+ + +
+
+ {#if !appDeployed} + + {:else} +
+ Unpublish your app to edit name and URL +
+ {/if} +
+
+
+ + diff --git a/packages/builder/src/components/common/UpdateAppTopNav.svelte b/packages/builder/src/components/common/UpdateAppTopNav.svelte new file mode 100644 index 0000000000..f4a76c4576 --- /dev/null +++ b/packages/builder/src/components/common/UpdateAppTopNav.svelte @@ -0,0 +1,68 @@ + + +
+ + +
{ + formPopover.show() + }} + > + + + + +
+
+ + { + formPopoverOpen = false + }} + on:open={() => { + formPopoverOpen = true + }} +> + +
+ { + formPopover.hide() + }} + /> +
+
+
+ + diff --git a/packages/builder/src/components/deploy/AppActions.svelte b/packages/builder/src/components/deploy/AppActions.svelte index 105d1ed958..bb950983a6 100644 --- a/packages/builder/src/components/deploy/AppActions.svelte +++ b/packages/builder/src/components/deploy/AppActions.svelte @@ -8,13 +8,11 @@ ActionButton, Icon, Link, - Modal, StatusLight, AbsTooltip, } from "@budibase/bbui" import RevertModal from "components/deploy/RevertModal.svelte" import VersionModal from "components/deploy/VersionModal.svelte" - import UpdateAppModal from "components/start/UpdateAppModal.svelte" import { processStringSync } from "@budibase/string-templates" import ConfirmDialog from "components/common/ConfirmDialog.svelte" import analytics, { Events, EventSource } from "analytics" @@ -26,7 +24,6 @@ isOnlyUser, appStore, deploymentStore, - initialise, sortedScreens, } from "stores/builder" import TourWrap from "components/portal/onboarding/TourWrap.svelte" @@ -37,7 +34,6 @@ export let loaded let unpublishModal - let updateAppModal let revertModal let versionModal let appActionPopover @@ -61,11 +57,6 @@ $: canPublish = !publishing && loaded && $sortedScreens.length > 0 $: lastDeployed = getLastDeployedString($deploymentStore, lastOpened) - const initialiseApp = async () => { - const applicationPkg = await API.fetchAppPackage($appStore.devId) - await initialise(applicationPkg) - } - const getLastDeployedString = deployments => { return deployments?.length ? processStringSync("Published {{ duration time 'millisecond' }} ago", { @@ -247,16 +238,12 @@ appActionPopover.hide() if (isPublished) { viewApp() - } else { - updateAppModal.show() } }} > {$appStore.url} {#if isPublished} - {:else} - {/if} @@ -330,20 +317,6 @@ Are you sure you want to unpublish the app {selectedApp?.name}? - - { - await initialiseApp() - }} - /> - - diff --git a/packages/builder/src/components/deploy/VersionModal.svelte b/packages/builder/src/components/deploy/VersionModal.svelte index 316a981325..653aa09fa6 100644 --- a/packages/builder/src/components/deploy/VersionModal.svelte +++ b/packages/builder/src/components/deploy/VersionModal.svelte @@ -7,10 +7,12 @@ Body, Button, StatusLight, + Link, } from "@budibase/bbui" import { appStore, initialise } from "stores/builder" import { API } from "api" import RevertModalVersionSelect from "./RevertModalVersionSelect.svelte" + import { ChangelogURL } from "constants" export function show() { updateModal.show() @@ -106,6 +108,10 @@ latest version available. {/if} + + Find the changelog for the latest release + here + {#if revertAvailable} You can revert this app to version diff --git a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ExportData.svelte b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ExportData.svelte index 096341783d..a857bc7ede 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ExportData.svelte +++ b/packages/builder/src/components/design/settings/controls/ButtonActionEditor/actions/ExportData.svelte @@ -49,17 +49,20 @@ }, ] - $: tables = findAllMatchingComponents($selectedScreen?.props, component => - component._component.endsWith("table") - ) - $: tableBlocks = findAllMatchingComponents( + $: components = findAllMatchingComponents( $selectedScreen?.props, - component => component._component.endsWith("tableblock") + component => { + const type = component._component + return ( + type.endsWith("/table") || + type.endsWith("/tableblock") || + type.endsWith("/gridblock") + ) + } ) - $: components = tables.concat(tableBlocks) $: componentOptions = components.map(table => ({ label: table._instanceName, - value: table._component.includes("tableblock") + value: table._component.endsWith("/tableblock") ? `${table._id}-table` : table._id, })) @@ -69,6 +72,7 @@ $: selectedTable = components.find( component => component._id === selectedTableId ) + $: parameters.rows = `{{ literal [${parameters.tableComponentId}].[selectedRows] }}` onMount(() => { if (!parameters.type) { diff --git a/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte index 9a88875140..2387fda683 100644 --- a/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte +++ b/packages/builder/src/components/design/settings/controls/ButtonConfiguration/ButtonSetting.svelte @@ -1,5 +1,5 @@ - { - if (!open) { - popover.show() - open = true - } - }} -/> + { - drawers = [] - $draggable.actions.select(componentInstance._id) - }} - on:close={() => { - open = false - if ($draggable.selected === componentInstance._id) { - $draggable.actions.select() - } - }} + open={isOpen} + on:close={close} {anchor} align="left-outside" showPopover={drawers.length === 0} clickOutsideOverride={drawers.length > 0} maxHeight={600} offset={18} - handlePostionUpdate={customPositionHandler} > diff --git a/packages/builder/src/components/design/settings/controls/EditComponentPopover/index.js b/packages/builder/src/components/design/settings/controls/EditComponentPopover/index.js deleted file mode 100644 index 2dc3f60185..0000000000 --- a/packages/builder/src/components/design/settings/controls/EditComponentPopover/index.js +++ /dev/null @@ -1,18 +0,0 @@ -export const customPositionHandler = (anchorBounds, eleBounds, cfg) => { - let { left, top, offset } = cfg - let percentageOffset = 30 - // left-outside - left = anchorBounds.left - eleBounds.width - (offset || 5) - - // shift up from the anchor, if space allows - let offsetPos = Math.floor(eleBounds.height / 100) * percentageOffset - let defaultTop = anchorBounds.top - offsetPos - - if (window.innerHeight - defaultTop < eleBounds.height) { - top = window.innerHeight - eleBounds.height - 5 - } else { - top = anchorBounds.top - offsetPos - } - - return { ...cfg, left, top } -} diff --git a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte index 771bcf20e0..27590a9858 100644 --- a/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte +++ b/packages/builder/src/components/design/settings/controls/FieldConfiguration/FieldSetting.svelte @@ -1,5 +1,5 @@ - - - ($validation.touched.name = true)} - on:change={nameToUrl($values.name)} - label="Name" - /> - - - - - ($validation.touched.url = true)} - on:change={tidyUrl($values.url)} - label="URL" - placeholder={$values.url - ? $values.url - : `/${resolveAppUrl(null, $values.name)}`} - /> - diff --git a/packages/builder/src/constants/backend/index.js b/packages/builder/src/constants/backend/index.js index be8bd75d93..6ac37e60be 100644 --- a/packages/builder/src/constants/backend/index.js +++ b/packages/builder/src/constants/backend/index.js @@ -33,7 +33,7 @@ export const FIELDS = { }, }, BARCODEQR: { - name: "Barcode/QR", + name: "Barcode / QR", type: FieldType.BARCODEQR, icon: TypeIconMap[FieldType.BARCODEQR], constraints: { @@ -43,7 +43,7 @@ export const FIELDS = { }, }, LONGFORM: { - name: "Long Form Text", + name: "Long form text", type: FieldType.LONGFORM, icon: TypeIconMap[FieldType.LONGFORM], constraints: { @@ -53,7 +53,7 @@ export const FIELDS = { }, }, OPTIONS: { - name: "Options", + name: "Single select", type: FieldType.OPTIONS, icon: TypeIconMap[FieldType.OPTIONS], constraints: { @@ -63,7 +63,7 @@ export const FIELDS = { }, }, ARRAY: { - name: "Multi-select", + name: "Multi select", type: FieldType.ARRAY, icon: TypeIconMap[FieldType.ARRAY], constraints: { @@ -83,7 +83,7 @@ export const FIELDS = { }, }, BIGINT: { - name: "BigInt", + name: "Big integer", type: FieldType.BIGINT, icon: TypeIconMap[FieldType.BIGINT], }, @@ -97,7 +97,7 @@ export const FIELDS = { }, }, DATETIME: { - name: "Date/Time", + name: "Date / time", type: FieldType.DATETIME, icon: TypeIconMap[FieldType.DATETIME], constraints: { @@ -111,7 +111,7 @@ export const FIELDS = { }, }, ATTACHMENT_SINGLE: { - name: "Attachment", + name: "Single attachment", type: FieldType.ATTACHMENT_SINGLE, icon: TypeIconMap[FieldType.ATTACHMENT_SINGLE], constraints: { @@ -119,7 +119,7 @@ export const FIELDS = { }, }, ATTACHMENTS: { - name: "Attachment List", + name: "Multi attachment", type: FieldType.ATTACHMENTS, icon: TypeIconMap[FieldType.ATTACHMENTS], constraints: { @@ -137,7 +137,7 @@ export const FIELDS = { }, }, AUTO: { - name: "Auto Column", + name: "Auto column", type: FieldType.AUTO, icon: TypeIconMap[FieldType.AUTO], constraints: {}, @@ -158,16 +158,18 @@ export const FIELDS = { }, }, USER: { - name: "User", - type: FieldType.BB_REFERENCE, + name: "Single user", + type: FieldType.BB_REFERENCE_SINGLE, subtype: BBReferenceFieldSubType.USER, - icon: TypeIconMap[FieldType.USER], + icon: TypeIconMap[FieldType.BB_REFERENCE_SINGLE][ + BBReferenceFieldSubType.USER + ], }, USERS: { - name: "Users", + name: "Multi user", type: FieldType.BB_REFERENCE, - subtype: BBReferenceFieldSubType.USERS, - icon: TypeIconMap[FieldType.USERS], + subtype: BBReferenceFieldSubType.USER, + icon: TypeIconMap[FieldType.BB_REFERENCE][BBReferenceFieldSubType.USER], constraints: { type: "array", }, diff --git a/packages/builder/src/constants/index.js b/packages/builder/src/constants/index.js index f556ee4b05..44c71f2e3b 100644 --- a/packages/builder/src/constants/index.js +++ b/packages/builder/src/constants/index.js @@ -70,3 +70,5 @@ export const PlanModel = { PER_USER: "perUser", DAY_PASS: "dayPass", } + +export const ChangelogURL = "https://docs.budibase.com/changelog" diff --git a/packages/builder/src/dataBinding.js b/packages/builder/src/dataBinding.js index aaccbd0e6b..4e48c237ca 100644 --- a/packages/builder/src/dataBinding.js +++ b/packages/builder/src/dataBinding.js @@ -29,6 +29,7 @@ import { JSONUtils, Constants } from "@budibase/frontend-core" import ActionDefinitions from "components/design/settings/controls/ButtonActionEditor/manifest.json" import { environment, licensing } from "stores/portal" import { convertOldFieldFormat } from "components/design/settings/controls/FieldConfiguration/utils" +import { FIELDS } from "constants/backend" const { ContextScopes } = Constants @@ -491,7 +492,7 @@ const generateComponentContextBindings = (asset, componentContext) => { icon: bindingCategory.icon, display: { name: `${fieldSchema.name || key}`, - type: fieldSchema.type, + type: fieldSchema.display?.type || fieldSchema.type, }, }) }) @@ -829,7 +830,7 @@ export const getActionBindings = (actions, actionId) => { * @return {{schema: Object, table: Object}} */ export const getSchemaForDatasourcePlus = (resourceId, options) => { - const isViewV2 = resourceId?.includes("view_") + const isViewV2 = resourceId?.startsWith("view_") const datasource = isViewV2 ? { type: "viewV2", @@ -1019,15 +1020,23 @@ export const getSchemaForDatasource = (asset, datasource, options) => { // are objects let fixedSchema = {} Object.entries(schema || {}).forEach(([fieldName, fieldSchema]) => { + const field = Object.values(FIELDS).find( + field => + field.type === fieldSchema.type && + field.subtype === fieldSchema.subtype + ) + if (typeof fieldSchema === "string") { fixedSchema[fieldName] = { type: fieldSchema, name: fieldName, + display: { type: fieldSchema }, } } else { fixedSchema[fieldName] = { ...fieldSchema, name: fieldName, + display: { type: field?.name || fieldSchema.type }, } } }) diff --git a/packages/builder/src/helpers/validation/yup/app.js b/packages/builder/src/helpers/validation/yup/app.js index 1947844f63..3a00b7a49f 100644 --- a/packages/builder/src/helpers/validation/yup/app.js +++ b/packages/builder/src/helpers/validation/yup/app.js @@ -19,11 +19,10 @@ export const name = (validation, { apps, currentApp } = { apps: [] }) => { // exit early, above validator will fail return true } - if (currentApp) { - // filter out the current app if present - apps = apps.filter(app => app.appId !== currentApp.appId) - } return !apps + .filter(app => { + return app.appId !== currentApp?.appId + }) .map(app => app.name) .some(appName => appName.toLowerCase() === value.toLowerCase()) } diff --git a/packages/builder/src/pages/builder/app/[application]/_layout.svelte b/packages/builder/src/pages/builder/app/[application]/_layout.svelte index 60c45fd2e4..6094c93a26 100644 --- a/packages/builder/src/pages/builder/app/[application]/_layout.svelte +++ b/packages/builder/src/pages/builder/app/[application]/_layout.svelte @@ -33,6 +33,7 @@ import { TOUR_KEYS } from "components/portal/onboarding/tours.js" import PreviewOverlay from "./_components/PreviewOverlay.svelte" import EnterpriseBasicTrialModal from "components/portal/onboarding/EnterpriseBasicTrialModal.svelte" + import UpdateAppTopNav from "components/common/UpdateAppTopNav.svelte" export let application @@ -158,7 +159,11 @@
- {$appStore.name} +
+ + {$appStore.name} + +
@@ -247,7 +252,6 @@ font-weight: 600; overflow: hidden; text-overflow: ellipsis; - padding: 0px var(--spacing-m); } .topleftnav { diff --git a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json index 03e22acd4d..0f85d2e3e3 100644 --- a/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json +++ b/packages/builder/src/pages/builder/app/[application]/design/[screenId]/[componentId]/new/_components/componentStructure.json @@ -71,6 +71,7 @@ "multifieldselect", "s3upload", "codescanner", + "bbreferencesinglefield", "bbreferencefield" ] }, diff --git a/packages/builder/src/pages/builder/app/[application]/settings/name-and-url.svelte b/packages/builder/src/pages/builder/app/[application]/settings/name-and-url.svelte index be580552c7..e91b8ac3a8 100644 --- a/packages/builder/src/pages/builder/app/[application]/settings/name-and-url.svelte +++ b/packages/builder/src/pages/builder/app/[application]/settings/name-and-url.svelte @@ -1,30 +1,6 @@ @@ -33,61 +9,5 @@ Edit your app's name and URL - - - - {$appStore?.name} - - - - -
- -
-
- - - - {$appStore.url} - - -
- -
+ - - - { - await initialiseApp() - }} - /> - - - diff --git a/packages/builder/src/pages/builder/portal/_layout.svelte b/packages/builder/src/pages/builder/portal/_layout.svelte index a62233fad5..73152a1cd5 100644 --- a/packages/builder/src/pages/builder/portal/_layout.svelte +++ b/packages/builder/src/pages/builder/portal/_layout.svelte @@ -1,7 +1,14 @@
- - - onRowClick?.({ row: e.detail })} - /> - - + onRowClick?.({ row: e.detail })} + />
+ + diff --git a/packages/client/src/components/app/blocks/FormBlockComponent.svelte b/packages/client/src/components/app/blocks/FormBlockComponent.svelte index 968ed36b8b..31610f79ac 100644 --- a/packages/client/src/components/app/blocks/FormBlockComponent.svelte +++ b/packages/client/src/components/app/blocks/FormBlockComponent.svelte @@ -21,6 +21,7 @@ [FieldType.JSON]: "jsonfield", [FieldType.BARCODEQR]: "codescanner", [FieldType.BB_REFERENCE]: "bbreferencefield", + [FieldType.BB_REFERENCE_SINGLE]: "bbreferencesinglefield", } const getFieldSchema = field => { diff --git a/packages/client/src/components/app/forms/AttachmentField.svelte b/packages/client/src/components/app/forms/AttachmentField.svelte index 3489fd809c..27286a8666 100644 --- a/packages/client/src/components/app/forms/AttachmentField.svelte +++ b/packages/client/src/components/app/forms/AttachmentField.svelte @@ -25,7 +25,7 @@ let fieldState let fieldApi - const { API, notificationStore } = getContext("sdk") + const { API, notificationStore, environmentStore } = getContext("sdk") const formContext = getContext("form") const BYTES_IN_MB = 1000000 @@ -87,7 +87,7 @@ error={fieldState.error} on:change={handleChange} {processFiles} - {handleFileTooLarge} + handleFileTooLarge={$environmentStore.cloud ? handleFileTooLarge : null} {handleTooManyFiles} {maximum} {extensions} diff --git a/packages/client/src/components/app/forms/BBReferenceField.svelte b/packages/client/src/components/app/forms/BBReferenceField.svelte index 5f00c503c2..c266268275 100644 --- a/packages/client/src/components/app/forms/BBReferenceField.svelte +++ b/packages/client/src/components/app/forms/BBReferenceField.svelte @@ -1,8 +1,10 @@ + + diff --git a/packages/client/src/components/app/forms/RelationshipField.svelte b/packages/client/src/components/app/forms/RelationshipField.svelte index 1fbd0df522..a6d0564f7f 100644 --- a/packages/client/src/components/app/forms/RelationshipField.svelte +++ b/packages/client/src/components/app/forms/RelationshipField.svelte @@ -1,9 +1,9 @@ { const componentId = Object.keys(selection).find( componentId => componentId === tableComponentId ) - return selection[componentId] || {} + return selection[componentId] } return { diff --git a/packages/client/src/utils/buttonActions.js b/packages/client/src/utils/buttonActions.js index 4de1012b01..0c3866768e 100644 --- a/packages/client/src/utils/buttonActions.js +++ b/packages/client/src/utils/buttonActions.js @@ -333,31 +333,59 @@ const s3UploadHandler = async action => { } } +/** + * For new configs, "rows" is defined and enriched to be the array of rows to + * export. For old configs it will be undefined and we need to use the legacy + * row selection store in combination with the tableComponentId parameter. + */ const exportDataHandler = async action => { - let selection = rowSelectionStore.actions.getSelection( - action.parameters.tableComponentId - ) - if (selection.selectedRows && selection.selectedRows.length > 0) { + let { tableComponentId, rows, type, columns, delimiter, customHeaders } = + action.parameters + let tableId + + // Handle legacy configs using the row selection store + if (!rows?.length) { + const selection = rowSelectionStore.actions.getSelection(tableComponentId) + if (selection?.selectedRows?.length) { + rows = selection.selectedRows + tableId = selection.tableId + } + } + + // Get table ID from first row if needed + if (!tableId) { + tableId = rows?.[0]?.tableId + } + + // Handle no rows selected + if (!rows?.length) { + notificationStore.actions.error("Please select at least one row") + } + // Handle case where we're not using a DS+ + else if (!tableId) { + notificationStore.actions.error( + "You can only export data from table datasources" + ) + } + // Happy path when we have both rows and table ID + else { try { + // Flatten rows if required + if (typeof rows[0] !== "string") { + rows = rows.map(row => row._id) + } const data = await API.exportRows({ - tableId: selection.tableId, - rows: selection.selectedRows, - format: action.parameters.type, - columns: action.parameters.columns?.map( - column => column.name || column - ), - delimiter: action.parameters.delimiter, - customHeaders: action.parameters.customHeaders, + tableId, + rows, + format: type, + columns: columns?.map(column => column.name || column), + delimiter, + customHeaders, }) - download( - new Blob([data], { type: "text/plain" }), - `${selection.tableId}.${action.parameters.type}` - ) + download(new Blob([data], { type: "text/plain" }), `${tableId}.${type}`) } catch (error) { notificationStore.actions.error("There was an error exporting the data") } - } else { - notificationStore.actions.error("Please select at least one row") } } diff --git a/packages/frontend-core/src/components/FilterBuilder.svelte b/packages/frontend-core/src/components/FilterBuilder.svelte index 2e61238cc8..fb7aa98405 100644 --- a/packages/frontend-core/src/components/FilterBuilder.svelte +++ b/packages/frontend-core/src/components/FilterBuilder.svelte @@ -125,6 +125,7 @@ filter.type = fieldSchema?.type filter.subtype = fieldSchema?.subtype filter.formulaType = fieldSchema?.formulaType + filter.constraints = fieldSchema?.constraints // Update external type based on field filter.externalType = getSchema(filter)?.externalType @@ -281,7 +282,7 @@ timeOnly={getSchema(filter)?.timeOnly} bind:value={filter.value} /> - {:else if filter.type === FieldType.BB_REFERENCE} + {:else if [FieldType.BB_REFERENCE, FieldType.BB_REFERENCE_SINGLE].includes(filter.type)} onChange(e.detail)} maximum={maximum || schema.constraints?.length?.maximum} {processFiles} - {handleFileTooLarge} + handleFileTooLarge={$props.isCloud ? handleFileTooLarge : null} />
diff --git a/packages/frontend-core/src/components/grid/cells/BBReferenceCell.svelte b/packages/frontend-core/src/components/grid/cells/BBReferenceCell.svelte index 1e409fb200..5d98ba903b 100644 --- a/packages/frontend-core/src/components/grid/cells/BBReferenceCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/BBReferenceCell.svelte @@ -1,19 +1,27 @@ + + diff --git a/packages/frontend-core/src/components/grid/cells/GutterCell.svelte b/packages/frontend-core/src/components/grid/cells/GutterCell.svelte index 377405786d..60b41a2b87 100644 --- a/packages/frontend-core/src/components/grid/cells/GutterCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/GutterCell.svelte @@ -16,6 +16,8 @@ const { config, dispatch, selectedRows } = getContext("grid") const svelteDispatch = createEventDispatcher() + $: selectionEnabled = $config.canSelectRows || $config.canDeleteRows + const select = e => { e.stopPropagation() svelteDispatch("select") @@ -52,7 +54,7 @@
@@ -60,7 +62,7 @@ {#if !disableNumber}
{row.__idx + 1} @@ -117,19 +119,11 @@ .expand { margin-right: 4px; } - .expand { + .expand:not(.visible), + .expand:not(.visible) :global(*) { opacity: 0; + pointer-events: none !important; } - .expand :global(.spectrum-Icon) { - pointer-events: none; - } - .expand.visible { - opacity: 1; - } - .expand.visible :global(.spectrum-Icon) { - pointer-events: all; - } - .delete:hover { cursor: pointer; } diff --git a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte index bb38c5094f..de527246ca 100644 --- a/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/HeaderCell.svelte @@ -23,7 +23,6 @@ subscribe, config, ui, - columns, definition, datasource, schema, @@ -158,17 +157,13 @@ } const makeDisplayColumn = () => { - columns.actions.changePrimaryDisplay(column.name) + datasource.actions.changePrimaryDisplay(column.name) open = false } const hideColumn = () => { - columns.update(state => { - const index = state.findIndex(col => col.name === column.name) - state[index].visible = false - return state.slice() - }) - columns.actions.saveChanges() + datasource.actions.addSchemaMutation(column.name, { visible: false }) + datasource.actions.saveSchemaMutations() open = false } @@ -386,7 +381,7 @@ > Hide column - {#if $config.canEditColumns && column.schema.type === "link" && column.schema.tableId === TableNames.USERS} + {#if $config.canEditColumns && column.schema.type === "link" && column.schema.tableId === TableNames.USERS && !column.schema.autocolumn} Migrate to user column diff --git a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte index a01baa5c8e..f5a8351604 100644 --- a/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte +++ b/packages/frontend-core/src/components/grid/cells/RelationshipCell.svelte @@ -17,6 +17,7 @@ export let contentLines = 1 export let searchFunction = API.searchTable export let primaryDisplay + export let hideCounter = false const color = getColor(0) @@ -263,7 +264,7 @@
{/if}
- {#if value?.length} + {#if !hideCounter && value?.length}
{value?.length || 0}
diff --git a/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte b/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte index 4e19e64297..5b74e01958 100644 --- a/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte +++ b/packages/frontend-core/src/components/grid/controls/HideColumnsButton.svelte @@ -3,7 +3,7 @@ import { ActionButton, Popover, Toggle, Icon } from "@budibase/bbui" import { getColumnIcon } from "../lib/utils" - const { columns, stickyColumn, dispatch } = getContext("grid") + const { columns, datasource, stickyColumn, dispatch } = getContext("grid") let open = false let anchor @@ -11,36 +11,20 @@ $: anyHidden = $columns.some(col => !col.visible) $: text = getText($columns) - const toggleVisibility = async (column, visible) => { - columns.update(state => { - const index = state.findIndex(col => col.name === column.name) - state[index].visible = visible - return state.slice() - }) - await columns.actions.saveChanges() + const toggleColumn = async (column, visible) => { + datasource.actions.addSchemaMutation(column.name, { visible }) + await datasource.actions.saveSchemaMutations() dispatch(visible ? "show-column" : "hide-column") } - const showAll = async () => { - columns.update(state => { - return state.map(col => ({ - ...col, - visible: true, - })) + const toggleAll = async visible => { + let mutations = {} + $columns.forEach(column => { + mutations[column.name] = { visible } }) - await columns.actions.saveChanges() - dispatch("show-column") - } - - const hideAll = async () => { - columns.update(state => { - return state.map(col => ({ - ...col, - visible: false, - })) - }) - await columns.actions.saveChanges() - dispatch("hide-column") + datasource.actions.addSchemaMutations(mutations) + await datasource.actions.saveSchemaMutations() + dispatch(visible ? "show-column" : "hide-column") } const getText = columns => { @@ -80,14 +64,14 @@ toggleVisibility(column, e.detail)} + on:change={e => toggleColumn(column, e.detail)} disabled={column.primaryDisplay} /> {/each}
- Show all - Hide all + toggleAll(true)}>Show all + toggleAll(false)}>Hide all
diff --git a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte index f0cdd7deab..8ecec03e0e 100644 --- a/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte +++ b/packages/frontend-core/src/components/grid/controls/MigrationModal.svelte @@ -7,11 +7,6 @@ } from "@budibase/bbui" import { getContext } from "svelte" import { ValidColumnNameRegex } from "@budibase/shared-core" - import { - BBReferenceFieldSubType, - FieldType, - RelationshipType, - } from "@budibase/types" const { API, definition, rows } = getContext("grid") @@ -33,20 +28,11 @@ } const migrateUserColumn = async () => { - let subtype = BBReferenceFieldSubType.USERS - if (column.schema.relationshipType === RelationshipType.ONE_TO_MANY) { - subtype = BBReferenceFieldSubType.USER - } - try { await API.migrateColumn({ tableId: $definition._id, - oldColumn: column.schema, - newColumn: { - name: newColumnName, - type: FieldType.BB_REFERENCE, - subtype, - }, + oldColumn: column.schema.name, + newColumn: newColumnName, }) notifications.success("Column migrated") } catch (e) { diff --git a/packages/frontend-core/src/components/grid/layout/Grid.svelte b/packages/frontend-core/src/components/grid/layout/Grid.svelte index 6edc48e537..7dbb86ab23 100644 --- a/packages/frontend-core/src/components/grid/layout/Grid.svelte +++ b/packages/frontend-core/src/components/grid/layout/Grid.svelte @@ -1,6 +1,6 @@