diff --git a/README.md b/README.md index 3b4192fbf..0d0c31da7 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,8 @@ Join us on Slack

-Azimutt is a full-stack database exploration tool, from modern ERD made for real world databases (big & messy), to fast data navigation, but also documentation everywhere and whole database analysis. +Azimutt is a **full-stack database exploration tool**. +From modern ERD made for real world databases (big & messy), to fast data navigation, but also documentation everywhere and whole database analysis. [![Azimutt screenshot](assets/azimutt-screenshot.png)](https://azimutt.app/45f571a6-d9b8-4752-8a13-93ac0d2b7984/c00d0c45-8db2-46b7-9b51-eba661640c3c?token=59166798-32de-4f46-a1b4-0f7327a91336) @@ -28,26 +29,48 @@ Azimutt is a full-stack database exploration tool, from modern ERD made for real Databases existed for more than 40 years and despite a lot of tool around them, we couldn't find any providing a great exploration experience. -- **Database clients** focus on querying experience, with auto-completion and table/column lists but no visual help -- **ERDs** have a great diagram UI but fall short when schema is growing (real-world use cases) -- **Data catalogs** are focused on data governance and lineage for data teams, miss relational db for developers +- **ERDs** have a great diagram UI, but fall short when schema is growing (real-world use cases) +- **Data catalogs** are focused on data governance and lineage, missing relational db knowledge +- **Database clients** focus on querying with auto-completion and table/column lists, but no visual help -So we decided to built it 💪 +So we decided to build the missing tool 💪 -Azimutt started as a schema exploration tool for databases with hundreds of tables, but now it has grown a lot: +We started with schema exploration for databases with hundreds of tables, but now, it has grown a lot: -- Design your schema using [AML](https://azimutt.app/aml) for a fast diagramming -- Explore your database schema using search everywhere, display only useful tables/columns and follow relations -- Query your data like never before, follow foreign keys and display entities in diagram -- Document using table/column notes and tags and layouts and memos for use cases, features or team scopes -- Analyze it to discover inconsistencies and best practices to apply +- **Design** your schema using [AML](https://azimutt.app/aml) for a fast diagramming +- **Explore** your schema using search everywhere, display only useful tables/columns and follow relations +- **Query** your data like never before, follow foreign keys and display entities in diagram +- **Document** using table/column notes and tags, layouts and memos for use cases, features or team scopes +- **Analyze** it to discover inconsistencies and best practices to apply -Azimutt goal is to be your ultimate tool to understand your database. +Azimutt goal is to be your **ultimate tool to understand your database**. -## Self hosted + +## Azimutt badge + +You can load any public SQL file in Azimutt with just an url parameter. +So if you have a SQL file in your repo, like [structure.sql](./backend/priv/repo/structure.sql), you can add a button allowing your visitors to quickly explore it: + +```markdown +[![explore database with Azimutt](https://img.shields.io/badge/PostgreSQL-browse_online-gray?labelColor=4169E1&logo=postgresql&logoColor=fff&style=flat)](https://azimutt.app/create?sql=https://raw.githubusercontent.com/azimuttapp/azimutt/refs/heads/main/backend/priv/repo/structure.sql) +``` + +Here are some examples: + +[![explore database with Azimutt](https://img.shields.io/badge/PostgreSQL-browse_online-gray?labelColor=4169E1&logo=postgresql&logoColor=fff&style=flat)](https://azimutt.app/create?sql=https://raw.githubusercontent.com/azimuttapp/azimutt/refs/heads/main/backend/priv/repo/structure.sql) +[![explore database with Azimutt](https://img.shields.io/badge/MySQL-browse_online-gray?labelColor=4479A1&logo=mysql&logoColor=fff&style=flat)](https://azimutt.app/create?sql=https://raw.githubusercontent.com/azimuttapp/azimutt/refs/heads/main/backend/priv/repo/structure.sql) +[![explore database with Azimutt](https://img.shields.io/badge/MariaDB-browse_online-gray?labelColor=003545&logo=mariadb&logoColor=fff&style=flat)](https://azimutt.app/create?sql=https://raw.githubusercontent.com/azimuttapp/azimutt/refs/heads/main/backend/priv/repo/structure.sql) + +Or use our custom button image: + +[![explore database with Azimutt](https://raw.githubusercontent.com/azimuttapp/azimutt/refs/heads/main/assets/azimutt-button.png)](https://azimutt.app/create?sql=https://raw.githubusercontent.com/azimuttapp/azimutt/refs/heads/main/backend/priv/repo/structure.sql) + + +## Self-hosted You can use our [Docker image](https://github.com/azimuttapp/azimutt/pkgs/container/azimutt) to easily deploy it. Here is the [full guide](INSTALL.md). + ## Deploy on Heroku You can use our Heroku template which includes Azimutt web app, a Postgres database, Stackhero S3 storage and Mailgun. @@ -70,16 +93,18 @@ heroku config:set S3_KEY_ID=$(heroku config:get S3_ROOT_ACCESS_KEY) heroku config:set S3_KEY_SECRET=$(heroku config:get S3_ROOT_SECRET_KEY) ``` -Finally you will need to create the `azimutt` bucket on Stackhero: +Finally, you will need to create the `azimutt` bucket on Stackhero: - connect to Stackhero from your Heroku dashboard - use values of `S3_ROOT_ACCESS_KEY` and `S3_ROOT_SECRET_KEY` to log in - create a bucket named `azimutt` + ## Deploy on Kubernetes Please read this [guide](./charts/azimutt/README.md) + ## Local development Azimutt is built with [Elixir](https://elixir-lang.org)/[Phoenix](https://www.phoenixframework.org) (backend & admin) and [Elm](https://elm-lang.org)/[elm-spa](https://www.elm-spa.dev) (editor). @@ -101,6 +126,7 @@ Other things: - API documentation is accessible at [`/api/v1/swagger`](http://localhost:4000/api/v1/swagger) - You can use `pnpm --filter "azimutt-editor" run book` to start Elm design system & components, and access it with [localhost:4002](http://localhost:4002) + ### command semantics We have a lot of projects with a lot of commands, here is how they are structured: @@ -116,10 +142,12 @@ We have a lot of projects with a lot of commands, here is how they are structure - `build:docker` same as `build` but in the docker image (paths are different 😕) - `update` bumps library versions + ### Development commands - `pnpm --filter "azimutt-editor" run book` to launch the Elm design system + ### Setup Stripe #### Config @@ -129,6 +157,7 @@ We have a lot of projects with a lot of commands, here is how they are structure - Copy your webhook signing secret to `STRIPE_WEBHOOK_SIGNING_SECRET` variable in your `.env` file (looks like `whsec_...`) - Go to [your Stripe dashboard](https://dashboard.stripe.com/test/apikeys) to obtain your API Key and copy it into `STRIPE_API_KEY` in your `.env` file (looks like: `sk_test_...`) + #### Payments When testing interactively, use a card number, such as `4242 4242 4242 4242`. Enter the card number in the Dashboard or in any payment form. @@ -138,6 +167,7 @@ Use any value you like for other form fields. See more in the [stripe testing documentation](https://stripe.com/docs/testing) + ## Stack - [Production](https://azimutt.app) & [Staging](https://azimutt.dev) @@ -145,6 +175,7 @@ See more in the [stripe testing documentation](https://stripe.com/docs/testing) - Design using [TailwindCSS Framework](https://tailwindcss.com) - [Credo](http://credo-ci.org) for static code analysis (automatically run with pre-commit) + ## License The tool is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). diff --git a/assets/azimutt-button.png b/assets/azimutt-button.png new file mode 100644 index 000000000..7b1f6a3b2 Binary files /dev/null and b/assets/azimutt-button.png differ diff --git a/backend/config/config.exs b/backend/config/config.exs index ae856e2fb..b1c743f22 100644 --- a/backend/config/config.exs +++ b/backend/config/config.exs @@ -28,8 +28,8 @@ config :azimutt, azimutt_github_issues_new: "https://github.com/azimuttapp/azimutt/issues/new", environment: config_env(), # TODO: find an automated process to build it - version: "2.1.9", - version_date: "2024-10-06T00:00:00.000Z", + version: "2.1.10", + version_date: "2024-10-11T00:00:00.000Z", commit_hash: System.cmd("git", ["log", "-1", "--pretty=format:%h"]) |> elem(0) |> String.trim(), commit_message: System.cmd("git", ["log", "-1", "--pretty=format:%s"]) |> elem(0) |> String.trim(), commit_date: System.cmd("git", ["log", "-1", "--pretty=format:%aI"]) |> elem(0) |> String.trim(), diff --git a/backend/lib/azimutt.ex b/backend/lib/azimutt.ex index e4a3699ee..f169c9e07 100644 --- a/backend/lib/azimutt.ex +++ b/backend/lib/azimutt.ex @@ -103,13 +103,14 @@ defmodule Azimutt do def active_plans, do: [plans().free, plans().solo, plans().team, plans().enterprise] def features do + # MUST stay in sync with frontend/src/Models/Feature.elm %{ # Database features schema_exploration: %{name: "Schema exploration", free: true, solo: true, team: true, enterprise: true, pro: true}, data_exploration: %{name: "Data exploration", free: true, solo: true, team: true, enterprise: true, pro: true}, - colors: %{name: "Custom colors", free: false, solo: true, team: true, enterprise: true, pro: true}, # TODO: rename `aml` to `db_design` - aml: %{name: "Database design (AML)", free: false, solo: true, team: true, enterprise: true, pro: true}, + aml: %{name: "Database design", free: 10, solo: nil, team: nil, enterprise: nil, pro: nil, description: "Allowed tables in AML"}, + colors: %{name: "Custom colors", free: false, solo: true, team: true, enterprise: true, pro: true}, # saved_queries: %{name: "Saved queries", free: false, solo: false, team: false, enterprise: true, pro: true, description: "Soon... Save and share useful queries."}, # dashboard: %{name: "Dashboard", free: false, solo: false, team: false, enterprise: true, pro: true, description: "Soon... Visually see query results."}, # db_stat_history: %{name: "Stats history", free: false, solo: false, team: false, enterprise: true, pro: true, description: "Soon... Keep evolutions of database stats."}, @@ -150,7 +151,7 @@ defmodule Azimutt do def streak do [ %{goal: 4, feature: :colors, limit: true}, - %{goal: 6, feature: :aml, limit: true}, + %{goal: 6, feature: :aml, limit: nil}, %{goal: 10, feature: :ai, limit: true}, %{goal: 15, feature: :project_layouts, limit: nil}, %{goal: 25, feature: :schema_export, limit: true}, diff --git a/backend/lib/azimutt_web/templates/partials/_streak.html.heex b/backend/lib/azimutt_web/templates/partials/_streak.html.heex index 6374bd013..87ac4022f 100644 --- a/backend/lib/azimutt_web/templates/partials/_streak.html.heex +++ b/backend/lib/azimutt_web/templates/partials/_streak.html.heex @@ -16,7 +16,7 @@ <%= render "_streak_step.html", step: 5, value: @value, color: "yellow", color_next: "red" %> <%= cond do %> <% @value <= 6 -> %> - <%= render "_streak_step.html", step: 6, value: @value, color: "red", reward: %{icon: "📝", label: "Day 6: unlock DB design with AML"} %> + <%= render "_streak_step.html", step: 6, value: @value, color: "red", reward: %{icon: "📝", label: "Day 6: unlock unlimited DB design with AML"} %> <% @value <= 10 -> %> <%= render "_streak_step.html", step: 10, value: @value, color: "red", reward: %{icon: "🪄", label: "Day 10: unlock AI features"} %> <% @value <= 15 -> %> diff --git a/backend/lib/azimutt_web/templates/website/pricing.html.heex b/backend/lib/azimutt_web/templates/website/pricing.html.heex index 876f1e4ec..ffdf6fe2d 100644 --- a/backend/lib/azimutt_web/templates/website/pricing.html.heex +++ b/backend/lib/azimutt_web/templates/website/pricing.html.heex @@ -43,8 +43,8 @@ <% feature_categories = [%{name: "Database features", features: [ Azimutt.features().schema_exploration, Azimutt.features().data_exploration, - Azimutt.features().colors, Azimutt.features().aml, + Azimutt.features().colors, Azimutt.features().schema_export, Azimutt.features().ai, Azimutt.features().analysis, diff --git a/backend/mix.exs b/backend/mix.exs index 667365dfb..c704a55b1 100644 --- a/backend/mix.exs +++ b/backend/mix.exs @@ -102,6 +102,10 @@ defmodule Azimutt.MixProject do setup: ["deps.get", "ecto.setup"], "ecto.setup": ["ecto.create", "ecto.migrate", "run priv/repo/seeds.exs"], "ecto.reset": ["ecto.drop", "ecto.setup"], + # generate structure.sql on migration + "db.migrate": ["ecto.migrate", "ecto.dump"], + # generate structure.sql on rollback + "db.rollback": ["ecto.rollback", "ecto.dump"], seeds: "run priv/repo/seeds.exs", test: ["ecto.create --quiet", "ecto.migrate --quiet", "test"], "assets.deploy": [ diff --git a/backend/priv/repo/structure.sql b/backend/priv/repo/structure.sql new file mode 100644 index 000000000..723425c28 --- /dev/null +++ b/backend/priv/repo/structure.sql @@ -0,0 +1,1105 @@ +-- +-- PostgreSQL database dump +-- + +-- Dumped from database version 16.4 (Ubuntu 16.4-0ubuntu0.24.04.2) +-- Dumped by pg_dump version 16.4 (Ubuntu 16.4-0ubuntu0.24.04.2) + +SET statement_timeout = 0; +SET lock_timeout = 0; +SET idle_in_transaction_session_timeout = 0; +SET client_encoding = 'UTF8'; +SET standard_conforming_strings = on; +SELECT pg_catalog.set_config('search_path', '', false); +SET check_function_bodies = false; +SET xmloption = content; +SET client_min_messages = warning; +SET row_security = off; + +-- +-- Name: SCHEMA public; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON SCHEMA public IS 'Main schema'; + + +-- +-- Name: citext; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS citext WITH SCHEMA public; + + +-- +-- Name: EXTENSION citext; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON EXTENSION citext IS 'data type for case-insensitive character strings'; + + +-- +-- Name: uuid-ossp; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS "uuid-ossp" WITH SCHEMA public; + + +-- +-- Name: EXTENSION "uuid-ossp"; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON EXTENSION "uuid-ossp" IS 'generate universally unique identifiers (UUIDs)'; + + +-- +-- Name: bug_status; Type: TYPE; Schema: public; Owner: - +-- + +CREATE TYPE public.bug_status AS ENUM ( + 'new', + 'open', + 'closed' +); + + +-- +-- Name: layout_position; Type: TYPE; Schema: public; Owner: - +-- + +CREATE TYPE public.layout_position AS ( + x integer, + y integer +); + + +-- +-- Name: post_status; Type: TYPE; Schema: public; Owner: - +-- + +CREATE TYPE public.post_status AS ENUM ( + 'draft', + 'published', + 'archived' +); + + +SET default_tablespace = ''; + +SET default_table_access_method = heap; + +-- +-- Name: clever_cloud_resources; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.clever_cloud_resources ( + id uuid NOT NULL, + addon_id character varying(255) NOT NULL, + owner_id character varying(255) NOT NULL, + owner_name character varying(255) NOT NULL, + user_id character varying(255) NOT NULL, + plan character varying(255) NOT NULL, + region character varying(255) NOT NULL, + callback_url character varying(255) NOT NULL, + logplex_token character varying(255) NOT NULL, + options jsonb, + organization_id uuid, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + deleted_at timestamp without time zone +); + + +-- +-- Name: events; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.events ( + id uuid NOT NULL, + name character varying(255) NOT NULL, + data jsonb, + details jsonb, + created_by uuid, + created_at timestamp without time zone NOT NULL, + organization_id uuid, + project_id uuid +); + + +-- +-- Name: COLUMN events.data; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.events.data IS 'event entity data'; + + +-- +-- Name: COLUMN events.details; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.events.details IS 'when additional data are needed'; + + +-- +-- Name: COLUMN events.project_id; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.events.project_id IS 'no FK to keep records when projects are deleted'; + + +-- +-- Name: gallery; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.gallery ( + id uuid NOT NULL, + project_id uuid NOT NULL, + slug character varying(255) NOT NULL, + icon character varying(255) NOT NULL, + color character varying(255) NOT NULL, + website character varying(255) NOT NULL, + banner character varying(255) NOT NULL, + tips text NOT NULL, + description text NOT NULL, + analysis text NOT NULL, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL +); + + +-- +-- Name: COLUMN gallery.website; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.gallery.website IS 'link for the website of the schema'; + + +-- +-- Name: COLUMN gallery.banner; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.gallery.banner IS 'banner image, 1600x900'; + + +-- +-- Name: COLUMN gallery.tips; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.gallery.tips IS 'shown on project creation'; + + +-- +-- Name: COLUMN gallery.description; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.gallery.description IS 'shown on list and detail view'; + + +-- +-- Name: COLUMN gallery.analysis; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.gallery.analysis IS 'markdown shown on detail view'; + + +-- +-- Name: heroku_resources; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.heroku_resources ( + id uuid NOT NULL, + name character varying(255) NOT NULL, + app character varying(255), + plan character varying(255) NOT NULL, + region character varying(255) NOT NULL, + options jsonb, + callback character varying(255) NOT NULL, + oauth_code uuid NOT NULL, + oauth_type character varying(255) NOT NULL, + oauth_expire timestamp without time zone NOT NULL, + organization_id uuid, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + deleted_at timestamp without time zone +); + + +-- +-- Name: organization_invitations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.organization_invitations ( + id uuid NOT NULL, + sent_to character varying(255) NOT NULL, + organization_id uuid NOT NULL, + expire_at timestamp without time zone NOT NULL, + created_by uuid NOT NULL, + created_at timestamp without time zone NOT NULL, + cancel_at timestamp without time zone, + answered_by uuid, + refused_at timestamp without time zone, + accepted_at timestamp without time zone, + role character varying(255) +); + + +-- +-- Name: COLUMN organization_invitations.sent_to; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.organization_invitations.sent_to IS 'email to send the invitation'; + + +-- +-- Name: organization_members; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.organization_members ( + user_id uuid NOT NULL, + organization_id uuid NOT NULL, + created_by uuid NOT NULL, + updated_by uuid NOT NULL, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + role character varying(255) DEFAULT 'owner'::character varying NOT NULL +); + + +-- +-- Name: COLUMN organization_members.role; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.organization_members.role IS 'values: owner, writer, reader'; + + +-- +-- Name: organizations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.organizations ( + id uuid NOT NULL, + slug character varying(255) NOT NULL, + name character varying(255) NOT NULL, + logo character varying(255) NOT NULL, + description text, + github_username character varying(255), + twitter_username character varying(255), + stripe_customer_id character varying(255), + is_personal boolean NOT NULL, + created_by uuid NOT NULL, + updated_by uuid NOT NULL, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + deleted_by uuid, + deleted_at timestamp without time zone, + data jsonb, + plan character varying(255), + plan_freq character varying(255), + plan_status character varying(255), + plan_seats integer, + plan_validated timestamp without time zone, + free_trial_used timestamp without time zone, + gateway character varying(255) +); + + +-- +-- Name: COLUMN organizations.is_personal; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.organizations.is_personal IS 'mimic user accounts when true'; + + +-- +-- Name: COLUMN organizations.deleted_at; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.organizations.deleted_at IS 'orga is cleared on deletion but kept for FKs'; + + +-- +-- Name: COLUMN organizations.data; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.organizations.data IS 'unstructured props for orgas'; + + +-- +-- Name: COLUMN organizations.plan; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.organizations.plan IS 'organization pricing plan, ex: free, solo, team... If null, it has to be computed and stored'; + + +-- +-- Name: COLUMN organizations.plan_freq; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.organizations.plan_freq IS 'subscription period, ex: monthly, yearly. If null, it has to be computed and stored'; + + +-- +-- Name: COLUMN organizations.plan_status; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.organizations.plan_status IS 'stripe status or ''manual'' to disable the sync with stripe'; + + +-- +-- Name: COLUMN organizations.plan_validated; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.organizations.plan_validated IS 'the last time the plan was computed and stored'; + + +-- +-- Name: COLUMN organizations.free_trial_used; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.organizations.free_trial_used IS 'when the free trial was used, null otherwise'; + + +-- +-- Name: COLUMN organizations.gateway; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.organizations.gateway IS 'custom gateway for the organization'; + + +-- +-- Name: project_tokens; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.project_tokens ( + id uuid NOT NULL, + project_id uuid NOT NULL, + name character varying(255) NOT NULL, + nb_access integer NOT NULL, + last_access timestamp without time zone, + expire_at timestamp without time zone, + revoked_at timestamp without time zone, + revoked_by uuid, + created_at timestamp without time zone NOT NULL, + created_by uuid NOT NULL +); + + +-- +-- Name: TABLE project_tokens; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON TABLE public.project_tokens IS 'grant access to projects'; + + +-- +-- Name: projects; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.projects ( + id uuid NOT NULL, + organization_id uuid NOT NULL, + slug public.citext NOT NULL, + name character varying(255) NOT NULL, + description text, + encoding_version integer NOT NULL, + storage_kind character varying(255) NOT NULL, + file character varying(255), + local_owner uuid, + nb_sources integer NOT NULL, + nb_tables integer NOT NULL, + nb_columns integer NOT NULL, + nb_relations integer NOT NULL, + nb_types integer NOT NULL, + nb_comments integer NOT NULL, + nb_notes integer NOT NULL, + nb_layouts integer NOT NULL, + created_by uuid NOT NULL, + updated_by uuid NOT NULL, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + archived_by uuid, + archived_at timestamp without time zone, + visibility character varying(255) DEFAULT 'none'::character varying NOT NULL, + nb_memos integer DEFAULT 0 NOT NULL +); + + +-- +-- Name: COLUMN projects.encoding_version; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.projects.encoding_version IS 'encoding version for the project'; + + +-- +-- Name: COLUMN projects.storage_kind; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.projects.storage_kind IS 'enum: local, remote'; + + +-- +-- Name: COLUMN projects.file; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.projects.file IS 'stored file reference for remote projects'; + + +-- +-- Name: COLUMN projects.local_owner; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.projects.local_owner IS 'user owning a local project'; + + +-- +-- Name: COLUMN projects.nb_types; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.projects.nb_types IS 'number of SQL custom types in the project'; + + +-- +-- Name: COLUMN projects.nb_comments; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.projects.nb_comments IS 'number of SQL comments in the project'; + + +-- +-- Name: COLUMN projects.visibility; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.projects.visibility IS 'enum: none, read, write'; + + +-- +-- Name: schema_migrations; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.schema_migrations ( + version bigint NOT NULL, + inserted_at timestamp(0) without time zone +); + + +-- +-- Name: user_auth_tokens; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.user_auth_tokens ( + id uuid NOT NULL, + user_id uuid NOT NULL, + name character varying(255) NOT NULL, + nb_access integer NOT NULL, + last_access timestamp without time zone, + expire_at timestamp without time zone, + created_at timestamp without time zone NOT NULL, + deleted_at timestamp without time zone +); + + +-- +-- Name: user_profiles; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.user_profiles ( + id uuid NOT NULL, + user_id uuid NOT NULL, + usecase character varying(255), + usage character varying(255), + role character varying(255), + location character varying(255), + description text, + company character varying(255), + company_size integer, + team_organization_id uuid, + plan character varying(255), + discovered_by character varying(255), + previously_tried character varying(255)[], + product_updates boolean, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + phone character varying(255), + industry character varying(255) +); + + +-- +-- Name: user_tokens; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.user_tokens ( + id uuid NOT NULL, + user_id uuid NOT NULL, + token bytea NOT NULL, + context character varying(255) NOT NULL, + sent_to character varying(255), + created_at timestamp without time zone NOT NULL +); + + +-- +-- Name: TABLE user_tokens; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON TABLE public.user_tokens IS 'needed for login/pass auth'; + + +-- +-- Name: COLUMN user_tokens.sent_to; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.user_tokens.sent_to IS 'email'; + + +-- +-- Name: users; Type: TABLE; Schema: public; Owner: - +-- + +CREATE TABLE public.users ( + id uuid NOT NULL, + slug public.citext NOT NULL, + name character varying(255) NOT NULL, + email public.citext NOT NULL, + provider character varying(255), + provider_uid character varying(255), + avatar character varying(255) NOT NULL, + github_username character varying(255), + twitter_username character varying(255), + is_admin boolean NOT NULL, + hashed_password character varying(255), + last_signin timestamp without time zone NOT NULL, + created_at timestamp without time zone NOT NULL, + updated_at timestamp without time zone NOT NULL, + confirmed_at timestamp without time zone, + deleted_at timestamp without time zone, + data jsonb, + onboarding character varying(255), + provider_data jsonb +); + + +-- +-- Name: COLUMN users.slug; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.users.slug IS 'friendly id to show on url'; + + +-- +-- Name: COLUMN users.hashed_password; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.users.hashed_password IS 'present only if user used login/pass auth'; + + +-- +-- Name: COLUMN users.confirmed_at; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.users.confirmed_at IS 'on email confirm or directly for sso'; + + +-- +-- Name: COLUMN users.deleted_at; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.users.deleted_at IS 'user is cleared on deletion but kept for FKs'; + + +-- +-- Name: COLUMN users.data; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.users.data IS 'unstructured props for user'; + + +-- +-- Name: COLUMN users.onboarding; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.users.onboarding IS 'current onboarding step when not finished'; + + +-- +-- Name: COLUMN users.provider_data; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON COLUMN public.users.provider_data IS 'connection object from provider'; + + +-- +-- Name: clever_cloud_resources clever_cloud_resources_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.clever_cloud_resources + ADD CONSTRAINT clever_cloud_resources_pkey PRIMARY KEY (id); + + +-- +-- Name: events events_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.events + ADD CONSTRAINT events_pkey PRIMARY KEY (id); + + +-- +-- Name: gallery gallery_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.gallery + ADD CONSTRAINT gallery_pkey PRIMARY KEY (id); + + +-- +-- Name: heroku_resources heroku_resources_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.heroku_resources + ADD CONSTRAINT heroku_resources_pkey PRIMARY KEY (id); + + +-- +-- Name: organization_invitations organization_invitations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.organization_invitations + ADD CONSTRAINT organization_invitations_pkey PRIMARY KEY (id); + + +-- +-- Name: organization_members organization_members_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.organization_members + ADD CONSTRAINT organization_members_pkey PRIMARY KEY (user_id, organization_id); + + +-- +-- Name: organizations organizations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.organizations + ADD CONSTRAINT organizations_pkey PRIMARY KEY (id); + + +-- +-- Name: project_tokens project_tokens_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.project_tokens + ADD CONSTRAINT project_tokens_pkey PRIMARY KEY (id); + + +-- +-- Name: projects projects_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.projects + ADD CONSTRAINT projects_pkey PRIMARY KEY (id); + + +-- +-- Name: schema_migrations schema_migrations_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.schema_migrations + ADD CONSTRAINT schema_migrations_pkey PRIMARY KEY (version); + + +-- +-- Name: CONSTRAINT schema_migrations_pkey ON schema_migrations; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON CONSTRAINT schema_migrations_pkey ON public.schema_migrations IS 'HELLO!'; + + +-- +-- Name: user_auth_tokens user_auth_tokens_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_auth_tokens + ADD CONSTRAINT user_auth_tokens_pkey PRIMARY KEY (id); + + +-- +-- Name: user_profiles user_profiles_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_profiles + ADD CONSTRAINT user_profiles_pkey PRIMARY KEY (id); + + +-- +-- Name: user_tokens user_tokens_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_tokens + ADD CONSTRAINT user_tokens_pkey PRIMARY KEY (id); + + +-- +-- Name: users users_pkey; Type: CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.users + ADD CONSTRAINT users_pkey PRIMARY KEY (id); + + +-- +-- Name: events_created_at_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX events_created_at_index ON public.events USING btree (created_at); + + +-- +-- Name: INDEX events_created_at_index; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON INDEX public.events_created_at_index IS 'Easy access of events by date'; + + +-- +-- Name: events_created_by_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX events_created_by_index ON public.events USING btree (created_by); + + +-- +-- Name: events_name_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX events_name_index ON public.events USING btree (name); + + +-- +-- Name: events_organization_id_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX events_organization_id_index ON public.events USING btree (organization_id); + + +-- +-- Name: events_project_id_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX events_project_id_index ON public.events USING btree (project_id); + + +-- +-- Name: gallery_project_id_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX gallery_project_id_index ON public.gallery USING btree (project_id); + + +-- +-- Name: gallery_slug_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX gallery_slug_index ON public.gallery USING btree (slug); + + +-- +-- Name: organization_invitations_organization_id_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX organization_invitations_organization_id_index ON public.organization_invitations USING btree (organization_id); + + +-- +-- Name: organizations_slug_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX organizations_slug_index ON public.organizations USING btree (slug); + + +-- +-- Name: organizations_stripe_customer_id_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX organizations_stripe_customer_id_index ON public.organizations USING btree (stripe_customer_id); + + +-- +-- Name: projects_organization_id_slug_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX projects_organization_id_slug_index ON public.projects USING btree (organization_id, slug); + + +-- +-- Name: user_tokens_context_token_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX user_tokens_context_token_index ON public.user_tokens USING btree (context, token); + + +-- +-- Name: user_tokens_user_id_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE INDEX user_tokens_user_id_index ON public.user_tokens USING btree (user_id); + + +-- +-- Name: users_email_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX users_email_index ON public.users USING btree (email); + + +-- +-- Name: users_slug_index; Type: INDEX; Schema: public; Owner: - +-- + +CREATE UNIQUE INDEX users_slug_index ON public.users USING btree (slug); + + +-- +-- Name: clever_cloud_resources clever_cloud_resources_organization_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.clever_cloud_resources + ADD CONSTRAINT clever_cloud_resources_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES public.organizations(id); + + +-- +-- Name: events events_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.events + ADD CONSTRAINT events_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: CONSTRAINT events_created_by_fkey ON events; Type: COMMENT; Schema: public; Owner: - +-- + +COMMENT ON CONSTRAINT events_created_by_fkey ON public.events IS 'Link to users'; + + +-- +-- Name: events events_organization_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.events + ADD CONSTRAINT events_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES public.organizations(id); + + +-- +-- Name: gallery gallery_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.gallery + ADD CONSTRAINT gallery_project_id_fkey FOREIGN KEY (project_id) REFERENCES public.projects(id); + + +-- +-- Name: heroku_resources heroku_resources_organization_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.heroku_resources + ADD CONSTRAINT heroku_resources_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES public.organizations(id); + + +-- +-- Name: organization_invitations organization_invitations_answered_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.organization_invitations + ADD CONSTRAINT organization_invitations_answered_by_fkey FOREIGN KEY (answered_by) REFERENCES public.users(id); + + +-- +-- Name: organization_invitations organization_invitations_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.organization_invitations + ADD CONSTRAINT organization_invitations_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: organization_invitations organization_invitations_organization_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.organization_invitations + ADD CONSTRAINT organization_invitations_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES public.organizations(id); + + +-- +-- Name: organization_members organization_members_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.organization_members + ADD CONSTRAINT organization_members_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: organization_members organization_members_organization_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.organization_members + ADD CONSTRAINT organization_members_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES public.organizations(id); + + +-- +-- Name: organization_members organization_members_updated_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.organization_members + ADD CONSTRAINT organization_members_updated_by_fkey FOREIGN KEY (updated_by) REFERENCES public.users(id); + + +-- +-- Name: organization_members organization_members_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.organization_members + ADD CONSTRAINT organization_members_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id); + + +-- +-- Name: organizations organizations_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.organizations + ADD CONSTRAINT organizations_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: organizations organizations_deleted_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.organizations + ADD CONSTRAINT organizations_deleted_by_fkey FOREIGN KEY (deleted_by) REFERENCES public.users(id); + + +-- +-- Name: organizations organizations_updated_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.organizations + ADD CONSTRAINT organizations_updated_by_fkey FOREIGN KEY (updated_by) REFERENCES public.users(id); + + +-- +-- Name: project_tokens project_tokens_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.project_tokens + ADD CONSTRAINT project_tokens_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: project_tokens project_tokens_project_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.project_tokens + ADD CONSTRAINT project_tokens_project_id_fkey FOREIGN KEY (project_id) REFERENCES public.projects(id); + + +-- +-- Name: project_tokens project_tokens_revoked_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.project_tokens + ADD CONSTRAINT project_tokens_revoked_by_fkey FOREIGN KEY (revoked_by) REFERENCES public.users(id); + + +-- +-- Name: projects projects_archived_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.projects + ADD CONSTRAINT projects_archived_by_fkey FOREIGN KEY (archived_by) REFERENCES public.users(id); + + +-- +-- Name: projects projects_created_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.projects + ADD CONSTRAINT projects_created_by_fkey FOREIGN KEY (created_by) REFERENCES public.users(id); + + +-- +-- Name: projects projects_local_owner_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.projects + ADD CONSTRAINT projects_local_owner_fkey FOREIGN KEY (local_owner) REFERENCES public.users(id); + + +-- +-- Name: projects projects_organization_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.projects + ADD CONSTRAINT projects_organization_id_fkey FOREIGN KEY (organization_id) REFERENCES public.organizations(id); + + +-- +-- Name: projects projects_updated_by_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.projects + ADD CONSTRAINT projects_updated_by_fkey FOREIGN KEY (updated_by) REFERENCES public.users(id); + + +-- +-- Name: user_auth_tokens user_auth_tokens_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_auth_tokens + ADD CONSTRAINT user_auth_tokens_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: user_profiles user_profiles_team_organization_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_profiles + ADD CONSTRAINT user_profiles_team_organization_id_fkey FOREIGN KEY (team_organization_id) REFERENCES public.organizations(id) ON DELETE CASCADE; + + +-- +-- Name: user_profiles user_profiles_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_profiles + ADD CONSTRAINT user_profiles_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- Name: user_tokens user_tokens_user_id_fkey; Type: FK CONSTRAINT; Schema: public; Owner: - +-- + +ALTER TABLE ONLY public.user_tokens + ADD CONSTRAINT user_tokens_user_id_fkey FOREIGN KEY (user_id) REFERENCES public.users(id) ON DELETE CASCADE; + + +-- +-- PostgreSQL database dump complete +-- + +INSERT INTO public."schema_migrations" (version) VALUES (20220903194509); +INSERT INTO public."schema_migrations" (version) VALUES (20221116095052); +INSERT INTO public."schema_migrations" (version) VALUES (20221121135140); +INSERT INTO public."schema_migrations" (version) VALUES (20221205110011); +INSERT INTO public."schema_migrations" (version) VALUES (20221230162039); +INSERT INTO public."schema_migrations" (version) VALUES (20230102142929); +INSERT INTO public."schema_migrations" (version) VALUES (20230107144621); +INSERT INTO public."schema_migrations" (version) VALUES (20230126103538); +INSERT INTO public."schema_migrations" (version) VALUES (20230412184510); +INSERT INTO public."schema_migrations" (version) VALUES (20230412190321); +INSERT INTO public."schema_migrations" (version) VALUES (20230412190524); +INSERT INTO public."schema_migrations" (version) VALUES (20230701191613); +INSERT INTO public."schema_migrations" (version) VALUES (20231110120742); +INSERT INTO public."schema_migrations" (version) VALUES (20240425124708); +INSERT INTO public."schema_migrations" (version) VALUES (20240624135054); +INSERT INTO public."schema_migrations" (version) VALUES (20240715092952); +INSERT INTO public."schema_migrations" (version) VALUES (20240724110212); diff --git a/frontend/src/Components/Slices/PlanDialog.elm b/frontend/src/Components/Slices/PlanDialog.elm index f895f6a3e..65e67c029 100644 --- a/frontend/src/Components/Slices/PlanDialog.elm +++ b/frontend/src/Components/Slices/PlanDialog.elm @@ -1,4 +1,4 @@ -module Components.Slices.PlanDialog exposing (ColorsModel, ColorsMsg(..), DocState, SharedDocState, aiDisabledAlert, amlDisabledAlert, analysisResults, analysisWarning, colorsInit, colorsModalBody, colorsUpdate, doc, docInit, layoutTablesModalBody, layoutsModalBody, layoutsWarning, privateLinkWarning, projectExportWarning, projectsNoSaveAlert, projectsTooManyAlert, schemaExportWarning) +module Components.Slices.PlanDialog exposing (ColorsModel, ColorsMsg(..), DocState, SharedDocState, aiDisabledAlert, amlDisabledAlert, amlWriteError, analysisResults, analysisWarning, colorsInit, colorsModalBody, colorsUpdate, doc, docInit, layoutTablesModalBody, layoutsModalBody, layoutsWarning, privateLinkWarning, projectExportWarning, projectsNoSaveAlert, projectsTooManyAlert, schemaExportWarning) import Components.Atoms.Button as Button import Components.Atoms.Icon as Icon exposing (Icon) @@ -80,6 +80,15 @@ amlDisabledAlert project = [ subscribeButtonSecondary project color feature Icon.Puzzle "Do some magic" ] +amlWriteError : ProjectRef -> String +amlWriteError project = + "AML is limited to " + ++ (project.organization |> Maybe.andThen (.plan >> .aml) |> Maybe.withDefault Feature.aml.default |> String.fromInt) + ++ " tables in " + ++ planName project + ++ ", use Solo free trial to continue" + + aiDisabledAlert : ProjectRef -> Html msg aiDisabledAlert project = let diff --git a/frontend/src/Models/Feature.elm b/frontend/src/Models/Feature.elm index 5a40e42b9..2e8bd3b0c 100644 --- a/frontend/src/Models/Feature.elm +++ b/frontend/src/Models/Feature.elm @@ -1,14 +1,16 @@ module Models.Feature exposing (ai, aml, analysis, colors, dataExploration, layoutTables, projectDbs, projectDoc, projectExport, projectLayouts, projectShare, projects, schemaExport) +-- MUST stay in sync with backend/lib/azimutt.ex + ai : { name : String, default : Bool } ai = { name = "ai", default = False } -aml : { name : String, default : Bool } +aml : { name : String, default : Int } aml = - { name = "aml", default = False } + { name = "aml", default = 10 } analysis : { name : String, preview : String, snapshot : String, trends : String, limit : Int, default : Bool } diff --git a/frontend/src/Models/Organization.elm b/frontend/src/Models/Organization.elm index b84a03371..2a465a1f6 100644 --- a/frontend/src/Models/Organization.elm +++ b/frontend/src/Models/Organization.elm @@ -1,4 +1,4 @@ -module Models.Organization exposing (Organization, canAnalyse, canChangeColor, canCreateLayout, canExportProject, canExportSchema, canSaveProject, canShareProject, canShowTables, canUseAi, canUseAml, decode, encode, isLastLayout, one, zero) +module Models.Organization exposing (Organization, canAnalyse, canChangeColor, canCreateLayout, canExportProject, canExportSchema, canSaveProject, canShareProject, canShowTables, canUseAi, canUseAml, canWriteAml, decode, encode, isLastLayout, one, zero) import Json.Decode as Decode import Json.Encode as Encode exposing (Value) @@ -37,8 +37,15 @@ canChangeColor projectRef = canUseAml : { x | organization : Maybe Organization } -> Bool -canUseAml projectRef = - projectRef.organization |> Maybe.mapOrElse (.plan >> .aml) Feature.aml.default +canUseAml _ = + -- now AML is always accessible, it's limited in term of number of tables, see `canWriteAml` + -- projectRef.organization |> Maybe.mapOrElse (.plan >> .aml) Feature.aml.default + True + + +canWriteAml : Int -> { x | organization : Maybe Organization } -> Bool +canWriteAml tables projectRef = + projectRef.organization |> Maybe.mapOrElse (.plan >> .aml >> Maybe.all (\max -> tables <= max)) (tables <= Feature.aml.default) canUseAi : { x | organization : Maybe Organization } -> Bool diff --git a/frontend/src/Models/Plan.elm b/frontend/src/Models/Plan.elm index 3257fa4bb..514695466 100644 --- a/frontend/src/Models/Plan.elm +++ b/frontend/src/Models/Plan.elm @@ -13,7 +13,7 @@ type alias Plan = , name : String , dataExploration : Bool -- TODO: add limitation (not done because available on all plans for now) , colors : Bool - , aml : Bool + , aml : Maybe Int , schemaExport : Bool , ai : Bool , analysis : String @@ -35,7 +35,7 @@ encode value = , ( "name", value.name |> Encode.string ) , ( Feature.dataExploration.name, value.dataExploration |> Encode.bool ) , ( Feature.colors.name, value.colors |> Encode.bool ) - , ( Feature.aml.name, value.aml |> Encode.bool ) + , ( Feature.aml.name, value.aml |> Encode.maybe Encode.int ) , ( Feature.schemaExport.name, value.schemaExport |> Encode.bool ) , ( Feature.ai.name, value.ai |> Encode.bool ) , ( Feature.analysis.name, value.analysis |> Encode.string ) @@ -57,7 +57,7 @@ decode = (Decode.field "name" Decode.string) (Decode.field Feature.dataExploration.name Decode.bool) (Decode.field Feature.colors.name Decode.bool) - (Decode.field Feature.aml.name Decode.bool) + (Decode.maybeField Feature.aml.name Decode.int) (Decode.field Feature.schemaExport.name Decode.bool) (Decode.field Feature.ai.name Decode.bool) (Decode.field Feature.analysis.name Decode.string) @@ -77,7 +77,7 @@ docSample = , name = "Sample plan" , dataExploration = True , colors = True - , aml = True + , aml = Just 10 , schemaExport = True , ai = True , analysis = "trends" diff --git a/frontend/src/PagesComponents/Organization_/Project_/Components/AmlSidebar.elm b/frontend/src/PagesComponents/Organization_/Project_/Components/AmlSidebar.elm index 46e5bd68a..9177012c0 100644 --- a/frontend/src/PagesComponents/Organization_/Project_/Components/AmlSidebar.elm +++ b/frontend/src/PagesComponents/Organization_/Project_/Components/AmlSidebar.elm @@ -108,11 +108,22 @@ update now projectRef msg model = (model.erd |> Maybe.andThen (.sources >> List.findBy .id id)) |> Maybe.map (\source -> + let + editorError : String -> ParserError + editorError message = + { message = message, kind = "EditorError", level = ParserError.Error, offset = { start = 0, end = 0 }, position = { start = { line = 1, column = 1 }, end = { line = 1, column = 1 } } } + in if source.id /= id then - ( model |> mapAmlSidebarM (setErrors [ { message = "Source has changed", kind = "EditorError", level = ParserError.Error, offset = { start = 0, end = 0 }, position = { start = { line = 1, column = 1 }, end = { line = 1, column = 1 } } } ]), Extra.none ) + ( model |> mapAmlSidebarM (setErrors [ editorError "Source has changed" ]), Extra.none ) else if String.length (Source.contentStr source) /= length then - ( model |> mapAmlSidebarM (setErrors [ { message = "AML has changed", kind = "EditorError", level = ParserError.Error, offset = { start = 0, end = 0 }, position = { start = { line = 1, column = 1 }, end = { line = 1, column = 1 } } } ]), Extra.none ) + ( model |> mapAmlSidebarM (setErrors [ editorError "AML has changed" ]), Extra.none ) + + else if errors |> List.any (\e -> e.level == ParserError.Error) then + ( model |> mapAmlSidebarM (setErrors errors), Extra.none ) + + else if schema |> Maybe.any (\s -> projectRef |> Organization.canWriteAml (List.length s.tables) |> not) then + ( model |> mapAmlSidebarM (setErrors [ projectRef |> PlanDialog.amlWriteError |> editorError ]), Extra.none ) else schema |> Maybe.map (\s -> model |> updateSource now source s errors |> setDirty) |> Maybe.withDefault ( model |> mapAmlSidebarM (setErrors errors), Extra.none ) diff --git a/frontend/tests/TestHelpers/OrganizationFuzzers.elm b/frontend/tests/TestHelpers/OrganizationFuzzers.elm index f42d7d0a6..37aa14746 100644 --- a/frontend/tests/TestHelpers/OrganizationFuzzers.elm +++ b/frontend/tests/TestHelpers/OrganizationFuzzers.elm @@ -34,7 +34,7 @@ organizationName = plan : Fuzzer Plan plan = - Fuzz.map16 Plan planId planName Fuzz.bool Fuzz.bool Fuzz.bool Fuzz.bool Fuzz.bool stringSmall Fuzz.bool (Fuzz.maybe intPosSmall) (Fuzz.maybe intPosSmall) (Fuzz.maybe intPosSmall) (Fuzz.maybe intPosSmall) (Fuzz.maybe intPosSmall) Fuzz.bool Fuzz.int + Fuzz.map16 Plan planId planName Fuzz.bool Fuzz.bool (Fuzz.maybe intPosSmall) Fuzz.bool Fuzz.bool stringSmall Fuzz.bool (Fuzz.maybe intPosSmall) (Fuzz.maybe intPosSmall) (Fuzz.maybe intPosSmall) (Fuzz.maybe intPosSmall) (Fuzz.maybe intPosSmall) Fuzz.bool Fuzz.int planId : Fuzzer String