From ac1c6465480e178d66de8cfdc393a453ffb05687 Mon Sep 17 00:00:00 2001 From: Lev Kokotov Date: Mon, 13 Feb 2023 13:16:34 -0800 Subject: [PATCH 01/22] Docker local dev (#536) --- .github/workflows/package-extension.yml | 43 +++++++++ pgml-extension/Dockerfile | 110 +++++++++--------------- pgml-extension/README.md | 8 +- 3 files changed, 86 insertions(+), 75 deletions(-) create mode 100644 .github/workflows/package-extension.yml diff --git a/.github/workflows/package-extension.yml b/.github/workflows/package-extension.yml new file mode 100644 index 000000000..0d622c4e3 --- /dev/null +++ b/.github/workflows/package-extension.yml @@ -0,0 +1,43 @@ +name: Build PostgresML Rust package + +on: + workflow_dispatch: + +jobs: + build: + strategy: + matrix: + os: [ubuntu-latest] + runs-on: ${{ matrix.os }} + steps: + - uses: actions/checkout@v3 + - name: Install dependencies + run: + sudo apt update + sudo apt install -y ruby curl + curl -sLO https://github.com/deb-s3/deb-s3/releases/download/0.11.4/deb-s3-0.11.4.gem + sudo gem install deb-s3-0.11.4.gem + - name: Build package + run: + docker run -v $(pwd):/app levkk/postgresml:build cargo pgx package --pg-control /usr/lib/postgresql/11/bin/pg_config + docker run -v $(pwd):/app levkk/postgresml:build cargo pgx package --pg-control /usr/lib/postgresql/12/bin/pg_config + docker run -v $(pwd):/app levkk/postgresml:build cargo pgx package --pg-control /usr/lib/postgresql/13/bin/pg_config + docker run -v $(pwd):/app levkk/postgresml:build cargo pgx package --pg-control /usr/lib/postgresql/14/bin/pg_config + docker run -v $(pwd):/app levkk/postgresml:build cargo pgx package --pg-control /usr/lib/postgresql/15/bin/pg_config + # - name: Publish package + # env: + # AWS_ACCESS_KEY_ID: ${{ vars.AWS_ACCESS_KEY_ID }} + # AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + # AWS_DEFAULT_REGION: ${{ vars.AWS_DEFAULT_REGION }} + # working-directory: postgresml/pgml-extension + # run: | + # if [[ $(uname -a) == *"aarch64"* ]]; then + # ARCH="arm64" + # else + # ARCH="amd64" + # fi + + # deb-s3 upload \ + # --bucket apt.postgresml.org \ + # postgresql-pgml-${{ inputs.postgresVersion }}_${{ inputs.packageVersion }}-ubuntu${{ inputs.ubuntuVersion }}-${ARCH}.deb \ + # --codename ${{ inputs.ubuntuName }} diff --git a/pgml-extension/Dockerfile b/pgml-extension/Dockerfile index 1b3f2d50d..6824d47a2 100644 --- a/pgml-extension/Dockerfile +++ b/pgml-extension/Dockerfile @@ -1,73 +1,47 @@ -# -# Extension builder. -# -ARG UBUNTU_VERSION=22.04 -FROM ubuntu:${UBUNTU_VERSION} - -ARG PGVERSION=14 +FROM ubuntu:jammy +MAINTAINER team@postgresml.com +RUN apt-get update ARG DEBIAN_FRONTEND=noninteractive -ARG PACKAGE_VERSION=2.0.0 -ARG PACKAGE_PYTHON=true -ARG PACKAGE_CUDA=false ENV TZ=Etc/UTC - -# Apt-fast -RUN apt-get update && apt-get install software-properties-common -y - +RUN apt-get install -y software-properties-common RUN add-apt-repository ppa:apt-fast/stable --yes -RUN apt-get update && apt-get -y install apt-fast - -RUN apt-fast install apt-fast curl unzip gpg wget -y - -# PostgreSQL -RUN curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg > /dev/null -RUN echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list - -# CMake -RUN curl -L https://apt.kitware.com/keys/kitware-archive-latest.asc | gpg --dearmor | tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null -RUN echo "deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ $(lsb_release -cs) main" | tee /etc/apt/sources.list.d/kitware.list >/dev/null - -RUN apt-get update && apt-fast install postgresql-${PGVERSION} libopenblas-dev cmake postgresql-server-dev-${PGVERSION} pkg-config libssl-dev build-essential libclang-dev clang libpython3-dev python3-dev -y - -# CUDA -COPY docker/install_cuda.sh install_cuda.sh -RUN bash install_cuda.sh - -USER postgres - - -# Install Rust +RUN apt update && apt-get install -y apt-fast +RUN apt-get update && apt-fast install -y \ + libopenblas-dev \ + libssl-dev \ + bison \ + flex \ + pkg-config \ + cmake \ + libreadline-dev \ + libz-dev \ + curl \ + lsb-release \ + tzdata \ + sudo \ + cmake \ + libpq-dev \ + libclang-dev \ + wget \ + postgresql-plpython3-14 \ + postgresql-14 \ + postgresql-server-dev-14 +RUN add-apt-repository ppa:deadsnakes/ppa --yes +RUN apt update && apt-fast install -y \ + python3.10 \ + python3-pip \ + libpython3.10-dev \ + python3.10-dev +RUN pip3 install xgboost scikit-learn torch lightgbm transformers datasets +RUN useradd postgresml -m -s /bin/bash -G sudo +RUN echo 'postgresml ALL=(ALL) NOPASSWD: ALL' >> /etc/sudoers +USER postgresml RUN curl https://sh.rustup.rs -sSf | sh -s -- -y -ENV PATH="/var/lib/postgresql/.cargo/bin:${PATH}" - -# Install tcdi/pgx -RUN cargo install cargo-pgx --version 0.7.1 -RUN cargo pgx init --pg${PGVERSION} /usr/bin/pg_config - - -COPY --chown=postgres:postgres . /app +RUN $HOME/.cargo/bin/cargo install cargo-pgx --version "0.7.1" +RUN $HOME/.cargo/bin/cargo pgx init +RUN curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg >/dev/null +RUN sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' +RUN sudo apt update +RUN sudo apt-get install -y postgresql-15 postgresql-13 postgresql-12 postgresql-11 +RUN sudo apt install -y postgresql-server-dev-15 postgresql-server-dev-15 postgresql-server-dev-12 postgresql-server-dev-11 WORKDIR /app - - -# cargo pgx package command ignores --no-default-features sadly, so we -# have to have a separate Cargo.toml without Python. No big deal. -# Make sure to diff it and update it if you change the main Cargo.toml. -ENV PACKAGE_PYTHON=${PACKAGE_PYTHON} -RUN bash -c 'if [[ "${PACKAGE_PYTHON}" == "false" ]]; then cp docker/Cargo.toml.no-python Cargo.toml; fi' - -ENV PACKAGE_CUDA=${PACKAGE_CUDA} -RUN bash -c 'if [[ "${PACKAGE_CUDA}" == "true" ]]; then cp docker/Cargo.toml.cuda Cargo.toml; fi' - -# Build and upload package to S3 -ENV CUDACXX=/usr/local/cuda/bin/nvcc -RUN cargo pgx package - -# Deb file goes here, mount it on your local system -VOLUME /output -USER root - -ENV PACKAGE_VERSION=$PACKAGE_VERSION -ENV OUTPUT_DIR=/output - -# Run -ENTRYPOINT ["bash", "docker/build_ubuntu_deb.sh", "/output"] diff --git a/pgml-extension/README.md b/pgml-extension/README.md index dd1efef5a..ee6fda651 100644 --- a/pgml-extension/README.md +++ b/pgml-extension/README.md @@ -1,7 +1 @@ -# PostgresML Extension - -PostgresML is a PostgreSQL extension providing end-to-end machine learning inside your database. The extension is primarily written in Rust using [pgx](https://github.com/tcdi/pgx) and provides a SQL interface to various machine learning algorithm implementations such as [XGBoost](https://github.com/dmlc/xgboost), [LightGBM](https://github.com/microsoft/LightGBM), and [other classical methods](https://github.com/rust-ml/linfa). - -See [our blog](https://postgresml.org/blog/postgresml-is-moving-to-rust-for-our-2.0-release/) for a performance comparison and further motivations. - -Please see the [quick start instructions](https://postgresml.org/user_guides/setup/quick_start_with_docker/) for general information on installing or deploying PostgresML. A [developer guide](https://postgresml.org/developer_guide/overview/) is also available for those who would like to contribute. +Please see the [quick start instructions](https://postgresml.org/user_guides/setup/quick_start_with_docker/) for general information on installing or deploying PostgresML. A [developer guide](https://postgresml.org/developer_guide/overview/) is also available for those who would like to contribute. \ No newline at end of file From cb1a6e24b0b6fe66984740d18a09b5e49055c64e Mon Sep 17 00:00:00 2001 From: Lev Kokotov Date: Mon, 13 Feb 2023 16:40:21 -0800 Subject: [PATCH 02/22] Streamline deb build (#543) --- .github/workflows/package-extension.yml | 58 +++++++++++++++---------- pgml-extension/Cargo.lock | 2 +- pgml-extension/control | 8 ++-- 3 files changed, 40 insertions(+), 28 deletions(-) diff --git a/.github/workflows/package-extension.yml b/.github/workflows/package-extension.yml index 0d622c4e3..fdbf8a46d 100644 --- a/.github/workflows/package-extension.yml +++ b/.github/workflows/package-extension.yml @@ -2,6 +2,9 @@ name: Build PostgresML Rust package on: workflow_dispatch: + inputs: + packageVersion: + default: "2.2.0" jobs: build: @@ -9,35 +12,44 @@ jobs: matrix: os: [ubuntu-latest] runs-on: ${{ matrix.os }} + defaults: + run: + working-directory: pgml-extension steps: - uses: actions/checkout@v3 - name: Install dependencies - run: + run: | sudo apt update sudo apt install -y ruby curl curl -sLO https://github.com/deb-s3/deb-s3/releases/download/0.11.4/deb-s3-0.11.4.gem sudo gem install deb-s3-0.11.4.gem + git submodule update --init --recursive + chmod 777 . -R + dpkg-deb --version - name: Build package - run: - docker run -v $(pwd):/app levkk/postgresml:build cargo pgx package --pg-control /usr/lib/postgresql/11/bin/pg_config - docker run -v $(pwd):/app levkk/postgresml:build cargo pgx package --pg-control /usr/lib/postgresql/12/bin/pg_config - docker run -v $(pwd):/app levkk/postgresml:build cargo pgx package --pg-control /usr/lib/postgresql/13/bin/pg_config - docker run -v $(pwd):/app levkk/postgresml:build cargo pgx package --pg-control /usr/lib/postgresql/14/bin/pg_config - docker run -v $(pwd):/app levkk/postgresml:build cargo pgx package --pg-control /usr/lib/postgresql/15/bin/pg_config - # - name: Publish package - # env: - # AWS_ACCESS_KEY_ID: ${{ vars.AWS_ACCESS_KEY_ID }} - # AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} - # AWS_DEFAULT_REGION: ${{ vars.AWS_DEFAULT_REGION }} - # working-directory: postgresml/pgml-extension - # run: | - # if [[ $(uname -a) == *"aarch64"* ]]; then - # ARCH="arm64" - # else - # ARCH="amd64" - # fi + run: | + docker run -v $(pwd):/app ghcr.io/postgresml/postgresml-builder:latest bash -c '$HOME/.cargo/bin/cargo pgx package --pg-config /usr/lib/postgresql/11/bin/pg_config' + docker run -v $(pwd):/app ghcr.io/postgresml/postgresml-builder:latest bash -c '$HOME/.cargo/bin/cargo pgx package --pg-config /usr/lib/postgresql/12/bin/pg_config' + docker run -v $(pwd):/app ghcr.io/postgresml/postgresml-builder:latest bash -c '$HOME/.cargo/bin/cargo pgx package --pg-config /usr/lib/postgresql/13/bin/pg_config' + docker run -v $(pwd):/app ghcr.io/postgresml/postgresml-builder:latest bash -c '$HOME/.cargo/bin/cargo pgx package --pg-config /usr/lib/postgresql/14/bin/pg_config' + docker run -v $(pwd):/app ghcr.io/postgresml/postgresml-builder:latest bash -c '$HOME/.cargo/bin/cargo pgx package --pg-config /usr/lib/postgresql/15/bin/pg_config' + - name: Build debs + env: + AWS_ACCESS_KEY_ID: ${{ vars.AWS_ACCESS_KEY_ID }} + AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + AWS_DEFAULT_REGION: ${{ vars.AWS_DEFAULT_REGION }} + run: | + for pg in {11..15}; do + export PACKAGE_VERSION=${{ inputs.packageVersion }} + export PGVERSION=${pg} + export ARCH=amd64 + + mkdir -p target/release/pgml-pg${pg}/DEBIAN + (cat control | envsubst) > target/release/pgml-pg${pg}/DEBIAN/control + dpkg-deb --build target/release/pgml-pg${pg} postgresql-pgml-${pg}_${PACKAGE_VERSION}-ubuntu1-amd64.deb - # deb-s3 upload \ - # --bucket apt.postgresml.org \ - # postgresql-pgml-${{ inputs.postgresVersion }}_${{ inputs.packageVersion }}-ubuntu${{ inputs.ubuntuVersion }}-${ARCH}.deb \ - # --codename ${{ inputs.ubuntuName }} + deb-s3 upload \ + --bucket apt.postgresml.org \ + postgresql-pgml-${pg}_${PACKAGE_VERSION}-ubuntu1-amd64.deb \ + --codename jammy + done diff --git a/pgml-extension/Cargo.lock b/pgml-extension/Cargo.lock index 3e56f8f95..de0ca2fe0 100644 --- a/pgml-extension/Cargo.lock +++ b/pgml-extension/Cargo.lock @@ -1680,7 +1680,7 @@ dependencies = [ [[package]] name = "pgml" -version = "2.1.2" +version = "2.2.0" dependencies = [ "anyhow", "blas", diff --git a/pgml-extension/control b/pgml-extension/control index 047ad67fb..640540353 100644 --- a/pgml-extension/control +++ b/pgml-extension/control @@ -1,9 +1,9 @@ -Package: postgresql-pgml-PGVERSION -Version: PACKAGE_VERSION +Package: postgresql-pgml-${PGVERSION} +Version: ${PACKAGE_VERSION} Section: database Priority: optional -Architecture: ARCH -Depends: postgresql-PGVERSION, libopenblas-dev, postgresql-server-dev-PGVERSION, python3, python3-dev, libpython3-dev +Architecture: ${ARCH} +Depends: postgresql-${PGVERSION}, libopenblas-dev, postgresql-server-dev-${PGVERSION}, python3, python3-dev, libpython3-dev Maintainer: PostgresML Homepage: https://postgresml.org Description: PostgresML - machine learning with PostgreSQL From efce953ce7e10a0ea16639c2c03d11cee6227cb8 Mon Sep 17 00:00:00 2001 From: Lev Kokotov Date: Tue, 14 Feb 2023 12:00:17 -0800 Subject: [PATCH 03/22] Fix .deb release (#544) --- .github/workflows/package-extension.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/package-extension.yml b/.github/workflows/package-extension.yml index fdbf8a46d..1796cf3fc 100644 --- a/.github/workflows/package-extension.yml +++ b/.github/workflows/package-extension.yml @@ -44,6 +44,9 @@ jobs: export PGVERSION=${pg} export ARCH=amd64 + # Fix permissions + sudo chown -R ${USER}:${USER} target/release + mkdir -p target/release/pgml-pg${pg}/DEBIAN (cat control | envsubst) > target/release/pgml-pg${pg}/DEBIAN/control dpkg-deb --build target/release/pgml-pg${pg} postgresql-pgml-${pg}_${PACKAGE_VERSION}-ubuntu1-amd64.deb From 82869e585575644bcc75f4d4bfc36457774fb9f8 Mon Sep 17 00:00:00 2001 From: Lev Kokotov Date: Wed, 15 Feb 2023 11:10:45 -0800 Subject: [PATCH 04/22] Fix deb again (#545) --- .github/workflows/package-extension.yml | 119 ++++++++++++++++++++---- 1 file changed, 102 insertions(+), 17 deletions(-) diff --git a/.github/workflows/package-extension.yml b/.github/workflows/package-extension.yml index 1796cf3fc..d262953e3 100644 --- a/.github/workflows/package-extension.yml +++ b/.github/workflows/package-extension.yml @@ -10,29 +10,117 @@ jobs: build: strategy: matrix: - os: [ubuntu-latest] + os: ["ubuntu-22.04"] runs-on: ${{ matrix.os }} defaults: run: working-directory: pgml-extension steps: - uses: actions/checkout@v3 + - uses: actions-rs/toolchain@v1 + with: + toolchain: stable + - name: Validate cargo is working + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: version - name: Install dependencies + env: + DEBIAN_FRONTEND: noninteractive + TZ: Etc/UTC run: | + git submodule update --init --recursive + + # PostgreSQL apt + curl https://www.postgresql.org/media/keys/ACCC4CF8.asc | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/apt.postgresql.org.gpg >/dev/null + sudo sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list' + + sudo apt-get install -y software-properties-common + sudo add-apt-repository ppa:apt-fast/stable --yes + sudo add-apt-repository ppa:deadsnakes/ppa --yes sudo apt update - sudo apt install -y ruby curl + + sudo apt-get install -y apt-fast + sudo apt-get update && sudo apt-fast install -y \ + libopenblas-dev \ + libssl-dev \ + bison \ + flex \ + pkg-config \ + cmake \ + libreadline-dev \ + libz-dev \ + curl \ + lsb-release \ + tzdata \ + sudo \ + cmake \ + libpq-dev \ + libclang-dev \ + wget \ + postgresql-15 \ + postgresql-14 \ + postgresql-13 \ + postgresql-12 \ + postgresql-11 \ + postgresql-server-dev-15 \ + postgresql-server-dev-14 \ + postgresql-server-dev-13 \ + postgresql-server-dev-12 \ + postgresql-server-dev-11 \ + lsb-release \ + python3.10 \ + python3-pip \ + libpython3.10-dev \ + python3.10-dev \ + ruby + curl -sLO https://github.com/deb-s3/deb-s3/releases/download/0.11.4/deb-s3-0.11.4.gem sudo gem install deb-s3-0.11.4.gem - git submodule update --init --recursive - chmod 777 . -R dpkg-deb --version - - name: Build package - run: | - docker run -v $(pwd):/app ghcr.io/postgresml/postgresml-builder:latest bash -c '$HOME/.cargo/bin/cargo pgx package --pg-config /usr/lib/postgresql/11/bin/pg_config' - docker run -v $(pwd):/app ghcr.io/postgresml/postgresml-builder:latest bash -c '$HOME/.cargo/bin/cargo pgx package --pg-config /usr/lib/postgresql/12/bin/pg_config' - docker run -v $(pwd):/app ghcr.io/postgresml/postgresml-builder:latest bash -c '$HOME/.cargo/bin/cargo pgx package --pg-config /usr/lib/postgresql/13/bin/pg_config' - docker run -v $(pwd):/app ghcr.io/postgresml/postgresml-builder:latest bash -c '$HOME/.cargo/bin/cargo pgx package --pg-config /usr/lib/postgresql/14/bin/pg_config' - docker run -v $(pwd):/app ghcr.io/postgresml/postgresml-builder:latest bash -c '$HOME/.cargo/bin/cargo pgx package --pg-config /usr/lib/postgresql/15/bin/pg_config' + - name: Install pgx + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: install + args: cargo-pgx --version "0.7.1" + - name: pgx init + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: pgx + args: init --pg11=/usr/lib/postgresql/11/bin/pg_config --pg12=/usr/lib/postgresql/12/bin/pg_config --pg13=/usr/lib/postgresql/13/bin/pg_config --pg14=/usr/lib/postgresql/14/bin/pg_config --pg15=/usr/lib/postgresql/15/bin/pg_config + - name: Build Postgres 11 + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: pgx + args: package --pg-config /usr/lib/postgresql/11/bin/pg_config + - name: Build Postgres 12 + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: pgx + args: package --pg-config /usr/lib/postgresql/12/bin/pg_config + - name: Build Postgres 13 + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: pgx + args: package --pg-config /usr/lib/postgresql/13/bin/pg_config + - name: Build Postgres 14 + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: pgx + args: package --pg-config /usr/lib/postgresql/14/bin/pg_config + - name: Build Postgres 15 + uses: postgresml/gh-actions-cargo@master + with: + working-directory: pgml-extension + command: pgx + args: package --pg-config /usr/lib/postgresql/15/bin/pg_config - name: Build debs env: AWS_ACCESS_KEY_ID: ${{ vars.AWS_ACCESS_KEY_ID }} @@ -44,15 +132,12 @@ jobs: export PGVERSION=${pg} export ARCH=amd64 - # Fix permissions - sudo chown -R ${USER}:${USER} target/release - mkdir -p target/release/pgml-pg${pg}/DEBIAN (cat control | envsubst) > target/release/pgml-pg${pg}/DEBIAN/control - dpkg-deb --build target/release/pgml-pg${pg} postgresql-pgml-${pg}_${PACKAGE_VERSION}-ubuntu1-amd64.deb + dpkg-deb --root-owner-group --build target/release/pgml-pg${pg} postgresql-pgml-${pg}_${PACKAGE_VERSION}-ubuntu22.04-amd64.deb deb-s3 upload \ --bucket apt.postgresml.org \ - postgresql-pgml-${pg}_${PACKAGE_VERSION}-ubuntu1-amd64.deb \ - --codename jammy + postgresql-pgml-${pg}_${PACKAGE_VERSION}-ubuntu22.04-amd64.deb \ + --codename $(lsb_release -cs) done From ed959885bf9aecb4ec7a57f1746ef71ec9a1241f Mon Sep 17 00:00:00 2001 From: Lev Kokotov Date: Wed, 15 Feb 2023 11:15:22 -0800 Subject: [PATCH 05/22] Cleanup GH actions (#548) --- .github/workflows/ci.yml | 2 +- .github/workflows/deploy-beta.yml | 23 ---------------------- .github/workflows/package-extension.yml | 2 +- .github/workflows/publish-docs.yml | 4 ++-- .github/workflows/python-publish.yml | 26 ------------------------- 5 files changed, 4 insertions(+), 53 deletions(-) delete mode 100644 .github/workflows/deploy-beta.yml delete mode 100644 .github/workflows/python-publish.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 801f016ee..b07bc57f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,4 +1,4 @@ -name: Tests +name: tests on: push: branches: diff --git a/.github/workflows/deploy-beta.yml b/.github/workflows/deploy-beta.yml deleted file mode 100644 index 1508b2f9b..000000000 --- a/.github/workflows/deploy-beta.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Deploy to beta - -on: - push: - branches: - - master - workflow_dispatch: - inputs: - name: - description: "Just a trigger" - required: false - default: "Hello" - -jobs: - deploy: - runs-on: ubuntu-latest - steps: - - name: Install ssh key - run: mkdir -p ~/.ssh && echo "${{ secrets.BETA_SSH_KEY }}" > ~/.ssh/id_rsa && chmod 600 ~/.ssh/id_rsa - - - name: Start deploy - run: ssh -o "StrictHostKeyChecking=no" root@${{ secrets.BETA_IP }} '(cd /root; bash infrastructure/ubuntu/ubuntu_20_04_lts.sh > /var/log/deploy.log 2>&1) >&- 2>&- <&- &' - diff --git a/.github/workflows/package-extension.yml b/.github/workflows/package-extension.yml index d262953e3..d9e31243d 100644 --- a/.github/workflows/package-extension.yml +++ b/.github/workflows/package-extension.yml @@ -1,4 +1,4 @@ -name: Build PostgresML Rust package +name: package extension (deb) on: workflow_dispatch: diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 1c3625db3..5df861d6f 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -1,4 +1,4 @@ -name: Publish Documentation +name: publish docs on: push: @@ -18,4 +18,4 @@ jobs: python-version: 3.x - run: pip install -r requirements.txt - run: mkdocs gh-deploy --force - \ No newline at end of file + diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml deleted file mode 100644 index ead0c1aeb..000000000 --- a/.github/workflows/python-publish.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: Upload Python Package - -on: - release: - types: [published] - -jobs: - deploy: - runs-on: ubuntu-latest - defaults: - run: - working-directory: pgml-extension - steps: - - uses: actions/checkout@v3 - - name: Set up Python - uses: actions/setup-python@v3 - with: - python-version: '3.x' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build twine wheel - - name: Build package - run: python -m build - - name: Publish package - run: twine upload -u __token__ -p ${{ secrets.PYPI_API_TOKEN }} dist/* From 85e2c885a51689c445c52e721bbf0220cbc10a79 Mon Sep 17 00:00:00 2001 From: Montana Low Date: Fri, 17 Feb 2023 10:19:09 -0800 Subject: [PATCH 06/22] Fix typo #550 --- pgml-extension/src/bindings/sklearn.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pgml-extension/src/bindings/sklearn.py b/pgml-extension/src/bindings/sklearn.py index 5d95062aa..0d4b2930f 100644 --- a/pgml-extension/src/bindings/sklearn.py +++ b/pgml-extension/src/bindings/sklearn.py @@ -215,7 +215,7 @@ def calculate_metric(metric_name): elif metric_name == "mcc": func = matthews_corrcoef elif metric_name == "mse": - func = mean_squarred_error + func = mean_squared_error elif metric_name == "mae": func = mean_absolute_error elif metric_name == "confusion_matrix": From 4d28952b0cb4ba07eab5ea3154338657cf173c9a Mon Sep 17 00:00:00 2001 From: santiatpml <124207123+santiatpml@users.noreply.github.com> Date: Tue, 21 Feb 2023 19:42:38 -0800 Subject: [PATCH 07/22] Dashboard snapshots fix (#552) --- pgml-dashboard/Cargo.lock | 2 +- pgml-dashboard/src/models.rs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/pgml-dashboard/Cargo.lock b/pgml-dashboard/Cargo.lock index 4d9318ea3..0039ab137 100644 --- a/pgml-dashboard/Cargo.lock +++ b/pgml-dashboard/Cargo.lock @@ -1529,7 +1529,7 @@ dependencies = [ [[package]] name = "pgml-dashboard" -version = "0.1.1" +version = "2.2.0" dependencies = [ "anyhow", "bigdecimal", diff --git a/pgml-dashboard/src/models.rs b/pgml-dashboard/src/models.rs index 45c78f9b4..196acb871 100644 --- a/pgml-dashboard/src/models.rs +++ b/pgml-dashboard/src/models.rs @@ -549,8 +549,7 @@ impl Snapshot { analysis, created_at, updated_at - FROM pgml.snapshots - " + FROM pgml.snapshots JOIN pg_class ON oid::regclass::text = relation_name" ) .fetch_all(pool) .await?) @@ -568,7 +567,7 @@ impl Snapshot { analysis, created_at, updated_at - FROM pgml.snapshots + FROM pgml.snapshots JOIN pg_class ON oid::regclass::text = relation_name WHERE id = $1", id, ) From 7f2e5c272c7d838cc9bb190d55464486a3b3629f Mon Sep 17 00:00:00 2001 From: santiatpml <124207123+santiatpml@users.noreply.github.com> Date: Thu, 23 Feb 2023 15:01:35 -0800 Subject: [PATCH 08/22] Santi dashboard snapshots fix2 (#554) --- pgml-dashboard/.gitignore | 1 + pgml-dashboard/src/guards.rs | 5 +- pgml-dashboard/src/lib.rs | 22 ++--- pgml-dashboard/src/main.rs | 4 +- pgml-dashboard/src/models.rs | 124 ++++++++++++++---------- pgml-dashboard/src/templates.rs | 2 - pgml-dashboard/templates/model.html | 4 +- pgml-dashboard/templates/snapshot.html | 9 +- pgml-dashboard/templates/snapshots.html | 2 +- 9 files changed, 101 insertions(+), 72 deletions(-) diff --git a/pgml-dashboard/.gitignore b/pgml-dashboard/.gitignore index 485dee64b..dc271fe75 100644 --- a/pgml-dashboard/.gitignore +++ b/pgml-dashboard/.gitignore @@ -1 +1,2 @@ .idea +test_table.sql diff --git a/pgml-dashboard/src/guards.rs b/pgml-dashboard/src/guards.rs index 6c672405a..d7fb22ac1 100644 --- a/pgml-dashboard/src/guards.rs +++ b/pgml-dashboard/src/guards.rs @@ -49,7 +49,10 @@ impl<'r> FromRequest<'r> for Cluster { None => return Outcome::Failure((Status::BadRequest, ())), }; - Outcome::Success(Cluster { pool, context: shared_state.get_context(cluster_id) }) + Outcome::Success(Cluster { + pool, + context: shared_state.get_context(cluster_id), + }) } } diff --git a/pgml-dashboard/src/lib.rs b/pgml-dashboard/src/lib.rs index dd58ce92c..27ed0fb5b 100644 --- a/pgml-dashboard/src/lib.rs +++ b/pgml-dashboard/src/lib.rs @@ -31,7 +31,7 @@ use sqlx::Executor; #[derive(Debug, Default, Clone)] pub struct Context { pub user: models::User, - pub cluster: models::Cluster, + pub cluster: models::Cluster, } /// Globally shared state, saved in memory. @@ -51,10 +51,13 @@ impl Clusters { .max_connections(5) .idle_timeout(std::time::Duration::from_millis(15_000)) .min_connections(0) - .after_connect(|conn, _meta| Box::pin(async move { - conn.execute("SET application_name = 'pgml_dashboard';").await?; - Ok(()) - })) + .after_connect(|conn, _meta| { + Box::pin(async move { + conn.execute("SET application_name = 'pgml_dashboard';") + .await?; + Ok(()) + }) + }) .connect_lazy(database_url)?; pools.insert(cluster_id, pool.clone()); @@ -417,18 +420,11 @@ pub async fn models_get(cluster: Cluster, id: i64) -> Result Result { let snapshots = models::Snapshot::all(cluster.pool()).await?; - let mut table_sizes = HashMap::new(); - - for snapshot in &snapshots { - let table_size = snapshot.table_size(cluster.pool()).await?; - table_sizes.insert(snapshot.id, table_size); - } Ok(ResponseOk( templates::Snapshots { topic: "snapshots".to_string(), snapshots, - table_sizes, context: cluster.context.clone(), } .render_once() @@ -440,6 +436,7 @@ pub async fn snapshots_index(cluster: Cluster) -> Result Result { let snapshot = models::Snapshot::get_by_id(cluster.pool(), id).await?; let samples = snapshot.samples(cluster.pool(), 500).await?; + let models = snapshot.models(cluster.pool()).await?; let mut projects = HashMap::new(); @@ -450,7 +447,6 @@ pub async fn snapshots_get(cluster: Cluster, id: i64) -> Result, pub created_at: PrimitiveDateTime, pub updated_at: PrimitiveDateTime, + pub exists: bool, + pub table_size: String, } impl Snapshot { @@ -548,8 +553,22 @@ impl Snapshot { columns, analysis, created_at, - updated_at - FROM pgml.snapshots JOIN pg_class ON oid::regclass::text = relation_name" + updated_at, + CASE + WHEN EXISTS ( + SELECT 1 + FROM pg_class c + WHERE c.oid::regclass::text = relation_name + ) THEN pg_size_pretty(pg_total_relation_size(relation_name::regclass)) + ELSE '0 Bytes' + END AS \"table_size!\", + EXISTS ( + SELECT 1 + FROM pg_class c + WHERE c.oid::regclass::text = relation_name + ) AS \"exists!\" + FROM pgml.snapshots + " ) .fetch_all(pool) .await?) @@ -566,25 +585,27 @@ impl Snapshot { columns, analysis, created_at, - updated_at - FROM pgml.snapshots JOIN pg_class ON oid::regclass::text = relation_name - WHERE id = $1", + updated_at, + CASE + WHEN EXISTS ( + SELECT 1 + FROM pg_class c + WHERE c.oid::regclass::text = relation_name + ) THEN pg_size_pretty(pg_total_relation_size(relation_name::regclass)) + ELSE '0 Bytes' + END AS \"table_size!\", + EXISTS ( + SELECT 1 + FROM pg_class c + WHERE c.oid::regclass::text = relation_name + ) AS \"exists!\" + FROM pgml.snapshots WHERE id = $1", id, ) .fetch_one(pool) .await?) } - pub async fn table_size(&self, pool: &PgPool) -> anyhow::Result { - let row = - sqlx::query("SELECT pg_size_pretty(pg_total_relation_size($1))::TEXT AS table_size") - .bind(&self.relation_name) - .fetch_one(pool) - .await?; - - Ok(row.try_get("table_size")?) - } - pub fn rows(&self) -> Option { match self.analysis.as_ref() { Some(analysis) => match analysis.get("samples") { @@ -600,26 +621,28 @@ impl Snapshot { pool: &PgPool, rows: i64, ) -> anyhow::Result>> { - let rows = sqlx::query(&format!( - "SELECT row_to_json(row) r - FROM (SELECT * FROM {} LIMIT $1) row", - self.relation_name - )) - .bind(rows) - .fetch_all(pool) - .await?; - let mut samples = HashMap::new(); - rows.iter().for_each(|row| { - let r: serde_json::Value = row.try_get("r").unwrap(); - let obj = r.as_object().unwrap(); + if self.exists { + let rows = sqlx::query(&format!( + "SELECT row_to_json(row) r + FROM (SELECT * FROM {} LIMIT $1) row", + self.relation_name + )) + .bind(rows) + .fetch_all(pool) + .await?; - for (key, value) in obj.iter() { - let rf = samples.entry(key.clone()).or_insert(Vec::new()); - rf.push(value.as_f64().unwrap_or(0.) as f32); - } - }); + rows.iter().for_each(|row| { + let r: serde_json::Value = row.try_get("r").unwrap(); + let obj = r.as_object().unwrap(); + + for (key, value) in obj.iter() { + let rf = samples.entry(key.clone()).or_insert(Vec::new()); + rf.push(value.as_f64().unwrap_or(0.) as f32); + } + }); + } Ok(samples) } @@ -664,7 +687,7 @@ impl Snapshot { } pub fn labels<'a>(&'a self) -> Option>> { - self.columns().map(|columns| + self.columns().map(|columns| { columns .into_iter() .filter(|column| { @@ -672,7 +695,7 @@ impl Snapshot { .contains(&column["name"].as_str().unwrap().to_string()) }) .collect() - ) + }) } pub async fn models(&self, pool: &PgPool) -> anyhow::Result> { @@ -680,32 +703,33 @@ impl Snapshot { } pub fn target_stddev(&self, name: &str) -> f32 { - match self.analysis + match self + .analysis .as_ref() .unwrap() .as_object() .unwrap() - .get(&format!("{}_stddev", name)) { + .get(&format!("{}_stddev", name)) + { // 2.1 Some(value) => value.as_f64().unwrap() as f32, // 2.2+ None => { let columns = self.columns().unwrap(); - let column = columns.iter().find(|column| - &column["name"].as_str().unwrap() == &name - ); + let column = columns + .iter() + .find(|column| &column["name"].as_str().unwrap() == &name); match column { - Some(column) => { - column.get("statistics") - .unwrap() - .as_object() - .unwrap() - .get("std_dev") - .unwrap() - .as_f64() - .unwrap() as f32 - }, - None => 0. + Some(column) => column + .get("statistics") + .unwrap() + .as_object() + .unwrap() + .get("std_dev") + .unwrap() + .as_f64() + .unwrap() as f32, + None => 0., } } } diff --git a/pgml-dashboard/src/templates.rs b/pgml-dashboard/src/templates.rs index b1a612a26..ec8f4235c 100644 --- a/pgml-dashboard/src/templates.rs +++ b/pgml-dashboard/src/templates.rs @@ -231,7 +231,6 @@ pub struct Model { pub struct Snapshots { pub topic: String, pub snapshots: Vec, - pub table_sizes: HashMap, pub context: Context, } @@ -242,7 +241,6 @@ pub struct Snapshot { pub snapshot: models::Snapshot, pub models: Vec, pub projects: HashMap, - pub table_size: String, pub samples: HashMap>, pub context: Context, } diff --git a/pgml-dashboard/templates/model.html b/pgml-dashboard/templates/model.html index 0011b43d1..ba41a4fee 100644 --- a/pgml-dashboard/templates/model.html +++ b/pgml-dashboard/templates/model.html @@ -12,8 +12,8 @@

model_training<%= model.algor
Project
<%= project.name %>
-
Snapshot
-
<%= snapshot.relation_name %>
+
Snapshot
+
<%= snapshot.relation_name %>
Created
diff --git a/pgml-dashboard/templates/snapshot.html b/pgml-dashboard/templates/snapshot.html index 1a9ba32d4..fdd890bdd 100644 --- a/pgml-dashboard/templates/snapshot.html +++ b/pgml-dashboard/templates/snapshot.html @@ -13,7 +13,7 @@

storage<%= snapshot.relation_
Snapshot ID
<%= snapshot.id %>
Table Size
-
<%= table_size %>
+
<%= snapshot.table_size %>
Rows
<%= snapshot.rows().unwrap() %>
Features
@@ -61,6 +61,7 @@

model_trainingModels

<% } %> +<% if snapshot.exists { %>

label_importantLabels

<% for label in snapshot.labels().unwrap().iter() { @@ -131,5 +132,9 @@

<%= name %> <%= feature["pg_type"].as_str().unwrap() | upper %> - +<%} else { %> +
+

Data no longer exists to plot statistics

+
+<% }%> <% include!("footer.html"); %> diff --git a/pgml-dashboard/templates/snapshots.html b/pgml-dashboard/templates/snapshots.html index 24778b701..6d9bf1832 100644 --- a/pgml-dashboard/templates/snapshots.html +++ b/pgml-dashboard/templates/snapshots.html @@ -7,7 +7,7 @@

storageSnapshots

  • <%= snapshot.relation_name %> - <%= table_sizes[&snapshot.id] %> + <%= snapshot.table_size %>
  • From 6eaa347f3b8c94f0fc23db3a486617798078757b Mon Sep 17 00:00:00 2001 From: santiatpml <124207123+santiatpml@users.noreply.github.com> Date: Fri, 24 Feb 2023 14:33:39 -0800 Subject: [PATCH 09/22] Dashboard backend tests using rocket (#556) --- pgml-dashboard/.gitignore | 2 +- pgml-dashboard/src/main.rs | 68 ++++++++++++++++++++++++++++++++++++ pgml-dashboard/src/models.rs | 3 -- 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/pgml-dashboard/.gitignore b/pgml-dashboard/.gitignore index dc271fe75..706fd07fa 100644 --- a/pgml-dashboard/.gitignore +++ b/pgml-dashboard/.gitignore @@ -1,2 +1,2 @@ .idea -test_table.sql +.vscode diff --git a/pgml-dashboard/src/main.rs b/pgml-dashboard/src/main.rs index 26b12726f..ea93ec056 100644 --- a/pgml-dashboard/src/main.rs +++ b/pgml-dashboard/src/main.rs @@ -29,3 +29,71 @@ async fn main() { .await .expect("failed to shut down Rocket"); } + +#[cfg(test)] +mod test { + use rocket::local::asynchronous::Client; + use pgml_dashboard::Clusters; + use pgml_dashboard::{index, migrate, paths}; + use rocket::fs::FileServer; + use rocket::{Build, Rocket}; + + + async fn rocket() -> Rocket { + let clusters = Clusters::new(); + clusters + .add(-1, &pgml_dashboard::guards::default_database_url()) + .unwrap(); + + migrate(&clusters.get(-1).unwrap()).await.unwrap(); + + rocket::build() + .manage(clusters) + .mount("/", rocket::routes![index,]) + .mount("/dashboard/static", FileServer::from("static")) + .mount("/dashboard", paths()) + } + + #[rocket::async_test] + async fn test_notebooks_index() { + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get("/dashboard/notebooks/").dispatch().await; + assert_eq!(response.status().code, 200); + } + + #[rocket::async_test] + async fn test_projects_index() { + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get("/dashboard/projects/").dispatch().await; + assert_eq!(response.status().code, 200); + } + + #[rocket::async_test] + async fn test_models_index() { + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get("/dashboard/models/").dispatch().await; + assert_eq!(response.status().code, 200); + } + + #[rocket::async_test] + async fn test_deployments_index() { + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get("/dashboard/deployments/").dispatch().await; + assert_eq!(response.status().code, 200); + } + + #[rocket::async_test] + async fn test_uploader() { + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get("/dashboard/uploader/").dispatch().await; + assert_eq!(response.status().code, 200); + } + + #[rocket::async_test] + async fn test_snapshots_index() { + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get("/dashboard/snapshots/").dispatch().await; + assert_eq!(response.status().code, 200); + } +} + diff --git a/pgml-dashboard/src/models.rs b/pgml-dashboard/src/models.rs index 01a2c333d..9469a8861 100644 --- a/pgml-dashboard/src/models.rs +++ b/pgml-dashboard/src/models.rs @@ -1,11 +1,9 @@ // Markdown use comrak::{markdown_to_html, ComrakExtensionOptions, ComrakOptions}; -use rocket::futures::TryFutureExt; // Templates use sailfish::TemplateOnce; -use sqlx::postgres::PgRow; // Database use sqlx::postgres::types::PgInterval; use sqlx::types::time::PrimitiveDateTime; @@ -19,7 +17,6 @@ use tokio::io::{AsyncBufReadExt, AsyncSeekExt}; use crate::templates; use std::collections::HashMap; -use std::str::FromStr; #[derive(FromRow, Debug, Clone)] pub struct Project { From 1e39fef285005ef6a2207ce9bd22975a412556b1 Mon Sep 17 00:00:00 2001 From: santiatpml <124207123+santiatpml@users.noreply.github.com> Date: Mon, 27 Feb 2023 11:19:03 -0800 Subject: [PATCH 10/22] Dashboard backend tests (#557) --- pgml-dashboard/Cargo.lock | 482 +++++++++++++++++++++++++++++++++++-- pgml-dashboard/Cargo.toml | 1 + pgml-dashboard/src/main.rs | 98 +++++++- 3 files changed, 559 insertions(+), 22 deletions(-) diff --git a/pgml-dashboard/Cargo.lock b/pgml-dashboard/Cargo.lock index 0039ab137..1a1a3af61 100644 --- a/pgml-dashboard/Cargo.lock +++ b/pgml-dashboard/Cargo.lock @@ -49,7 +49,7 @@ version = "0.7.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcb51a0695d8f838b1ee009b3fbf66bda078cd64590202a864a8f3e8c4315c47" dependencies = [ - "getrandom", + "getrandom 0.2.8", "once_cell", "version_check", ] @@ -354,6 +354,12 @@ dependencies = [ "xdg", ] +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "cookie" version = "0.16.2" @@ -365,7 +371,7 @@ dependencies = [ "hkdf", "hmac", "percent-encoding", - "rand", + "rand 0.8.5", "sha2", "subtle", "time 0.3.17", @@ -437,10 +443,37 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", - "rand_core", + "rand_core 0.6.4", "typenum", ] +[[package]] +name = "cssparser" +version = "0.27.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "754b69d351cdc2d8ee09ae203db831e005560fc6030da058f86ad60c92a9cb0a" +dependencies = [ + "cssparser-macros", + "dtoa-short", + "itoa 0.4.8", + "matches", + "phf 0.8.0", + "proc-macro2", + "quote", + "smallvec", + "syn", +] + +[[package]] +name = "cssparser-macros" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfae75de57f2b2e85e8768c3ea840fd159c8f33e2b6522c7835b7abac81be16e" +dependencies = [ + "quote", + "syn", +] + [[package]] name = "csv-async" version = "1.2.5" @@ -451,7 +484,7 @@ dependencies = [ "cfg-if", "csv-core", "futures", - "itoa", + "itoa 1.0.5", "ryu", "serde", ] @@ -518,6 +551,19 @@ dependencies = [ "syn", ] +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "devise" version = "0.3.1" @@ -588,6 +634,27 @@ version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03d8c417d7a8cb362e0c37e5d815f5eb7c37f79ff93707329d5a194e42e54ca0" +[[package]] +name = "dtoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "56899898ce76aaf4a0f24d914c97ea6ed976d42fec6ad33fcbb0a1103e07b2b0" + +[[package]] +name = "dtoa-short" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bde03329ae10e79ede66c9ce4dc930aa8599043b0743008548680f25b91502d6" +dependencies = [ + "dtoa", +] + +[[package]] +name = "ego-tree" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a68a4904193147e0a8dec3314640e6db742afd5f6e634f428a6af230d9b3591" + [[package]] name = "either" version = "1.8.1" @@ -709,6 +776,16 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "futf" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df420e2e84819663797d1ec6544b13c5be84629e7bb00dc960d6917db2987843" +dependencies = [ + "mac", + "new_debug_unreachable", +] + [[package]] name = "futures" version = "0.3.26" @@ -809,6 +886,15 @@ dependencies = [ "slab", ] +[[package]] +name = "fxhash" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c" +dependencies = [ + "byteorder", +] + [[package]] name = "generator" version = "0.7.2" @@ -832,6 +918,26 @@ dependencies = [ "version_check", ] +[[package]] +name = "getopts" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "14dbbfd5c71d70241ecf9e6f13737f7b5ce823821063188d7e46c41d371eebd5" +dependencies = [ + "unicode-width", +] + +[[package]] +name = "getrandom" +version = "0.1.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +dependencies = [ + "cfg-if", + "libc", + "wasi 0.9.0+wasi-snapshot-preview1", +] + [[package]] name = "getrandom" version = "0.2.8" @@ -962,6 +1068,20 @@ dependencies = [ "winapi", ] +[[package]] +name = "html5ever" +version = "0.26.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bea68cab48b8459f17cf1c944c67ddc572d272d9f2b274140f223ecb1da4a3b7" +dependencies = [ + "log", + "mac", + "markup5ever", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "http" version = "0.2.8" @@ -970,7 +1090,7 @@ checksum = "75f43d41e26995c17e71ee126451dd3941010b0514a81a9d11f3b341debc2399" dependencies = [ "bytes", "fnv", - "itoa", + "itoa 1.0.5", ] [[package]] @@ -1011,7 +1131,7 @@ dependencies = [ "http-body", "httparse", "httpdate", - "itoa", + "itoa 1.0.5", "pin-project-lite", "socket2", "tokio", @@ -1120,6 +1240,12 @@ dependencies = [ "either", ] +[[package]] +name = "itoa" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b71991ff56294aa922b450139ee08b3bfc70982c6b2c7562771375cf73542dd4" + [[package]] name = "itoa" version = "1.0.5" @@ -1217,6 +1343,26 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "mac" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c41e0c4fef86961ac6d6f8a82609f55f31b05e4fce149ac5710e439df7619ba4" + +[[package]] +name = "markup5ever" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a2629bb1404f3d34c2e921f21fd34ba00b206124c81f65c50b43b6aaefeb016" +dependencies = [ + "log", + "phf 0.10.1", + "phf_codegen 0.10.0", + "string_cache", + "string_cache_codegen", + "tendril", +] + [[package]] name = "matchers" version = "0.1.0" @@ -1226,6 +1372,12 @@ dependencies = [ "regex-automata", ] +[[package]] +name = "matches" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2532096657941c2fea9c289d370a250971c689d4f143798ff67113ec042024a5" + [[package]] name = "md-5" version = "0.10.5" @@ -1294,6 +1446,18 @@ dependencies = [ "version_check", ] +[[package]] +name = "new_debug_unreachable" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54" + +[[package]] +name = "nodrop" +version = "0.1.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72ef4a56884ca558e5ddb05a1d1e7e1bfd9a68d9ed024c21704cc98872dae1bb" + [[package]] name = "nom" version = "7.1.3" @@ -1537,14 +1701,107 @@ dependencies = [ "comrak", "csv-async", "parking_lot 0.12.1", - "rand", + "rand 0.8.5", "rocket", "sailfish", + "scraper", "serde_json", "sqlx", "tokio", ] +[[package]] +name = "phf" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3dfb61232e34fcb633f43d12c58f83c1df82962dcdfa565a4e866ffc17dafe12" +dependencies = [ + "phf_macros", + "phf_shared 0.8.0", + "proc-macro-hack", +] + +[[package]] +name = "phf" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fabbf1ead8a5bcbc20f5f8b939ee3f5b0f6f281b6ad3468b84656b658b455259" +dependencies = [ + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_codegen" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cbffee61585b0411840d3ece935cce9cb6321f01c45477d30066498cd5e1a815" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", +] + +[[package]] +name = "phf_codegen" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb1c3a8bc4dd4e5cfce29b44ffc14bedd2ee294559a294e2a4d4c9e9a6a13cd" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", +] + +[[package]] +name = "phf_generator" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" +dependencies = [ + "phf_shared 0.8.0", + "rand 0.7.3", +] + +[[package]] +name = "phf_generator" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5285893bb5eb82e6aaf5d59ee909a06a16737a8970984dd7746ba9283498d6" +dependencies = [ + "phf_shared 0.10.0", + "rand 0.8.5", +] + +[[package]] +name = "phf_macros" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f6fde18ff429ffc8fe78e2bf7f8b7a5a5a6e2a8b58bc5a9ac69198bbda9189c" +dependencies = [ + "phf_generator 0.8.0", + "phf_shared 0.8.0", + "proc-macro-hack", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "phf_shared" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" +dependencies = [ + "siphasher", +] + +[[package]] +name = "phf_shared" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096" +dependencies = [ + "siphasher", +] + [[package]] name = "pin-project-lite" version = "0.2.9" @@ -1595,6 +1852,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "precomputed-hash" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" + [[package]] name = "proc-macro-error" version = "1.0.4" @@ -1619,6 +1882,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "proc-macro-hack" +version = "0.5.20+deprecated" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc375e1527247fe1a97d8b7156678dfe7c1af2fc075c9a4db3690ecd2a148068" + [[package]] name = "proc-macro2" version = "1.0.51" @@ -1659,6 +1928,20 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rand" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03" +dependencies = [ + "getrandom 0.1.16", + "libc", + "rand_chacha 0.2.2", + "rand_core 0.5.1", + "rand_hc", + "rand_pcg", +] + [[package]] name = "rand" version = "0.8.5" @@ -1666,8 +1949,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402" +dependencies = [ + "ppv-lite86", + "rand_core 0.5.1", ] [[package]] @@ -1677,7 +1970,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_core" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19" +dependencies = [ + "getrandom 0.1.16", ] [[package]] @@ -1686,7 +1988,25 @@ version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" dependencies = [ - "getrandom", + "getrandom 0.2.8", +] + +[[package]] +name = "rand_hc" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c" +dependencies = [ + "rand_core 0.5.1", +] + +[[package]] +name = "rand_pcg" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16abd0c1b639e9eb4d7c50c0b8100b0d0f849be2349829c740fe8e6eb4816429" +dependencies = [ + "rand_core 0.5.1", ] [[package]] @@ -1704,7 +2024,7 @@ version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b033d837a7cf162d7993aded9304e30a83213c648b6e389db233191f891e5c2b" dependencies = [ - "getrandom", + "getrandom 0.2.8", "redox_syscall", "thiserror", ] @@ -1801,7 +2121,7 @@ dependencies = [ "num_cpus", "parking_lot 0.12.1", "pin-project-lite", - "rand", + "rand 0.8.5", "ref-cast", "rocket_codegen", "rocket_http", @@ -1860,6 +2180,15 @@ dependencies = [ "uncased", ] +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "rustix" version = "0.36.8" @@ -1972,6 +2301,22 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "scraper" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7cb4dae083699a22a65aa9d2699c27f525e35dffaec38b10801e958ed4cf27" +dependencies = [ + "cssparser", + "ego-tree", + "getopts", + "html5ever", + "matches", + "selectors", + "smallvec", + "tendril", +] + [[package]] name = "scratch" version = "1.0.3" @@ -1988,6 +2333,32 @@ dependencies = [ "untrusted", ] +[[package]] +name = "selectors" +version = "0.22.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df320f1889ac4ba6bc0cdc9c9af7af4bd64bb927bccdf32d81140dc1f9be12fe" +dependencies = [ + "bitflags", + "cssparser", + "derive_more", + "fxhash", + "log", + "matches", + "phf 0.8.0", + "phf_codegen 0.8.0", + "precomputed-hash", + "servo_arc", + "smallvec", + "thin-slice", +] + +[[package]] +name = "semver" +version = "1.0.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "58bc9567378fc7690d6b2addae4e60ac2eeea07becb2c64b9f218b53865cba2a" + [[package]] name = "serde" version = "1.0.152" @@ -2014,11 +2385,21 @@ version = "1.0.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7434af0dc1cbd59268aa98b4c22c131c0584d2232f6fb166efb993e2832e896a" dependencies = [ - "itoa", + "itoa 1.0.5", "ryu", "serde", ] +[[package]] +name = "servo_arc" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d98238b800e0d1576d8b6e3de32827c2d74bee68bb97748dcf5071fb53965432" +dependencies = [ + "nodrop", + "stable_deref_trait", +] + [[package]] name = "sha1" version = "0.10.5" @@ -2065,6 +2446,12 @@ dependencies = [ "libc", ] +[[package]] +name = "siphasher" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de" + [[package]] name = "slab" version = "0.4.7" @@ -2151,7 +2538,7 @@ dependencies = [ "hkdf", "hmac", "indexmap", - "itoa", + "itoa 1.0.5", "libc", "log", "md-5", @@ -2160,7 +2547,7 @@ dependencies = [ "once_cell", "paste", "percent-encoding", - "rand", + "rand 0.8.5", "rustls", "rustls-pemfile", "serde", @@ -2222,6 +2609,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "state" version = "0.5.3" @@ -2231,6 +2624,32 @@ dependencies = [ "loom", ] +[[package]] +name = "string_cache" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "213494b7a2b503146286049378ce02b482200519accc31872ee8be91fa820a08" +dependencies = [ + "new_debug_unreachable", + "once_cell", + "parking_lot 0.12.1", + "phf_shared 0.10.0", + "precomputed-hash", + "serde", +] + +[[package]] +name = "string_cache_codegen" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6bb30289b722be4ff74a408c3cc27edeaad656e06cb1fe8fa9231fa59c728988" +dependencies = [ + "phf_generator 0.10.0", + "phf_shared 0.10.0", + "proc-macro2", + "quote", +] + [[package]] name = "stringprep" version = "0.1.2" @@ -2302,6 +2721,17 @@ dependencies = [ "winapi", ] +[[package]] +name = "tendril" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d24a120c5fc464a3458240ee02c299ebcb9d67b5249c8848b09d639dca8d7bb0" +dependencies = [ + "futf", + "mac", + "utf-8", +] + [[package]] name = "termcolor" version = "1.2.0" @@ -2321,6 +2751,12 @@ dependencies = [ "windows-sys 0.42.0", ] +[[package]] +name = "thin-slice" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8eaa81235c7058867fa8c0e7314f33dcce9c215f535d1913822a2b3f5e289f3c" + [[package]] name = "thiserror" version = "1.0.38" @@ -2367,7 +2803,7 @@ version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" dependencies = [ - "itoa", + "itoa 1.0.5", "serde", "time-core", "time-macros", @@ -2662,6 +3098,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "utf-8" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9" + [[package]] name = "uuid" version = "1.3.0" @@ -2701,6 +3143,12 @@ dependencies = [ "try-lock", ] +[[package]] +name = "wasi" +version = "0.9.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519" + [[package]] name = "wasi" version = "0.10.0+wasi-snapshot-preview1" diff --git a/pgml-dashboard/Cargo.toml b/pgml-dashboard/Cargo.toml index 47523e0b6..08bb713b8 100644 --- a/pgml-dashboard/Cargo.toml +++ b/pgml-dashboard/Cargo.toml @@ -24,3 +24,4 @@ bigdecimal = "0.3" chrono = "0.4" serde_json = "1" csv-async = "1" +scraper = "0.14.0" diff --git a/pgml-dashboard/src/main.rs b/pgml-dashboard/src/main.rs index ea93ec056..05bdc6e7c 100644 --- a/pgml-dashboard/src/main.rs +++ b/pgml-dashboard/src/main.rs @@ -32,21 +32,22 @@ async fn main() { #[cfg(test)] mod test { - use rocket::local::asynchronous::Client; use pgml_dashboard::Clusters; use pgml_dashboard::{index, migrate, paths}; use rocket::fs::FileServer; + use rocket::local::asynchronous::Client; use rocket::{Build, Rocket}; - + use scraper::{Html, Selector}; + use std::vec::Vec; async fn rocket() -> Rocket { let clusters = Clusters::new(); clusters .add(-1, &pgml_dashboard::guards::default_database_url()) .unwrap(); - + migrate(&clusters.get(-1).unwrap()).await.unwrap(); - + rocket::build() .manage(clusters) .mount("/", rocket::routes![index,]) @@ -54,6 +55,19 @@ mod test { .mount("/dashboard", paths()) } + fn get_href_links(body: &str, pattern: &str) -> Vec { + let document = Html::parse_document(body); + let selector = Selector::parse("a").unwrap(); + let mut output = Vec::::new(); + for element in document.select(&selector) { + let href = element.value().attr("href").unwrap(); + if href.contains(pattern) && href != pattern { + output.push(String::from(href)); + } + } + output + } + #[rocket::async_test] async fn test_notebooks_index() { let client = Client::tracked(rocket().await).await.unwrap(); @@ -95,5 +109,79 @@ mod test { let response = client.get("/dashboard/snapshots/").dispatch().await; assert_eq!(response.status().code, 200); } -} + #[rocket::async_test] + async fn test_snapshot_entries() { + let snapshots_endpoint = "/dashboard/snapshots/"; + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get(snapshots_endpoint).dispatch().await; + + let body = response.into_string().await.unwrap(); + let snapshot_links = get_href_links(body.as_str(), snapshots_endpoint); + + for link in snapshot_links { + let response = client.get(link.as_str()).dispatch().await; + assert_eq!(response.status().code, 200); + } + } + + #[rocket::async_test] + async fn test_notebook_entries() { + let notebooks_endpoint = "/dashboard/notebooks/"; + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get(notebooks_endpoint).dispatch().await; + + let body = response.into_string().await.unwrap(); + let notebook_links = get_href_links(body.as_str(), notebooks_endpoint); + + for link in notebook_links { + let response = client.get(link.as_str()).dispatch().await; + assert_eq!(response.status().code, 200); + } + } + + #[rocket::async_test] + async fn test_project_entries() { + let projects_endpoint = "/dashboard/projects/"; + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get(projects_endpoint).dispatch().await; + + let body = response.into_string().await.unwrap(); + let project_links = get_href_links(body.as_str(), projects_endpoint); + + for link in project_links { + let response = client.get(link.as_str()).dispatch().await; + assert_eq!(response.status().code, 200); + } + } + + #[rocket::async_test] + async fn test_model_entries() { + let models_endpoint = "/dashboard/models/"; + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get(models_endpoint).dispatch().await; + + let body = response.into_string().await.unwrap(); + let model_links = get_href_links(body.as_str(), models_endpoint); + + for link in model_links { + let response = client.get(link.as_str()).dispatch().await; + assert_eq!(response.status().code, 200); + } + } + + #[rocket::async_test] + async fn test_deployment_entries() { + let deployments_endpoint = "/dashboard/deployments/"; + let client = Client::tracked(rocket().await).await.unwrap(); + let response = client.get(deployments_endpoint).dispatch().await; + + let body = response.into_string().await.unwrap(); + let deployment_links = get_href_links(body.as_str(), deployments_endpoint); + + for link in deployment_links { + let response = client.get(link.as_str()).dispatch().await; + assert_eq!(response.status().code, 200); + } + } +} From 26a7619fc5da6a695a50082ce491dad93f590a18 Mon Sep 17 00:00:00 2001 From: Montana Low Date: Wed, 1 Mar 2023 09:27:52 -0800 Subject: [PATCH 11/22] Building the app depends on the extension being installed (#559) --- docker-compose.yml | 15 +++++++-------- pgml-dashboard/Dockerfile | 2 +- 2 files changed, 8 insertions(+), 9 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index 2852f8788..3d49c620b 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,10 +2,10 @@ version: "3" services: postgres: healthcheck: - test: psql -c 'SELECT 1' -U postgres -h 127.0.0.1 - interval: 2s - retries: 3 - timeout: 2s + test: [ "CMD-SHELL", "pg_isready" ] + interval: 1s + timeout: 5s + retries: 100 build: context: ./pgml-extension/ dockerfile: Dockerfile.local @@ -16,7 +16,8 @@ services: - infinity dashboard: depends_on: - - postgres + postgres: + condition: service_healthy build: context: ./pgml-dashboard/ dockerfile: Dockerfile @@ -25,9 +26,7 @@ services: environment: ROCKET_ADDRESS: 0.0.0.0 DATABASE_URL: postgres://postgres:postgres@postgres:5432/pgml_development - command: - - cargo - - run + command: bash -c "sqlx migrate run && cargo run" docs: build: context: ./pgml-docs/ diff --git a/pgml-dashboard/Dockerfile b/pgml-dashboard/Dockerfile index a800d772d..a6bef3507 100644 --- a/pgml-dashboard/Dockerfile +++ b/pgml-dashboard/Dockerfile @@ -1,4 +1,4 @@ FROM rust:1 COPY . /app WORKDIR /app -RUN cargo build +RUN cargo install sqlx-cli From b1a3aea8064234c6000931de26397ab9b5e40998 Mon Sep 17 00:00:00 2001 From: Dan <39170265+chillenberger@users.noreply.github.com> Date: Wed, 1 Mar 2023 14:08:43 -0700 Subject: [PATCH 12/22] Add Dan to docs team page (#560) --- pgml-docs/docs/about/team.md | 5 +++++ pgml-docs/docs/images/team/daniel.jpg | Bin 0 -> 50259 bytes 2 files changed, 5 insertions(+) create mode 100644 pgml-docs/docs/images/team/daniel.jpg diff --git a/pgml-docs/docs/about/team.md b/pgml-docs/docs/about/team.md index 999c2c823..5a0027160 100644 --- a/pgml-docs/docs/about/team.md +++ b/pgml-docs/docs/about/team.md @@ -27,4 +27,9 @@

    Santi Adavani

    santiatpml +
  • + Daniel Illenberger +

    Daniel Illenberger

    + chillenberger +
  • diff --git a/pgml-docs/docs/images/team/daniel.jpg b/pgml-docs/docs/images/team/daniel.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ab31d934fe5ae4058b8d634bb6d67272e6e0034f GIT binary patch literal 50259 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%0DYjqIn|Sv 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 z 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 Date: Sat, 11 Mar 2023 10:23:25 -0800 Subject: [PATCH 13/22] fix vscode error (#561) --- pgml-dashboard/Cargo.lock | 12 +- pgml-dashboard/Cargo.toml | 3 +- pgml-dashboard/sqlx-data.json | 1088 --------------------------------- pgml-dashboard/src/main.rs | 2 + pgml-extension/Cargo.lock | 86 +-- pgml-extension/Cargo.toml | 6 +- 6 files changed, 57 insertions(+), 1140 deletions(-) delete mode 100644 pgml-dashboard/sqlx-data.json diff --git a/pgml-dashboard/Cargo.lock b/pgml-dashboard/Cargo.lock index 1a1a3af61..dbbe5015c 100644 --- a/pgml-dashboard/Cargo.lock +++ b/pgml-dashboard/Cargo.lock @@ -628,6 +628,12 @@ dependencies = [ "winapi", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + [[package]] name = "dotenvy" version = "0.15.6" @@ -660,9 +666,6 @@ name = "either" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91" -dependencies = [ - "serde", -] [[package]] name = "encoding_rs" @@ -1700,6 +1703,7 @@ dependencies = [ "chrono", "comrak", "csv-async", + "dotenv", "parking_lot 0.12.1", "rand 0.8.5", "rocket", @@ -2576,11 +2580,9 @@ dependencies = [ "dotenvy", "either", "heck", - "hex", "once_cell", "proc-macro2", "quote", - "serde", "serde_json", "sha2", "sqlx-core", diff --git a/pgml-dashboard/Cargo.toml b/pgml-dashboard/Cargo.toml index 08bb713b8..29f2cee05 100644 --- a/pgml-dashboard/Cargo.toml +++ b/pgml-dashboard/Cargo.toml @@ -14,7 +14,7 @@ include = ["src/", "sqlx-data.json", "templates/", "migrations/", "static/"] [dependencies] sailfish = "0.5.0" rocket = {version = "0.5.0-rc.2", features = ["secrets"] } -sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "postgres", "json", "tls", "migrate", "time", "uuid", "bigdecimal", "offline",] } +sqlx = { version = "0.6", features = [ "runtime-tokio-rustls", "postgres", "json", "tls", "migrate", "time", "uuid", "bigdecimal",] } anyhow = "1" tokio = { version = "1", features = ["full"] } rand = "0.8" @@ -25,3 +25,4 @@ chrono = "0.4" serde_json = "1" csv-async = "1" scraper = "0.14.0" +dotenv = "0.15" diff --git a/pgml-dashboard/sqlx-data.json b/pgml-dashboard/sqlx-data.json deleted file mode 100644 index 2cabe6a07..000000000 --- a/pgml-dashboard/sqlx-data.json +++ /dev/null @@ -1,1088 +0,0 @@ -{ - "db": "PostgreSQL", - "2b949ed3fd8c81fac4d4e75bc2aa545f506933ee1310f6443c6da8656090f868": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "notebook_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "cell_type", - "ordinal": 2, - "type_info": "Int4" - }, - { - "name": "contents", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "rendering", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "execution_time", - "ordinal": 5, - "type_info": "Interval" - }, - { - "name": "cell_number", - "ordinal": 6, - "type_info": "Int4" - }, - { - "name": "version", - "ordinal": 7, - "type_info": "Int4" - }, - { - "name": "deleted_at", - "ordinal": 8, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - true, - true, - false, - false, - true - ], - "parameters": { - "Left": [ - "Int8", - "Int4", - "Text" - ] - } - }, - "query": "\n WITH\n lock AS (\n SELECT * FROM notebooks WHERE id = $1 FOR UPDATE\n ),\n max_cell AS (\n SELECT COALESCE(MAX(cell_number), 0) AS cell_number\n FROM notebook_cells\n WHERE notebook_id = $1\n AND deleted_at IS NULL\n )\n INSERT INTO notebook_cells\n (notebook_id, cell_type, contents, cell_number, version)\n VALUES\n ($1, $2, $3, (SELECT cell_number + 1 FROM max_cell), 1)\n RETURNING id,\n notebook_id,\n cell_type,\n contents,\n rendering,\n execution_time,\n cell_number,\n version,\n deleted_at" - }, - "30733a476a80f506339edafc421e2a848e3490aed976a1fb98d20632985cb5ca": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int8", - "Int4" - ] - } - }, - "query": "UPDATE notebook_cells\n SET\n execution_time = NULL,\n rendering = NULL\n WHERE notebook_id = $1\n AND cell_type = $2" - }, - "51257105e59d2bce390aae36f75744d49bb1cc7e59de6056234df1010663661d": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "created_at", - "ordinal": 1, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false - ], - "parameters": { - "Left": [] - } - }, - "query": "INSERT INTO uploaded_files (id, created_at) VALUES (DEFAULT, DEFAULT)\n RETURNING id, created_at" - }, - "5504a78267b524894a27c15681790984275dc66c320c59e35b4ab8a198482fab": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "created_at", - "ordinal": 2, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 3, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Varchar" - ] - } - }, - "query": "INSERT INTO notebooks (name) VALUES ($1) RETURNING *" - }, - "5a6f49b8f5a11545a42c1192c05cc567b4f3ebe798117323f711915f60f73d62": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "created_at", - "ordinal": 2, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 3, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false - ], - "parameters": { - "Left": [] - } - }, - "query": "SELECT * FROM notebooks" - }, - "66537e5976abe208d4f7821e0daf731bf259fa1b6851d761cab0460f233e7220": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Int4", - "Text", - "Int8" - ] - } - }, - "query": "UPDATE notebook_cells\n SET\n cell_type = $1,\n contents = $2,\n version = version + 1\n WHERE id = $3" - }, - "6f41282ede12eecff1f423e1cf75244c53ac26e8117910efd889fe74c786acbe": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "notebook_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "cell_type", - "ordinal": 2, - "type_info": "Int4" - }, - { - "name": "contents", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "rendering", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "execution_time", - "ordinal": 5, - "type_info": "Interval" - }, - { - "name": "cell_number", - "ordinal": 6, - "type_info": "Int4" - }, - { - "name": "version", - "ordinal": 7, - "type_info": "Int4" - }, - { - "name": "deleted_at", - "ordinal": 8, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - true, - true, - false, - false, - true - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "UPDATE notebook_cells\n SET deleted_at = NOW()\n WHERE id = $1\n RETURNING id,\n notebook_id,\n cell_type,\n contents,\n rendering,\n execution_time,\n cell_number,\n version,\n deleted_at" - }, - "7095e7b76e23fa7af3ab2cacc42778645f8cd748e5e0c2ec392208dac6755622": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "snapshot_id", - "ordinal": 2, - "type_info": "Int8" - }, - { - "name": "num_features", - "ordinal": 3, - "type_info": "Int4" - }, - { - "name": "algorithm", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "runtime", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "hyperparams", - "ordinal": 6, - "type_info": "Jsonb" - }, - { - "name": "status", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "metrics", - "ordinal": 8, - "type_info": "Jsonb" - }, - { - "name": "search", - "ordinal": 9, - "type_info": "Text" - }, - { - "name": "search_params", - "ordinal": 10, - "type_info": "Jsonb" - }, - { - "name": "search_args", - "ordinal": 11, - "type_info": "Jsonb" - }, - { - "name": "created_at", - "ordinal": 12, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 13, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - null, - false, - false, - true, - true, - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT\n id,\n project_id,\n snapshot_id,\n num_features,\n algorithm,\n runtime::TEXT,\n hyperparams,\n status,\n metrics,\n search,\n search_params,\n search_args,\n created_at,\n updated_at\n FROM pgml.models\n WHERE snapshot_id = $1\n " - }, - "7285e17ea8ee359929b9df1e6631f6fd94da94c6ff19acc6c144bbe46b9b902b": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "model_id", - "ordinal": 2, - "type_info": "Int8" - }, - { - "name": "strategy", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "created_at", - "ordinal": 4, - "type_info": "Timestamp" - }, - { - "name": "active", - "ordinal": 5, - "type_info": "Bool" - } - ], - "nullable": [ - false, - false, - false, - null, - false, - null - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT\n a.id,\n project_id,\n model_id,\n strategy::TEXT,\n created_at,\n a.id = last_deployment.id AS active\n FROM pgml.deployments a\n CROSS JOIN LATERAL (\n SELECT id FROM pgml.deployments b\n WHERE b.project_id = a.project_id\n ORDER BY b.id DESC\n LIMIT 1\n ) last_deployment\n WHERE project_id = $1\n ORDER BY a.id DESC" - }, - "78edce72d0197433efa2c7d1fdeaf7bcc98db17edc4941d8069c99de2a636418": { - "describe": { - "columns": [], - "nullable": [], - "parameters": { - "Left": [ - "Text", - "Int8" - ] - } - }, - "query": "UPDATE notebook_cells SET rendering = $1 WHERE id = $2" - }, - "96ba78cf2502167ee92b77f34c8955b63a94befd6bfabb209b3f8c477ec1170f": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "snapshot_id", - "ordinal": 2, - "type_info": "Int8" - }, - { - "name": "num_features", - "ordinal": 3, - "type_info": "Int4" - }, - { - "name": "algorithm", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "runtime", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "hyperparams", - "ordinal": 6, - "type_info": "Jsonb" - }, - { - "name": "status", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "metrics", - "ordinal": 8, - "type_info": "Jsonb" - }, - { - "name": "search", - "ordinal": 9, - "type_info": "Text" - }, - { - "name": "search_params", - "ordinal": 10, - "type_info": "Jsonb" - }, - { - "name": "search_args", - "ordinal": 11, - "type_info": "Jsonb" - }, - { - "name": "created_at", - "ordinal": 12, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 13, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - null, - false, - false, - true, - true, - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT\n id,\n project_id,\n snapshot_id,\n num_features,\n algorithm,\n runtime::TEXT,\n hyperparams,\n status,\n metrics,\n search,\n search_params,\n search_args,\n created_at,\n updated_at\n FROM pgml.models\n WHERE project_id = $1\n " - }, - "a1bfdea1adb5c535590879f89be80cd12163c1dc95f290c11ca9dd19617883c9": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "relation_name", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "y_column_name", - "ordinal": 2, - "type_info": "TextArray" - }, - { - "name": "test_size", - "ordinal": 3, - "type_info": "Float4" - }, - { - "name": "test_sampling", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "status", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "columns", - "ordinal": 6, - "type_info": "Jsonb" - }, - { - "name": "analysis", - "ordinal": 7, - "type_info": "Jsonb" - }, - { - "name": "created_at", - "ordinal": 8, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 9, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - null, - false, - true, - true, - false, - false - ], - "parameters": { - "Left": [] - } - }, - "query": "SELECT id,\n relation_name,\n y_column_name,\n test_size,\n test_sampling::TEXT,\n status,\n columns,\n analysis,\n created_at,\n updated_at\n FROM pgml.snapshots\n " - }, - "a517e8deb38ba9eddc46e4fee9b776f02718384bba453f8f33b38f56578e813d": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Varchar" - }, - { - "name": "created_at", - "ordinal": 2, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 3, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT * FROM notebooks WHERE id = $1" - }, - "bf168ae743f7b366e4a30a7e891fa1d16b3815181d19ac7a35d553b20d72f983": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "notebook_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "cell_type", - "ordinal": 2, - "type_info": "Int4" - }, - { - "name": "contents", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "rendering", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "execution_time", - "ordinal": 5, - "type_info": "Interval" - }, - { - "name": "cell_number", - "ordinal": 6, - "type_info": "Int4" - }, - { - "name": "version", - "ordinal": 7, - "type_info": "Int4" - }, - { - "name": "deleted_at", - "ordinal": 8, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - true, - true, - false, - false, - true - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT\n id,\n notebook_id,\n cell_type,\n contents,\n rendering,\n execution_time,\n cell_number,\n version,\n deleted_at\n FROM notebook_cells\n WHERE id = $1\n " - }, - "bf7d4a13b5e1e0e45e6a6ab6f5bf946e1d4cb16a59c53c79b37a46c34f5aea01": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "notebook_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "cell_type", - "ordinal": 2, - "type_info": "Int4" - }, - { - "name": "cell_number", - "ordinal": 3, - "type_info": "Int4" - }, - { - "name": "version", - "ordinal": 4, - "type_info": "Int4" - }, - { - "name": "contents", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "rendering", - "ordinal": 6, - "type_info": "Text" - }, - { - "name": "execution_time", - "ordinal": 7, - "type_info": "Interval" - }, - { - "name": "deleted_at", - "ordinal": 8, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - false, - true, - true, - true - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT * FROM notebook_cells\n WHERE notebook_id = $1\n AND deleted_at IS NULL\n ORDER BY cell_number" - }, - "c51dddac8ca1272eb957b5cbfd789e63c9e8897d62bc2c57c168eba5ada12dc3": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "task", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "created_at", - "ordinal": 3, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - null, - false - ], - "parameters": { - "Left": [] - } - }, - "query": "SELECT\n id,\n name,\n task::TEXT,\n created_at\n FROM pgml.projects\n ORDER BY id DESC" - }, - "c5faa3dc630e649d97e10720dbc33351c7d792ee69a4a90ce26d61448e031520": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "model_id", - "ordinal": 2, - "type_info": "Int8" - }, - { - "name": "strategy", - "ordinal": 3, - "type_info": "Text" - }, - { - "name": "created_at", - "ordinal": 4, - "type_info": "Timestamp" - }, - { - "name": "active", - "ordinal": 5, - "type_info": "Bool" - } - ], - "nullable": [ - false, - false, - false, - null, - false, - null - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT\n a.id,\n project_id,\n model_id,\n strategy::TEXT,\n created_at,\n a.id = last_deployment.id AS active\n FROM pgml.deployments a\n CROSS JOIN LATERAL (\n SELECT id FROM pgml.deployments b\n WHERE b.project_id = a.project_id\n ORDER BY b.id DESC\n LIMIT 1\n ) last_deployment\n WHERE a.id = $1\n ORDER BY a.id DESC" - }, - "d8fb565e5ca7f3b60a28e00080902ec34a9036a77ffdde04957f8a6fd543e31d": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "name", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "task", - "ordinal": 2, - "type_info": "Text" - }, - { - "name": "created_at", - "ordinal": 3, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - null, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT\n id,\n name,\n task::TEXT,\n created_at\n FROM pgml.projects\n WHERE id = $1" - }, - "da28d578e5935c65851410fbb4e3a260201c16f9bfacfc9bbe05292c292894a2": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "project_id", - "ordinal": 1, - "type_info": "Int8" - }, - { - "name": "snapshot_id", - "ordinal": 2, - "type_info": "Int8" - }, - { - "name": "num_features", - "ordinal": 3, - "type_info": "Int4" - }, - { - "name": "algorithm", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "runtime", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "hyperparams", - "ordinal": 6, - "type_info": "Jsonb" - }, - { - "name": "status", - "ordinal": 7, - "type_info": "Text" - }, - { - "name": "metrics", - "ordinal": 8, - "type_info": "Jsonb" - }, - { - "name": "search", - "ordinal": 9, - "type_info": "Text" - }, - { - "name": "search_params", - "ordinal": 10, - "type_info": "Jsonb" - }, - { - "name": "search_args", - "ordinal": 11, - "type_info": "Jsonb" - }, - { - "name": "created_at", - "ordinal": 12, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 13, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - false, - null, - false, - false, - true, - true, - false, - false, - false, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT\n id,\n project_id,\n snapshot_id,\n num_features,\n algorithm,\n runtime::TEXT,\n hyperparams,\n status,\n metrics,\n search,\n search_params,\n search_args,\n created_at,\n updated_at\n FROM pgml.models\n WHERE id = $1\n " - }, - "e863424eeed1de411a24bc4dff7bf10fee68ff8c98e499c6fb621e23a6b0d63a": { - "describe": { - "columns": [ - { - "name": "id", - "ordinal": 0, - "type_info": "Int8" - }, - { - "name": "relation_name", - "ordinal": 1, - "type_info": "Text" - }, - { - "name": "y_column_name", - "ordinal": 2, - "type_info": "TextArray" - }, - { - "name": "test_size", - "ordinal": 3, - "type_info": "Float4" - }, - { - "name": "test_sampling", - "ordinal": 4, - "type_info": "Text" - }, - { - "name": "status", - "ordinal": 5, - "type_info": "Text" - }, - { - "name": "columns", - "ordinal": 6, - "type_info": "Jsonb" - }, - { - "name": "analysis", - "ordinal": 7, - "type_info": "Jsonb" - }, - { - "name": "created_at", - "ordinal": 8, - "type_info": "Timestamp" - }, - { - "name": "updated_at", - "ordinal": 9, - "type_info": "Timestamp" - } - ], - "nullable": [ - false, - false, - false, - false, - null, - false, - true, - true, - false, - false - ], - "parameters": { - "Left": [ - "Int8" - ] - } - }, - "query": "SELECT id,\n relation_name,\n y_column_name,\n test_size,\n test_sampling::TEXT,\n status,\n columns,\n analysis,\n created_at,\n updated_at\n FROM pgml.snapshots\n WHERE id = $1" - }, - "f1a0941049c71bee1ea74ede2e3199d88bf0fc739ca2e2510ee9f6178b12e80a": { - "describe": { - "columns": [ - { - "name": "deployed", - "ordinal": 0, - "type_info": "Bool" - } - ], - "nullable": [ - null - ], - "parameters": { - "Left": [ - "Int8", - "Int8" - ] - } - }, - "query": "SELECT\n (model_id = $1) AS deployed\n FROM pgml.deployments\n WHERE project_id = $2\n ORDER BY created_at DESC\n LIMIT 1" - } -} \ No newline at end of file diff --git a/pgml-dashboard/src/main.rs b/pgml-dashboard/src/main.rs index 05bdc6e7c..ac466ddfb 100644 --- a/pgml-dashboard/src/main.rs +++ b/pgml-dashboard/src/main.rs @@ -8,6 +8,8 @@ async fn index() -> Redirect { #[rocket::main] async fn main() { + dotenv::dotenv().ok(); + let clusters = pgml_dashboard::Clusters::new(); clusters .add(-1, &pgml_dashboard::guards::default_database_url()) diff --git a/pgml-extension/Cargo.lock b/pgml-extension/Cargo.lock index de0ca2fe0..5d22613eb 100644 --- a/pgml-extension/Cargo.lock +++ b/pgml-extension/Cargo.lock @@ -84,7 +84,7 @@ checksum = "1cd7fce9ba8c3c042128ce72d8b2ddbf3a05747efb67ea0313c635e10bda47a2" dependencies = [ "proc-macro2", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -204,7 +204,7 @@ dependencies = [ "regex", "rustc-hash", "shlex 1.1.0", - "syn 1.0.107", + "syn 1.0.109", "which 4.4.0", ] @@ -385,7 +385,7 @@ dependencies = [ "proc-macro-error", "proc-macro2", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -534,7 +534,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d2301688392eb071b0bf1a37be05c469d3cc4dbbd95df672fe28ab021e6a096" dependencies = [ "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -558,7 +558,7 @@ dependencies = [ "proc-macro2", "quote 1.0.23", "strsim 0.10.0", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -569,7 +569,7 @@ checksum = "b36230598a2d5de7ec1c6f51f72d8a99a9208daff41de2084d06e3fd3ea56685" dependencies = [ "darling_core", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -611,7 +611,7 @@ dependencies = [ "darling", "proc-macro2", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -621,7 +621,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f0314b72bed045f3a68671b3c86328386762c93f82d98c65c3cb5e5f573dd68" dependencies = [ "derive_builder_core 0.11.2", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -832,7 +832,7 @@ checksum = "95a73af87da33b5acf53acfebdc339fe592ecf5357ac7c0a7734ab9d8c876a70" dependencies = [ "proc-macro2", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -891,7 +891,7 @@ checksum = "41973d4c45f7a35af8753ba3457cc99d406d863941fd7f52663cff54a5ab99b3" dependencies = [ "proc-macro2", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -1508,9 +1508,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.17.0" +version = "1.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66" +checksum = "b7e5500299e16ebb147ae15a00a942af264cf3688f47923b8fc2cd5858f23ad3" [[package]] name = "openblas-build" @@ -1561,7 +1561,7 @@ checksum = "b501e44f11665960c7e7fcf062c7d96a14ade4aa98116c004b2e37b5be7d736c" dependencies = [ "proc-macro2", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -1715,9 +1715,9 @@ dependencies = [ [[package]] name = "pgx" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc91f19f84e7c1ba7b25953b042bd487b6e1bbec4c3af09f61a6ac31207ff776" +checksum = "1c3d224bd3a4fe3798498c16cb37a955e7c4a4e4e9bb01e6dfd8c3738c903d4f" dependencies = [ "atomic-traits", "bitflags", @@ -1742,21 +1742,21 @@ dependencies = [ [[package]] name = "pgx-macros" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1ebfde3c33353d42c2fbcc76bea758b37018b33b1391c93d6402546569914e94" +checksum = "100cd28f400753e7aeb54820d63ebafff8c1b87018d55f18404477f8c047dd9e" dependencies = [ "pgx-sql-entity-graph", "proc-macro2", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "pgx-pg-config" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e97c27bab88fdb7b94e549b02267ab9595bd9d1043718d6d72bc2d34cf1e3952" +checksum = "6ce4f7099005b82e1b386a82a98dd436b734804974ee539b814eb4d1a78e4c1f" dependencies = [ "dirs 4.0.0", "eyre", @@ -1771,9 +1771,9 @@ dependencies = [ [[package]] name = "pgx-pg-sys" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b79c48c564bed305d202b852321603107e5f3ac31f25ea2cc4031475f38d0b3" +checksum = "081d104fcb693fdef1a911f11c2aad295a6b778de67069e54c40b46702c1b2d8" dependencies = [ "bindgen 0.60.1", "eyre", @@ -1788,14 +1788,14 @@ dependencies = [ "serde", "shlex 1.1.0", "sptr", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "pgx-sql-entity-graph" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "573a8d8c23be24c39f7b7fbbc7e15d95aa0327acd61ba95c9c9f237fec51f205" +checksum = "e03b43619e70e955894ba94883132d88ff7acf0cd15fd14e50c837a2bd3a6595" dependencies = [ "convert_case", "eyre", @@ -1804,7 +1804,7 @@ dependencies = [ "quote 1.0.23", "regex", "seq-macro", - "syn 1.0.107", + "syn 1.0.109", "tracing", "tracing-error", "tracing-subscriber", @@ -1813,9 +1813,9 @@ dependencies = [ [[package]] name = "pgx-tests" -version = "0.7.1" +version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fc09f25ae560bc4e3308022999416966beda5b60d2957b9ab92bffaf2d6a86c3" +checksum = "d4069f5b6c98d5542bacac0fdb072d6198297267df132fedd4499a156937e4ca" dependencies = [ "clap-cargo", "eyre", @@ -1928,7 +1928,7 @@ dependencies = [ "proc-macro-error-attr", "proc-macro2", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", "version_check", ] @@ -1998,7 +1998,7 @@ dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -2009,7 +2009,7 @@ checksum = "c8df9be978a2d2f0cdebabb03206ed73b11314701a5bfe71b0d753b81997777f" dependencies = [ "proc-macro2", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -2357,14 +2357,14 @@ checksum = "af487d118eecd09402d70a5d72551860e788df87b464af30e5ea6a38c75c541e" dependencies = [ "proc-macro2", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] name = "serde_json" -version = "1.0.92" +version = "1.0.94" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7434af0dc1cbd59268aa98b4c22c131c0584d2232f6fb166efb993e2832e896a" +checksum = "1c533a59c9d8a93a09c6ab31f0fd5e5f4dd1b8fc9434804029839884765d04ea" dependencies = [ "indexmap", "itoa 1.0.5", @@ -2572,9 +2572,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.107" +version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5" +checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote 1.0.23", @@ -2694,7 +2694,7 @@ checksum = "1fb327af4685e4d03fa8cbcf1716380da910eeb2bb8be417e7f9fd3fb164f36f" dependencies = [ "proc-macro2", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -2708,9 +2708,9 @@ dependencies = [ [[package]] name = "time" -version = "0.3.17" +version = "0.3.20" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a561bf4617eebd33bca6434b988f39ed798e527f51a1e797d0ee4f61c0a38376" +checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890" dependencies = [ "itoa 1.0.5", "libc", @@ -2728,9 +2728,9 @@ checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd" [[package]] name = "time-macros" -version = "0.2.6" +version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d967f99f534ca7e495c575c62638eebc2898a8c84c119b89e250477bc4ba16b2" +checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36" dependencies = [ "time-core", ] @@ -2833,7 +2833,7 @@ checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a" dependencies = [ "proc-macro2", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] @@ -2912,7 +2912,7 @@ checksum = "8f9568611f0de5e83e0993b85c54679cd0afd659adcfcb0233f16280b980492e" dependencies = [ "proc-macro2", "quote 1.0.23", - "syn 1.0.107", + "syn 1.0.109", ] [[package]] diff --git a/pgml-extension/Cargo.toml b/pgml-extension/Cargo.toml index df5f12c9f..dd209de9e 100644 --- a/pgml-extension/Cargo.toml +++ b/pgml-extension/Cargo.toml @@ -18,8 +18,8 @@ python = ["pyo3"] cuda = ["xgboost/cuda", "lightgbm/cuda"] [dependencies] -pgx = "=0.7.1" -pgx-pg-sys = "=0.7.1" +pgx = "=0.7.2" +pgx-pg-sys = "=0.7.2" xgboost = { git="https://github.com/postgresml/rust-xgboost.git", branch = "master" } once_cell = "1" rand = "0.8" @@ -48,7 +48,7 @@ flate2 = "1.0" csv = "1.1" [dev-dependencies] -pgx-tests = "=0.7.1" +pgx-tests = "=0.7.2" [profile.dev] panic = "unwind" From 14e51055beb7b18b4f7521480b543cbba43fe2b7 Mon Sep 17 00:00:00 2001 From: Dan <39170265+chillenberger@users.noreply.github.com> Date: Mon, 13 Mar 2023 09:01:04 -0600 Subject: [PATCH 14/22] Dan cell xscroll fix (#562) --- pgml-dashboard/static/css/base.css | 178 ++++++++++++++----------- pgml-dashboard/static/css/markdown.css | 1 + pgml-dashboard/templates/cell.html | 28 ++-- pgml-dashboard/templates/notebook.html | 18 ++- pgml-dashboard/templates/undo.html | 4 +- 5 files changed, 122 insertions(+), 107 deletions(-) diff --git a/pgml-dashboard/static/css/base.css b/pgml-dashboard/static/css/base.css index 8894b13fd..a128940c3 100644 --- a/pgml-dashboard/static/css/base.css +++ b/pgml-dashboard/static/css/base.css @@ -305,67 +305,6 @@ footer { height: 0; } -@media only screen and (max-width: 690px) { - header { - top: unset; - left: 0; - bottom: 0; - position: fixed; - border: 0; - padding: 0; - width: 100%; - } - header nav a { - font-size: 0; - } - header nav ul li.logo { - display: none; - } - header nav ul li { - width: 24%; - padding: 15px 0; - text-align: center; - } - header nav a span.material-symbols-outlined { - font-size: 3rem; - } - main { - padding: 0px 0 100px; - } - dl { - max-height: 6em; - } - dt { - margin-top: 1em; - } - dt, dd { - margin-right: 2em; - } - figure { - display: inline-block; - width: 75vmin; - height: 75vmin; - margin: 30px 0; - } - section { - border-radius: 0; - margin: 0 0 20px; - } -} - -@media only screen and (max-width: 300px) { - dl { - max-height: 12em; - } - dt { - margin-top: 1em; - } - dt, dd { - margin-right: 2em; - } -} - - ol.object_list { width: 100%; } @@ -503,6 +442,10 @@ nav ol, nav ul { list-style: none; } +.notebook-title { + padding: 1rem; + margin: 20px auto 20px auto; +} .notebook-cell.active { border: 1px solid var(--gray-8); @@ -510,7 +453,6 @@ nav ol, nav ul { } .notebook-cell-edit { - border: 1px solid var(--gray-8); margin-bottom: 1rem; } @@ -519,13 +461,12 @@ nav ol, nav ul { } main turbo-frame:first-of-type .notebook-cell { - border-top-left-radius: 10px; - border-top-right-radius: 10px; + border-radius: 10px 10px 0px 0px; border-top: 1px solid var(--gray-8); } .notebook-cell { - margin: 0 20px; + margin: 0 auto; border-radius: 0; padding: 1rem; border-left: 2px solid var(--gray-8); @@ -535,6 +476,10 @@ main turbo-frame:first-of-type .notebook-cell { background: var(--gray-10); } +.notebook-last-cell { + border-radius: 0px 0px 10px 10px; +} + .notebook-cell article.markdown-body { background-color: inherit; } @@ -564,8 +509,10 @@ main turbo-frame:first-of-type .notebook-cell { color: #000; } -.notebook-button { - margin: 0.1rem; +.notebook-buttons { + display: flex; + flex-direction: row; + gap: 0.2rem; } .notebook-contents code { @@ -596,6 +543,10 @@ main turbo-frame:first-of-type .notebook-cell { align-items: flex-end; } +.flex-start { + align-items: flex-start; +} + .flex-row-reverse { flex-direction: row-reverse; } @@ -605,16 +556,9 @@ main turbo-frame:first-of-type .notebook-cell { font-family: monospace; } -.notebook-cell .button { - height: fit-content; - width: fit-content; - padding: 2px 5px; - margin-bottom: 0.12rem; - margin-left: 0.2rem; -} - -.notebook-cell .notebook-button button, .markdown-body button { +.notebook-cell button, .notebook-title button, .markdown-body button { padding: 2px 5px; + height: 2rem; } .notebook-cell .button-delete { @@ -625,15 +569,20 @@ main turbo-frame:first-of-type .notebook-cell { font-weight: bold; background: rgb(240, 240, 240); border: 1px solid var(--gray-7); - padding: 0.5rem 6px; + height: 2rem; border-radius: 2px; - margin-bottom: 2px; } .notebook-duration { margin-top: 1rem; } +.notebook-render-container { + min-width: 0px; + flex: 1; + margin-right: 1rem; +} + .notebook-rendering .markdown-body { margin-top: 1rem; } @@ -651,6 +600,8 @@ main turbo-frame:first-of-type .notebook-cell { */ .CodeMirror { font-size: 1rem; + border: 1px solid var(--gray-8); + border-radius: 10px; } /* @@ -737,3 +688,74 @@ input[type=checkbox]:checked:after { display: flex; align-items: center; } + + +@media only screen and (max-width: 690px) { + header { + top: unset; + left: 0; + bottom: 0; + position: fixed; + border: 0; + padding: 0; + width: 100%; + } + header nav a { + font-size: 0; + } + header nav ul li.logo { + display: none; + } + header nav ul li { + width: 24%; + padding: 15px 0; + text-align: center; + } + header nav a span.material-symbols-outlined { + font-size: 3rem; + } + main { + padding: 0px 0 100px; + } + dl { + max-height: 6em; + } + dt { + margin-top: 1em; + } + dt, dd { + margin-right: 2em; + } + figure { + display: inline-block; + width: 75vmin; + height: 75vmin; + margin: 30px 0; + } + section { + border-radius: 0; + margin: 0 0 20px; + } + main turbo-frame:first-of-type .notebook-cell { + border-radius: 0px; + } + .notebook-last-cell { + border-radius: 0px; + } + .notebook-buttons { + flex-direction: column; + justify-content: flex-start; + } +} + +@media only screen and (max-width: 300px) { + dl { + max-height: 12em; + } + dt { + margin-top: 1em; + } + dt, dd { + margin-right: 2em; + } +} diff --git a/pgml-dashboard/static/css/markdown.css b/pgml-dashboard/static/css/markdown.css index b41ed0112..d6743c5f2 100644 --- a/pgml-dashboard/static/css/markdown.css +++ b/pgml-dashboard/static/css/markdown.css @@ -14,6 +14,7 @@ font-size: 16px; line-height: 1.5; word-wrap: break-word; + max-width: 75vw; } .markdown-body .octicon { diff --git a/pgml-dashboard/templates/cell.html b/pgml-dashboard/templates/cell.html index e4dfe1d0a..7ba13f121 100644 --- a/pgml-dashboard/templates/cell.html +++ b/pgml-dashboard/templates/cell.html @@ -8,18 +8,18 @@
    -
    +
    -
    +
    - -
    @@ -35,7 +35,7 @@
    <%= cell.cell_number %>
    -
    +
    <% if cell.code() { %>
    <%- cell.contents %>
    @@ -43,14 +43,14 @@ <% } %> <% if cell.html().is_some() { %> -
    - <%- cell.html().unwrap() %> -
    +
    + <%- cell.html().unwrap() %> +
    <% } %>
    - - <% if cell.code() && !edit { %> -
    + +
    + <% if cell.code() && !edit { %> -
    - <% } %> -
    + <% } %>
    -
    -
    @@ -36,19 +34,19 @@

    include!("cell.html"); } %> -
    +
    -
    +
    -
    +
    -
    diff --git a/pgml-dashboard/templates/undo.html b/pgml-dashboard/templates/undo.html index a329eadc0..39108aa38 100644 --- a/pgml-dashboard/templates/undo.html +++ b/pgml-dashboard/templates/undo.html @@ -10,7 +10,7 @@
    -
    +
    -
    -