From b63207986dfe95965b5c27df4939fbad02072634 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 21:28:28 +0800 Subject: [PATCH 01/43] Add environment setup guide for CI/CD configuration Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/environment-setup.md | 304 ++++++++++++++++++++ 1 file changed, 304 insertions(+) create mode 100644 docs/2-getting-started/environment-setup.md diff --git a/docs/2-getting-started/environment-setup.md b/docs/2-getting-started/environment-setup.md new file mode 100644 index 0000000..2d35956 --- /dev/null +++ b/docs/2-getting-started/environment-setup.md @@ -0,0 +1,304 @@ +--- +title: Environment Setup +--- + +# Environment Setup + +This guide covers advanced configuration for data teams with complex setups—multiple schemas, separate development warehouses, or CI/CD automation requirements. + +## Goal + +Configure your dbt targets and CI/CD environment variables to support base vs current comparison in Recce. After completing this guide, your CI/CD workflows can automatically create isolated schemas for each PR and compare them against production. + +## Prerequisites + +- [x] **dbt project**: A working dbt project with `profiles.yml` configured +- [x] **CI/CD platform**: GitHub Actions, GitLab CI, or similar +- [x] **Warehouse access**: Credentials with permissions to create schemas dynamically + +## Why separate schemas matter + +Recce compares two sets of data to validate changes: + +- **Base**: The production state (main branch) +- **Current**: The PR branch with your changes + +For accurate validation, these must point to different schemas in your warehouse. Without separation, you would compare identical data and miss meaningful differences. + +Common patterns include: + +| Pattern | Base Schema | Current Schema | Best For | +|---------|-------------|----------------|----------| +| Schema-per-PR | `public` (prod) | `pr_123` | Teams with many concurrent PRs | +| Shared dev | `public` (prod) | `dev` | Solo developers or small teams | +| Staging-based | `staging` | `pr_123` | Teams wanting consistent source data | + +## Configure profiles.yml + +Your `profiles.yml` file defines how dbt connects to your warehouse. Add separate targets for production, development, and CI. + +### Example: Snowflake with dynamic CI schema + +```yaml +jaffle_shop: + target: dev + outputs: + # Local development + dev: + type: snowflake + account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}" + user: "{{ env_var('SNOWFLAKE_USER') }}" + password: "{{ env_var('SNOWFLAKE_PASSWORD') }}" + role: DEVELOPER + database: analytics + warehouse: COMPUTE_WH + schema: "{{ env_var('DEV_SCHEMA', 'dev_' ~ env_var('USER', 'default')) }}" + threads: 4 + + # CI environment with dynamic schema + ci: + type: snowflake + account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}" + user: "{{ env_var('SNOWFLAKE_USER') }}" + password: "{{ env_var('SNOWFLAKE_PASSWORD') }}" + role: DEVELOPER + database: analytics + warehouse: COMPUTE_WH + schema: "{{ env_var('CI_SCHEMA') }}" + threads: 4 + + # Production + prod: + type: snowflake + account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}" + user: "{{ env_var('SNOWFLAKE_USER') }}" + password: "{{ env_var('SNOWFLAKE_PASSWORD') }}" + role: DEVELOPER + database: analytics + warehouse: COMPUTE_WH + schema: public + threads: 4 +``` + +Key points: + +- The `ci` target uses `env_var('CI_SCHEMA')` for dynamic schema assignment +- The `prod` target uses a fixed schema (`public`) for consistency +- The `dev` target can use a developer-specific schema or a shared one + +### Example: BigQuery with dataset separation + +```yaml +jaffle_shop: + target: dev + outputs: + dev: + type: bigquery + method: service-account + project: "{{ env_var('GCP_PROJECT') }}" + dataset: "{{ env_var('DEV_DATASET', 'dev') }}" + threads: 4 + keyfile: "{{ env_var('GOOGLE_APPLICATION_CREDENTIALS') }}" + + ci: + type: bigquery + method: service-account + project: "{{ env_var('GCP_PROJECT') }}" + dataset: "{{ env_var('CI_DATASET') }}" + threads: 4 + keyfile: "{{ env_var('GOOGLE_APPLICATION_CREDENTIALS') }}" + + prod: + type: bigquery + method: service-account + project: "{{ env_var('GCP_PROJECT') }}" + dataset: production + threads: 4 + keyfile: "{{ env_var('GOOGLE_APPLICATION_CREDENTIALS') }}" +``` + +## Set CI/CD environment variables + +Your CI/CD workflow sets the schema dynamically for each PR. This creates isolated data for validation. + +### GitHub Actions example + +In your PR workflow file (`.github/workflows/pr-recce.yml`): + +```yaml +name: PR Validation + +on: + pull_request: + branches: [main] + +jobs: + validate: + runs-on: ubuntu-latest + env: + # Dynamic schema based on PR number + CI_SCHEMA: "pr_${{ github.event.pull_request.number }}" + + # Warehouse credentials from secrets + SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }} + SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }} + SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }} + + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Build current branch + run: | + dbt deps + dbt build --target ci + dbt docs generate --target ci +``` + +This creates schemas like `pr_123`, `pr_456` for each PR automatically. + +### GitLab CI example + +In your `.gitlab-ci.yml`: + +```yaml +variables: + CI_SCHEMA: "mr_${CI_MERGE_REQUEST_IID}" + +validate_pr: + stage: test + script: + - pip install -r requirements.txt + - dbt deps + - dbt build --target ci + - dbt docs generate --target ci + rules: + - if: $CI_MERGE_REQUEST_IID +``` + +## Common patterns + +### Pattern 1: Schema-per-PR + +Creates an isolated schema for each PR. Ideal for teams with multiple concurrent PRs. + +```yaml +# profiles.yml +ci: + schema: "{{ env_var('CI_SCHEMA') }}" + +# GitHub Actions +env: + CI_SCHEMA: "pr_${{ github.event.pull_request.number }}" +``` + +Benefits: + +- Complete isolation between PRs +- Parallel PR validation without conflicts +- Easy cleanup by dropping the schema + +### Pattern 2: Shared development schema + +Uses a single `dev` schema for all development work. Simpler but requires coordination. + +```yaml +# profiles.yml +dev: + schema: dev + +# No dynamic schema needed +``` + +Benefits: + +- Simpler configuration +- Faster iteration for solo developers +- Lower warehouse storage usage + +### Pattern 3: Staging as base + +Uses a staging schema (with limited data) as the base for faster comparisons. + +```yaml +# profiles.yml +staging: + schema: staging # Base for comparison + +ci: + schema: "{{ env_var('CI_SCHEMA') }}" # Current PR schema +``` + +Benefits: + +- Consistent source data between base and current +- Faster diffs with limited data ranges +- Reduced warehouse costs + +See [Best Practices for Preparing Environments](../7-cicd/best-practices-prep-env.md) for detailed strategies on limiting data ranges and managing source data consistency. + +## Verification + +After configuring your setup, verify that both base and current schemas are accessible. + +### Check configuration locally + +Run the debug command to verify your profile configuration: + +```shell +recce debug +``` + +This checks artifacts, directories, and warehouse connections. + +### Verify in CI + +Add a verification step to your workflow: + +```yaml +- name: Verify schema access + run: | + dbt debug --target ci + echo "CI_SCHEMA is set to: $CI_SCHEMA" +``` + +### Verify in Recce interface + +Launch Recce and check **Environment Info** in the top-right corner. You should see: + +- **Base**: Your production schema (e.g., `public`) +- **Current**: Your PR-specific schema (e.g., `pr_123`) + +## Troubleshooting + +| Issue | Solution | +|-------|----------| +| Schema creation fails | Verify your CI credentials have `CREATE SCHEMA` permissions | +| Environment variable not found | Check that secrets are configured in your CI/CD platform settings | +| Base and current show same schema | Ensure `--target ci` is used in CI, not `--target dev` | +| Profile not found | Verify `profiles.yml` is accessible in CI (check path or use `DBT_PROFILES_DIR`) | +| Connection timeout | Check warehouse IP allowlists include CI runner IP ranges | + +### Debugging environment variables + +If schemas are not resolving correctly, add debugging to your workflow: + +```yaml +- name: Debug environment + run: | + echo "CI_SCHEMA: $CI_SCHEMA" + dbt debug --target ci 2>&1 | grep -i schema +``` + +## Related + +- [Get Started with Recce Cloud](start-free-with-cloud.md) - Complete onboarding guide +- [Best Practices for Preparing Environments](../7-cicd/best-practices-prep-env.md) - Strategies for source data and schema management +- [Setup CI](../7-cicd/setup-ci.md) - CI workflow configuration details From 6f406a3412953ca3220744fb71eebc20a4b4279f Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 21:31:15 +0800 Subject: [PATCH 02/43] Add dbt Cloud setup guide Tutorial for setting up Recce Cloud when dbt runs on dbt Cloud, covering artifact retrieval via dbt Cloud API and GitHub Actions workflow configuration. Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/dbt-cloud-setup.md | 256 ++++++++++++++++++++++ 1 file changed, 256 insertions(+) create mode 100644 docs/2-getting-started/dbt-cloud-setup.md diff --git a/docs/2-getting-started/dbt-cloud-setup.md b/docs/2-getting-started/dbt-cloud-setup.md new file mode 100644 index 0000000..ccfab97 --- /dev/null +++ b/docs/2-getting-started/dbt-cloud-setup.md @@ -0,0 +1,256 @@ +--- +title: dbt Cloud Setup +--- + +# dbt Cloud Setup + +This guide helps you set up Recce Cloud when your dbt project runs on dbt Cloud. Since dbt Cloud manages your dbt runs, you'll retrieve artifacts via the dbt Cloud API instead of generating them locally. + +## Goal + +After completing this setup, you'll have automated data validation on every pull request, with Recce comparing your PR changes against production. The workflow retrieves dbt artifacts directly from dbt Cloud and uploads them to Recce Cloud for validation. + +## Prerequisites + +- [x] **Recce Cloud account**: free trial at [cloud.reccehq.com](https://cloud.reccehq.com) +- [x] **dbt Cloud account**: with CI and CD jobs configured +- [x] **dbt Cloud API token**: with read access to job artifacts +- [x] **GitHub repository**: with admin access to add workflows and secrets +- [x] **Data warehouse**: read access for data diffing + +## How it works + +When your dbt project runs on dbt Cloud, the artifacts (`manifest.json`, `catalog.json`) are stored in dbt Cloud rather than your local environment. To use Recce, you'll: + +1. Retrieve Base artifacts from your CD job (production runs) +2. Retrieve Current artifacts from your CI job (PR runs) +3. Upload both to Recce Cloud for validation + +## Setup steps + +### 1. Enable "Generate docs on run" in dbt Cloud + +Recce requires `catalog.json` for schema comparisons. Enable documentation generation for both your CI and CD jobs in dbt Cloud. + +**For CD jobs (production):** + +1. Go to your CD job settings in dbt Cloud +2. Under **Execution settings**, enable **Generate docs on run** + +**For CI jobs (pull requests):** + +1. Go to your CI job settings in dbt Cloud +2. Under **Advanced settings**, enable **Generate docs on run** + +!!! note + Without this setting, dbt Cloud won't generate `catalog.json`, and Recce won't be able to compare schemas between environments. + +### 2. Get your dbt Cloud credentials + +Collect the following from your dbt Cloud account: + +| Credential | Where to find it | +| --- | --- | +| **Account ID** | URL when viewing any job: `cloud.getdbt.com/deploy/{ACCOUNT_ID}/projects/...` | +| **CD Job ID** | URL of your production/CD job: `...jobs/{JOB_ID}` | +| **CI Job ID** | URL of your PR/CI job: `...jobs/{JOB_ID}` | +| **API Token** | Account Settings > API Tokens > Create Service Token | + +!!! tip + Create a service token with "Job Admin" or "Member" permissions. This allows read access to job artifacts. + +### 3. Configure GitHub secrets + +Add the following secrets to your GitHub repository (Settings > Secrets and variables > Actions): + +**dbt Cloud secrets:** + +- `DBT_CLOUD_API_TOKEN` - Your dbt Cloud API token +- `DBT_CLOUD_ACCOUNT_ID` - Your dbt Cloud account ID +- `DBT_CLOUD_CD_JOB_ID` - Your production/CD job ID +- `DBT_CLOUD_CI_JOB_ID` - Your PR/CI job ID + +**Recce Cloud secrets:** + +- `RECCE_STATE_PASSWORD` - Password to encrypt state files (create any secure string) + +**Data warehouse secrets** (for data diffing): + +Add your warehouse credentials based on your adapter. For Snowflake: + +- `SNOWFLAKE_ACCOUNT` +- `SNOWFLAKE_USER` +- `SNOWFLAKE_PASSWORD` +- `SNOWFLAKE_SCHEMA` + +!!! note + `GITHUB_TOKEN` is automatically provided by GitHub Actions, no configuration needed. + +### 4. Create the GitHub Actions workflow + +Create `.github/workflows/recce-dbt-cloud.yml` with the workflow configuration. The workflow: + +1. **Retrieves Base artifacts** from your CD job run matching the PR's base commit +2. **Retrieves Current artifacts** from your CI job run for the PR's head commit +3. **Runs Recce validation** and uploads results to Recce Cloud +4. **Posts a summary comment** on the pull request + +```yaml +name: Recce with dbt Cloud + +on: + pull_request: + branches: [main] + +env: + DBT_CLOUD_API_BASE: "https://cloud.getdbt.com/api/v2/accounts/${{ secrets.DBT_CLOUD_ACCOUNT_ID }}" + DBT_CLOUD_API_TOKEN: ${{ secrets.DBT_CLOUD_API_TOKEN }} + +jobs: + recce-validation: + name: Validate PR with Recce + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - uses: actions/setup-python@v5 + with: + python-version: "3.10" + cache: "pip" + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Retrieve Base artifacts (CD job) + env: + DBT_CLOUD_CD_JOB_ID: ${{ secrets.DBT_CLOUD_CD_JOB_ID }} + BASE_GITHUB_SHA: ${{ github.event.pull_request.base.sha }} + run: | + set -eo pipefail + CD_RUNS_URL="${DBT_CLOUD_API_BASE}/runs/?job_definition_id=${DBT_CLOUD_CD_JOB_ID}&order_by=-id" + CD_RUNS_RESPONSE=$(curl -sSf -H "Authorization: Bearer ${DBT_CLOUD_API_TOKEN}" "${CD_RUNS_URL}") + DBT_CLOUD_CD_RUN_ID=$(echo "${CD_RUNS_RESPONSE}" | jq -r ".data[] | select(.git_sha == \"${BASE_GITHUB_SHA}\") | .id" | head -n1) + echo "DBT_CLOUD_CD_RUN_ID=${DBT_CLOUD_CD_RUN_ID}" >> $GITHUB_ENV + mkdir -p target-base + for artifact in manifest.json catalog.json; do + ARTIFACT_URL="${DBT_CLOUD_API_BASE}/runs/${DBT_CLOUD_CD_RUN_ID}/artifacts/${artifact}" + curl -sSf -H "Authorization: Bearer ${DBT_CLOUD_API_TOKEN}" "${ARTIFACT_URL}" -o "target-base/${artifact}" + done + + - name: Retrieve Current artifacts (CI job) + env: + DBT_CLOUD_CI_JOB_ID: ${{ secrets.DBT_CLOUD_CI_JOB_ID }} + CURRENT_GITHUB_SHA: ${{ github.event.pull_request.head.sha }} + run: | + set -eo pipefail + CI_RUNS_URL="${DBT_CLOUD_API_BASE}/runs/?job_definition_id=${DBT_CLOUD_CI_JOB_ID}&order_by=-id" + fetch_ci_run_id() { + CI_RUNS_RESPONSE=$(curl -sSf -H "Authorization: Bearer ${DBT_CLOUD_API_TOKEN}" "${CI_RUNS_URL}") + echo "${CI_RUNS_RESPONSE}" | jq -r ".data[] | select(.git_sha == \"${CURRENT_GITHUB_SHA}\") | .id" | head -n1 + } + DBT_CLOUD_CI_RUN_ID=$(fetch_ci_run_id) + while [ -z "$DBT_CLOUD_CI_RUN_ID" ]; do + sleep 5 + DBT_CLOUD_CI_RUN_ID=$(fetch_ci_run_id) + done + echo "DBT_CLOUD_CI_RUN_ID=${DBT_CLOUD_CI_RUN_ID}" >> $GITHUB_ENV + CI_RUN_URL="${DBT_CLOUD_API_BASE}/runs/${DBT_CLOUD_CI_RUN_ID}/" + while true; do + CI_RUN_RESPONSE=$(curl -sSf -H "Authorization: Bearer ${DBT_CLOUD_API_TOKEN}" "${CI_RUN_URL}") + CI_RUN_SUCCESS=$(echo "${CI_RUN_RESPONSE}" | jq '.data.is_complete and .data.is_success') + CI_RUN_FAILED=$(echo "${CI_RUN_RESPONSE}" | jq '.data.is_complete and (.data.is_error or .data.is_cancelled)') + if $CI_RUN_SUCCESS; then + echo "CI job completed successfully." + break + elif $CI_RUN_FAILED; then + status=$(echo ${CI_RUN_RESPONSE} | jq -r '.data.status_humanized') + echo "CI job failed or was cancelled. Status: $status" + exit 1 + fi + sleep 5 + done + mkdir -p target + for artifact in manifest.json catalog.json; do + ARTIFACT_URL="${DBT_CLOUD_API_BASE}/runs/${DBT_CLOUD_CI_RUN_ID}/artifacts/${artifact}" + curl -sSf -H "Authorization: Bearer ${DBT_CLOUD_API_TOKEN}" "${ARTIFACT_URL}" -o "target/${artifact}" + done + + - name: Run Recce validation + env: + SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }} + SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }} + SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }} + SNOWFLAKE_SCHEMA: "PR_${{ github.event.pull_request.number }}" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RECCE_STATE_PASSWORD: ${{ secrets.RECCE_STATE_PASSWORD }} + run: recce run --cloud + + - name: Generate Recce summary + id: recce-summary + env: + SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }} + SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }} + SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }} + SNOWFLAKE_SCHEMA: "PR_${{ github.event.pull_request.number }}" + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + RECCE_STATE_PASSWORD: ${{ secrets.RECCE_STATE_PASSWORD }} + run: | + set -eo pipefail + recce summary --cloud > recce_summary.md + cat recce_summary.md >> $GITHUB_STEP_SUMMARY + + - name: Comment on pull request + uses: thollander/actions-comment-pull-request@v2 + with: + filePath: recce_summary.md + comment_tag: recce +``` + +### 5. Adapt for your data warehouse + +The workflow above uses Snowflake. Update the environment variables in the "Run Recce validation" and "Generate Recce summary" steps to match your warehouse configuration. + +For other warehouses, replace the Snowflake variables with your adapter's required credentials. See [Connect to Warehouse](../5-data-diffing/connect-to-warehouse.md) for adapter-specific configuration. + +## Verification + +After setting up: + +1. **Create a test PR** with a small model change +2. **Wait for dbt Cloud CI job** to complete +3. **Check GitHub Actions** - the Recce workflow should run +4. **Review the PR comment** - Recce validation summary appears +5. **Launch Recce instance** - from Recce Cloud dashboard, open the PR session + +!!! tip + If the workflow fails on the first run, check that your CD job has run on the base commit. The workflow looks for artifacts from a specific git SHA. + +## Troubleshooting + +| Issue | Solution | +| --- | --- | +| "CD run not found" | Ensure your CD job has run on the base branch commit. Try rebasing your PR to trigger a new CD run. | +| "CI job timeout" | The workflow waits for dbt Cloud CI to complete. Check if your CI job is stuck or taking longer than expected. | +| "Artifact not found" | Verify "Generate docs on run" is enabled for both CI and CD jobs. | +| "API authentication failed" | Check your `DBT_CLOUD_API_TOKEN` has correct permissions and is stored in GitHub secrets. | +| "Warehouse connection failed" | Verify warehouse credentials in GitHub secrets. Check IP whitelisting if applicable. | +| No PR comment appears | Ensure `GITHUB_TOKEN` has write permissions for pull requests. Check workflow permissions. | + +### CD job timing considerations + +The workflow retrieves Base artifacts from the CD job run that matches the PR's base commit SHA. If your CD job runs on a schedule (not on every merge), the base commit might not have artifacts available. + +**Solutions:** + +- Configure CD to run on merge to main (recommended) +- Rebase your PR to a commit that has CD artifacts +- Modify the workflow to use the latest CD run instead of commit-matched artifacts + +## Related + +- [Get Started with Recce Cloud](./start-free-with-cloud.md) - Standard setup for self-hosted dbt +- [Setup CD](../7-cicd/setup-cd.md) - CD workflow configuration +- [Setup CI](../7-cicd/setup-ci.md) - CI workflow configuration +- [Best Practices for Preparing Environments](../7-cicd/best-practices-prep-env.md) - Environment strategy guidance From a2b7cad76b9ab6fb78ab170357c12386e2d557a9 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 21:51:43 +0800 Subject: [PATCH 03/43] Add setup choice callout for CI/CD section Helps users identify their setup path: - Own dbt run vs platform (dbt Cloud) - Simple vs advanced environment Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/start-free-with-cloud.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/2-getting-started/start-free-with-cloud.md b/docs/2-getting-started/start-free-with-cloud.md index 4edc0d0..1e8414f 100644 --- a/docs/2-getting-started/start-free-with-cloud.md +++ b/docs/2-getting-started/start-free-with-cloud.md @@ -87,6 +87,20 @@ First, go to [cloud.reccehq.com](https://cloud.reccehq.com) and create your free ### 3. Add Recce to CI/CD +Before proceeding, identify your setup: + +!!! info "Choose your setup" + + **How do you run dbt?** + + - **You own your dbt run** (GitHub Actions, GitLab CI, CircleCI): Continue with this guide + - **You run dbt on a platform** (dbt Cloud, Paradigms, etc.): See [dbt Cloud Setup](dbt-cloud-setup.md) + + **How complex is your environment?** + + - **Simple** (single prod schema, PR schemas): Continue with this guide + - **Advanced** (multiple schemas, staging environments): See [Environment Setup](environment-setup.md) + This step adds CI/CD workflow files to your repository. The agent creates these automatically. For manual setup, create and merge a PR with the templates below. > **Note**: This guide uses GitHub Actions. For other CI/CD platforms, see [Setup CD](../7-cicd/setup-cd.md) and [Setup CI](../7-cicd/setup-ci.md). From 508bc0e40ad345f4fed79a06757d8c7d313fab63 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 21:59:13 +0800 Subject: [PATCH 04/43] Update setup callout with per-PR explanation Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/start-free-with-cloud.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/2-getting-started/start-free-with-cloud.md b/docs/2-getting-started/start-free-with-cloud.md index 1e8414f..0319ee9 100644 --- a/docs/2-getting-started/start-free-with-cloud.md +++ b/docs/2-getting-started/start-free-with-cloud.md @@ -89,17 +89,16 @@ First, go to [cloud.reccehq.com](https://cloud.reccehq.com) and create your free Before proceeding, identify your setup: -!!! info "Choose your setup" +**Choose your setup** +1. How do you run dbt? - **How do you run dbt?** + - **You own your dbt run** (GitHub Actions, GitLab CI, CircleCI): Continue with this guide + - **You run dbt on a platform** (dbt Cloud, Paradigms, etc.): See [dbt Cloud Setup](dbt-cloud-setup.md) - - **You own your dbt run** (GitHub Actions, GitLab CI, CircleCI): Continue with this guide - - **You run dbt on a platform** (dbt Cloud, Paradigms, etc.): See [dbt Cloud Setup](dbt-cloud-setup.md) +2. How complex is your environment? - **How complex is your environment?** - - - **Simple** (single prod schema, PR schemas): Continue with this guide - - **Advanced** (multiple schemas, staging environments): See [Environment Setup](environment-setup.md) + - **Simple** (prod and dev targets): Continue with this guide. We use per-PR schemas for fast setup. To learn why, see [Environment Setup](environment-setup.md). + - **Advanced** (multiple schemas, staging environments): See [Environment Setup](environment-setup.md) This step adds CI/CD workflow files to your repository. The agent creates these automatically. For manual setup, create and merge a PR with the templates below. From 909b5d69a4216e48bd981fbbc40540f5e37cab1a Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 22:01:22 +0800 Subject: [PATCH 05/43] Add CI/CD platform as item 3 in setup callout Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/start-free-with-cloud.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/2-getting-started/start-free-with-cloud.md b/docs/2-getting-started/start-free-with-cloud.md index 0319ee9..8833ec1 100644 --- a/docs/2-getting-started/start-free-with-cloud.md +++ b/docs/2-getting-started/start-free-with-cloud.md @@ -100,9 +100,12 @@ Before proceeding, identify your setup: - **Simple** (prod and dev targets): Continue with this guide. We use per-PR schemas for fast setup. To learn why, see [Environment Setup](environment-setup.md). - **Advanced** (multiple schemas, staging environments): See [Environment Setup](environment-setup.md) -This step adds CI/CD workflow files to your repository. The agent creates these automatically. For manual setup, create and merge a PR with the templates below. +3. What's your CI/CD platform? + + - **GitHub Actions**: Continue with this guide + - **Other platforms** (GitLab CI, CircleCI, etc.): See [Setup CD](../7-cicd/setup-cd.md) and [Setup CI](../7-cicd/setup-ci.md) -> **Note**: This guide uses GitHub Actions. For other CI/CD platforms, see [Setup CD](../7-cicd/setup-cd.md) and [Setup CI](../7-cicd/setup-ci.md). +This step adds CI/CD workflow files to your repository. The agent creates these automatically. For manual setup, create and merge a PR with the templates below. #### Set Up Profile.yml From 0f54fe177231aeb0dd277ba6ad9f73440d77a57d Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 22:02:43 +0800 Subject: [PATCH 06/43] Move web agent intro above setup options Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/start-free-with-cloud.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/2-getting-started/start-free-with-cloud.md b/docs/2-getting-started/start-free-with-cloud.md index 8833ec1..f5cf29d 100644 --- a/docs/2-getting-started/start-free-with-cloud.md +++ b/docs/2-getting-started/start-free-with-cloud.md @@ -87,9 +87,10 @@ First, go to [cloud.reccehq.com](https://cloud.reccehq.com) and create your free ### 3. Add Recce to CI/CD -Before proceeding, identify your setup: +This step adds CI/CD workflow files to your repository. The web agent detects your setup and guides you through. For manual setup, use the templates below. + +#### Choose your setup -**Choose your setup** 1. How do you run dbt? - **You own your dbt run** (GitHub Actions, GitLab CI, CircleCI): Continue with this guide @@ -105,8 +106,6 @@ Before proceeding, identify your setup: - **GitHub Actions**: Continue with this guide - **Other platforms** (GitLab CI, CircleCI, etc.): See [Setup CD](../7-cicd/setup-cd.md) and [Setup CI](../7-cicd/setup-ci.md) -This step adds CI/CD workflow files to your repository. The agent creates these automatically. For manual setup, create and merge a PR with the templates below. - #### Set Up Profile.yml The profile.yml file tells your system where to look for the "base" and "current" builds. We have a sample `profile.yml` file: From 679121f39b65ab1cc64dcffe223e068818150ba6 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 22:09:03 +0800 Subject: [PATCH 07/43] Add 'Choose your setup' decision tree to CI/CD section - Restructure setup guidance into nested decision tree - Question 1: Where dbt runs (self-hosted vs dbt Cloud) - If self-hosted: sub-options for GitHub Actions vs GitLab/CircleCI - Question 2: Environment complexity (simple vs advanced) - Link to dbt Cloud Setup for platform users - Link to Environment Setup for advanced configurations --- docs/2-getting-started/start-free-with-cloud.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/docs/2-getting-started/start-free-with-cloud.md b/docs/2-getting-started/start-free-with-cloud.md index 4edc0d0..d5a7b64 100644 --- a/docs/2-getting-started/start-free-with-cloud.md +++ b/docs/2-getting-started/start-free-with-cloud.md @@ -87,9 +87,21 @@ First, go to [cloud.reccehq.com](https://cloud.reccehq.com) and create your free ### 3. Add Recce to CI/CD -This step adds CI/CD workflow files to your repository. The agent creates these automatically. For manual setup, create and merge a PR with the templates below. +This step adds CI/CD workflow files to your repository. The web agent detects your setup and guides you through. For manual setup, use the templates below. -> **Note**: This guide uses GitHub Actions. For other CI/CD platforms, see [Setup CD](../7-cicd/setup-cd.md) and [Setup CI](../7-cicd/setup-ci.md). +#### Choose your setup + +1. How do you run dbt? + + - **You own your dbt run** + - **GitHub Actions**: Continue with this guide + - **GitLab CI, CircleCI**: See [Setup CD](../7-cicd/setup-cd.md) and [Setup CI](../7-cicd/setup-ci.md) + - **You run dbt on a platform** (dbt Cloud, Paradigms, etc.): See [dbt Cloud Setup](dbt-cloud-setup.md) + +2. How complex is your environment? + + - **Simple** (prod and dev targets): Continue with this guide. We use per-PR schemas for fast setup. To learn why, see [Environment Setup](environment-setup.md). + - **Advanced** (multiple schemas, staging environments): See [Environment Setup](environment-setup.md) #### Set Up Profile.yml From 99e1e4bc3babf451f68a1ba638eee2406890c156 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 22:14:52 +0800 Subject: [PATCH 08/43] Fix nested list indentation in Choose your setup section --- docs/2-getting-started/start-free-with-cloud.md | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/docs/2-getting-started/start-free-with-cloud.md b/docs/2-getting-started/start-free-with-cloud.md index d5a7b64..9a1f27e 100644 --- a/docs/2-getting-started/start-free-with-cloud.md +++ b/docs/2-getting-started/start-free-with-cloud.md @@ -92,16 +92,14 @@ This step adds CI/CD workflow files to your repository. The web agent detects yo #### Choose your setup 1. How do you run dbt? - - - **You own your dbt run** - - **GitHub Actions**: Continue with this guide - - **GitLab CI, CircleCI**: See [Setup CD](../7-cicd/setup-cd.md) and [Setup CI](../7-cicd/setup-ci.md) - - **You run dbt on a platform** (dbt Cloud, Paradigms, etc.): See [dbt Cloud Setup](dbt-cloud-setup.md) + - **You own your dbt run** + - **GitHub Actions**: Continue with this guide + - **GitLab CI, CircleCI**: See [Setup CD](../7-cicd/setup-cd.md) and [Setup CI](../7-cicd/setup-ci.md) + - **You run dbt on a platform** (dbt Cloud, Paradime, etc.): See [dbt Cloud Setup](dbt-cloud-setup.md) 2. How complex is your environment? - - - **Simple** (prod and dev targets): Continue with this guide. We use per-PR schemas for fast setup. To learn why, see [Environment Setup](environment-setup.md). - - **Advanced** (multiple schemas, staging environments): See [Environment Setup](environment-setup.md) + - **Simple** (prod and dev targets): Continue with this guide. We use per-PR schemas for fast setup. To learn why, see [Environment Setup](environment-setup.md). + - **Advanced** (multiple schemas, staging environments): See [Environment Setup](environment-setup.md) #### Set Up Profile.yml From 94d4554c5d82dfa01654d7ff0c182afb84e96356 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 22:27:38 +0800 Subject: [PATCH 09/43] Add environment setup docs and move CI/CD setup pages - Update environment-setup.md with CI/CD workflow explanation - Create environment-best-practices.md (from best-practices-prep-env.md) - Move setup-cd.md to 2-getting-started (for GitLab/CircleCI users) - Move setup-ci.md to 2-getting-started (for GitLab/CircleCI users) - Update internal links to new locations --- .../environment-best-practices.md | 169 +++++++++ docs/2-getting-started/environment-setup.md | 18 +- docs/2-getting-started/setup-cd.md | 347 ++++++++++++++++++ docs/2-getting-started/setup-ci.md | 285 ++++++++++++++ 4 files changed, 815 insertions(+), 4 deletions(-) create mode 100644 docs/2-getting-started/environment-best-practices.md create mode 100644 docs/2-getting-started/setup-cd.md create mode 100644 docs/2-getting-started/setup-ci.md diff --git a/docs/2-getting-started/environment-best-practices.md b/docs/2-getting-started/environment-best-practices.md new file mode 100644 index 0000000..0830913 --- /dev/null +++ b/docs/2-getting-started/environment-best-practices.md @@ -0,0 +1,169 @@ +--- +title: Environment Best Practices +--- + +# Environment Best Practices + +Strategies for preparing reliable, efficient environments for Recce data validation. + +## Overview + +Recce compares base and current environments to validate data changes. Several factors can affect comparison accuracy: + +- Source data updates continuously +- Transformations take time to run +- Other PRs merge into the base branch +- Generated environments accumulate in the warehouse + +This guide covers strategies to manage these challenges. + +## Use per-PR schemas + +Each PR should have its own isolated schema. This prevents interference between concurrent PRs and makes cleanup straightforward. + +```yaml +# profiles.yml +ci: + schema: "{{ env_var('CI_SCHEMA') }}" + +# CI workflow +env: + CI_SCHEMA: "pr_${{ github.event.pull_request.number }}" +``` + +Benefits: + +- Complete isolation between PRs +- Parallel validation without conflicts +- Easy cleanup by dropping the schema + +See [Environment Setup](environment-setup.md) for detailed configuration. + +## Prepare a single base environment + +Use one consistent base environment for all PRs to compare against. Options: + +| Base Environment | Characteristics | Best For | +|------------------|-----------------|----------| +| Production | Latest merged code, full data | Accurate production comparison | +| Staging | Latest merged code, limited data | Faster comparisons, lower cost | + +If using staging as base: + +- Ensure transformed results reflect the latest commit of the base branch +- Use the same source data as PR environments +- Use the same transformation logic as PR environments + +The staging environment should match PR environments as closely as possible, differing only in git commit. + +## Limit source data range + +Most data is temporal. Using only recent data reduces transformation time while still validating correctness. + +**Strategy:** Use data from the last month, excluding the current week. This ensures consistent results regardless of when transformations run. + +```sql +SELECT * +FROM {{ source('your_source_name', 'orders') }} +{% if target.name != 'prod' %} +WHERE + order_date >= DATEADD(month, -1, CURRENT_DATE) + AND order_date < DATE_TRUNC('week', CURRENT_DATE) +{% endif %} +``` + +Benefits: + +- Faster transformation execution +- Consistent comparison results +- Reduced warehouse costs + +## Reduce source data volatility + +If source data updates frequently (hourly or more), comparison results can vary based on timing rather than code changes. + +**Strategies:** + +- **Zero-copy clone** (Snowflake, BigQuery, Databricks): Freeze source data at a specific point in time +- **Weekly snapshots**: Update source data weekly to reduce variability + +![Source data cloning](../assets/images/7-cicd/prep-env-clone-source.png){: .shadow} + +## Keep base environment current + +The base environment can become outdated in two scenarios: + +1. **New source data**: If you update data weekly, update the base environment at least weekly +2. **PRs merged to main**: Trigger base environment update on merge events + +Configure your CD workflow to run: + +- On merge to main (immediate update) +- On schedule (e.g., daily at 2 AM UTC) + +See [Setup CD](setup-cd.md) for workflow configuration. + +## Keep PR branch in sync with base + +If a PR runs after other PRs merge to main, the comparison mixes: + +- Changes from the current PR +- Changes from other merged PRs + +This produces comparison results that don't accurately reflect the current PR's impact. + +![PR out of sync](../assets/images/7-cicd/prep-env-pr-outdated.png){: .shadow} + +**GitHub**: Enable [branch protection](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/keeping-your-pull-request-in-sync-with-the-base-branch) to show when PRs are outdated. + +**CI check**: Add a workflow step to verify the PR is up-to-date: + +```yaml +- name: Check if PR is up-to-date + if: github.event_name == 'pull_request' + run: | + git fetch origin main + UPSTREAM=${GITHUB_BASE_REF:-'main'} + HEAD=${GITHUB_HEAD_REF:-${GITHUB_REF#refs/heads/}} + if [ "$(git rev-list --left-only --count ${HEAD}...origin/${UPSTREAM})" -eq 0 ]; then + echo "Branch is up-to-date" + else + echo "Branch is not up-to-date" + exit 1 + fi +``` + +## Clean up PR environments + +As PRs accumulate, so do generated schemas. Implement cleanup to manage warehouse storage. + +**On PR close**: Create a workflow that drops the PR schema when the PR closes. + +```jinja +{% macro clear_schema(schema_name) %} +{% set drop_schema_command = "DROP SCHEMA IF EXISTS " ~ schema_name ~ " CASCADE;" %} +{% do run_query(drop_schema_command) %} +{% endmacro %} +``` + +Run the cleanup: + +```shell +dbt run-operation clear_schema --args "{'schema_name': 'pr_123'}" +``` + +**Scheduled cleanup**: Remove schemas not used for a week. + +## Example configuration + +| Environment | Schema | When to Run | Count | Data Range | +|-------------|--------|-------------|-------|------------| +| Production | `public` | Daily | 1 | All | +| Staging | `staging` | Daily + on merge | 1 | 1 month, excluding current week | +| PR | `pr_` | On push | # of open PRs | 1 month, excluding current week | + +## Related + +- [Environment Setup](environment-setup.md) - Technical configuration for profiles.yml and CI/CD +- [Setup CD](setup-cd.md) - Configure automatic baseline updates +- [Setup CI](setup-ci.md) - Configure PR validation diff --git a/docs/2-getting-started/environment-setup.md b/docs/2-getting-started/environment-setup.md index 2d35956..4bb6073 100644 --- a/docs/2-getting-started/environment-setup.md +++ b/docs/2-getting-started/environment-setup.md @@ -16,6 +16,15 @@ Configure your dbt targets and CI/CD environment variables to support base vs cu - [x] **CI/CD platform**: GitHub Actions, GitLab CI, or similar - [x] **Warehouse access**: Credentials with permissions to create schemas dynamically +## How CI/CD works with Recce + +Recce uses both continuous delivery (CD) and continuous integration (CI) to automate data validation: + +- **CD (Continuous Delivery)**: Runs after merge to main. Updates baseline artifacts with latest production state. +- **CI (Continuous Integration)**: Runs on PR/MR. Validates proposed changes against baseline. + +**Set up CD first**, then CI. CD establishes your baseline (production artifacts), which CI uses for comparison. + ## Why separate schemas matter Recce compares two sets of data to validate changes: @@ -29,8 +38,8 @@ Common patterns include: | Pattern | Base Schema | Current Schema | Best For | |---------|-------------|----------------|----------| -| Schema-per-PR | `public` (prod) | `pr_123` | Teams with many concurrent PRs | | Shared dev | `public` (prod) | `dev` | Solo developers or small teams | +| Schema-per-PR | `public` (prod) | `pr_123` | Teams with many concurrent PRs | | Staging-based | `staging` | `pr_123` | Teams wanting consistent source data | ## Configure profiles.yml @@ -242,7 +251,7 @@ Benefits: - Faster diffs with limited data ranges - Reduced warehouse costs -See [Best Practices for Preparing Environments](../7-cicd/best-practices-prep-env.md) for detailed strategies on limiting data ranges and managing source data consistency. +See [Environment Best Practices](environment-best-practices.md) for detailed strategies on limiting data ranges and managing source data consistency. ## Verification @@ -300,5 +309,6 @@ If schemas are not resolving correctly, add debugging to your workflow: ## Related - [Get Started with Recce Cloud](start-free-with-cloud.md) - Complete onboarding guide -- [Best Practices for Preparing Environments](../7-cicd/best-practices-prep-env.md) - Strategies for source data and schema management -- [Setup CI](../7-cicd/setup-ci.md) - CI workflow configuration details +- [Environment Best Practices](environment-best-practices.md) - Strategies for source data and schema management +- [Setup CD](setup-cd.md) - CD workflow for GitHub Actions and GitLab CI +- [Setup CI](setup-ci.md) - CI workflow for GitHub Actions and GitLab CI diff --git a/docs/2-getting-started/setup-cd.md b/docs/2-getting-started/setup-cd.md new file mode 100644 index 0000000..b03b317 --- /dev/null +++ b/docs/2-getting-started/setup-cd.md @@ -0,0 +1,347 @@ +--- +title: Setup CD +--- + +# Setup CD - Auto-Update Baseline + +Set up automatic updates for your Recce Cloud base sessions. Keep your data comparison baseline current every time you merge to main, with no manual work required. + +## What This Does + +**Automated Base Session Management** eliminates manual baseline maintenance: + +- **Triggers**: Merge to main + scheduled updates + manual runs +- **Action**: Auto-update base Recce session with latest production artifacts +- **Benefit**: Current comparison baseline for all future PRs/MRs + +## Prerequisites + +Before setting up CD, ensure you have: + +- [x] **Recce Cloud account** - [Start free trial](https://cloud.reccehq.com/) +- [x] **Repository connected** to Recce Cloud - [Connect Git Provider](start-free-with-cloud.md#2-connect-git-provider) +- [x] **dbt artifacts** - Know how to generate `manifest.json` and `catalog.json` from your dbt project + +## Setup + +### GitHub Actions + +Create `.github/workflows/base-workflow.yml`: + +```yaml linenums="1" +name: Update Base Metadata + +on: + push: + branches: ["main"] + schedule: + - cron: "0 2 * * *" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }} + cancel-in-progress: true + +jobs: + update-base-session: + runs-on: ubuntu-latest + timeout-minutes: 30 + permissions: + contents: read + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Prepare dbt artifacts + run: | + dbt deps + dbt build --target prod + dbt docs generate --target prod + env: + SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }} + SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }} + SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }} + SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }} + SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }} + + - name: Upload to Recce Cloud + run: | + pip install recce-cloud + recce-cloud upload --type prod + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + +**Key points:** + +- `dbt build` and `dbt docs generate` create the required artifacts (`manifest.json` and `catalog.json`) +- `recce-cloud upload --type prod` uploads the Base metadata to Recce Cloud +- [`GITHUB_TOKEN`](https://docs.github.com/en/actions/concepts/security/github_token) authenticates with Recce Cloud + +### GitLab CI/CD + +Add to your `.gitlab-ci.yml`: + +```yaml linenums="1" hl_lines="30-31" +stages: + - build + - upload + +variables: + DBT_TARGET_PROD: "prod" + +# Production build - runs on schedule or main branch push +prod-build: + stage: build + image: python:3.11-slim + script: + - pip install -r requirements.txt + - dbt deps + # Optional: dbt build --target $DBT_TARGET_PROD + - dbt docs generate --target $DBT_TARGET_PROD + artifacts: + paths: + - target/ + expire_in: 7 days + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" + - if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH + +# Upload to Recce Cloud +recce-upload-prod: + stage: upload + image: python:3.11-slim + script: + - pip install recce-cloud + - recce-cloud upload --type prod + dependencies: + - prod-build + rules: + - if: $CI_PIPELINE_SOURCE == "schedule" + - if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH +``` + +**Key points:** + +- Authentication is automatic via `CI_JOB_TOKEN` +- Configure schedule in **CI/CD → Schedules** (e.g., `0 2 * * *` for daily at 2 AM UTC) +- `recce-cloud upload --type prod` tells Recce this is a baseline session + +### Platform Comparison + +| Aspect | GitHub Actions | GitLab CI/CD | +| -------------------- | ----------------------------------- | ------------------------------------------------------------------------------ | +| **Config file** | `.github/workflows/base-workflow.yml` | `.gitlab-ci.yml` | +| **Trigger on merge** | `on: push: branches: ["main"]` | `if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH` | +| **Schedule setup** | In workflow YAML (`schedule:`) | In UI: **CI/CD → Schedules** | +| **Authentication** | Explicit (`GITHUB_TOKEN`) | Automatic (`CI_JOB_TOKEN`) | +| **Manual trigger** | `workflow_dispatch:` | Pipeline run from UI | + +## Verification + +### Test the Workflow + +**GitHub:** + +1. Go to **Actions** tab → Select "Update Base Recce Session" +2. Click **Run workflow** → Monitor for completion + +**GitLab:** + +1. Go to **CI/CD → Pipelines** → Click **Run pipeline** +2. Select **main** branch → Monitor for completion + +### Verify Success + +Look for these indicators: + +- [x] **Workflow/Pipeline completes** without errors +- [x] **Base session updated** in [Recce Cloud](https://cloud.reccehq.com) + +**GitHub:** + +![Recce Cloud showing updated base sessions](../assets/images/7-cicd/verify-setup-github-cd.png){: .shadow} + +**GitLab:** + +![Recce Cloud showing updated base sessions](../assets/images/7-cicd/verify-setup-gitlab-cd.png){: .shadow} + +### Expected Output + +When the upload succeeds, you'll see output like this in your workflow logs: + +**GitHub:** + +```hl_lines="2 3 13" +─────────────────────────── CI Environment Detection ─────────────────────────── +Platform: github-actions +Session Type: prod +Commit SHA: def456ab... +Source Branch: main +Repository: your-org/your-repo +Info: Using GITHUB_TOKEN for platform-specific authentication +────────────────────────── Creating/touching session ─────────────────────────── +Session ID: abc123-def456-ghi789 +Uploading manifest from path "target/manifest.json" +Uploading catalog from path "target/catalog.json" +Notifying upload completion... +──────────────────────────── Uploaded Successfully ───────────────────────────── +Uploaded dbt artifacts to Recce Cloud for session ID "abc123-def456-ghi789" +Artifacts from: "/home/runner/work/your-repo/your-repo/target" +``` + +**GitLab:** + +```hl_lines="2 3 13" +─────────────────────────── CI Environment Detection ─────────────────────────── +Platform: gitlab-ci +Session Type: prod +Commit SHA: a1b2c3d4... +Source Branch: main +Repository: your-org/your-project +Info: Using CI_JOB_TOKEN for platform-specific authentication +────────────────────────── Creating/touching session ─────────────────────────── +Session ID: abc123-def456-ghi789 +Uploading manifest from path "target/manifest.json" +Uploading catalog from path "target/catalog.json" +Notifying upload completion... +──────────────────────────── Uploaded Successfully ───────────────────────────── +Uploaded dbt artifacts to Recce Cloud for session ID "abc123-def456-ghi789" +Artifacts from: "/builds/your-org/your-project/target" +``` + +## Advanced Options + +### Custom Artifact Path + +If your dbt artifacts are in a non-standard location: + +```bash +recce-cloud upload --type prod --target-path custom-target +``` + +### External Artifact Sources + +You can download artifacts from external sources before uploading: + +```yaml +# GitHub example +- name: Download from dbt Cloud + run: | + # Your download logic here + # Artifacts should end up in target/ directory + +- name: Upload to Recce Cloud + run: | + pip install recce-cloud + recce-cloud upload --type prod +``` + +### Dry Run Testing + +Test your configuration without actually uploading: + +```bash +recce-cloud upload --type prod --dry-run +``` + +## Troubleshooting + +### Missing dbt artifacts + +**Error**: `Missing manifest.json` or `Missing catalog.json` + +**Solution**: Ensure `dbt docs generate` runs successfully before upload: + +**GitHub:** + +```yaml +- name: Prepare dbt artifacts + run: | + dbt deps + dbt docs generate --target prod # Required +``` + +**GitLab:** + +```yaml +prod-build: + script: + - dbt deps + - dbt docs generate --target $DBT_TARGET_PROD # Required + artifacts: + paths: + - target/ +``` + +### Authentication issues + +**Error**: `Failed to create session: 401 Unauthorized` + +**Solutions**: + +1. Verify your repository is connected in [Recce Cloud settings](https://cloud.reccehq.com/settings) +2. **For GitHub**: Ensure `GITHUB_TOKEN` is passed explicitly to the upload step and the job has `contents: read` permission +3. **For GitLab**: Verify project has GitLab integration configured + - Check that you've created a [Personal Access Token](gitlab-pat-guide.md) + - Ensure the token has appropriate scope (`api` or `read_api`) + - Verify the project is connected in Recce Cloud settings + +### Upload failures + +**Error**: `Failed to upload manifest/catalog` + +**Solutions**: + +1. Check network connectivity to Recce Cloud +2. Verify artifact files exist in `target/` directory +3. Review workflow/pipeline logs for detailed error messages +4. **For GitLab**: Ensure artifacts are passed between jobs: + + ```yaml + prod-build: + artifacts: + paths: + - target/ # Must include dbt artifacts + + recce-upload-prod: + dependencies: + - prod-build # Required to access artifacts + ``` + +### Session not appearing + +**Issue**: Upload succeeds but session doesn't appear in Recce Cloud + +**Solutions**: + +1. Check you're viewing the correct repository in Recce Cloud +2. Verify you're looking at the production/base sessions (not PR/MR sessions) +3. Check session filters in Recce Cloud (may be hidden by filters) +4. Refresh the Recce Cloud page + +### Schedule not triggering (GitLab only) + +**Issue**: Scheduled pipeline doesn't run + +**Solutions**: + +1. Verify schedule is **Active** in CI/CD → Schedules +2. Check schedule timezone settings (UTC by default) +3. Ensure target branch (`main`) exists +4. Review project's CI/CD minutes quota +5. Verify schedule owner has appropriate permissions + +## Next Steps + +**[Setup CI](setup-ci.md)** to automatically validate PR/MR changes against your updated base session. This completes your CI/CD pipeline by adding automated data validation for every pull request or merge request. diff --git a/docs/2-getting-started/setup-ci.md b/docs/2-getting-started/setup-ci.md new file mode 100644 index 0000000..70f50a2 --- /dev/null +++ b/docs/2-getting-started/setup-ci.md @@ -0,0 +1,285 @@ +--- +title: Setup CI +--- + +# Setup CI - Auto-Validate PRs/MRs + +Automatically validate your data changes in every pull request or merge request using Recce Cloud. Catch data issues before they reach production, with validation results right in your PR/MR. + +## What This Does + +**Automated PR/MR Validation** prevents data regressions before merge: + +- **Triggers**: PR/MR opened or updated against main +- **Action**: Auto-update Recce session for validation +- **Benefit**: Automated data validation and comparison visible in your PR/MR + +## Prerequisites + +Before setting up CI, ensure you have: + +- [x] **Recce Cloud account** - [Start free trial](https://cloud.reccehq.com/) +- [x] **Repository connected** to Recce Cloud - [Connect Git Provider](start-free-with-cloud.md#2-connect-git-provider) +- [x] **dbt artifacts** - Know how to generate `manifest.json` and `catalog.json` from your dbt project +- [x] **CD configured** - [Setup CD](setup-cd.md) to establish baseline for comparisons + +## Setup + +### GitHub Actions + +Create `.github/workflows/pr-workflow.yml`: + +```yaml linenums="1" +name: Validate PR Changes + +on: + pull_request: + branches: ["main"] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + validate-changes: + runs-on: ubuntu-latest + timeout-minutes: 45 + permissions: + contents: read + pull-requests: write + + steps: + - name: Checkout PR branch + uses: actions/checkout@v4 + with: + fetch-depth: 2 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + cache: "pip" + + - name: Install dependencies + run: pip install -r requirements.txt + + - name: Build current branch artifacts + run: | + dbt deps + dbt build --target ci + dbt docs generate --target ci + env: + SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }} + SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }} + SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }} + SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }} + SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }} + SNOWFLAKE_SCHEMA: "PR_${{ github.event.pull_request.number }}" + + - name: Upload to Recce Cloud + run: | + pip install recce-cloud + recce-cloud upload + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} +``` + +**Key points:** + +- Creates a per-PR schema (`PR_123`, `PR_456`, etc.) using the dynamic `SNOWFLAKE_SCHEMA` environment variable to isolate each PR's data +- `dbt build` and `dbt docs generate` create the required artifacts (`manifest.json` and `catalog.json`) +- `recce-cloud upload` (without `--type`) auto-detects this is a PR session +- [`GITHUB_TOKEN`](https://docs.github.com/en/actions/concepts/security/github_token) authenticates with Recce Cloud + +### GitLab CI/CD + +Add to your `.gitlab-ci.yml`: + +```yaml linenums="1" hl_lines="29-30" +stages: + - build + - upload + +variables: + DBT_TARGET: "ci" + +# MR build - runs on merge requests +dbt-build: + stage: build + image: python:3.11-slim + script: + - pip install -r requirements.txt + - dbt deps + # Optional: dbt build --target $DBT_TARGET + - dbt docs generate --target $DBT_TARGET + artifacts: + paths: + - target/ + expire_in: 1 week + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" + +# Upload to Recce Cloud +recce-upload: + stage: upload + image: python:3.11-slim + script: + - pip install recce-cloud + - recce-cloud upload + dependencies: + - dbt-build + rules: + - if: $CI_PIPELINE_SOURCE == "merge_request_event" +``` + +**Key points:** + +- Authentication is automatic via `CI_JOB_TOKEN` +- `recce-cloud upload` (without `--type`) auto-detects this is an MR session +- `dbt docs generate` creates the required `manifest.json` and `catalog.json` + +### Platform Comparison + +| Aspect | GitHub Actions | GitLab CI/CD | +| -------------------- | ----------------------------------- | -------------------------------------------------- | +| **Config file** | `.github/workflows/pr-workflow.yml` | `.gitlab-ci.yml` | +| **Trigger** | `on: pull_request:` | `if: $CI_PIPELINE_SOURCE == "merge_request_event"` | +| **Authentication** | Explicit (`GITHUB_TOKEN`) | Automatic (`CI_JOB_TOKEN`) | +| **Session type** | Auto-detected from PR context | Auto-detected from MR context | +| **Artifact passing** | Not needed (single job) | Use `artifacts:` + `dependencies:` | + +## Verification + +### Test with a PR/MR + +**GitHub:** + +1. Create a test PR with small data changes +2. Check **Actions** tab for CI workflow execution +3. Verify validation runs successfully + +**GitLab:** + +1. Create a test MR with small data changes +2. Check **CI/CD → Pipelines** for workflow execution +3. Verify validation runs successfully + +### Verify Success + +Look for these indicators: + +- [x] **Workflow/Pipeline completes** without errors +- [x] **PR/MR session created** in [Recce Cloud](https://cloud.reccehq.com) +- [x] **Session URL** appears in workflow/pipeline output + +**GitHub:** + +![Recce Cloud showing PR validation session](../assets/images/7-cicd/verify-setup-github-ci.png){: .shadow} + +**GitLab:** + +![Recce Cloud showing MR validation session](../assets/images/7-cicd/verify-setup-gitlab-ci.png){: .shadow} + +### Expected Output + +When the upload succeeds, you'll see output like this in your workflow logs: + +**GitHub:** + +```hl_lines="2 5 16" +─────────────────────────── CI Environment Detection ─────────────────────────── +Platform: github-actions +PR Number: 42 +PR URL: https://github.com/your-org/your-repo/pull/42 +Session Type: cr +Commit SHA: abc123de... +Base Branch: main +Source Branch: feature/your-feature +Repository: your-org/your-repo +Info: Using GITHUB_TOKEN for platform-specific authentication +────────────────────────── Creating/touching session ─────────────────────────── +Session ID: f8b0f7ca-ea59-411d-abd8-88b80b9f87ad +Uploading manifest from path "target/manifest.json" +Uploading catalog from path "target/catalog.json" +Notifying upload completion... +──────────────────────────── Uploaded Successfully ───────────────────────────── +Uploaded dbt artifacts to Recce Cloud for session ID "f8b0f7ca-ea59-411d-abd8-88b80b9f87ad" +Artifacts from: "/home/runner/work/your-repo/your-repo/target" +Change request: https://github.com/your-org/your-repo/pull/42 +``` + +**GitLab:** + +```hl_lines="2 5 16" +─────────────────────────── CI Environment Detection ─────────────────────────── +Platform: gitlab-ci +MR Number: 4 +MR URL: https://gitlab.com/your-org/your-project/-/merge_requests/4 +Session Type: cr +Commit SHA: c928e3d5... +Base Branch: main +Source Branch: feature/your-feature +Repository: your-org/your-project +Info: Using CI_JOB_TOKEN for platform-specific authentication +────────────────────────── Creating/touching session ─────────────────────────── +Session ID: f8b0f7ca-ea59-411d-abd8-88b80b9f87ad +Uploading manifest from path "target/manifest.json" +Uploading catalog from path "target/catalog.json" +Notifying upload completion... +──────────────────────────── Uploaded Successfully ───────────────────────────── +Uploaded dbt artifacts to Recce Cloud for session ID "f8b0f7ca-ea59-411d-abd8-88b80b9f87ad" +Artifacts from: "/builds/your-org/your-project/target" +Change request: https://gitlab.com/your-org/your-project/-/merge_requests/4 +``` + +### Review PR/MR Session + +To analyze the changes in detail: + +1. Go to your [Recce Cloud](https://cloud.reccehq.com) +2. Find the PR/MR session that was created +3. Launch Recce instance to explore data differences + +## Advanced Options + +### Custom Artifact Path + +If your dbt artifacts are in a non-standard location: + +```bash +recce-cloud upload --target-path custom-target +``` + +### Dry Run Testing + +Test your configuration without actually uploading: + +```bash +recce-cloud upload --dry-run +``` + +## Troubleshooting + +If CI is not working, the issue is likely in your CD setup. Most problems are shared between CI and CD: + +**Common issues:** + +- Missing dbt artifacts +- Authentication failures +- Upload errors +- Sessions not appearing + +**→ See the [Setup CD Troubleshooting section](setup-cd.md#troubleshooting)** for detailed solutions. + +**CI-specific tip:** If CD works but CI doesn't, verify: + +1. PR/MR trigger conditions in your workflow configuration +2. The PR/MR is targeting the correct base branch (usually `main`) +3. You're looking at PR/MR sessions in Recce Cloud (not production sessions) + +## Next Steps + +After setting up CI, explore these guides: + +- [Environment Best Practices](environment-best-practices.md) - Strategies for source data and schema management +- [Get Started with Recce Cloud](start-free-with-cloud.md) - Complete onboarding guide From 7e5dec7df057c7c479658ed35131c3ba2e02b095 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 22:32:15 +0800 Subject: [PATCH 10/43] Add dbt-cloud-setup to nav and update setup-cd/ci links - Add dbt Cloud Setup to mkdocs.yml navigation - Update setup-cd and setup-ci links to new location (2-getting-started/) --- docs/2-getting-started/start-free-with-cloud.md | 2 +- mkdocs.yml | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/2-getting-started/start-free-with-cloud.md b/docs/2-getting-started/start-free-with-cloud.md index 9a1f27e..5dbdc2c 100644 --- a/docs/2-getting-started/start-free-with-cloud.md +++ b/docs/2-getting-started/start-free-with-cloud.md @@ -94,7 +94,7 @@ This step adds CI/CD workflow files to your repository. The web agent detects yo 1. How do you run dbt? - **You own your dbt run** - **GitHub Actions**: Continue with this guide - - **GitLab CI, CircleCI**: See [Setup CD](../7-cicd/setup-cd.md) and [Setup CI](../7-cicd/setup-ci.md) + - **GitLab CI, CircleCI**: See [Setup CD](setup-cd.md) and [Setup CI](setup-ci.md) - **You run dbt on a platform** (dbt Cloud, Paradime, etc.): See [dbt Cloud Setup](dbt-cloud-setup.md) 2. How complex is your environment? diff --git a/mkdocs.yml b/mkdocs.yml index bee8774..10c1c93 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -54,6 +54,7 @@ nav: - Getting Started: - 2-getting-started/oss-vs-cloud.md - 2-getting-started/start-free-with-cloud.md + - dbt Cloud Setup: 2-getting-started/dbt-cloud-setup.md #- 2-getting-started/cloud-5min-tutorial.md - 2-getting-started/installation.md - Claude Plugin: 2-getting-started/claude-plugin.md From ac212607df3ce12df25967974dd3598edb763a03 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 22:39:13 +0800 Subject: [PATCH 11/43] Simplify environment-setup based on feedback - Remove 'advanced configuration' framing - this is basic setup - Reorder: 'Why separate schemas' before 'How CI/CD works' - Reduce profiles.yml to single example with note for other warehouses - Shorten CI/CD env var examples (full workflows in setup-cd/ci) - Recommend schema-per-PR as primary pattern - Add 'not recommended' warning for shared dev schema - Add pros AND cons for staging-as-base pattern --- docs/2-getting-started/environment-setup.md | 237 +++++--------------- 1 file changed, 58 insertions(+), 179 deletions(-) diff --git a/docs/2-getting-started/environment-setup.md b/docs/2-getting-started/environment-setup.md index 4bb6073..11b84f0 100644 --- a/docs/2-getting-started/environment-setup.md +++ b/docs/2-getting-started/environment-setup.md @@ -4,11 +4,11 @@ title: Environment Setup # Environment Setup -This guide covers advanced configuration for data teams with complex setups—multiple schemas, separate development warehouses, or CI/CD automation requirements. +Configure your dbt profiles and CI/CD environment variables for Recce data validation. ## Goal -Configure your dbt targets and CI/CD environment variables to support base vs current comparison in Recce. After completing this guide, your CI/CD workflows can automatically create isolated schemas for each PR and compare them against production. +Set up isolated schemas for base vs current comparison. After completing this guide, your CI/CD workflows automatically create per-PR schemas and compare them against production. ## Prerequisites @@ -16,15 +16,6 @@ Configure your dbt targets and CI/CD environment variables to support base vs cu - [x] **CI/CD platform**: GitHub Actions, GitLab CI, or similar - [x] **Warehouse access**: Credentials with permissions to create schemas dynamically -## How CI/CD works with Recce - -Recce uses both continuous delivery (CD) and continuous integration (CI) to automate data validation: - -- **CD (Continuous Delivery)**: Runs after merge to main. Updates baseline artifacts with latest production state. -- **CI (Continuous Integration)**: Runs on PR/MR. Validates proposed changes against baseline. - -**Set up CD first**, then CI. CD establishes your baseline (production artifacts), which CI uses for comparison. - ## Why separate schemas matter Recce compares two sets of data to validate changes: @@ -34,55 +25,49 @@ Recce compares two sets of data to validate changes: For accurate validation, these must point to different schemas in your warehouse. Without separation, you would compare identical data and miss meaningful differences. -Common patterns include: +## How CI/CD works with Recce -| Pattern | Base Schema | Current Schema | Best For | -|---------|-------------|----------------|----------| -| Shared dev | `public` (prod) | `dev` | Solo developers or small teams | -| Schema-per-PR | `public` (prod) | `pr_123` | Teams with many concurrent PRs | -| Staging-based | `staging` | `pr_123` | Teams wanting consistent source data | +Recce uses both continuous delivery (CD) and continuous integration (CI) to automate data validation: -## Configure profiles.yml +- **CD (Continuous Delivery)**: Runs after merge to main. Updates baseline artifacts with latest production state. +- **CI (Continuous Integration)**: Runs on PR/MR. Validates proposed changes against baseline. + +**Set up CD first**, then CI. CD establishes your baseline (production artifacts), which CI uses for comparison. -Your `profiles.yml` file defines how dbt connects to your warehouse. Add separate targets for production, development, and CI. +## Configure profiles.yml -### Example: Snowflake with dynamic CI schema +Your `profiles.yml` file defines how dbt connects to your warehouse. Add a `ci` target with a dynamic schema for PR isolation. ```yaml jaffle_shop: target: dev outputs: - # Local development dev: type: snowflake account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}" user: "{{ env_var('SNOWFLAKE_USER') }}" password: "{{ env_var('SNOWFLAKE_PASSWORD') }}" - role: DEVELOPER database: analytics warehouse: COMPUTE_WH - schema: "{{ env_var('DEV_SCHEMA', 'dev_' ~ env_var('USER', 'default')) }}" + schema: dev threads: 4 - # CI environment with dynamic schema + # CI environment with dynamic schema per PR ci: type: snowflake account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}" user: "{{ env_var('SNOWFLAKE_USER') }}" password: "{{ env_var('SNOWFLAKE_PASSWORD') }}" - role: DEVELOPER database: analytics warehouse: COMPUTE_WH schema: "{{ env_var('CI_SCHEMA') }}" threads: 4 - # Production prod: type: snowflake account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}" user: "{{ env_var('SNOWFLAKE_USER') }}" password: "{{ env_var('SNOWFLAKE_PASSWORD') }}" - role: DEVELOPER database: analytics warehouse: COMPUTE_WH schema: public @@ -93,165 +78,85 @@ Key points: - The `ci` target uses `env_var('CI_SCHEMA')` for dynamic schema assignment - The `prod` target uses a fixed schema (`public`) for consistency -- The `dev` target can use a developer-specific schema or a shared one - -### Example: BigQuery with dataset separation - -```yaml -jaffle_shop: - target: dev - outputs: - dev: - type: bigquery - method: service-account - project: "{{ env_var('GCP_PROJECT') }}" - dataset: "{{ env_var('DEV_DATASET', 'dev') }}" - threads: 4 - keyfile: "{{ env_var('GOOGLE_APPLICATION_CREDENTIALS') }}" - - ci: - type: bigquery - method: service-account - project: "{{ env_var('GCP_PROJECT') }}" - dataset: "{{ env_var('CI_DATASET') }}" - threads: 4 - keyfile: "{{ env_var('GOOGLE_APPLICATION_CREDENTIALS') }}" - - prod: - type: bigquery - method: service-account - project: "{{ env_var('GCP_PROJECT') }}" - dataset: production - threads: 4 - keyfile: "{{ env_var('GOOGLE_APPLICATION_CREDENTIALS') }}" -``` +- Adapt this pattern for other warehouses (BigQuery uses `dataset` instead of `schema`) ## Set CI/CD environment variables -Your CI/CD workflow sets the schema dynamically for each PR. This creates isolated data for validation. +Your CI/CD workflow sets the schema dynamically for each PR. The key configuration: -### GitHub Actions example - -In your PR workflow file (`.github/workflows/pr-recce.yml`): +**GitHub Actions:** ```yaml -name: PR Validation - -on: - pull_request: - branches: [main] - -jobs: - validate: - runs-on: ubuntu-latest - env: - # Dynamic schema based on PR number - CI_SCHEMA: "pr_${{ github.event.pull_request.number }}" - - # Warehouse credentials from secrets - SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }} - SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }} - SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }} - - steps: - - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - - - name: Install dependencies - run: pip install -r requirements.txt - - - name: Build current branch - run: | - dbt deps - dbt build --target ci - dbt docs generate --target ci +env: + CI_SCHEMA: "pr_${{ github.event.pull_request.number }}" ``` -This creates schemas like `pr_123`, `pr_456` for each PR automatically. - -### GitLab CI example - -In your `.gitlab-ci.yml`: +**GitLab CI:** ```yaml variables: CI_SCHEMA: "mr_${CI_MERGE_REQUEST_IID}" - -validate_pr: - stage: test - script: - - pip install -r requirements.txt - - dbt deps - - dbt build --target ci - - dbt docs generate --target ci - rules: - - if: $CI_MERGE_REQUEST_IID ``` -## Common patterns +This creates schemas like `pr_123`, `pr_456` for each PR automatically. -### Pattern 1: Schema-per-PR +For complete workflow examples, see [Setup CD](setup-cd.md) and [Setup CI](setup-ci.md). -Creates an isolated schema for each PR. Ideal for teams with multiple concurrent PRs. +## Recommended pattern: Schema-per-PR -```yaml -# profiles.yml -ci: - schema: "{{ env_var('CI_SCHEMA') }}" +Create an isolated schema for each PR. This is the recommended approach for teams. -# GitHub Actions -env: - CI_SCHEMA: "pr_${{ github.event.pull_request.number }}" -``` +| Base Schema | Current Schema | Example | +|-------------|----------------|---------| +| `public` (prod) | `pr_123` | PR #123 gets its own schema | -Benefits: +**Why this pattern:** - Complete isolation between PRs -- Parallel PR validation without conflicts -- Easy cleanup by dropping the schema +- Multiple PRs can run validation in parallel without conflicts +- Easy cleanup by dropping the schema when PR closes +- Clear audit trail of what data each PR produced -### Pattern 2: Shared development schema +## Alternative patterns -Uses a single `dev` schema for all development work. Simpler but requires coordination. +### Using staging as base -```yaml -# profiles.yml -dev: - schema: dev +Instead of comparing against production, compare against a staging environment with limited data. -# No dynamic schema needed -``` +| Base Schema | Current Schema | Use Case | +|-------------|----------------|----------| +| `staging` | `pr_123` | Teams wanting faster comparisons | -Benefits: +**Pros:** -- Simpler configuration -- Faster iteration for solo developers -- Lower warehouse storage usage +- Faster diffs with limited data ranges +- Consistent source data between base and current +- Reduced warehouse costs -### Pattern 3: Staging as base +**Cons:** -Uses a staging schema (with limited data) as the base for faster comparisons. +- Staging may drift from production +- Issues caught in staging might not reflect production behavior +- Requires maintaining an additional environment -```yaml -# profiles.yml -staging: - schema: staging # Base for comparison +See [Environment Best Practices](environment-best-practices.md) for strategies on limiting data ranges. -ci: - schema: "{{ env_var('CI_SCHEMA') }}" # Current PR schema -``` +### Shared development schema (not recommended) -Benefits: +Using a single `dev` schema for all development work. -- Consistent source data between base and current -- Faster diffs with limited data ranges -- Reduced warehouse costs +| Base Schema | Current Schema | Use Case | +|-------------|----------------|----------| +| `public` (prod) | `dev` | Solo developers only | + +**Why this is not recommended:** + +- Multiple PRs overwrite each other's data +- Cannot run parallel validations +- Comparison results may include changes from other work +- Difficult to isolate issues to specific PRs -See [Environment Best Practices](environment-best-practices.md) for detailed strategies on limiting data ranges and managing source data consistency. +Only use this pattern for individual local development, not for CI/CD automation. ## Verification @@ -259,23 +164,8 @@ After configuring your setup, verify that both base and current schemas are acce ### Check configuration locally -Run the debug command to verify your profile configuration: - ```shell -recce debug -``` - -This checks artifacts, directories, and warehouse connections. - -### Verify in CI - -Add a verification step to your workflow: - -```yaml -- name: Verify schema access - run: | - dbt debug --target ci - echo "CI_SCHEMA is set to: $CI_SCHEMA" +dbt debug --target ci ``` ### Verify in Recce interface @@ -295,17 +185,6 @@ Launch Recce and check **Environment Info** in the top-right corner. You should | Profile not found | Verify `profiles.yml` is accessible in CI (check path or use `DBT_PROFILES_DIR`) | | Connection timeout | Check warehouse IP allowlists include CI runner IP ranges | -### Debugging environment variables - -If schemas are not resolving correctly, add debugging to your workflow: - -```yaml -- name: Debug environment - run: | - echo "CI_SCHEMA: $CI_SCHEMA" - dbt debug --target ci 2>&1 | grep -i schema -``` - ## Related - [Get Started with Recce Cloud](start-free-with-cloud.md) - Complete onboarding guide From 65bf84c8a84ea5b43aa4b4671078f37d50e2574d Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 22:43:57 +0800 Subject: [PATCH 12/43] Add environment strategy section to setup-cd and setup-ci - setup-cd: References environment-setup, clarifies main branch uses prod target - setup-ci: References environment-setup, clarifies per-PR schema with ci target - Both link to environment-setup.md for profiles.yml configuration --- docs/2-getting-started/setup-cd.md | 7 +++++++ docs/2-getting-started/setup-ci.md | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/docs/2-getting-started/setup-cd.md b/docs/2-getting-started/setup-cd.md index b03b317..5bf7e9e 100644 --- a/docs/2-getting-started/setup-cd.md +++ b/docs/2-getting-started/setup-cd.md @@ -21,6 +21,13 @@ Before setting up CD, ensure you have: - [x] **Recce Cloud account** - [Start free trial](https://cloud.reccehq.com/) - [x] **Repository connected** to Recce Cloud - [Connect Git Provider](start-free-with-cloud.md#2-connect-git-provider) - [x] **dbt artifacts** - Know how to generate `manifest.json` and `catalog.json` from your dbt project +- [x] **Environment configured** - [Environment Setup](environment-setup.md) with `prod` target for base artifacts + +## Environment strategy + +This workflow uses the **main branch** with the `prod` target as the base environment. The base artifacts represent your production state, which PRs compare against. + +See [Environment Setup](environment-setup.md) for profiles.yml configuration. ## Setup diff --git a/docs/2-getting-started/setup-ci.md b/docs/2-getting-started/setup-ci.md index 70f50a2..ea943f4 100644 --- a/docs/2-getting-started/setup-ci.md +++ b/docs/2-getting-started/setup-ci.md @@ -22,6 +22,13 @@ Before setting up CI, ensure you have: - [x] **Repository connected** to Recce Cloud - [Connect Git Provider](start-free-with-cloud.md#2-connect-git-provider) - [x] **dbt artifacts** - Know how to generate `manifest.json` and `catalog.json` from your dbt project - [x] **CD configured** - [Setup CD](setup-cd.md) to establish baseline for comparisons +- [x] **Environment configured** - [Environment Setup](environment-setup.md) with `ci` target for per-PR schemas + +## Environment strategy + +This workflow uses **per-PR schemas** with the `ci` target as the current environment. Each PR gets an isolated schema (e.g., `pr_123`) that compares against the base artifacts from CD. + +See [Environment Setup](environment-setup.md) for profiles.yml configuration and why per-PR schemas are recommended. ## Setup From 01414234a7feb6b4160bf2aea59b8731a4e92652 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 22:46:26 +0800 Subject: [PATCH 13/43] Add environment and CI/CD pages to mkdocs.yml navigation --- mkdocs.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index bee8774..b80f5aa 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -54,6 +54,10 @@ nav: - Getting Started: - 2-getting-started/oss-vs-cloud.md - 2-getting-started/start-free-with-cloud.md + - Environment Setup: 2-getting-started/environment-setup.md + - Environment Best Practices: 2-getting-started/environment-best-practices.md + - Setup CD: 2-getting-started/setup-cd.md + - Setup CI: 2-getting-started/setup-ci.md #- 2-getting-started/cloud-5min-tutorial.md - 2-getting-started/installation.md - Claude Plugin: 2-getting-started/claude-plugin.md From cb8241bbfa27c89ad9d538752f0e9a8f4adb3773 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 23:20:55 +0800 Subject: [PATCH 14/43] Apply QA and AISEO fixes to PR2c docs - Add problem statement to dbt-cloud-setup opening - Remove redundant content between intro and goal - Spell out PR and CI/CD acronyms on first use - Fix "bolded steps" references to explicit commands - Improve image alt text for accessibility - Rename "Related" to "Next steps" with descriptive links Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/dbt-cloud-setup.md | 26 ++++++++++--------- .../start-free-with-cloud.md | 10 +++---- 2 files changed, 19 insertions(+), 17 deletions(-) diff --git a/docs/2-getting-started/dbt-cloud-setup.md b/docs/2-getting-started/dbt-cloud-setup.md index ccfab97..7cee6a0 100644 --- a/docs/2-getting-started/dbt-cloud-setup.md +++ b/docs/2-getting-started/dbt-cloud-setup.md @@ -4,27 +4,29 @@ title: dbt Cloud Setup # dbt Cloud Setup -This guide helps you set up Recce Cloud when your dbt project runs on dbt Cloud. Since dbt Cloud manages your dbt runs, you'll retrieve artifacts via the dbt Cloud API instead of generating them locally. +When your dbt project runs on dbt Cloud, validating pull request (PR) data changes requires retrieving artifacts from the dbt Cloud API rather than generating them locally. ## Goal -After completing this setup, you'll have automated data validation on every pull request, with Recce comparing your PR changes against production. The workflow retrieves dbt artifacts directly from dbt Cloud and uploads them to Recce Cloud for validation. +After completing this tutorial, every PR triggers automated data validation. Recce compares your PR changes against production, with results visible in Recce Cloud. ## Prerequisites - [x] **Recce Cloud account**: free trial at [cloud.reccehq.com](https://cloud.reccehq.com) -- [x] **dbt Cloud account**: with CI and CD jobs configured +- [x] **dbt Cloud account**: with CI (continuous integration) and CD (continuous deployment) jobs configured - [x] **dbt Cloud API token**: with read access to job artifacts - [x] **GitHub repository**: with admin access to add workflows and secrets - [x] **Data warehouse**: read access for data diffing -## How it works +## How Recce retrieves dbt Cloud artifacts -When your dbt project runs on dbt Cloud, the artifacts (`manifest.json`, `catalog.json`) are stored in dbt Cloud rather than your local environment. To use Recce, you'll: +Recce needs both base (production) and current (PR) dbt artifacts to compare changes. When using dbt Cloud, these artifacts live in dbt Cloud's API rather than your local filesystem. Your GitHub Actions workflow retrieves them via API calls before running Recce validation. -1. Retrieve Base artifacts from your CD job (production runs) -2. Retrieve Current artifacts from your CI job (PR runs) -3. Upload both to Recce Cloud for validation +The workflow: + +1. Retrieves Base artifacts from your CD job (production deployments that run on merge to main) +2. Retrieves Current artifacts from your CI job (PR-triggered builds that validate changes) +3. Uploads both to Recce Cloud for validation ## Setup steps @@ -248,9 +250,9 @@ The workflow retrieves Base artifacts from the CD job run that matches the PR's - Rebase your PR to a commit that has CD artifacts - Modify the workflow to use the latest CD run instead of commit-matched artifacts -## Related +## Next steps - [Get Started with Recce Cloud](./start-free-with-cloud.md) - Standard setup for self-hosted dbt -- [Setup CD](../7-cicd/setup-cd.md) - CD workflow configuration -- [Setup CI](../7-cicd/setup-ci.md) - CI workflow configuration -- [Best Practices for Preparing Environments](../7-cicd/best-practices-prep-env.md) - Environment strategy guidance +- [Configure CD to establish your production baseline](../7-cicd/setup-cd.md) +- [Configure CI for automated PR validation](../7-cicd/setup-ci.md) +- [Learn environment strategies for reliable comparisons](../7-cicd/best-practices-prep-env.md) diff --git a/docs/2-getting-started/start-free-with-cloud.md b/docs/2-getting-started/start-free-with-cloud.md index 5dbdc2c..996c354 100644 --- a/docs/2-getting-started/start-free-with-cloud.md +++ b/docs/2-getting-started/start-free-with-cloud.md @@ -12,7 +12,7 @@ This tutorial helps analytics engineers and data engineers set up Recce Cloud to ## Goal -Reviewing data changes in PRs is error-prone without visibility into downstream impact. After setup, the Recce agent reviews your data changes on every PR—showing what changed and what it affects. +Reviewing data changes in pull requests (PRs) is error-prone without visibility into downstream impact. After completing this tutorial, the Recce agent reviews your data changes on every PR—showing what changed and what it affects. To validate changes, Recce compares **Base** vs **Current** environments: @@ -22,7 +22,7 @@ To validate changes, Recce compares **Base** vs **Current** environments: Recce requires dbt artifacts from both environments. This guide covers: - dbt profile configuration for Base and Current -- CI/CD workflow setup +- CI/CD (Continuous Integration/Continuous Deployment) workflow setup For accurate comparisons, both environments should use consistent data ranges. See [Best Practices for Preparing Environments](../7-cicd/best-practices-prep-env.md) for environment strategies. @@ -225,7 +225,7 @@ This sample workflow: - **Calls `dbt docs generate`** to generate artifacts - **Calls `recce-cloud upload --type prod`** to upload the Base metadata, using `GITHUB_TOKEN` for authentication -To integrate into your own configuration, ensure your workflow includes the bolded steps. +To integrate into your own configuration, ensure your workflow calls `dbt docs generate` and `recce-cloud upload --type prod`. #### Set Up Current Metadata Updates @@ -293,7 +293,7 @@ This sample workflow: - **Calls `dbt docs generate --target ci`** to generate artifacts for the PR branch - **Calls `recce-cloud upload`** to upload the Current metadata, using `GITHUB_TOKEN` for authentication -To integrate into your own configuration, ensure your workflow includes the bolded steps. +To integrate into your own configuration, ensure your workflow calls `dbt docs generate --target ci` and `recce-cloud upload`. ### 4. Merge the CI/CD change @@ -309,7 +309,7 @@ In Recce Cloud, verify you see: - Production Metadata: Updated automatically - PR Sessions: all open PRs appear in the list. Only PRs with uploaded metadata can be launched for review. -![Recce Cloud dashboard after setup](../assets/images/2-getting-started/cloud-onboarding-completed.png){: .shadow} +![Recce Cloud dashboard showing connected GitHub integration, warehouse connection, and production metadata status](../assets/images/2-getting-started/cloud-onboarding-completed.png){: .shadow} ### 5. Final Steps From 4f8f80f914bada8d438c7dcd5a4ac149edbb910e Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 23:21:02 +0800 Subject: [PATCH 15/43] Apply QA and AISEO fixes to PR2b docs - Standardize PR terminology (remove PR/MR references) - Add problem statements to openings - Improve image alt text for accessibility - Define key terms inline (base/current environment) - Add "When to use this guide" section to best practices - Rename "Related" to "Next steps" - Spell out CI/CD acronyms on first use Co-Authored-By: Claude Opus 4.5 --- .../environment-best-practices.md | 42 +++++++++++++++---- docs/2-getting-started/environment-setup.md | 4 +- docs/2-getting-started/setup-cd.md | 10 +++-- docs/2-getting-started/setup-ci.md | 30 ++++++------- 4 files changed, 57 insertions(+), 29 deletions(-) diff --git a/docs/2-getting-started/environment-best-practices.md b/docs/2-getting-started/environment-best-practices.md index 0830913..4db2b3f 100644 --- a/docs/2-getting-started/environment-best-practices.md +++ b/docs/2-getting-started/environment-best-practices.md @@ -4,19 +4,26 @@ title: Environment Best Practices # Environment Best Practices -Strategies for preparing reliable, efficient environments for Recce data validation. +Unreliable comparison environments produce misleading validation results. When source data drifts, branches fall behind, or environments collide, you cannot trust what Recce reports. -## Overview +This guide covers strategies to prepare reliable, efficient environments for Recce data validation. Recce compares a *base environment* (production or staging, representing your main branch) against a *current environment* (representing your pull request branch). -Recce compares base and current environments to validate data changes. Several factors can affect comparison accuracy: +## When to use this guide + +- Setting up CI/CD for Recce for the first time +- Seeing inconsistent diff results across PRs +- Managing warehouse costs from accumulated PR environments +- Troubleshooting validation results that don't match expectations + +## Challenges this guide addresses + +Several factors can affect comparison accuracy: - Source data updates continuously - Transformations take time to run -- Other PRs merge into the base branch +- Other pull requests (PRs) merge into the base branch - Generated environments accumulate in the warehouse -This guide covers strategies to manage these challenges. - ## Use per-PR schemas Each PR should have its own isolated schema. This prevents interference between concurrent PRs and makes cleanup straightforward. @@ -72,6 +79,8 @@ WHERE {% endif %} ``` +![Diagram showing how limiting data to the previous month excluding current week creates consistent comparison windows](../assets/images/7-cicd/prep-env-limit-data-range.png){: .shadow} + Benefits: - Faster transformation execution @@ -87,7 +96,7 @@ If source data updates frequently (hourly or more), comparison results can vary - **Zero-copy clone** (Snowflake, BigQuery, Databricks): Freeze source data at a specific point in time - **Weekly snapshots**: Update source data weekly to reduce variability -![Source data cloning](../assets/images/7-cicd/prep-env-clone-source.png){: .shadow} +![Diagram showing zero-copy clone creating a frozen snapshot of source data for consistent CI comparisons](../assets/images/7-cicd/prep-env-clone-source.png){: .shadow} ## Keep base environment current @@ -103,6 +112,21 @@ Configure your CD workflow to run: See [Setup CD](setup-cd.md) for workflow configuration. +## Obtain artifacts for environments + +Recce uses base and current environment artifacts (`manifest.json`, `catalog.json`) to find corresponding tables in the data warehouse for comparison. + +**Recommended approaches:** + +- **Recce Cloud** - Automatic artifact management via `recce-cloud upload`. See [Setup CD](setup-cd.md) and [Setup CI](setup-ci.md). +- **dbt Cloud** - Download artifacts from dbt Cloud jobs. See [dbt Cloud Setup](dbt-cloud-setup.md). + +**Alternative approaches** (for custom setups): + +- **Cloud storage** - Upload artifacts to S3, GCS, or Azure Blob in CI +- **GitHub Actions artifacts** - Use `gh run download` to retrieve from workflow runs +- **Stateless** - Checkout the base branch and run `dbt docs generate` on-demand + ## Keep PR branch in sync with base If a PR runs after other PRs merge to main, the comparison mixes: @@ -112,7 +136,7 @@ If a PR runs after other PRs merge to main, the comparison mixes: This produces comparison results that don't accurately reflect the current PR's impact. -![PR out of sync](../assets/images/7-cicd/prep-env-pr-outdated.png){: .shadow} +![Diagram showing how an outdated PR branch mixes changes from other merged PRs into comparison results](../assets/images/7-cicd/prep-env-pr-outdated.png){: .shadow} **GitHub**: Enable [branch protection](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/keeping-your-pull-request-in-sync-with-the-base-branch) to show when PRs are outdated. @@ -162,7 +186,7 @@ dbt run-operation clear_schema --args "{'schema_name': 'pr_123'}" | Staging | `staging` | Daily + on merge | 1 | 1 month, excluding current week | | PR | `pr_` | On push | # of open PRs | 1 month, excluding current week | -## Related +## Next steps - [Environment Setup](environment-setup.md) - Technical configuration for profiles.yml and CI/CD - [Setup CD](setup-cd.md) - Configure automatic baseline updates diff --git a/docs/2-getting-started/environment-setup.md b/docs/2-getting-started/environment-setup.md index 11b84f0..1d9d5b6 100644 --- a/docs/2-getting-started/environment-setup.md +++ b/docs/2-getting-started/environment-setup.md @@ -30,7 +30,7 @@ For accurate validation, these must point to different schemas in your warehouse Recce uses both continuous delivery (CD) and continuous integration (CI) to automate data validation: - **CD (Continuous Delivery)**: Runs after merge to main. Updates baseline artifacts with latest production state. -- **CI (Continuous Integration)**: Runs on PR/MR. Validates proposed changes against baseline. +- **CI (Continuous Integration)**: Runs on PR. Validates proposed changes against baseline. **Set up CD first**, then CI. CD establishes your baseline (production artifacts), which CI uses for comparison. @@ -185,7 +185,7 @@ Launch Recce and check **Environment Info** in the top-right corner. You should | Profile not found | Verify `profiles.yml` is accessible in CI (check path or use `DBT_PROFILES_DIR`) | | Connection timeout | Check warehouse IP allowlists include CI runner IP ranges | -## Related +## Next steps - [Get Started with Recce Cloud](start-free-with-cloud.md) - Complete onboarding guide - [Environment Best Practices](environment-best-practices.md) - Strategies for source data and schema management diff --git a/docs/2-getting-started/setup-cd.md b/docs/2-getting-started/setup-cd.md index 5bf7e9e..2a6cd11 100644 --- a/docs/2-getting-started/setup-cd.md +++ b/docs/2-getting-started/setup-cd.md @@ -4,7 +4,9 @@ title: Setup CD # Setup CD - Auto-Update Baseline -Set up automatic updates for your Recce Cloud base sessions. Keep your data comparison baseline current every time you merge to main, with no manual work required. +Manually updating your Recce Cloud baseline after every merge is tedious and error-prone. This guide shows you how to automate baseline updates so your data comparison stays current without manual intervention. + +After completing this guide, your continuous deployment (CD) workflow automatically uploads dbt artifacts to Recce Cloud whenever code merges to main. ## What This Does @@ -12,7 +14,7 @@ Set up automatic updates for your Recce Cloud base sessions. Keep your data comp - **Triggers**: Merge to main + scheduled updates + manual runs - **Action**: Auto-update base Recce session with latest production artifacts -- **Benefit**: Current comparison baseline for all future PRs/MRs +- **Benefit**: Current comparison baseline for all future PRs ## Prerequisites @@ -177,11 +179,11 @@ Look for these indicators: **GitHub:** -![Recce Cloud showing updated base sessions](../assets/images/7-cicd/verify-setup-github-cd.png){: .shadow} +![Recce Cloud Sessions page displaying a successfully uploaded production baseline from GitHub Actions](../assets/images/7-cicd/verify-setup-github-cd.png){: .shadow} **GitLab:** -![Recce Cloud showing updated base sessions](../assets/images/7-cicd/verify-setup-gitlab-cd.png){: .shadow} +![Recce Cloud Sessions page displaying a successfully uploaded production baseline from GitLab CI](../assets/images/7-cicd/verify-setup-gitlab-cd.png){: .shadow} ### Expected Output diff --git a/docs/2-getting-started/setup-ci.md b/docs/2-getting-started/setup-ci.md index ea943f4..2115bb8 100644 --- a/docs/2-getting-started/setup-ci.md +++ b/docs/2-getting-started/setup-ci.md @@ -2,17 +2,19 @@ title: Setup CI --- -# Setup CI - Auto-Validate PRs/MRs +# Setup CI - Auto-Validate PRs -Automatically validate your data changes in every pull request or merge request using Recce Cloud. Catch data issues before they reach production, with validation results right in your PR/MR. +Manual data validation before merging is error-prone and slows down PR reviews. This guide shows you how to set up continuous integration (CI) that automatically validates data changes in every pull request (PR). + +After completing this guide, your CI workflow validates every PR against your production baseline, with results appearing in Recce Cloud. ## What This Does -**Automated PR/MR Validation** prevents data regressions before merge: +**Automated PR Validation** prevents data regressions before merge: -- **Triggers**: PR/MR opened or updated against main +- **Triggers**: PR opened or updated against main - **Action**: Auto-update Recce session for validation -- **Benefit**: Automated data validation and comparison visible in your PR/MR +- **Benefit**: Automated data validation and comparison visible in your PR ## Prerequisites @@ -157,7 +159,7 @@ recce-upload: ## Verification -### Test with a PR/MR +### Test with a PR **GitHub:** @@ -176,16 +178,16 @@ recce-upload: Look for these indicators: - [x] **Workflow/Pipeline completes** without errors -- [x] **PR/MR session created** in [Recce Cloud](https://cloud.reccehq.com) +- [x] **PR session created** in [Recce Cloud](https://cloud.reccehq.com) - [x] **Session URL** appears in workflow/pipeline output **GitHub:** -![Recce Cloud showing PR validation session](../assets/images/7-cicd/verify-setup-github-ci.png){: .shadow} +![Recce Cloud Sessions page displaying a PR validation session created from GitHub Actions](../assets/images/7-cicd/verify-setup-github-ci.png){: .shadow} **GitLab:** -![Recce Cloud showing MR validation session](../assets/images/7-cicd/verify-setup-gitlab-ci.png){: .shadow} +![Recce Cloud Sessions page displaying a MR validation session created from GitLab CI](../assets/images/7-cicd/verify-setup-gitlab-ci.png){: .shadow} ### Expected Output @@ -239,12 +241,12 @@ Artifacts from: "/builds/your-org/your-project/target" Change request: https://gitlab.com/your-org/your-project/-/merge_requests/4 ``` -### Review PR/MR Session +### Review PR Session To analyze the changes in detail: 1. Go to your [Recce Cloud](https://cloud.reccehq.com) -2. Find the PR/MR session that was created +2. Find the PR session that was created 3. Launch Recce instance to explore data differences ## Advanced Options @@ -280,9 +282,9 @@ If CI is not working, the issue is likely in your CD setup. Most problems are sh **CI-specific tip:** If CD works but CI doesn't, verify: -1. PR/MR trigger conditions in your workflow configuration -2. The PR/MR is targeting the correct base branch (usually `main`) -3. You're looking at PR/MR sessions in Recce Cloud (not production sessions) +1. PR trigger conditions in your workflow configuration +2. The PR is targeting the correct base branch (usually `main`) +3. You're looking at PR sessions in Recce Cloud (not production sessions) ## Next Steps From 6876bd655c70578bdeed13a76ca1904811919cd3 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Sun, 1 Mar 2026 22:43:17 +0800 Subject: [PATCH 16/43] Add OSS setup guides and Cloud vs OSS comparison - Create oss-setup.md: OSS installation and local validation guide - Create jaffle-shop-tutorial.md: Hands-on tutorial with DuckDB - Create cloud-vs-oss.md: Cloud vs Open Source comparison - Delete old oss-vs-cloud.md and get-started-jaffle-shop.md - Update navigation in mkdocs.yml Co-Authored-By: Claude Opus 4.5 --- docs/1-whats-recce/cloud-vs-oss.md | 123 ++++++++++++ .../get-started-jaffle-shop.md | 93 ---------- .../2-getting-started/jaffle-shop-tutorial.md | 175 ++++++++++++++++++ docs/2-getting-started/oss-setup.md | 141 ++++++++++++++ docs/2-getting-started/oss-vs-cloud.md | 52 ------ mkdocs.yml | 5 +- 6 files changed, 442 insertions(+), 147 deletions(-) create mode 100644 docs/1-whats-recce/cloud-vs-oss.md delete mode 100644 docs/2-getting-started/get-started-jaffle-shop.md create mode 100644 docs/2-getting-started/jaffle-shop-tutorial.md create mode 100644 docs/2-getting-started/oss-setup.md delete mode 100644 docs/2-getting-started/oss-vs-cloud.md diff --git a/docs/1-whats-recce/cloud-vs-oss.md b/docs/1-whats-recce/cloud-vs-oss.md new file mode 100644 index 0000000..1e01577 --- /dev/null +++ b/docs/1-whats-recce/cloud-vs-oss.md @@ -0,0 +1,123 @@ +--- +title: Cloud vs Open Source +--- + +# Cloud vs Open Source + +Validating data changes manually takes time and slows PR review. Recce is a data validation agent. Open Source gives you the core validation engine to run yourself, Cloud gives you the full Agent experience with automated validation on every PR. + +```mermaid +flowchart LR + subgraph Cloud + direction LR + C1[You open PR] --> C2[Agent validates automatically] + C2 --> C3[Summary posted to PR] + end + + subgraph OSS["Open Source"] + direction LR + O1[You open PR] --> O2[You run checks manually] + O2 --> O3[You copy results to PR] + end +``` + +## The Core Difference + +| | Cloud | Open Source | +|--|-------|-------------| +| **Experience** | Recce Agent works alongside you | You run validation manually | +| **PR validation** | Agent validates automatically, posts summary | You run checks, copy results to PR | +| **During development** | CLI + Agent assistance | CLI tools only | +| **Learning curve** | Agent guides you through validation | Learn the tools, run them yourself | + +## Cloud + +Recce Cloud connects to your Git repository and data warehouse so the Recce Agent can validate your data changes automatically. When you open a PR, the Agent analyzes your changes, runs validation checks, and posts findings directly to your PR — no manual work required. + +**On pull requests:** + +The Agent runs automatically when you open a PR. It: + +- Analyzes your data model changes +- Runs relevant validation checks +- Posts a summary to your PR with findings +- Updates as you push new commits + +**During development:** + +The Agent works with your CLI through MCP (Model Context Protocol, a standard for AI assistants to interact with tools): + +- Answers questions about your changes +- Suggests validation approaches +- Helps interpret diff results + +**For your team:** + +- Define what "correct" means for your repo with preset checks that apply across all PRs +- Share validation standards as institutional knowledge — everyone validates the same way +- Developers and reviewers collaborate on validation, going back and forth until the change is verified + +**Pricing:** + +Recce Cloud is free to start. See [Pricing](https://www.reccehq.com/pricing) for plan details. + +**Choose Cloud when:** +- You want automated validation on every PR +- You want Agent assistance during development +- Your team reviews data changes in PRs + +## Open Source + +Recce OSS is the core validation engine you run locally. You control when and how validation happens — run checks, explore results, and decide what to share. Everything stays on your machine unless you export it. + +You get: + +- Lineage Diff between branches +- Data comparison (row count, schema, profile, value, top-k, histogram diff) +- Query diff for custom validations +- Checklist to track your checks + +**Choose OSS when:** +- Exploring Recce before adopting Cloud +- Working in environments without external connectivity +- Contributing to Recce development + +## Feature Comparison + +| Feature | Cloud | OSS | +|---------|-------|-----| +| Lineage Diff | :white_check_mark: | :white_check_mark: | +| Data diff (row count, schema, profile, value, top-k, histogram diff) | :white_check_mark: | :white_check_mark: | +| Query diff | :white_check_mark: | :white_check_mark: | +| Checklist | :white_check_mark: | :white_check_mark: | +| Recce Agent on PRs | :white_check_mark: | :x: | +| Agent CLI assistance | :white_check_mark: | Manual | +| Preset checks across PRs | :white_check_mark: | Manual | +| Shared validation standards | :white_check_mark: | Manual | +| Developer-reviewer collaboration | :white_check_mark: | Manual | +| PR comments & summaries | :white_check_mark: | :x: | +| LLM-powered insights | :white_check_mark: | :x: | + +## FAQ + +**Can I start with OSS and upgrade to Cloud later?** + +Yes. OSS and Cloud use the same validation engine. Your existing checklists and workflows carry over when you connect to Cloud. + +**Does Cloud require a different setup than OSS?** + +Cloud connects to your Git repository and data warehouse directly. You don't need to generate artifacts locally — the Agent handles that automatically. + +**What data does Recce Cloud access?** + +Recce Cloud accesses your dbt artifacts (manifest.json, catalog.json) and runs queries against your data warehouse to perform validation. Your data stays in your warehouse. + +## Getting Started + +- **Cloud:** [Start Free with Cloud](../2-getting-started/start-free-with-cloud.md) +- **OSS:** [OSS Setup](../2-getting-started/oss-setup.md) + +## Related + +- [What the Agent Does](../5-what-the-agent-does/index.md) — How the Recce Agent validates your changes +- [Data Developer Workflow](../3-using-recce/data-developer.md) — Using Recce throughout development diff --git a/docs/2-getting-started/get-started-jaffle-shop.md b/docs/2-getting-started/get-started-jaffle-shop.md deleted file mode 100644 index 80babdb..0000000 --- a/docs/2-getting-started/get-started-jaffle-shop.md +++ /dev/null @@ -1,93 +0,0 @@ ---- -title: Open Source Tutorial ---- - -Jaffle Shop is an example project officially provided by [dbt Labs](https://www.getdbt.com). This document uses [jaffle_shop_duckdb](https://github.com/dbt-labs/jaffle_shop_duckdb) to enable you to start using Recce locally from scratch within five minutes. - -!!! tip - - [DuckDB](https://duckdb.org/) projects like jaffle_shop_duckdb don’t use a server-based connection or cloud warehouse credentials. Be aware that a few setup steps differ from those for cloud-based warehouses. - -## Step by Step - -1. Clone the “Jaffle Shop” dbt data project - ```shell - git clone git@github.com:dbt-labs/jaffle_shop_duckdb.git - cd jaffle_shop_duckdb - ``` -2. Prepare virtual env - ```shell - python -m venv venv - source venv/bin/activate - ``` -3. Installation - ```shell - pip install -r requirements.txt - pip install recce - ``` -4. Provide additional environment to compare
- Edit `./profiles.yml` to add one more target to serve as the base environment for comparison. -

Note: This step is only necessary for file-based engines like DuckDB. For cloud warehouses (e.g., Snowflake, BigQuery), Recce auto-detects your active dbt profile and schema, so no changes required. - ```diff - jaffle_shop: - target: dev - outputs: - dev: - type: duckdb - path: 'jaffle_shop.duckdb' - threads: 24 - + prod: - + type: duckdb - + path: 'jaffle_shop.duckdb' - + schema: prod - + threads: 24 - ``` -5. Prepare production environment
- Using DuckDB, you need to generate the artifacts for the base environment. Checkout the `main` branch of your project and generate the required artifacts into `target-base`. You can skip `dbt build` if this environment already exists. -

Note: This step is only necessary for file-based engines like DuckDB. For most data warehouses, you don’t need to re-run production locally. You can download the dbt artifacts generated from the main branch, and save them to a `target-base/` folder. - ```shell - dbt seed --target prod - dbt run --target prod - dbt docs generate --target prod --target-path ./target-base - ``` -6. Prepare development environment. First, edit an existing model `./models/staging/stg_payments.sql`. - ```diff - ... - - renamed as ( - payment_method, - - - -- `amount` is currently stored in cents, so we convert it to dollars - - amount / 100 as amount - + amount - - from source - ) - ``` - run on development environment. - ```shell - dbt seed - dbt run - dbt docs generate - ``` -7. Run the recce server - ```shell - recce server - ``` - Open the link http://0.0.0.0:8000, you can see the lineage diff - ![Lineage diff](../assets/images/2-getting-started/jaffle-shop-lineage.png) -8. Switch to the **Query** tab, run this query - ```sql - select * from {{ ref("orders") }} order by 1 - ``` - Click the `Run Diff` or press `Cmd + Shift + Enter` - Click on the 🔑 icon next to the `order_id` column to compare records that are uniquely identified by their `order_id`. - ![Query in Recce](../assets/images/2-getting-started/jaffle-shop-query.png) -9. Click the blue `Add to Checklist` button on the right bottom corner to add the query result to checklist - ![Add query to checklist](../assets/images/2-getting-started/jaffle-shop-check-buttun.png) - -## What’s Next -By following this DuckDB tutorial, you’ve seen how Recce works locally. -You can now return to the [Open Source Setup](./installation.md) to set up Recce with your cloud data warehouse. - -Got questions? [Let us know](../1-whats-recce/community-support.md). We're happy to help! diff --git a/docs/2-getting-started/jaffle-shop-tutorial.md b/docs/2-getting-started/jaffle-shop-tutorial.md new file mode 100644 index 0000000..17cc6d9 --- /dev/null +++ b/docs/2-getting-started/jaffle-shop-tutorial.md @@ -0,0 +1,175 @@ +--- +title: Jaffle Shop Tutorial +--- + +# Jaffle Shop Tutorial + +When you change a dbt model, how do you know what data actually changed? Running your model isn't enough — you need to compare outputs against the previous version. + +**Goal:** Make a model change and validate the data impact using Recce with the dbt Labs example project. + +This tutorial uses [jaffle_shop_duckdb](https://github.com/dbt-labs/jaffle_shop_duckdb), a sample project from dbt Labs. You'll modify a model, see how the change affects downstream data, and add a validation to your checklist. + +## Prerequisites + +- [ ] Python 3.9+ installed +- [ ] Git installed + +## Steps + +### 1. Clone Jaffle Shop + +```shell +git clone git@github.com:dbt-labs/jaffle_shop_duckdb.git +cd jaffle_shop_duckdb +``` + +**Expected result:** You're in the `jaffle_shop_duckdb` directory. + +### 2. Set up virtual environment + +```shell +python -m venv venv +source venv/bin/activate +``` + +**Expected result:** Your terminal prompt shows `(venv)`. + +### 3. Install dependencies + +```shell +pip install -r requirements.txt +pip install recce +``` + +**Expected result:** Both dbt and Recce install without errors. + +### 4. Configure DuckDB profile for comparison + +Recce compares two environments. Edit `./profiles.yml` to add a `prod` target for the base environment. + +Add the following under `outputs:`: + +```yaml + prod: + type: duckdb + path: 'jaffle_shop.duckdb' + schema: prod + threads: 24 +``` + +Your complete `profiles.yml` should look like: + +```yaml +jaffle_shop: + target: dev + outputs: + dev: + type: duckdb + path: 'jaffle_shop.duckdb' + threads: 24 + prod: + type: duckdb + path: 'jaffle_shop.duckdb' + schema: prod + threads: 24 +``` + +**Expected result:** `profiles.yml` has both `dev` and `prod` targets. + +### 5. Build base environment + +Generate the production data and artifacts that Recce uses as baseline. + +```shell +dbt seed --target prod +dbt run --target prod +dbt docs generate --target prod --target-path ./target-base +``` + +**Expected result:** `target-base/` folder contains `manifest.json` and `catalog.json`. + +### 6. Make a model change + +Edit `./models/staging/stg_payments.sql` to introduce a data change: + +```diff +renamed as ( + payment_method, + +- -- `amount` is currently stored in cents, so we convert it to dollars +- amount / 100 as amount ++ amount + + from source +) +``` + +This removes the cents-to-dollars conversion — downstream models will now show values 100x larger. + +**Expected result:** `stg_payments.sql` outputs `amount` in cents instead of dollars. + +### 7. Build development environment + +```shell +dbt seed +dbt run +dbt docs generate +``` + +**Expected result:** `target/` folder contains updated `manifest.json` and `catalog.json`. + +### 8. Start Recce server + +```shell +recce server +``` + +**Expected result:** Server starts at http://0.0.0.0:8000 + +Open http://localhost:8000 in your browser. The Lineage tab shows `stg_payments` and downstream models highlighted. + +![Recce Lineage Diff showing stg_payments and downstream models highlighted](../assets/images/2-getting-started/jaffle-shop-lineage.png) + +### 9. Run a Query Diff + +Switch to the **Query** tab and run: + +```sql +select * from {{ ref("orders") }} order by 1 +``` + +Click **Run Diff** (or press `Cmd+Shift+Enter`). + +**Expected result:** Query Diff shows the `amount` column with values 100x larger in the current environment. + +![Query Diff results showing the amount column with values 100x larger in current environment](../assets/images/2-getting-started/jaffle-shop-query.png) + +### 10. Add to checklist + +Click **Add to Checklist** (blue button, bottom right) to save this validation. + +**Expected result:** Checklist tab shows your saved Query Diff. + +![Add to Checklist button in bottom-right corner of Query Diff results](../assets/images/2-getting-started/jaffle-shop-check-buttun.png) + +## Verify Success + +Confirm you completed the tutorial: + +1. Lineage Diff shows `stg_payments` and downstream models highlighted +2. Query Diff on `orders` shows the amount change (100x difference) +3. Checklist contains your saved validation + +## Troubleshooting + +| Issue | Solution | +|-------|----------| +| "No artifacts found" error | Run `dbt docs generate` for both prod (`--target-path ./target-base`) and dev | +| Empty Lineage Diff | Ensure you made the model change in step 6 and ran `dbt run` + `dbt docs generate` | +| DuckDB lock error | Close any other processes using `jaffle_shop.duckdb` | + +## Next Steps + +- [OSS Setup](oss-setup.md) — Set up Recce with your own dbt project +- [Cloud vs Open Source](../1-whats-recce/cloud-vs-oss.md) — Compare OSS and Cloud features diff --git a/docs/2-getting-started/oss-setup.md b/docs/2-getting-started/oss-setup.md new file mode 100644 index 0000000..2f1b6f1 --- /dev/null +++ b/docs/2-getting-started/oss-setup.md @@ -0,0 +1,141 @@ +--- +title: OSS Setup +--- + +# Set Up Recce OSS + +When you change dbt models, you need to compare the data before and after to catch unintended impacts. Recce OSS lets you run this validation locally. + +**Goal:** Install and run Recce locally for manual data validation. + +Recce OSS gives you the core validation engine to run locally. For the full experience with Recce Agent assistance on PRs and during development, see [Cloud vs Open Source](../1-whats-recce/cloud-vs-oss.md). + +## Prerequisites + +- [ ] Python 3.9+ installed +- [ ] A dbt project with at least one model +- [ ] Git installed (for version comparison) + +## Steps + +### 1. Install Recce + +Install Recce in your dbt project's virtual environment. + +```shell +pip install recce +``` + +**Expected result:** Installation completes without errors. + +### 2. Generate base environment artifacts + +Recce compares two states of your dbt project. First, generate artifacts for your base (production) state. + +```shell +git checkout main +dbt docs generate --target-path ./target-base +``` + +**Expected result:** `target-base/` folder contains `manifest.json` and `catalog.json`. + +!!! note "Different approaches by environment" + - **File-based (DuckDB):** Run `dbt build` first to create data. See [Jaffle Shop Tutorial](jaffle-shop-tutorial.md). + - **Cloud warehouses with dbt Cloud:** Download artifacts from dbt Cloud API. See [For dbt Cloud Users](#for-dbt-cloud-users) below. + +### 3. Generate current environment artifacts + +Switch to your development branch and generate artifacts for comparison. + +```shell +git checkout your-feature-branch +dbt run +dbt docs generate +``` + +**Expected result:** `target/` folder contains updated `manifest.json` and `catalog.json`. + +### 4. Start Recce server + +Launch the Recce web interface. + +```shell +recce server +``` + +**Expected result:** Server starts and displays: + +``` +Recce server is running at http://0.0.0.0:8000 +``` + +### 5. Explore changes in the UI + +Open http://localhost:8000 in your browser. + +- **Lineage tab:** See which models changed and their downstream impact +- **Query tab:** Run SQL queries to compare data between base and current states + +**Expected result:** Lineage Diff shows your modified models highlighted. + +### 6. Add validation checks to checklist + +After running a query or diff: + +1. Review the results +2. Click **Add to Checklist** to save the validation +3. Repeat for each check you want to track + +**Expected result:** Checklist shows your saved validations. + +## Verify Success + +Run `recce server` and confirm you can: + +1. See Lineage Diff between base and current +2. Run a Query Diff on a modified model +3. Add the result to your checklist + +## Try It: Jaffle Shop Tutorial + +Want a hands-on walkthrough with DuckDB? The [Jaffle Shop Tutorial](jaffle-shop-tutorial.md) guides you through making a model change, comparing data, and validating the impact. + +## For dbt Cloud Users + +If you use dbt Cloud for CI/CD, download production artifacts instead of generating them locally. + +**Get artifacts from dbt Cloud API:** + +```shell +# Set your dbt Cloud credentials +export DBT_CLOUD_API_TOKEN="your-token" +export DBT_CLOUD_ACCOUNT_ID="your-account-id" +export DBT_CLOUD_JOB_ID="your-production-job-id" + +# Download artifacts from your production job +curl -H "Authorization: Token $DBT_CLOUD_API_TOKEN" \ + "https://cloud.getdbt.com/api/v2/accounts/$DBT_CLOUD_ACCOUNT_ID/jobs/$DBT_CLOUD_JOB_ID/artifacts/manifest.json" \ + -o target-base/manifest.json + +curl -H "Authorization: Token $DBT_CLOUD_API_TOKEN" \ + "https://cloud.getdbt.com/api/v2/accounts/$DBT_CLOUD_ACCOUNT_ID/jobs/$DBT_CLOUD_JOB_ID/artifacts/catalog.json" \ + -o target-base/catalog.json +``` + +Then generate current artifacts locally (`dbt docs generate`) and run `recce server` as usual. + +!!! tip "Recce Cloud automates this" + With Recce Cloud, the Agent retrieves artifacts automatically — no manual downloads. See [Start Free with Cloud](start-free-with-cloud.md). + +## Troubleshooting + +| Issue | Solution | +|-------|----------| +| "No artifacts found" error | Run `dbt docs generate` for both base and current states | +| Empty Lineage Diff | Ensure you have uncommitted model changes vs the base branch | +| Port 8000 already in use | Use `recce server --port 8001` to specify a different port | + +## Next Steps + +- [Cloud vs Open Source](../1-whats-recce/cloud-vs-oss.md) — Compare OSS and Cloud features +- [Start Free with Cloud](start-free-with-cloud.md) — Get Recce Agent on your PRs and CLI diff --git a/docs/2-getting-started/oss-vs-cloud.md b/docs/2-getting-started/oss-vs-cloud.md deleted file mode 100644 index b88f5ba..0000000 --- a/docs/2-getting-started/oss-vs-cloud.md +++ /dev/null @@ -1,52 +0,0 @@ ---- -title: Choose best for you ---- - -# Choose what's best for you - -Recce offers two ways to validate your data changes: Open Source and Recce Cloud. **We recommend starting with Recce Cloud** for the easiest setup and team collaboration. - -## Quick Comparison - -| | **Recce Cloud** ⭐ *Recommended* | **Open Source** | -|---|---|---| -| **Setup** | 30 seconds - just sign up | 5-10 minutes installation | -| **Team Collaboration** | ✅ Real-time sharing, checklist sync | ❌ Local only | -| **PR Integration** | ✅ Automatic PR gating | ⚠️ Manual setup required | -| **Exclusive Features** | ✅ LLM validation insights, upcoming innovations | ❌ Core features only | -| **Best For** | Data teams focused on validation | Initial experimentation | - -## Choose Recce Cloud If You... - -- Want the **fastest setup** and immediate results -- Want to **focus on data validation**, not infrastructure setup -- Want to do a **PoC** for your team to evaluate data tools -- Need **team collaboration** and stakeholder reviews -- Want **exclusive features** like LLM validation insights -- Want **automatic updates** with new innovations as they're released -- Prefer to spend time on data problems, not tool configuration - -👉 **[Start with Recce Cloud](start-free-with-cloud.md)** - -### Cloud Plans Overview - -- **Free Plan**: All core validation features with zero setup effort -- **Team Plan**: Advanced collaboration + LLM validation insights + automated workflows -- **Enterprise Plan**: Full governance + BYOC + SSO + dedicated support - -View [pricing](https://reccehq.com/pricing) to learn more - -## Choose Open Source If You... - -- Want to **experiment locally** before team adoption -- Need to **explore Recce** without creating accounts first -- Want to **understand the basics** before moving to Cloud - -👉 **[Open Source Setup](installation.md)** - - -## What's Next - -**New to Recce?** We recommend **[Cloud](start-free-with-cloud.md)** for the smoothest experience. You can validate data changes and collaborate with your team within minutes. - -**Already technical?** **Cloud is still the smart choice.** Why spend time on infrastructure when you could focus on data validation? Plus you get exclusive features like LLM insights and automatic updates with new innovations. \ No newline at end of file diff --git a/mkdocs.yml b/mkdocs.yml index bee8774..0a05778 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -49,15 +49,16 @@ nav: - Guides: - What's Recce: - index.md + - 1-whats-recce/cloud-vs-oss.md - 1-whats-recce/community-support.md - Changelog: "https://reccehq.com/changelog/" - Getting Started: - - 2-getting-started/oss-vs-cloud.md - 2-getting-started/start-free-with-cloud.md #- 2-getting-started/cloud-5min-tutorial.md - 2-getting-started/installation.md - Claude Plugin: 2-getting-started/claude-plugin.md - - 2-getting-started/get-started-jaffle-shop.md + - 2-getting-started/oss-setup.md + - 2-getting-started/jaffle-shop-tutorial.md - Visualized Change: - 3-visualized-change/lineage.md - 3-visualized-change/code-change.md From 9179577b7e0d8f9450ae4a102515e8ba7a3b9480 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Sun, 1 Mar 2026 22:43:18 +0800 Subject: [PATCH 17/43] Add admin setup guide for team collaboration - Create 3-using-recce/admin-setup.md: Organization setup tutorial - Delete 6-collaboration/invitation.md (replaced by admin-setup) - Update navigation in mkdocs.yml Co-Authored-By: Claude Opus 4.5 --- docs/3-using-recce/admin-setup.md | 119 +++++++++++++++++++++++++++++ docs/6-collaboration/invitation.md | 57 -------------- mkdocs.yml | 3 +- 3 files changed, 121 insertions(+), 58 deletions(-) create mode 100644 docs/3-using-recce/admin-setup.md delete mode 100644 docs/6-collaboration/invitation.md diff --git a/docs/3-using-recce/admin-setup.md b/docs/3-using-recce/admin-setup.md new file mode 100644 index 0000000..0debf91 --- /dev/null +++ b/docs/3-using-recce/admin-setup.md @@ -0,0 +1,119 @@ +--- +title: Admin Setup +--- + +# Set Up Your Organization + +After connecting your Git repo to Recce Cloud, you need to configure your organization so your team can collaborate on PR validation. + +**Goal:** Configure your Recce Cloud organization for team collaboration. + +When you sign up for Recce Cloud, you get one organization and one project. After connecting to Git, your organization and project names automatically map to your Git provider's names. You can rename them and invite team members. + +## Prerequisites + +- [ ] Recce Cloud account with owner/admin access +- [ ] Git repository connected to Recce Cloud +- [ ] Team members' email addresses + +## Steps + +### 1. Access organization settings + +Navigate to your organization configuration. + +1. Log in to [Recce Cloud](https://cloud.reccehq.com) +2. Click **Settings** → **Organization** in the side panel + +**Expected result:** Organization settings page displays your current organization. + +![Recce Cloud Organization Settings page showing organization name and members section](../assets/images/6-collaboration/recce-cloud-org-setting-fs8.png){: .shadow} + +### 2. Rename your organization (optional) + +Update the organization name to match your company or team. + +1. In Organization Settings, find the **Organization Name** field +2. Enter your preferred name +3. Click **Save** + +**Expected result:** Organization name updates across all Recce Cloud pages. + +### 3. Set up additional projects (monorepo) + +!!! note "For monorepo users" + If your repository contains multiple dbt projects, set up additional projects before inviting team members. Skip this step if you have a single dbt project. + +1. In Organization Settings, navigate to **Projects** +2. Click **Add Project** +3. Enter the project name and select the subdirectory path +4. Click **Create** + +**Expected result:** New project appears in the project list and sidebar. + +### 4. Invite team members + +Add collaborators to your organization. + +1. In Organization Settings, find the **Members** section +2. Click **Invite Members** +3. Enter email addresses (use SSO email if members use SSO login) +4. Select a role for each invitee: + +| Role | Permissions | +|------|-------------| +| **Owner** | The one who created this organization. Full organization management: update info, manage roles, remove members | +| **Admin** | Same permissions as Owner | +| **Member** | Upload metadata, launch Recce instances, view organization info | + +!!! tip "SSO login requires Team plan or above" + SSO login is available on the Team plan and above. See [Pricing](https://www.reccehq.com/pricing) for plan details. + +1. Click **Send Invitation** + +**Expected result:** Invitees receive email invitations and see notifications when logged in. + +![Invite Members dialog with email input field and role selection](../assets/images/6-collaboration/recce-cloud-org-invitation-fs8.png){: .shadow} + +## Additional Settings + +### Rename your project + +Update the project name if needed. + +1. Navigate to your project → click **Settings** +2. Enter the new project name +3. Click **Save** + +**Expected result:** Project name updates in the sidebar and project list. + +## Verify Success + +Confirm your setup by checking: + +1. Organization name displays correctly in the sidebar +2. Invited members appear in the Members list (pending or active) +3. All projects are listed under Settings → Projects + +## Troubleshooting + +| Issue | Solution | +|-------|----------| +| Invitation not received | Check spam folder; verify email address matches SSO provider | +| Member sees their own org, not company org | They may have signed up with a different email than the one you invited; ask them to log in with the invited email | +| Cannot change organization name | Confirm you have Admin role | +| Project not appearing | Refresh the page; verify the subdirectory path is correct | + +## For Invited Users + +When you receive an invitation: + +1. **Immediate response:** A notification modal appears on login — accept or decline directly +2. **Later:** Navigate to **Settings** → **Organization** to view pending invitations + +![Recce Cloud home page showing pending invitation notification modal](../assets/images/6-collaboration/recce-cloud-org-pending-invitation-home-fs8.png){: .shadow} + +## Next Steps + +- [Data Developer Workflow](data-developer.md) — Learn how developers validate changes +- [Data Reviewer Workflow](data-reviewer.md) — Learn how reviewers approve PRs diff --git a/docs/6-collaboration/invitation.md b/docs/6-collaboration/invitation.md deleted file mode 100644 index bf10875..0000000 --- a/docs/6-collaboration/invitation.md +++ /dev/null @@ -1,57 +0,0 @@ ---- -title: Invitation ---- - -## Inviting Team Members to Your Recce Organization - -To collaborate effectively within Recce Cloud, you can invite team members to join your organization. Follow these steps to send invitations: - -### Step 1: Access Organization Settings -- Log in to your Recce Cloud account -- Navigate to **Settings** → **Organization** from the side panel -- Alternatively, you can access directly via: `https://cloud.reccehq.com/settings#organization` -- In the Organization Settings section, select your desired organization - -![Organization Settings](../assets/images/6-collaboration/recce-cloud-org-setting-fs8.png){: .shadow} - -### Step 2: Invite Members - -!!! Note - Please use the SSO email address if your member uses SSO login. - -- In the **Members** section, click the **Invite Members** button -- Enter the email addresses of the individuals you wish to invite -- Select the appropriate role for each invitee based on the roles below: - -#### Organization Roles - -| Role | Key Responsibilities | Permissions | -|------|---------------------|-------------| -| **ADMIN** | Full organization management | • Update organization info
• Manage member roles
• Remove members
• Transfer storage regions | -| **MEMBER** | Upload metadata and launch instances | • Upload metadata
• Launch Recce instances
• View organization info and member list
• Leave organization | -| **VIEWER** | Only instance launch | • Launch Recce instances
• View organization info and member list
• Leave organization | - -![Invite Members](../assets/images/6-collaboration/recce-cloud-org-invitation-fs8.png){: .shadow} - -### Step 3: Send Invitation -- Click the **Send Invitation** button to dispatch the invites -- Each invitee will receive an email with a link to join your organization -- Logged-in invitees will also see notifications on their home page or can view pending invitations in **Settings** → **Organization** - -## For Invited Users - -When you receive an invitation to join a Recce organization, you have several ways to respond: - -### Immediate Response -- Upon login, you'll see a notification modal with the invitations -- You can immediately accept or decline the invitations directly from the notification without navigating elsewhere - -![Invitation Notifications](../assets/images/6-collaboration/recce-cloud-org-pending-invitation-home-fs8.png){: .shadow} - -### Managing Invitations Later -- Navigate to **Settings** → **Organization** in your account -- View all pending invitations in the "Pending Invitations" section -- Review the organization and role -- Accept or decline each invitation as needed - -![Pending Invitations In Settings](../assets/images/6-collaboration/recce-cloud-org-pending-invitation-fs8.png){: .shadow} diff --git a/mkdocs.yml b/mkdocs.yml index bee8774..ff2b425 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -63,6 +63,8 @@ nav: - 3-visualized-change/code-change.md - 3-visualized-change/column-level-lineage.md - 3-visualized-change/multi-models.md + - Using Recce: + - 3-using-recce/admin-setup.md - Downstream Impacts: #- 4-downstream-impacts/metadata-first.md - 4-downstream-impacts/impact-radius.md @@ -78,7 +80,6 @@ nav: - 5-data-diffing/query.md - MCP Server (AI Agents): 5-data-diffing/mcp-server.md - Collaborate Validation: - - 6-collaboration/invitation.md - 6-collaboration/checklist.md - 6-collaboration/share.md - CI/CD: From 928fe1ab1c6d7e577f0d35b44810c432399cb3a5 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Mon, 2 Mar 2026 10:59:57 +0800 Subject: [PATCH 18/43] docs: refine OSS docs and remove installation.md - Fix MCP link in cloud-vs-oss.md - Change prerequisites to checked format [x] - Change "dbt models" to "data models" for broader audience - Remove installation.md (content covered in oss-setup.md) - Update mkdocs.yml navigation Co-Authored-By: Claude Opus 4.5 --- docs/1-whats-recce/cloud-vs-oss.md | 6 +- docs/2-getting-started/installation.md | 202 ------------------ .../2-getting-started/jaffle-shop-tutorial.md | 4 +- docs/2-getting-started/oss-setup.md | 8 +- mkdocs.yml | 2 - 5 files changed, 10 insertions(+), 212 deletions(-) delete mode 100644 docs/2-getting-started/installation.md diff --git a/docs/1-whats-recce/cloud-vs-oss.md b/docs/1-whats-recce/cloud-vs-oss.md index 1e01577..4d6da7a 100644 --- a/docs/1-whats-recce/cloud-vs-oss.md +++ b/docs/1-whats-recce/cloud-vs-oss.md @@ -45,7 +45,7 @@ The Agent runs automatically when you open a PR. It: **During development:** -The Agent works with your CLI through MCP (Model Context Protocol, a standard for AI assistants to interact with tools): +The Agent works with your CLI through [Recce MCP](/5-data-diffing/mcp-server/) (Model Context Protocol): - Answers questions about your changes - Suggests validation approaches @@ -62,6 +62,7 @@ The Agent works with your CLI through MCP (Model Context Protocol, a standard fo Recce Cloud is free to start. See [Pricing](https://www.reccehq.com/pricing) for plan details. **Choose Cloud when:** + - You want automated validation on every PR - You want Agent assistance during development - Your team reviews data changes in PRs @@ -78,6 +79,7 @@ You get: - Checklist to track your checks **Choose OSS when:** + - Exploring Recce before adopting Cloud - Working in environments without external connectivity - Contributing to Recce development @@ -87,7 +89,7 @@ You get: | Feature | Cloud | OSS | |---------|-------|-----| | Lineage Diff | :white_check_mark: | :white_check_mark: | -| Data diff (row count, schema, profile, value, top-k, histogram diff) | :white_check_mark: | :white_check_mark: | +| Data diff
(row count, schema, profile, value, top-k, histogram diff) | :white_check_mark: | :white_check_mark: | | Query diff | :white_check_mark: | :white_check_mark: | | Checklist | :white_check_mark: | :white_check_mark: | | Recce Agent on PRs | :white_check_mark: | :x: | diff --git a/docs/2-getting-started/installation.md b/docs/2-getting-started/installation.md deleted file mode 100644 index 764de7a..0000000 --- a/docs/2-getting-started/installation.md +++ /dev/null @@ -1,202 +0,0 @@ ---- -title: Open Source Setup ---- - -# Open Source Setup - -## Install Open Source - -From within a dbt project directory: -```shell -cd your-dbt-project/ # if you're not already there -pip install -U recce -``` - - -## Launch -To start Recce in the current environment: -```shell -recce server -``` -Launching Recce enables: - -- **Lineage clarity**: Trace changes down to the column level - -- **Query insights**: Explore logic and run custom queries - -- **Live diffing**: Reload and inspect changes as you iterate - -Best suited for quick exploration before moving to structured validation using Diff. - - - - -## Configure Diff - -To compare changes, Recce needs a baseline. This guide explains the concept of Diff in Recce and how it fits into data validation workflows. Setup steps vary by environment, so this guide focuses on the core ideas rather than copy-paste instructions. - -For a concrete example, refer to the [5-minute Jaffle Shop tutorial](./get-started-jaffle-shop.md). - -To configure a comparison in Recce, two components are required: - -### 1. Artifacts - -Recce uses dbt [artifacts](https://docs.getdbt.com/reference/artifacts/dbt-artifacts) to perform diffs. These files are generated with each dbt run and typically saved in the `target/` folder. - -In addition to the current artifacts, a second set is needed to serve as the baseline for comparison. Recce looks for these in the `target-base/` folder. - -- `target/` – Artifacts from the current development environment -- `target-base/` – Artifacts from a baseline environment (e.g., production) - -For most setups, retrieve the existing artifacts that generated from the main branch (usually from a CI run or build cache) and save them into a `target-base/` folder. - -### 2. Schemas - -Recce also compares the actual query results between two dbt [environments](https://docs.getdbt.com/docs/core/dbt-core-environments), each pointing to a different [schema](https://docs.getdbt.com/docs/core/connect-data-platform/connection-profiles#understanding-target-schemas). This allows validation beyond metadata by comparing the data itself. - -For example: - -- `prod` schema for production -- `dev` schema for development - -These schemas represent where dbt builds its models. - -!!! tip - - In dbt, an environment typically maps to a schema. To compare data results, separate schemas are required. Learn more in [dbt environments](https://docs.getdbt.com/docs/core/dbt-core-environments). - -Schemas are typically configured in the `profiles.yml` file, which defines how dbt connects to the data platform. Both schemas must be accessible for Recce to perform environment-based comparisons. - -Once both artifacts and schemas are configured, Recce can surface meaningful diffs across logic, metadata, and data. - -## Verify your setup - -There are two ways to check that your configuration is complete: - -### 1. Debug Command (CLI) - -Run `recce debug` from the command line to verify your setup before launching the server: - -```bash -recce debug -``` - -This command checks artifacts, directories, and warehouse connection, providing detailed feedback on any missing components. - -### 2. Environment Info (Web UI) - -Use **Environment Info** in the top-right corner of the Recce web interface to verify your configuration. - -A correctly configured setup will display two environments: - -- **Base** – the reference schema used for comparison (e.g., production) -- **Current** – the schema for the environment under development (e.g., staging or dev) - -This confirms that both the artifacts and schemas are properly connected for diffing. -![Environment Info](../assets/images/shared/environment-info.png) - - -## Start with dbt Cloud - -dbt Cloud is a hosted service that provides a managed environment for running dbt projects by [dbt Labs](https://docs.getdbt.com/docs/cloud/about-cloud/dbt-cloud-features). This document provides a step-by-step guide to get started Recce with dbt Cloud. - -### Prerequisites - -Recce will compare the data models between two environments. That means you need to have two environments in your dbt Cloud project. For example, one for production and another for development. -Also, you need to provide the credentials profile for both environments in your `profiles.yml` file to let Recce access your data warehouse. - -#### Suggestions for setting up dbt Cloud - -To integrate the dbt Cloud with Recce, we suggest to set up two run jobs in your dbt Cloud project. - -#### Production Run Job - -The production run should be the main branch of your dbt project. You can trigger the dbt Cloud job on every merge to the main branch or schedule it to run at a daily specific time. - -### Development Run Job - -The development run should be a separate branch of your dbt project. You can trigger the dbt Cloud job on every merge to the pull-request branch. - -### Set up dbt profiles with credentials - -You need to provide the credentials profile for both environments in your `profiles.yml` file. Here is an example of how your `profiles.yml` file might look like: - -```yaml -dbt-example-project: - target: dev - outputs: - dev: - type: snowflake - account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}" - - # User/password auth - user: "{{ env_var('SNOWFLAKE_USER') | as_text }}" - password: "{{ env_var('SNOWFLAKE_PASSWORD') | as_text }}" - - role: DEVELOPER - database: cloud_database - warehouse: LOAD_WH - schema: "{{ env_var('SNOWFLAKE_SCHEMA') | as_text }}" - threads: 4 - prod: - type: snowflake - account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}" - - # User/password auth - user: "{{ env_var('SNOWFLAKE_USER') | as_text }}" - password: "{{ env_var('SNOWFLAKE_PASSWORD') | as_text }}" - - role: DEVELOPER - database: cloud_database - warehouse: LOAD_WH - schema: PUBLIC - threads: 4 -``` - - -### Execute Recce with dbt Cloud - -To compare the data models between two environments, you need to download the dbt Cloud artifacts for both environments. The artifacts include the manifest.json file and the catalog.json file. You can download the artifacts from the dbt Cloud UI. - -#### Login to your dbt Cloud account - -![dbt Cloud login](../assets/images/shared/login-dbt-cloud.png) - -#### Go to the project you want to compare - -![dbt Cloud login](../assets/images/shared/select-run-job.png) - -#### Download the dbt artifacts - -Download the artifacts from the latest run of both run jobs. You can download the artifacts from the `Artifacts` tab. - -![dbt Cloud login](../assets/images/shared/prod-artifacts.png) -![dbt Cloud login](../assets/images/shared/dev-artifacts.png) - -### Set up the dbt artifacts folders - -Extract the downloaded artifacts and keep them in a separate folder. The production artifacts should be in the `target-base` folder and the development artifacts should be in the `target` folder. - -```bash -$ tree target target-base -target -├── catalog.json -└── manifest.json -target-base/ -├── catalog.json -└── manifest.json -``` - -### Setup dbt project - -Move the `target` and `target-base` folders to the root of your dbt project. -You should also have the `profiles.yml` file in the root of your dbt project with the credentials profile for both environments. - -### Launch Recce - -Run the command to compare the data models between the two environments. - -```shell -recce server -``` - diff --git a/docs/2-getting-started/jaffle-shop-tutorial.md b/docs/2-getting-started/jaffle-shop-tutorial.md index 17cc6d9..46432fe 100644 --- a/docs/2-getting-started/jaffle-shop-tutorial.md +++ b/docs/2-getting-started/jaffle-shop-tutorial.md @@ -12,8 +12,8 @@ This tutorial uses [jaffle_shop_duckdb](https://github.com/dbt-labs/jaffle_shop_ ## Prerequisites -- [ ] Python 3.9+ installed -- [ ] Git installed +- [x] Python 3.9+ installed +- [x] Git installed ## Steps diff --git a/docs/2-getting-started/oss-setup.md b/docs/2-getting-started/oss-setup.md index 2f1b6f1..085713c 100644 --- a/docs/2-getting-started/oss-setup.md +++ b/docs/2-getting-started/oss-setup.md @@ -4,7 +4,7 @@ title: OSS Setup # Set Up Recce OSS -When you change dbt models, you need to compare the data before and after to catch unintended impacts. Recce OSS lets you run this validation locally. +When you change data models, you need to compare the data before and after to catch unintended impacts. Recce OSS lets you run this validation locally. **Goal:** Install and run Recce locally for manual data validation. @@ -12,9 +12,9 @@ Recce OSS gives you the core validation engine to run locally. For the full expe ## Prerequisites -- [ ] Python 3.9+ installed -- [ ] A dbt project with at least one model -- [ ] Git installed (for version comparison) +- [x] Python 3.9+ installed +- [x] A dbt project with at least one model +- [x] Git installed (for version comparison) ## Steps diff --git a/mkdocs.yml b/mkdocs.yml index 0a05778..a027bc3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -54,8 +54,6 @@ nav: - Changelog: "https://reccehq.com/changelog/" - Getting Started: - 2-getting-started/start-free-with-cloud.md - #- 2-getting-started/cloud-5min-tutorial.md - - 2-getting-started/installation.md - Claude Plugin: 2-getting-started/claude-plugin.md - 2-getting-started/oss-setup.md - 2-getting-started/jaffle-shop-tutorial.md From 35c70d7c4450ad79fa133edcc8ef8aad52a86eb0 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Mon, 2 Mar 2026 11:00:43 +0800 Subject: [PATCH 19/43] docs: fix terminology - use PR instead of pull request Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/dbt-cloud-setup.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/2-getting-started/dbt-cloud-setup.md b/docs/2-getting-started/dbt-cloud-setup.md index 7cee6a0..596ce0d 100644 --- a/docs/2-getting-started/dbt-cloud-setup.md +++ b/docs/2-getting-started/dbt-cloud-setup.md @@ -4,7 +4,7 @@ title: dbt Cloud Setup # dbt Cloud Setup -When your dbt project runs on dbt Cloud, validating pull request (PR) data changes requires retrieving artifacts from the dbt Cloud API rather than generating them locally. +When your dbt project runs on dbt Cloud, validating PR data changes requires retrieving artifacts from the dbt Cloud API rather than generating them locally. ## Goal From 428557b38c40874fda8af9152c58707aafd28964 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Mon, 2 Mar 2026 11:00:44 +0800 Subject: [PATCH 20/43] docs: add expected outcomes to environment-setup steps Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/environment-setup.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/2-getting-started/environment-setup.md b/docs/2-getting-started/environment-setup.md index 1d9d5b6..11655d2 100644 --- a/docs/2-getting-started/environment-setup.md +++ b/docs/2-getting-started/environment-setup.md @@ -74,6 +74,8 @@ jaffle_shop: threads: 4 ``` +After saving, your profile supports three targets: `dev` for local development, `ci` for PR validation, and `prod` for production. + Key points: - The `ci` target uses `env_var('CI_SCHEMA')` for dynamic schema assignment @@ -98,7 +100,7 @@ variables: CI_SCHEMA: "mr_${CI_MERGE_REQUEST_IID}" ``` -This creates schemas like `pr_123`, `pr_456` for each PR automatically. +This creates schemas like `pr_123`, `pr_456` for each PR automatically. When a PR opens, the workflow sets `CI_SCHEMA` and dbt writes to that isolated schema. For complete workflow examples, see [Setup CD](setup-cd.md) and [Setup CI](setup-ci.md). From 31cdb154e672aa14d821d5a7be22c1cc3d5596ec Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Mon, 2 Mar 2026 11:09:11 +0800 Subject: [PATCH 21/43] docs: add redirects for renamed/moved pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Redirects: - installation.md → oss-setup.md - get-started-jaffle-shop.md → jaffle-shop-tutorial.md - oss-vs-cloud.md → cloud-vs-oss.md Co-Authored-By: Claude Opus 4.5 --- mkdocs.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index a027bc3..2659d38 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -154,6 +154,11 @@ theme: plugins: - search + - redirects: + redirect_maps: + '2-getting-started/installation.md': '2-getting-started/oss-setup.md' + '2-getting-started/get-started-jaffle-shop.md': '2-getting-started/jaffle-shop-tutorial.md' + '2-getting-started/oss-vs-cloud.md': '1-whats-recce/cloud-vs-oss.md' - glightbox: skip_classes: - skip-glightbox From 9be3a9163b545ddbc9064b34d3e152435fdd953b Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Mon, 2 Mar 2026 14:59:49 +0800 Subject: [PATCH 22/43] =?UTF-8?q?docs:=20add=20redirect=20for=20invitation?= =?UTF-8?q?.md=20=E2=86=92=20admin-setup.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 4.5 --- mkdocs.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mkdocs.yml b/mkdocs.yml index ff2b425..61e9d50 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -156,6 +156,9 @@ theme: plugins: - search + - redirects: + redirect_maps: + '6-collaboration/invitation.md': '3-using-recce/admin-setup.md' - glightbox: skip_classes: - skip-glightbox From 90c2bd451508066eb57958a31eea1f0252262075 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Mon, 2 Mar 2026 15:49:52 +0800 Subject: [PATCH 23/43] docs: add redirects and fix broken link MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add mkdocs-redirects plugin to requirements.txt - Add redirects for 7-cicd/ → 2-getting-started/ paths - Fix broken link to dbt-cloud-setup.md in environment-best-practices.md Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/environment-best-practices.md | 2 +- mkdocs.yml | 6 ++++++ requirements.txt | 1 + 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/docs/2-getting-started/environment-best-practices.md b/docs/2-getting-started/environment-best-practices.md index 4db2b3f..3845d66 100644 --- a/docs/2-getting-started/environment-best-practices.md +++ b/docs/2-getting-started/environment-best-practices.md @@ -119,7 +119,7 @@ Recce uses base and current environment artifacts (`manifest.json`, `catalog.jso **Recommended approaches:** - **Recce Cloud** - Automatic artifact management via `recce-cloud upload`. See [Setup CD](setup-cd.md) and [Setup CI](setup-ci.md). -- **dbt Cloud** - Download artifacts from dbt Cloud jobs. See [dbt Cloud Setup](dbt-cloud-setup.md). +- **dbt Cloud** - Download artifacts from dbt Cloud jobs. See dbt Cloud Setup (separate guide). **Alternative approaches** (for custom setups): diff --git a/mkdocs.yml b/mkdocs.yml index b80f5aa..1280137 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -159,6 +159,12 @@ theme: plugins: - search + - redirects: + redirect_maps: + '7-cicd/setup-cd.md': '2-getting-started/setup-cd.md' + '7-cicd/setup-ci.md': '2-getting-started/setup-ci.md' + '7-cicd/ci-cd-getting-started.md': '2-getting-started/environment-setup.md' + '7-cicd/best-practices-prep-env.md': '2-getting-started/environment-best-practices.md' - glightbox: skip_classes: - skip-glightbox diff --git a/requirements.txt b/requirements.txt index 7359048..44e20f7 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,4 @@ mkdocs-material +mkdocs-redirects mkdocs-glightbox mkdocs-material[imaging] \ No newline at end of file From 4744f2f591d539b14fd6af111b2b369e32dd10ea Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Mon, 2 Mar 2026 15:53:02 +0800 Subject: [PATCH 24/43] docs: fix navigation and broken links for moved files - Remove old 7-cicd/setup-cd.md and setup-ci.md from nav (moved to Getting Started) - Update links in start-free-with-cloud.md to point to new locations - Update links in ci-cd-getting-started.md to point to new locations - Delete old setup-cd.md and setup-ci.md from 7-cicd/ Co-Authored-By: Claude Opus 4.5 --- .../start-free-with-cloud.md | 10 +- docs/7-cicd/ci-cd-getting-started.md | 10 +- docs/7-cicd/setup-cd.md | 347 ------------------ docs/7-cicd/setup-ci.md | 286 --------------- mkdocs.yml | 6 +- 5 files changed, 11 insertions(+), 648 deletions(-) delete mode 100644 docs/7-cicd/setup-cd.md delete mode 100644 docs/7-cicd/setup-ci.md diff --git a/docs/2-getting-started/start-free-with-cloud.md b/docs/2-getting-started/start-free-with-cloud.md index 4edc0d0..1a80265 100644 --- a/docs/2-getting-started/start-free-with-cloud.md +++ b/docs/2-getting-started/start-free-with-cloud.md @@ -89,7 +89,7 @@ First, go to [cloud.reccehq.com](https://cloud.reccehq.com) and create your free This step adds CI/CD workflow files to your repository. The agent creates these automatically. For manual setup, create and merge a PR with the templates below. -> **Note**: This guide uses GitHub Actions. For other CI/CD platforms, see [Setup CD](../7-cicd/setup-cd.md) and [Setup CI](../7-cicd/setup-ci.md). +> **Note**: This guide uses GitHub Actions. For other CI/CD platforms, see [Setup CD](setup-cd.md) and [Setup CI](setup-ci.md). #### Set Up Profile.yml @@ -330,7 +330,7 @@ You can now: ## Related Resources -- [CI/CD Getting Started](../7-cicd/ci-cd-getting-started.md) -- [Setup CD](../7-cicd/setup-cd.md) -- [Setup CI](../7-cicd/setup-ci.md) -- [Best Practices for Preparing Environments](../7-cicd/best-practices-prep-env.md) +- [Environment Setup](environment-setup.md) +- [Setup CD](setup-cd.md) +- [Setup CI](setup-ci.md) +- [Environment Best Practices](environment-best-practices.md) diff --git a/docs/7-cicd/ci-cd-getting-started.md b/docs/7-cicd/ci-cd-getting-started.md index eda3981..1bfda5c 100644 --- a/docs/7-cicd/ci-cd-getting-started.md +++ b/docs/7-cicd/ci-cd-getting-started.md @@ -65,14 +65,14 @@ Before setting up, ensure you have: Both GitHub and GitLab follow the same simple pattern: ### 1. Setup CD - Auto-update baseline -[**Setup CD Guide**](./setup-cd.md) - Configure automatic baseline updates when you merge to main +[**Setup CD Guide**](../2-getting-started/setup-cd.md) - Configure automatic baseline updates when you merge to main - Updates your production baseline artifacts automatically - Runs on merge to main + optional scheduled updates - Works with both GitHub Actions and GitLab CI/CD ### 2. Setup CI - Auto-validate PRs/MRs -[**Setup CI Guide**](./setup-ci.md) - Enable automatic validation for every PR/MR +[**Setup CI Guide**](../2-getting-started/setup-ci.md) - Enable automatic validation for every PR/MR - Validates data changes in every pull request or merge request - Catches issues before they reach production @@ -84,9 +84,9 @@ Start with **CD first** to establish your baseline (production artifacts), then ## Next Steps -1. **[Setup CD](./setup-cd.md)** - Establish automatic baseline updates -2. **[Setup CI](./setup-ci.md)** - Enable PR/MR validation -3. Review [best practices](./best-practices-prep-env.md) for environment preparation +1. **[Setup CD](../2-getting-started/setup-cd.md)** - Establish automatic baseline updates +2. **[Setup CI](../2-getting-started/setup-ci.md)** - Enable PR/MR validation +3. Review [Environment Best Practices](../2-getting-started/environment-best-practices.md) for environment preparation ## Related workflows diff --git a/docs/7-cicd/setup-cd.md b/docs/7-cicd/setup-cd.md deleted file mode 100644 index 075d0d5..0000000 --- a/docs/7-cicd/setup-cd.md +++ /dev/null @@ -1,347 +0,0 @@ ---- -title: Setup CD ---- - -# Setup CD - Auto-Update Baseline - -Set up automatic updates for your Recce Cloud base sessions. Keep your data comparison baseline current every time you merge to main, with no manual work required. - -## What This Does - -**Automated Base Session Management** eliminates manual baseline maintenance: - -- **Triggers**: Merge to main + scheduled updates + manual runs -- **Action**: Auto-update base Recce session with latest production artifacts -- **Benefit**: Current comparison baseline for all future PRs/MRs - -## Prerequisites - -Before setting up CD, ensure you have: - -- [x] **Recce Cloud account** - [Start free trial](https://cloud.reccehq.com/) -- [x] **Repository connected** to Recce Cloud - [Connect Git Provider](../2-getting-started/start-free-with-cloud.md#2-connect-git-provider) -- [x] **dbt artifacts** - Know how to generate `manifest.json` and `catalog.json` from your dbt project - -## Setup - -### GitHub Actions - -Create `.github/workflows/base-workflow.yml`: - -```yaml linenums="1" -name: Update Base Metadata - -on: - push: - branches: ["main"] - schedule: - - cron: "0 2 * * *" - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }} - cancel-in-progress: true - -jobs: - update-base-session: - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - contents: read - - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - cache: "pip" - - - name: Install dependencies - run: pip install -r requirements.txt - - - name: Prepare dbt artifacts - run: | - dbt deps - dbt build --target prod - dbt docs generate --target prod - env: - SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }} - SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }} - SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }} - SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }} - SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }} - - - name: Upload to Recce Cloud - run: | - pip install recce-cloud - recce-cloud upload --type prod - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -``` - -**Key points:** - -- `dbt build` and `dbt docs generate` create the required artifacts (`manifest.json` and `catalog.json`) -- `recce-cloud upload --type prod` uploads the Base metadata to Recce Cloud -- [`GITHUB_TOKEN`](https://docs.github.com/en/actions/concepts/security/github_token) authenticates with Recce Cloud - -### GitLab CI/CD - -Add to your `.gitlab-ci.yml`: - -```yaml linenums="1" hl_lines="30-31" -stages: - - build - - upload - -variables: - DBT_TARGET_PROD: "prod" - -# Production build - runs on schedule or main branch push -prod-build: - stage: build - image: python:3.11-slim - script: - - pip install -r requirements.txt - - dbt deps - # Optional: dbt build --target $DBT_TARGET_PROD - - dbt docs generate --target $DBT_TARGET_PROD - artifacts: - paths: - - target/ - expire_in: 7 days - rules: - - if: $CI_PIPELINE_SOURCE == "schedule" - - if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH - -# Upload to Recce Cloud -recce-upload-prod: - stage: upload - image: python:3.11-slim - script: - - pip install recce-cloud - - recce-cloud upload --type prod - dependencies: - - prod-build - rules: - - if: $CI_PIPELINE_SOURCE == "schedule" - - if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH -``` - -**Key points:** - -- Authentication is automatic via `CI_JOB_TOKEN` -- Configure schedule in **CI/CD → Schedules** (e.g., `0 2 * * *` for daily at 2 AM UTC) -- `recce-cloud upload --type prod` tells Recce this is a baseline session - -### Platform Comparison - -| Aspect | GitHub Actions | GitLab CI/CD | -| -------------------- | ----------------------------------- | ------------------------------------------------------------------------------ | -| **Config file** | `.github/workflows/base-workflow.yml` | `.gitlab-ci.yml` | -| **Trigger on merge** | `on: push: branches: ["main"]` | `if: $CI_PIPELINE_SOURCE == "push" && $CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH` | -| **Schedule setup** | In workflow YAML (`schedule:`) | In UI: **CI/CD → Schedules** | -| **Authentication** | Explicit (`GITHUB_TOKEN`) | Automatic (`CI_JOB_TOKEN`) | -| **Manual trigger** | `workflow_dispatch:` | Pipeline run from UI | - -## Verification - -### Test the Workflow - -**GitHub:** - -1. Go to **Actions** tab → Select "Update Base Recce Session" -2. Click **Run workflow** → Monitor for completion - -**GitLab:** - -1. Go to **CI/CD → Pipelines** → Click **Run pipeline** -2. Select **main** branch → Monitor for completion - -### Verify Success - -Look for these indicators: - -- [x] **Workflow/Pipeline completes** without errors -- [x] **Base session updated** in [Recce Cloud](https://cloud.reccehq.com) - -**GitHub:** - -![Recce Cloud showing updated base sessions](../assets/images/7-cicd/verify-setup-github-cd.png){: .shadow} - -**GitLab:** - -![Recce Cloud showing updated base sessions](../assets/images/7-cicd/verify-setup-gitlab-cd.png){: .shadow} - -### Expected Output - -When the upload succeeds, you'll see output like this in your workflow logs: - -**GitHub:** - -```hl_lines="2 3 13" -─────────────────────────── CI Environment Detection ─────────────────────────── -Platform: github-actions -Session Type: prod -Commit SHA: def456ab... -Source Branch: main -Repository: your-org/your-repo -Info: Using GITHUB_TOKEN for platform-specific authentication -────────────────────────── Creating/touching session ─────────────────────────── -Session ID: abc123-def456-ghi789 -Uploading manifest from path "target/manifest.json" -Uploading catalog from path "target/catalog.json" -Notifying upload completion... -──────────────────────────── Uploaded Successfully ───────────────────────────── -Uploaded dbt artifacts to Recce Cloud for session ID "abc123-def456-ghi789" -Artifacts from: "/home/runner/work/your-repo/your-repo/target" -``` - -**GitLab:** - -```hl_lines="2 3 13" -─────────────────────────── CI Environment Detection ─────────────────────────── -Platform: gitlab-ci -Session Type: prod -Commit SHA: a1b2c3d4... -Source Branch: main -Repository: your-org/your-project -Info: Using CI_JOB_TOKEN for platform-specific authentication -────────────────────────── Creating/touching session ─────────────────────────── -Session ID: abc123-def456-ghi789 -Uploading manifest from path "target/manifest.json" -Uploading catalog from path "target/catalog.json" -Notifying upload completion... -──────────────────────────── Uploaded Successfully ───────────────────────────── -Uploaded dbt artifacts to Recce Cloud for session ID "abc123-def456-ghi789" -Artifacts from: "/builds/your-org/your-project/target" -``` - -## Advanced Options - -### Custom Artifact Path - -If your dbt artifacts are in a non-standard location: - -```bash -recce-cloud upload --type prod --target-path custom-target -``` - -### External Artifact Sources - -You can download artifacts from external sources before uploading: - -```yaml -# GitHub example -- name: Download from dbt Cloud - run: | - # Your download logic here - # Artifacts should end up in target/ directory - -- name: Upload to Recce Cloud - run: | - pip install recce-cloud - recce-cloud upload --type prod -``` - -### Dry Run Testing - -Test your configuration without actually uploading: - -```bash -recce-cloud upload --type prod --dry-run -``` - -## Troubleshooting - -### Missing dbt artifacts - -**Error**: `Missing manifest.json` or `Missing catalog.json` - -**Solution**: Ensure `dbt docs generate` runs successfully before upload: - -**GitHub:** - -```yaml -- name: Prepare dbt artifacts - run: | - dbt deps - dbt docs generate --target prod # Required -``` - -**GitLab:** - -```yaml -prod-build: - script: - - dbt deps - - dbt docs generate --target $DBT_TARGET_PROD # Required - artifacts: - paths: - - target/ -``` - -### Authentication issues - -**Error**: `Failed to create session: 401 Unauthorized` - -**Solutions**: - -1. Verify your repository is connected in [Recce Cloud settings](https://cloud.reccehq.com/settings) -2. **For GitHub**: Ensure `GITHUB_TOKEN` is passed explicitly to the upload step and the job has `contents: read` permission -3. **For GitLab**: Verify project has GitLab integration configured - - Check that you've created a [Personal Access Token](../2-getting-started/gitlab-pat-guide.md) - - Ensure the token has appropriate scope (`api` or `read_api`) - - Verify the project is connected in Recce Cloud settings - -### Upload failures - -**Error**: `Failed to upload manifest/catalog` - -**Solutions**: - -1. Check network connectivity to Recce Cloud -2. Verify artifact files exist in `target/` directory -3. Review workflow/pipeline logs for detailed error messages -4. **For GitLab**: Ensure artifacts are passed between jobs: - - ```yaml - prod-build: - artifacts: - paths: - - target/ # Must include dbt artifacts - - recce-upload-prod: - dependencies: - - prod-build # Required to access artifacts - ``` - -### Session not appearing - -**Issue**: Upload succeeds but session doesn't appear in Recce Cloud - -**Solutions**: - -1. Check you're viewing the correct repository in Recce Cloud -2. Verify you're looking at the production/base sessions (not PR/MR sessions) -3. Check session filters in Recce Cloud (may be hidden by filters) -4. Refresh the Recce Cloud page - -### Schedule not triggering (GitLab only) - -**Issue**: Scheduled pipeline doesn't run - -**Solutions**: - -1. Verify schedule is **Active** in CI/CD → Schedules -2. Check schedule timezone settings (UTC by default) -3. Ensure target branch (`main`) exists -4. Review project's CI/CD minutes quota -5. Verify schedule owner has appropriate permissions - -## Next Steps - -**[Setup CI](./setup-ci.md)** to automatically validate PR/MR changes against your updated base session. This completes your CI/CD pipeline by adding automated data validation for every pull request or merge request. diff --git a/docs/7-cicd/setup-ci.md b/docs/7-cicd/setup-ci.md deleted file mode 100644 index 9310ee0..0000000 --- a/docs/7-cicd/setup-ci.md +++ /dev/null @@ -1,286 +0,0 @@ ---- -title: Setup CI ---- - -# Setup CI - Auto-Validate PRs/MRs - -Automatically validate your data changes in every pull request or merge request using Recce Cloud. Catch data issues before they reach production, with validation results right in your PR/MR. - -## What This Does - -**Automated PR/MR Validation** prevents data regressions before merge: - -- **Triggers**: PR/MR opened or updated against main -- **Action**: Auto-update Recce session for validation -- **Benefit**: Automated data validation and comparison visible in your PR/MR - -## Prerequisites - -Before setting up CI, ensure you have: - -- [x] **Recce Cloud account** - [Start free trial](https://cloud.reccehq.com/) -- [x] **Repository connected** to Recce Cloud - [Connect Git Provider](../2-getting-started/start-free-with-cloud.md#2-connect-git-provider) -- [x] **dbt artifacts** - Know how to generate `manifest.json` and `catalog.json` from your dbt project -- [x] **CD configured** - [Setup CD](./setup-cd.md) to establish baseline for comparisons - -## Setup - -### GitHub Actions - -Create `.github/workflows/pr-workflow.yml`: - -```yaml linenums="1" -name: Validate PR Changes - -on: - pull_request: - branches: ["main"] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - validate-changes: - runs-on: ubuntu-latest - timeout-minutes: 45 - permissions: - contents: read - pull-requests: write - - steps: - - name: Checkout PR branch - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - cache: "pip" - - - name: Install dependencies - run: pip install -r requirements.txt - - - name: Build current branch artifacts - run: | - dbt deps - dbt build --target ci - dbt docs generate --target ci - env: - SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }} - SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }} - SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }} - SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }} - SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }} - SNOWFLAKE_SCHEMA: "PR_${{ github.event.pull_request.number }}" - - - name: Upload to Recce Cloud - run: | - pip install recce-cloud - recce-cloud upload - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -``` - -**Key points:** - -- Creates a per-PR schema (`PR_123`, `PR_456`, etc.) using the dynamic `SNOWFLAKE_SCHEMA` environment variable to isolate each PR's data -- `dbt build` and `dbt docs generate` create the required artifacts (`manifest.json` and `catalog.json`) -- `recce-cloud upload` (without `--type`) auto-detects this is a PR session -- [`GITHUB_TOKEN`](https://docs.github.com/en/actions/concepts/security/github_token) authenticates with Recce Cloud - -### GitLab CI/CD - -Add to your `.gitlab-ci.yml`: - -```yaml linenums="1" hl_lines="29-30" -stages: - - build - - upload - -variables: - DBT_TARGET: "ci" - -# MR build - runs on merge requests -dbt-build: - stage: build - image: python:3.11-slim - script: - - pip install -r requirements.txt - - dbt deps - # Optional: dbt build --target $DBT_TARGET - - dbt docs generate --target $DBT_TARGET - artifacts: - paths: - - target/ - expire_in: 1 week - rules: - - if: $CI_PIPELINE_SOURCE == "merge_request_event" - -# Upload to Recce Cloud -recce-upload: - stage: upload - image: python:3.11-slim - script: - - pip install recce-cloud - - recce-cloud upload - dependencies: - - dbt-build - rules: - - if: $CI_PIPELINE_SOURCE == "merge_request_event" -``` - -**Key points:** - -- Authentication is automatic via `CI_JOB_TOKEN` -- `recce-cloud upload` (without `--type`) auto-detects this is an MR session -- `dbt docs generate` creates the required `manifest.json` and `catalog.json` - -### Platform Comparison - -| Aspect | GitHub Actions | GitLab CI/CD | -| -------------------- | ----------------------------------- | -------------------------------------------------- | -| **Config file** | `.github/workflows/pr-workflow.yml` | `.gitlab-ci.yml` | -| **Trigger** | `on: pull_request:` | `if: $CI_PIPELINE_SOURCE == "merge_request_event"` | -| **Authentication** | Explicit (`GITHUB_TOKEN`) | Automatic (`CI_JOB_TOKEN`) | -| **Session type** | Auto-detected from PR context | Auto-detected from MR context | -| **Artifact passing** | Not needed (single job) | Use `artifacts:` + `dependencies:` | - -## Verification - -### Test with a PR/MR - -**GitHub:** - -1. Create a test PR with small data changes -2. Check **Actions** tab for CI workflow execution -3. Verify validation runs successfully - -**GitLab:** - -1. Create a test MR with small data changes -2. Check **CI/CD → Pipelines** for workflow execution -3. Verify validation runs successfully - -### Verify Success - -Look for these indicators: - -- [x] **Workflow/Pipeline completes** without errors -- [x] **PR/MR session created** in [Recce Cloud](https://cloud.reccehq.com) -- [x] **Session URL** appears in workflow/pipeline output - -**GitHub:** - -![Recce Cloud showing PR validation session](../assets/images/7-cicd/verify-setup-github-ci.png){: .shadow} - -**GitLab:** - -![Recce Cloud showing MR validation session](../assets/images/7-cicd/verify-setup-gitlab-ci.png){: .shadow} - -### Expected Output - -When the upload succeeds, you'll see output like this in your workflow logs: - -**GitHub:** - -```hl_lines="2 5 16" -─────────────────────────── CI Environment Detection ─────────────────────────── -Platform: github-actions -PR Number: 42 -PR URL: https://github.com/your-org/your-repo/pull/42 -Session Type: cr -Commit SHA: abc123de... -Base Branch: main -Source Branch: feature/your-feature -Repository: your-org/your-repo -Info: Using GITHUB_TOKEN for platform-specific authentication -────────────────────────── Creating/touching session ─────────────────────────── -Session ID: f8b0f7ca-ea59-411d-abd8-88b80b9f87ad -Uploading manifest from path "target/manifest.json" -Uploading catalog from path "target/catalog.json" -Notifying upload completion... -──────────────────────────── Uploaded Successfully ───────────────────────────── -Uploaded dbt artifacts to Recce Cloud for session ID "f8b0f7ca-ea59-411d-abd8-88b80b9f87ad" -Artifacts from: "/home/runner/work/your-repo/your-repo/target" -Change request: https://github.com/your-org/your-repo/pull/42 -``` - -**GitLab:** - -```hl_lines="2 5 16" -─────────────────────────── CI Environment Detection ─────────────────────────── -Platform: gitlab-ci -MR Number: 4 -MR URL: https://gitlab.com/your-org/your-project/-/merge_requests/4 -Session Type: cr -Commit SHA: c928e3d5... -Base Branch: main -Source Branch: feature/your-feature -Repository: your-org/your-project -Info: Using CI_JOB_TOKEN for platform-specific authentication -────────────────────────── Creating/touching session ─────────────────────────── -Session ID: f8b0f7ca-ea59-411d-abd8-88b80b9f87ad -Uploading manifest from path "target/manifest.json" -Uploading catalog from path "target/catalog.json" -Notifying upload completion... -──────────────────────────── Uploaded Successfully ───────────────────────────── -Uploaded dbt artifacts to Recce Cloud for session ID "f8b0f7ca-ea59-411d-abd8-88b80b9f87ad" -Artifacts from: "/builds/your-org/your-project/target" -Change request: https://gitlab.com/your-org/your-project/-/merge_requests/4 -``` - -### Review PR/MR Session - -To analyze the changes in detail: - -1. Go to your [Recce Cloud](https://cloud.reccehq.com) -2. Find the PR/MR session that was created -3. Launch Recce instance to explore data differences - -## Advanced Options - -### Custom Artifact Path - -If your dbt artifacts are in a non-standard location: - -```bash -recce-cloud upload --target-path custom-target -``` - -### Dry Run Testing - -Test your configuration without actually uploading: - -```bash -recce-cloud upload --dry-run -``` - -## Troubleshooting - -If CI is not working, the issue is likely in your CD setup. Most problems are shared between CI and CD: - -**Common issues:** - -- Missing dbt artifacts -- Authentication failures -- Upload errors -- Sessions not appearing - -**→ See the [Setup CD Troubleshooting section](./setup-cd.md#troubleshooting)** for detailed solutions. - -**CI-specific tip:** If CD works but CI doesn't, verify: - -1. PR/MR trigger conditions in your workflow configuration -2. The PR/MR is targeting the correct base branch (usually `main`) -3. You're looking at PR/MR sessions in Recce Cloud (not production sessions) - -## Next Steps - -After setting up CI, explore these workflow guides: - -- [PR/MR review workflow](./scenario-pr-review.md) - Collaborate with teammates using Recce -- [Preset checks](./preset-checks.md) - Configure automatic validation checks -- [Best practices](./best-practices-prep-env.md) - Environment preparation tips diff --git a/mkdocs.yml b/mkdocs.yml index 1280137..9ccf200 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -86,15 +86,11 @@ nav: - 6-collaboration/checklist.md - 6-collaboration/share.md - CI/CD: - - 7-cicd/ci-cd-getting-started.md - - 7-cicd/setup-cd.md - - 7-cicd/setup-ci.md - 7-cicd/pr-mr-summary.md #- 7-cicd/recce-debug.md # content outdated - - 7-cicd/scenario-dev.md + - 7-cicd/scenario-dev.md - 7-cicd/scenario-pr-review.md - 7-cicd/preset-checks.md - - 7-cicd/best-practices-prep-env.md - Technical Concepts: - 8-technical-concepts/state-file.md From dda95f13a7f8532c55a60dd2ef816b0d27d83d62 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Mon, 2 Mar 2026 16:00:22 +0800 Subject: [PATCH 25/43] docs: fix prerequisites format to use [x] instead of [ ] Co-Authored-By: Claude Opus 4.5 --- docs/3-using-recce/admin-setup.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/3-using-recce/admin-setup.md b/docs/3-using-recce/admin-setup.md index 0debf91..422f5f8 100644 --- a/docs/3-using-recce/admin-setup.md +++ b/docs/3-using-recce/admin-setup.md @@ -12,9 +12,9 @@ When you sign up for Recce Cloud, you get one organization and one project. Afte ## Prerequisites -- [ ] Recce Cloud account with owner/admin access -- [ ] Git repository connected to Recce Cloud -- [ ] Team members' email addresses +- [x] Recce Cloud account with owner/admin access +- [x] Git repository connected to Recce Cloud +- [x] Team members' email addresses ## Steps From 6d8607c82bdb719da46600de699359e51258e47b Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Mon, 2 Mar 2026 16:08:12 +0800 Subject: [PATCH 26/43] docs: move rename project to step 4, add step 6 for invitee instructions - Add step 4 "Rename your project (optional)" with updated instructions - Renumber "Invite team members" to step 5 - Add step 6 telling admins to inform invitees about acceptance modal - Remove redundant "Additional Settings > Rename your project" section Co-Authored-By: Claude Opus 4.5 --- docs/3-using-recce/admin-setup.md | 34 +++++++++++++++---------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/docs/3-using-recce/admin-setup.md b/docs/3-using-recce/admin-setup.md index 422f5f8..a11d72c 100644 --- a/docs/3-using-recce/admin-setup.md +++ b/docs/3-using-recce/admin-setup.md @@ -51,14 +51,27 @@ Update the organization name to match your company or team. **Expected result:** New project appears in the project list and sidebar. -### 4. Invite team members +### 4. Rename your project (optional) + +Update the project name if needed. + +1. In Organization Settings, navigate to **Projects** +2. Click on the project you want to rename +3. Enter the new project name +4. Click **Save** + +**Expected result:** Project name updates in the sidebar and project list. + +### 5. Invite team members Add collaborators to your organization. 1. In Organization Settings, find the **Members** section 2. Click **Invite Members** 3. Enter email addresses (use SSO email if members use SSO login) -4. Select a role for each invitee: +4. Select a role for each invitee +5. Click **Send Invitation** +6. Tell invitees: when they log in, a modal appears asking them to accept the invitation. See [For Invited Users](#for-invited-users) | Role | Permissions | |------|-------------| @@ -69,24 +82,11 @@ Add collaborators to your organization. !!! tip "SSO login requires Team plan or above" SSO login is available on the Team plan and above. See [Pricing](https://www.reccehq.com/pricing) for plan details. -1. Click **Send Invitation** **Expected result:** Invitees receive email invitations and see notifications when logged in. ![Invite Members dialog with email input field and role selection](../assets/images/6-collaboration/recce-cloud-org-invitation-fs8.png){: .shadow} -## Additional Settings - -### Rename your project - -Update the project name if needed. - -1. Navigate to your project → click **Settings** -2. Enter the new project name -3. Click **Save** - -**Expected result:** Project name updates in the sidebar and project list. - ## Verify Success Confirm your setup by checking: @@ -113,7 +113,7 @@ When you receive an invitation: ![Recce Cloud home page showing pending invitation notification modal](../assets/images/6-collaboration/recce-cloud-org-pending-invitation-home-fs8.png){: .shadow} -## Next Steps + From 7abd18a28f8d6c7535f2aa590341a0ea94f5f0f3 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 21:51:43 +0800 Subject: [PATCH 27/43] Add setup choice callout for CI/CD section Helps users identify their setup path: - Own dbt run vs platform (dbt Cloud) - Simple vs advanced environment Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/start-free-with-cloud.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/2-getting-started/start-free-with-cloud.md b/docs/2-getting-started/start-free-with-cloud.md index 1a80265..1862303 100644 --- a/docs/2-getting-started/start-free-with-cloud.md +++ b/docs/2-getting-started/start-free-with-cloud.md @@ -87,6 +87,20 @@ First, go to [cloud.reccehq.com](https://cloud.reccehq.com) and create your free ### 3. Add Recce to CI/CD +Before proceeding, identify your setup: + +!!! info "Choose your setup" + + **How do you run dbt?** + + - **You own your dbt run** (GitHub Actions, GitLab CI, CircleCI): Continue with this guide + - **You run dbt on a platform** (dbt Cloud, Paradigms, etc.): See [dbt Cloud Setup](dbt-cloud-setup.md) + + **How complex is your environment?** + + - **Simple** (single prod schema, PR schemas): Continue with this guide + - **Advanced** (multiple schemas, staging environments): See [Environment Setup](environment-setup.md) + This step adds CI/CD workflow files to your repository. The agent creates these automatically. For manual setup, create and merge a PR with the templates below. > **Note**: This guide uses GitHub Actions. For other CI/CD platforms, see [Setup CD](setup-cd.md) and [Setup CI](setup-ci.md). From 5f2b5b442a7f4efd3fec884e93c69e369588b89f Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 21:59:13 +0800 Subject: [PATCH 28/43] Update setup callout with per-PR explanation Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/start-free-with-cloud.md | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/docs/2-getting-started/start-free-with-cloud.md b/docs/2-getting-started/start-free-with-cloud.md index 1862303..37cc42b 100644 --- a/docs/2-getting-started/start-free-with-cloud.md +++ b/docs/2-getting-started/start-free-with-cloud.md @@ -89,17 +89,16 @@ First, go to [cloud.reccehq.com](https://cloud.reccehq.com) and create your free Before proceeding, identify your setup: -!!! info "Choose your setup" +**Choose your setup** +1. How do you run dbt? - **How do you run dbt?** + - **You own your dbt run** (GitHub Actions, GitLab CI, CircleCI): Continue with this guide + - **You run dbt on a platform** (dbt Cloud, Paradigms, etc.): See [dbt Cloud Setup](dbt-cloud-setup.md) - - **You own your dbt run** (GitHub Actions, GitLab CI, CircleCI): Continue with this guide - - **You run dbt on a platform** (dbt Cloud, Paradigms, etc.): See [dbt Cloud Setup](dbt-cloud-setup.md) +2. How complex is your environment? - **How complex is your environment?** - - - **Simple** (single prod schema, PR schemas): Continue with this guide - - **Advanced** (multiple schemas, staging environments): See [Environment Setup](environment-setup.md) + - **Simple** (prod and dev targets): Continue with this guide. We use per-PR schemas for fast setup. To learn why, see [Environment Setup](environment-setup.md). + - **Advanced** (multiple schemas, staging environments): See [Environment Setup](environment-setup.md) This step adds CI/CD workflow files to your repository. The agent creates these automatically. For manual setup, create and merge a PR with the templates below. From 69f72ae736ebba9da9bc05d233b76e39ab9b1faf Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 22:01:22 +0800 Subject: [PATCH 29/43] Add CI/CD platform as item 3 in setup callout Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/start-free-with-cloud.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/docs/2-getting-started/start-free-with-cloud.md b/docs/2-getting-started/start-free-with-cloud.md index 37cc42b..1e78d63 100644 --- a/docs/2-getting-started/start-free-with-cloud.md +++ b/docs/2-getting-started/start-free-with-cloud.md @@ -100,9 +100,12 @@ Before proceeding, identify your setup: - **Simple** (prod and dev targets): Continue with this guide. We use per-PR schemas for fast setup. To learn why, see [Environment Setup](environment-setup.md). - **Advanced** (multiple schemas, staging environments): See [Environment Setup](environment-setup.md) -This step adds CI/CD workflow files to your repository. The agent creates these automatically. For manual setup, create and merge a PR with the templates below. +3. What's your CI/CD platform? + + - **GitHub Actions**: Continue with this guide + - **Other platforms** (GitLab CI, CircleCI, etc.): See [Setup CD](setup-cd.md) and [Setup CI](setup-ci.md) -> **Note**: This guide uses GitHub Actions. For other CI/CD platforms, see [Setup CD](setup-cd.md) and [Setup CI](setup-ci.md). +This step adds CI/CD workflow files to your repository. The agent creates these automatically. For manual setup, create and merge a PR with the templates below. #### Set Up Profile.yml From d3ac5e03f32e6fe0fcde81998dad111aa2b77ee4 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Fri, 27 Feb 2026 22:02:43 +0800 Subject: [PATCH 30/43] Move web agent intro above setup options Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/start-free-with-cloud.md | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/docs/2-getting-started/start-free-with-cloud.md b/docs/2-getting-started/start-free-with-cloud.md index 1e78d63..22da290 100644 --- a/docs/2-getting-started/start-free-with-cloud.md +++ b/docs/2-getting-started/start-free-with-cloud.md @@ -87,9 +87,10 @@ First, go to [cloud.reccehq.com](https://cloud.reccehq.com) and create your free ### 3. Add Recce to CI/CD -Before proceeding, identify your setup: +This step adds CI/CD workflow files to your repository. The web agent detects your setup and guides you through. For manual setup, use the templates below. + +#### Choose your setup -**Choose your setup** 1. How do you run dbt? - **You own your dbt run** (GitHub Actions, GitLab CI, CircleCI): Continue with this guide @@ -105,8 +106,6 @@ Before proceeding, identify your setup: - **GitHub Actions**: Continue with this guide - **Other platforms** (GitLab CI, CircleCI, etc.): See [Setup CD](setup-cd.md) and [Setup CI](setup-ci.md) -This step adds CI/CD workflow files to your repository. The agent creates these automatically. For manual setup, create and merge a PR with the templates below. - #### Set Up Profile.yml The profile.yml file tells your system where to look for the "base" and "current" builds. We have a sample `profile.yml` file: From 3afff2f912bd39f73f1e07e2be83a0bb1c576ade Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Tue, 3 Mar 2026 12:35:33 +0800 Subject: [PATCH 31/43] docs: simplify dbt Cloud setup - remove warehouse, use recce-cloud upload Based on review feedback: - Remove warehouse connection requirement (runs in Recce Cloud) - Remove "Run Recce validation", "Generate Recce summary", "Comment on PR" steps - Split into two workflows: base (CD) and PR (CI) - Use recce-cloud upload --type prod for base - Use recce-cloud upload for PR Co-Authored-By: Claude Opus 4.5 --- docs/2-getting-started/dbt-cloud-setup.md | 167 ++++++++++------------ 1 file changed, 74 insertions(+), 93 deletions(-) diff --git a/docs/2-getting-started/dbt-cloud-setup.md b/docs/2-getting-started/dbt-cloud-setup.md index 596ce0d..9164684 100644 --- a/docs/2-getting-started/dbt-cloud-setup.md +++ b/docs/2-getting-started/dbt-cloud-setup.md @@ -16,17 +16,15 @@ After completing this tutorial, every PR triggers automated data validation. Rec - [x] **dbt Cloud account**: with CI (continuous integration) and CD (continuous deployment) jobs configured - [x] **dbt Cloud API token**: with read access to job artifacts - [x] **GitHub repository**: with admin access to add workflows and secrets -- [x] **Data warehouse**: read access for data diffing ## How Recce retrieves dbt Cloud artifacts -Recce needs both base (production) and current (PR) dbt artifacts to compare changes. When using dbt Cloud, these artifacts live in dbt Cloud's API rather than your local filesystem. Your GitHub Actions workflow retrieves them via API calls before running Recce validation. +Recce needs both base (production) and current (PR) dbt artifacts to compare changes. When using dbt Cloud, these artifacts live in dbt Cloud's API rather than your local filesystem. Your GitHub Actions workflows retrieve them via API calls and upload to Recce Cloud. -The workflow: +Two workflows handle this: -1. Retrieves Base artifacts from your CD job (production deployments that run on merge to main) -2. Retrieves Current artifacts from your CI job (PR-triggered builds that validate changes) -3. Uploads both to Recce Cloud for validation +1. **Base workflow** (on merge to main): Downloads production artifacts from your CD job → uploads with `recce-cloud upload --type prod` +2. **PR workflow** (on pull request): Downloads PR artifacts from your CI job → uploads with `recce-cloud upload` ## Setup steps @@ -72,76 +70,87 @@ Add the following secrets to your GitHub repository (Settings > Secrets and vari - `DBT_CLOUD_CD_JOB_ID` - Your production/CD job ID - `DBT_CLOUD_CI_JOB_ID` - Your PR/CI job ID -**Recce Cloud secrets:** - -- `RECCE_STATE_PASSWORD` - Password to encrypt state files (create any secure string) - -**Data warehouse secrets** (for data diffing): - -Add your warehouse credentials based on your adapter. For Snowflake: - -- `SNOWFLAKE_ACCOUNT` -- `SNOWFLAKE_USER` -- `SNOWFLAKE_PASSWORD` -- `SNOWFLAKE_SCHEMA` - !!! note `GITHUB_TOKEN` is automatically provided by GitHub Actions, no configuration needed. -### 4. Create the GitHub Actions workflow - -Create `.github/workflows/recce-dbt-cloud.yml` with the workflow configuration. The workflow: +### 4. Create the base workflow (CD) -1. **Retrieves Base artifacts** from your CD job run matching the PR's base commit -2. **Retrieves Current artifacts** from your CI job run for the PR's head commit -3. **Runs Recce validation** and uploads results to Recce Cloud -4. **Posts a summary comment** on the pull request +Create `.github/workflows/recce-base.yml` to update your production baseline when merging to main. ```yaml -name: Recce with dbt Cloud +name: Update Base Metadata (dbt Cloud) on: - pull_request: + push: branches: [main] + workflow_dispatch: env: DBT_CLOUD_API_BASE: "https://cloud.getdbt.com/api/v2/accounts/${{ secrets.DBT_CLOUD_ACCOUNT_ID }}" DBT_CLOUD_API_TOKEN: ${{ secrets.DBT_CLOUD_API_TOKEN }} jobs: - recce-validation: - name: Validate PR with Recce + update-base: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - uses: actions/setup-python@v5 with: python-version: "3.10" - cache: "pip" - - name: Install dependencies - run: pip install -r requirements.txt + - name: Install recce-cloud + run: pip install recce-cloud - - name: Retrieve Base artifacts (CD job) + - name: Retrieve artifacts from CD job env: DBT_CLOUD_CD_JOB_ID: ${{ secrets.DBT_CLOUD_CD_JOB_ID }} - BASE_GITHUB_SHA: ${{ github.event.pull_request.base.sha }} run: | set -eo pipefail - CD_RUNS_URL="${DBT_CLOUD_API_BASE}/runs/?job_definition_id=${DBT_CLOUD_CD_JOB_ID}&order_by=-id" + CD_RUNS_URL="${DBT_CLOUD_API_BASE}/runs/?job_definition_id=${DBT_CLOUD_CD_JOB_ID}&order_by=-id&limit=1" CD_RUNS_RESPONSE=$(curl -sSf -H "Authorization: Bearer ${DBT_CLOUD_API_TOKEN}" "${CD_RUNS_URL}") - DBT_CLOUD_CD_RUN_ID=$(echo "${CD_RUNS_RESPONSE}" | jq -r ".data[] | select(.git_sha == \"${BASE_GITHUB_SHA}\") | .id" | head -n1) - echo "DBT_CLOUD_CD_RUN_ID=${DBT_CLOUD_CD_RUN_ID}" >> $GITHUB_ENV - mkdir -p target-base + DBT_CLOUD_CD_RUN_ID=$(echo "${CD_RUNS_RESPONSE}" | jq -r ".data[0].id") + mkdir -p target for artifact in manifest.json catalog.json; do ARTIFACT_URL="${DBT_CLOUD_API_BASE}/runs/${DBT_CLOUD_CD_RUN_ID}/artifacts/${artifact}" - curl -sSf -H "Authorization: Bearer ${DBT_CLOUD_API_TOKEN}" "${ARTIFACT_URL}" -o "target-base/${artifact}" + curl -sSf -H "Authorization: Bearer ${DBT_CLOUD_API_TOKEN}" "${ARTIFACT_URL}" -o "target/${artifact}" done - - name: Retrieve Current artifacts (CI job) + - name: Upload to Recce Cloud + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: recce-cloud upload --type prod +``` + +### 5. Create the PR workflow (CI) + +Create `.github/workflows/recce-pr.yml` to validate PR changes. + +```yaml +name: Validate PR (dbt Cloud) + +on: + pull_request: + branches: [main] + +env: + DBT_CLOUD_API_BASE: "https://cloud.getdbt.com/api/v2/accounts/${{ secrets.DBT_CLOUD_ACCOUNT_ID }}" + DBT_CLOUD_API_TOKEN: ${{ secrets.DBT_CLOUD_API_TOKEN }} + +jobs: + validate-pr: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-python@v5 + with: + python-version: "3.10" + + - name: Install recce-cloud + run: pip install recce-cloud + + - name: Wait for dbt Cloud CI job env: DBT_CLOUD_CI_JOB_ID: ${{ secrets.DBT_CLOUD_CI_JOB_ID }} CURRENT_GITHUB_SHA: ${{ github.event.pull_request.head.sha }} @@ -154,7 +163,8 @@ jobs: } DBT_CLOUD_CI_RUN_ID=$(fetch_ci_run_id) while [ -z "$DBT_CLOUD_CI_RUN_ID" ]; do - sleep 5 + echo "Waiting for dbt Cloud CI job to start..." + sleep 10 DBT_CLOUD_CI_RUN_ID=$(fetch_ci_run_id) done echo "DBT_CLOUD_CI_RUN_ID=${DBT_CLOUD_CI_RUN_ID}" >> $GITHUB_ENV @@ -164,70 +174,44 @@ jobs: CI_RUN_SUCCESS=$(echo "${CI_RUN_RESPONSE}" | jq '.data.is_complete and .data.is_success') CI_RUN_FAILED=$(echo "${CI_RUN_RESPONSE}" | jq '.data.is_complete and (.data.is_error or .data.is_cancelled)') if $CI_RUN_SUCCESS; then - echo "CI job completed successfully." + echo "dbt Cloud CI job completed successfully." break elif $CI_RUN_FAILED; then status=$(echo ${CI_RUN_RESPONSE} | jq -r '.data.status_humanized') - echo "CI job failed or was cancelled. Status: $status" + echo "dbt Cloud CI job failed or was cancelled. Status: $status" exit 1 fi - sleep 5 + echo "Waiting for dbt Cloud CI job to complete..." + sleep 10 done + + - name: Retrieve artifacts from CI job + run: | + set -eo pipefail mkdir -p target for artifact in manifest.json catalog.json; do ARTIFACT_URL="${DBT_CLOUD_API_BASE}/runs/${DBT_CLOUD_CI_RUN_ID}/artifacts/${artifact}" curl -sSf -H "Authorization: Bearer ${DBT_CLOUD_API_TOKEN}" "${ARTIFACT_URL}" -o "target/${artifact}" done - - name: Run Recce validation - env: - SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }} - SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }} - SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }} - SNOWFLAKE_SCHEMA: "PR_${{ github.event.pull_request.number }}" - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RECCE_STATE_PASSWORD: ${{ secrets.RECCE_STATE_PASSWORD }} - run: recce run --cloud - - - name: Generate Recce summary - id: recce-summary + - name: Upload to Recce Cloud env: - SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }} - SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }} - SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }} - SNOWFLAKE_SCHEMA: "PR_${{ github.event.pull_request.number }}" GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - RECCE_STATE_PASSWORD: ${{ secrets.RECCE_STATE_PASSWORD }} - run: | - set -eo pipefail - recce summary --cloud > recce_summary.md - cat recce_summary.md >> $GITHUB_STEP_SUMMARY - - - name: Comment on pull request - uses: thollander/actions-comment-pull-request@v2 - with: - filePath: recce_summary.md - comment_tag: recce + run: recce-cloud upload ``` -### 5. Adapt for your data warehouse - -The workflow above uses Snowflake. Update the environment variables in the "Run Recce validation" and "Generate Recce summary" steps to match your warehouse configuration. - -For other warehouses, replace the Snowflake variables with your adapter's required credentials. See [Connect to Warehouse](../5-data-diffing/connect-to-warehouse.md) for adapter-specific configuration. - ## Verification After setting up: -1. **Create a test PR** with a small model change -2. **Wait for dbt Cloud CI job** to complete -3. **Check GitHub Actions** - the Recce workflow should run -4. **Review the PR comment** - Recce validation summary appears -5. **Launch Recce instance** - from Recce Cloud dashboard, open the PR session +1. **Trigger the base workflow** - Push to main or run manually to upload production baseline +2. **Create a test PR** with a small model change +3. **Wait for dbt Cloud CI job** to complete +4. **Check GitHub Actions** - the Recce PR workflow should run after dbt Cloud CI completes +5. **Open Recce Cloud** - the PR session appears with validation results !!! tip - If the workflow fails on the first run, check that your CD job has run on the base commit. The workflow looks for artifacts from a specific git SHA. + Run the base workflow first to establish your production baseline. The PR workflow compares against this baseline. ## Troubleshooting @@ -237,18 +221,15 @@ After setting up: | "CI job timeout" | The workflow waits for dbt Cloud CI to complete. Check if your CI job is stuck or taking longer than expected. | | "Artifact not found" | Verify "Generate docs on run" is enabled for both CI and CD jobs. | | "API authentication failed" | Check your `DBT_CLOUD_API_TOKEN` has correct permissions and is stored in GitHub secrets. | -| "Warehouse connection failed" | Verify warehouse credentials in GitHub secrets. Check IP whitelisting if applicable. | -| No PR comment appears | Ensure `GITHUB_TOKEN` has write permissions for pull requests. Check workflow permissions. | ### CD job timing considerations -The workflow retrieves Base artifacts from the CD job run that matches the PR's base commit SHA. If your CD job runs on a schedule (not on every merge), the base commit might not have artifacts available. +The base workflow retrieves artifacts from the latest CD job run. For accurate comparisons, ensure your dbt Cloud CD job runs on every merge to main. -**Solutions:** +If your CD job runs on a schedule: -- Configure CD to run on merge to main (recommended) -- Rebase your PR to a commit that has CD artifacts -- Modify the workflow to use the latest CD run instead of commit-matched artifacts +- The baseline may be outdated compared to the actual main branch +- Consider triggering the CD job manually before validating PRs ## Next steps From fab5b460e4bbe1f650f371ed990cc16abee278ec Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Tue, 3 Mar 2026 17:41:46 +0800 Subject: [PATCH 32/43] docs: add What the Agent Does section - Add index.md with agent overview - Add automated-validation.md explaining PR validation workflow - Add impact-analysis.md covering lineage and change classification - Update mkdocs.yml navigation Co-Authored-By: Claude Opus 4.5 --- .../automated-validation.md | 61 ++++++++++++++++ docs/4-what-the-agent-does/impact-analysis.md | 70 +++++++++++++++++++ docs/4-what-the-agent-does/index.md | 54 ++++++++++++++ mkdocs.yml | 4 ++ 4 files changed, 189 insertions(+) create mode 100644 docs/4-what-the-agent-does/automated-validation.md create mode 100644 docs/4-what-the-agent-does/impact-analysis.md create mode 100644 docs/4-what-the-agent-does/index.md diff --git a/docs/4-what-the-agent-does/automated-validation.md b/docs/4-what-the-agent-does/automated-validation.md new file mode 100644 index 0000000..a5b4290 --- /dev/null +++ b/docs/4-what-the-agent-does/automated-validation.md @@ -0,0 +1,61 @@ +--- +title: Automated Validation +--- + +# Automated Validation + +Manual data validation slows down every pull request. Developers must remember which checks to run, execute them correctly, and communicate results to reviewers. The Recce Agent automates this process, running the right validation checks based on what changed in your PR. + +## How It Works + +When a PR is opened or updated, the Recce Agent analyzes your changes and determines what needs validation. + +### 1. PR Triggers the Agent + +Your CI/CD pipeline runs `recce-cloud upload` when dbt metadata is updated. This triggers the agent to analyze the changes. + +### 2. Agent Analyzes Changes + +The agent reads dbt artifacts from both your base branch and PR branch. It identifies: + +- Which models were modified +- What schema changes occurred +- Which downstream models are affected + +### 3. Agent Runs Validation + +Based on the analysis, the agent executes appropriate validation checks against your warehouse: + +- **Schema diff** - Detects added, removed, or modified columns +- **Row count diff** - Compares record counts between branches +- **Profile diff** - Analyzes statistical changes in column values +- **Breaking change analysis** - Identifies changes that affect downstream models + +### 4. Agent Posts Summary + +The agent generates a data review summary and posts it directly to your PR. Reviewers see: + +- What changed and why it matters +- Validation results with pass/fail status +- Recommended actions for review + +## When to Use + +- **Every PR that modifies dbt models** - The agent runs automatically for all data changes +- **Large-scale refactoring** - When many models change, automated validation catches issues you might miss +- **Critical path changes** - When modifying models that power dashboards or reports +- **Continuous integration** - As part of your CI pipeline to validate every change + +## Triggering Validation + +You can trigger the data review summary in three ways: + +1. **Automatic trigger** - Runs when `recce-cloud upload` executes in CI +2. **Manual trigger from UI** - Click the Data Review button in a PR/MR session +3. **GitHub comment** - Comment `/recce` on your GitHub PR to generate a new summary + +## Related + +- [Impact Analysis](impact-analysis.md) - How the agent analyzes change scope +- [PR/MR Data Review Summary](../7-cicd/pr-mr-summary.md) - Understanding the summary output +- [Setup CI](../7-cicd/setup-ci.md) - Configure automated validation diff --git a/docs/4-what-the-agent-does/impact-analysis.md b/docs/4-what-the-agent-does/impact-analysis.md new file mode 100644 index 0000000..b523bf6 --- /dev/null +++ b/docs/4-what-the-agent-does/impact-analysis.md @@ -0,0 +1,70 @@ +--- +title: Impact Analysis +--- + +# Impact Analysis + +A single column change can break dashboards, reports, and downstream models you never intended to affect. Impact analysis maps the full scope of your changes before they reach production, helping you understand exactly what will be affected. + +## How It Works + +The Recce Agent analyzes your changes at multiple levels to determine their true impact. + +### Lineage Analysis + +The agent traces dependencies through your dbt project to identify all models affected by your changes. It builds a graph of: + +- **Direct dependencies** - Models that reference your modified model +- **Transitive dependencies** - Models further downstream in the lineage +- **Column-level dependencies** - Specific columns that reference modified columns + +### Schema Comparison + +The agent compares schemas between your base and PR branches to detect: + +- Added columns +- Removed columns +- Renamed columns +- Data type changes + +### Change Classification + +The agent categorizes each change based on its downstream impact: + +| Type | Description | Example | +|------|-------------|---------| +| **Breaking** | Affects all downstream models | Adding a filter condition, changing GROUP BY | +| **Partial breaking** | Affects only models that reference specific modified columns | Removing or renaming a column | +| **Non-breaking** | Does not affect downstream models | Adding a new column, formatting changes | + +### Downstream Effects + +For each modified model, the agent identifies: + +- Which downstream models are affected +- Which specific columns in those models are impacted +- Whether the impact is direct or indirect + +## When to Use + +- **Before merging any PR** - Understand the full scope of your changes +- **During development** - Validate that changes are isolated to intended models +- **Code review** - Help reviewers understand what will be affected +- **Breaking change assessment** - Determine if coordination with downstream consumers is needed + +## Example: Column Change Impact + +When you modify a column like `stg_orders.status`: + +1. The agent identifies that `orders` model selects this column directly (partial impact) +2. The agent detects that `customers` model uses `status` in a WHERE clause (full impact) +3. The agent traces that `customer_segments` depends on `customers` (indirect impact) + +This lets you know that your seemingly simple column change affects models you may not have considered. + +## Related + +- [Impact Radius](../4-downstream-impacts/impact-radius.md) - Visualize affected models +- [Breaking Change Analysis](../4-downstream-impacts/breaking-change-analysis.md) - Understand change types +- [Lineage Diff](../3-visualized-change/lineage.md) - See lineage changes +- [Column-Level Lineage](../3-visualized-change/column-level-lineage.md) - Trace column dependencies diff --git a/docs/4-what-the-agent-does/index.md b/docs/4-what-the-agent-does/index.md new file mode 100644 index 0000000..40625b3 --- /dev/null +++ b/docs/4-what-the-agent-does/index.md @@ -0,0 +1,54 @@ +--- +title: What the Agent Does +--- + +# What the Recce Agent Does + +Data validation for pull requests is time-consuming. You need to understand what changed, identify downstream impacts, run the right checks, and communicate findings to reviewers. The Recce Agent automates this entire workflow. + +## How It Works + +The Recce Agent monitors your pull requests and acts as an automated data reviewer. When you open or update a PR that modifies dbt models, the agent: + +1. **Analyzes your changes** - Reads dbt artifacts and compares your branch against the base branch +2. **Identifies impact** - Traces lineage to find all affected models and columns +3. **Runs validation checks** - Executes schema diffs, row count comparisons, and other relevant checks +4. **Generates insights** - Produces a data review summary with actionable findings +5. **Posts results** - Adds the summary directly to your PR for reviewers to see + +This happens automatically in your CI/CD pipeline. No manual intervention required. + +## When to Use + +- **Every PR with data changes** - The agent runs automatically when dbt models are modified +- **Complex refactoring** - When changes affect many models, the agent maps the full impact radius +- **Critical model updates** - When validating changes to models that power dashboards or reports +- **Team collaboration** - When reviewers need context about data changes without running Recce locally + +## Agent Capabilities + +The Recce Agent provides three core capabilities: + +### Automated Validation + +The agent determines what needs validation based on your changes and runs appropriate checks automatically. It executes schema comparisons, row count diffs, and other validation queries against your warehouse. + +[Learn more about Automated Validation](automated-validation.md) + +### Impact Analysis + +Before running checks, the agent analyzes your model changes to understand the scope of impact. It traces column-level lineage and categorizes changes as breaking, partial breaking, or non-breaking. + +[Learn more about Impact Analysis](impact-analysis.md) + +### Data Review Summary + +After validation completes, the agent generates a comprehensive summary that explains what changed, what was validated, and whether the changes are safe to merge. + +[Learn more about the Data Review Summary](../7-cicd/pr-mr-summary.md) + +## Related + +- [Data Developer Workflow](../3-using-recce/data-developer.md) - How developers validate changes +- [Data Reviewer Workflow](../3-using-recce/data-reviewer.md) - How reviewers approve PRs +- [CI/CD Getting Started](../7-cicd/ci-cd-getting-started.md) - Set up automated validation diff --git a/mkdocs.yml b/mkdocs.yml index bee8774..0d655dc 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -63,6 +63,10 @@ nav: - 3-visualized-change/code-change.md - 3-visualized-change/column-level-lineage.md - 3-visualized-change/multi-models.md + - What the Agent Does: + - 4-what-the-agent-does/index.md + - 4-what-the-agent-does/automated-validation.md + - 4-what-the-agent-does/impact-analysis.md - Downstream Impacts: #- 4-downstream-impacts/metadata-first.md - 4-downstream-impacts/impact-radius.md From 844f8b95d25b24601171d58ace6cc9f9ef8d3985 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Tue, 3 Mar 2026 17:44:30 +0800 Subject: [PATCH 33/43] Add data developer and reviewer workflow guides - data-developer.md: Dev sessions (before PR) and CI/CD validation (after PR) - data-reviewer.md: Review data changes using Recce summaries Part of docs v3 migration - PR4 Co-Authored-By: Claude Opus 4.5 --- docs/3-using-recce/data-developer.md | 162 +++++++++++++++++++++++++++ docs/3-using-recce/data-reviewer.md | 125 +++++++++++++++++++++ mkdocs.yml | 3 + 3 files changed, 290 insertions(+) create mode 100644 docs/3-using-recce/data-developer.md create mode 100644 docs/3-using-recce/data-reviewer.md diff --git a/docs/3-using-recce/data-developer.md b/docs/3-using-recce/data-developer.md new file mode 100644 index 0000000..c0e13db --- /dev/null +++ b/docs/3-using-recce/data-developer.md @@ -0,0 +1,162 @@ +--- +title: Data Developer Workflow +--- + +# Data Developer Workflow + +Validate data changes throughout your development lifecycle. This guide covers validating changes before creating a PR (dev sessions) and iterating on feedback after your PR is open. + +**Goal:** Validate data changes at every stage of development, from local work through PR merge. + +## Prerequisites + +- [x] Recce Cloud account +- [x] dbt project with CI/CD configured for Recce +- [x] Access to your data warehouse + +## Development Stages + +### Before PR: Dev Sessions + +Validate changes locally before pushing to remote. Dev sessions let you run Recce validation without creating a PR. + +#### Upload via Web UI + +1. Go to [Recce Cloud](https://cloud.reccehq.com) +2. Navigate to your project +3. Click **New Dev Session** +4. Upload your dbt artifacts: + - `target/manifest.json` + - `target/catalog.json` +5. Select your base environment for comparison + +**Expected result:** Dev session opens with lineage diff showing your changes. + +#### Upload via CLI + +Run from your dbt project directory: + +```bash +recce-cloud upload --type dev +``` + +This uploads your current `target/` artifacts and creates a dev session. + +**Required files:** + +| File | Location | Generated by | +|------|----------|--------------| +| `manifest.json` | `target/` | `dbt run`, `dbt build`, or `dbt compile` | +| `catalog.json` | `target/` | `dbt docs generate` | + +#### When to Use Dev Sessions + +- Testing changes before committing +- Validating complex refactoring locally +- Exploring impact without creating a PR +- Sharing work-in-progress with teammates + +### After PR: CI/CD Validation + +Once you push changes and open a PR, the Recce Agent validates automatically. + +#### What Happens + +1. Your CI pipeline runs `recce-cloud upload` +2. The agent compares your PR branch against the base branch +3. The agent runs validation checks based on detected changes +4. A data review summary posts to your PR + +#### Understanding the Agent Summary + +The summary includes: + +- **Change overview** - Which models changed and how +- **Impact analysis** - Downstream models affected +- **Validation results** - Schema diffs, row counts, and other checks +- **Recommendations** - Suggested actions for review + +#### Fixing Issues + +When the agent identifies issues: + +1. Review the validation results in the PR comment +2. Click **Launch Recce** to explore details in the web UI +3. Identify the root cause using lineage and data diffs +4. Make fixes in your branch +5. Push changes - the agent re-validates automatically + +#### Iterating Until Checks Pass + +Each push triggers a new validation cycle: + +1. Agent re-analyzes your changes +2. New validation results post to the PR +3. Previous results are updated (not duplicated) +4. Continue until all checks pass + +## Validation Techniques + +### Check Lineage First + +Start with lineage diff to understand your change scope: + +- Modified models highlighted in the DAG +- Downstream impact visible at a glance +- Schema changes shown per model + +### Validate Metadata + +Low-cost checks using model metadata: + +- **Schema diff** - Column additions, removals, type changes +- **Row count diff** - Record count comparison (uses warehouse metadata) + +### Validate Data + +Higher-cost checks that query your warehouse: + +- **Value diff** - Column-level match percentage +- **Profile diff** - Statistical comparison (count, distinct, min, max, avg) +- **Histogram diff** - Distribution changes for numeric columns +- **Top-K diff** - Distribution changes for categorical columns + +### Custom Queries + +For flexible validation, use query diff: + +```sql +SELECT + date_trunc('month', order_date) AS month, + SUM(amount) AS revenue +FROM {{ ref('orders') }} +GROUP BY month +ORDER BY month DESC +``` + +Add queries to your checklist for repeated use. + +## Verification + +Confirm your workflow works: + +1. Make a small model change locally +2. Generate artifacts: `dbt build && dbt docs generate` +3. Upload dev session: `recce-cloud upload --type dev` +4. Verify session appears in Recce Cloud +5. Create PR and confirm agent posts summary + +## Troubleshooting + +| Issue | Solution | +|-------|----------| +| Dev session upload fails | Check artifacts exist in `target/`; run `dbt docs generate` | +| Agent doesn't run on PR | Verify CI workflow includes `recce-cloud upload` | +| Validation results missing | Check warehouse credentials in CI secrets | +| Summary not appearing | Confirm `GITHUB_TOKEN` has PR write permissions | + +## Related + +- [Data Reviewer Workflow](data-reviewer.md) - How reviewers use Recce +- [Admin Setup](admin-setup.md) - Set up your organization +- [PR/MR Data Review](../7-cicd/pr-mr-summary.md) - Understanding agent summaries diff --git a/docs/3-using-recce/data-reviewer.md b/docs/3-using-recce/data-reviewer.md new file mode 100644 index 0000000..3aceef0 --- /dev/null +++ b/docs/3-using-recce/data-reviewer.md @@ -0,0 +1,125 @@ +--- +title: Data Reviewer Workflow +--- + +# Data Reviewer Workflow + +Review data changes in pull requests using Recce. Your admin set up Recce for your team - here's how to use it as a reviewer. + +**Goal:** Review and approve data changes in PRs with confidence. + +## Prerequisites + +- [x] Recce Cloud account (via team invitation) +- [x] Access to the project in Recce Cloud +- [x] PR with Recce validation results + +## Reviewing a PR + +### 1. Find the Data Review Summary + +When a PR modifies dbt models, the Recce Agent posts a summary comment: + +1. Open the PR in GitHub/GitLab +2. Scroll to the Recce bot comment +3. Review the summary sections + +**Expected result:** Summary shows change overview, impact analysis, and validation results. + +### 2. Understand the Summary + +The summary includes: + +| Section | What It Shows | +|---------|---------------| +| **Change Overview** | Which models changed and the type of change | +| **Impact Analysis** | Downstream models affected by the changes | +| **Validation Results** | Schema diffs, row counts, and check outcomes | +| **Recommendations** | Suggested actions based on findings | + +### 3. Explore in Recce Cloud + +For deeper investigation: + +1. Click **Launch Recce** in the PR comment (or go to Recce Cloud) +2. Select the PR session from the list +3. Explore the changes interactively + +**What you can do:** + +- View lineage diff to see affected models +- Drill into schema changes per model +- Run additional data diffs (row count, profile, value) +- Execute custom queries to investigate specific concerns + +### 4. Review Validation Results + +Check each validation result: + +- **Pass** - Change validated successfully +- **Warning** - Review recommended but not blocking +- **Fail** - Issue detected that needs attention + +For failures, click through to see: +- What was compared +- Expected vs actual results +- Specific differences found + +### 5. Approve or Request Changes + +Based on your review: + +**Approve the PR:** + +- Validation results meet expectations +- Impact scope is understood and acceptable +- No unexpected data changes + +**Request changes:** + +- Validation failures need investigation +- Impact scope is broader than expected +- Questions about specific changes + +Leave comments referencing specific validation results to help the developer address issues. + +## Common Review Scenarios + +### Schema Changes + +When columns are added, removed, or modified: + +1. Check if downstream models are affected +2. Verify the change is intentional +3. Confirm breaking changes are coordinated + +### Row Count Differences + +When record counts change: + +1. Determine if the change is expected +2. Check if filters or joins were modified +3. Verify the magnitude is reasonable + +### Performance Impact + +When models are refactored: + +1. Compare query complexity +2. Check for unintended full table scans +3. Review impact on downstream refresh times + +## Verification + +Confirm you can review PRs: + +1. Open a PR with Recce validation results +2. Find the Recce bot comment +3. Click Launch Recce to open the session +4. Navigate the lineage and view a diff result + +## Related + +- [Data Developer Workflow](data-developer.md) - How developers validate changes +- [Admin Setup](admin-setup.md) - Organization and team setup +- [Checklist](../6-collaboration/checklist.md) - Adding checks to track diff --git a/mkdocs.yml b/mkdocs.yml index bee8774..71a2e41 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -58,6 +58,9 @@ nav: - 2-getting-started/installation.md - Claude Plugin: 2-getting-started/claude-plugin.md - 2-getting-started/get-started-jaffle-shop.md + - Using Recce: + - 3-using-recce/data-developer.md + - 3-using-recce/data-reviewer.md - Visualized Change: - 3-visualized-change/lineage.md - 3-visualized-change/code-change.md From e548ae44ff58c584008af0a56edc0d81348643a0 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Tue, 3 Mar 2026 20:15:30 +0800 Subject: [PATCH 34/43] Add redirects plugin and document cleanup tasks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add mkdocs-redirects plugin with redirect mappings for all restructured pages - PR 6: Lineage and data diffing → 5-what-you-can-explore/ - PR 7: preset-checks.md → 6-collaboration/ - PR 8: Technical concepts → 7-reference/ - PR 9: Community support → 8-community/ - Create CLEANUP-TODO.md documenting files to delete after all PRs merge Co-Authored-By: Claude Opus 4.5 --- docs/CLEANUP-TODO.md | 85 ++++++++++++++++++++++++++++++++++++++++++++ mkdocs.yml | 19 ++++++++++ 2 files changed, 104 insertions(+) create mode 100644 docs/CLEANUP-TODO.md diff --git a/docs/CLEANUP-TODO.md b/docs/CLEANUP-TODO.md new file mode 100644 index 0000000..65f5909 --- /dev/null +++ b/docs/CLEANUP-TODO.md @@ -0,0 +1,85 @@ +# Post-Merge Cleanup Tasks + +This document tracks files to delete after all documentation restructuring PRs (6-9) are merged into `docs-v3`. + +## Important + +- Do NOT delete these files until all PRs are merged +- The redirects plugin in `mkdocs.yml` handles URL redirections automatically +- Delete this file after cleanup is complete + +## Files to Delete + +### From PR 6 - Lineage and Data Diffing Consolidation + +**Old Visualized Change section:** + +- `docs/3-visualized-change/lineage.md` → Moved to `5-what-you-can-explore/lineage-diff.md` + +**Old Downstream Impacts section:** + +- `docs/4-downstream-impacts/impact-radius.md` → Moved to `5-what-you-can-explore/impact-radius.md` +- `docs/4-downstream-impacts/breaking-change-analysis.md` → Moved to `5-what-you-can-explore/breaking-change-analysis.md` +- `docs/4-downstream-impacts/metadata-first.md` → Deprecated (if exists) +- `docs/4-downstream-impacts/transformation-types.md` → Deprecated (if exists) + +**Old Data Diffing section:** + +- `docs/5-data-diffing/row-count-diff.md` → Consolidated into `5-what-you-can-explore/data-diffing.md` +- `docs/5-data-diffing/profile-diff.md` → Consolidated into `5-what-you-can-explore/data-diffing.md` +- `docs/5-data-diffing/value-diff.md` → Consolidated into `5-what-you-can-explore/data-diffing.md` +- `docs/5-data-diffing/topK-diff.md` → Consolidated into `5-what-you-can-explore/data-diffing.md` +- `docs/5-data-diffing/histogram-diff.md` → Consolidated into `5-what-you-can-explore/data-diffing.md` +- `docs/5-data-diffing/query.md` → Consolidated into `5-what-you-can-explore/data-diffing.md` + +### From PR 7 - CI/CD Reorganization + +- `docs/7-cicd/preset-checks.md` → Moved to `6-collaboration/preset-checks.md` + +### From PR 8 - Reference Section + +- `docs/8-technical-concepts/configuration.md` → Moved to `7-reference/configuration.md` +- `docs/8-technical-concepts/state-file.md` → Moved to `7-reference/state-file.md` + +### From PR 9 - Community Section + +- `docs/1-whats-recce/community-support.md` → Moved to `8-community/support.md` + +## Empty Directories to Remove + +After deleting files, remove these directories if empty: + +- `docs/4-downstream-impacts/` +- `docs/5-data-diffing/` +- `docs/8-technical-concepts/` + +## Cleanup Command + +After all PRs are merged, run this to delete old files: + +```bash +# Delete old files (run from repository root) +rm -f docs/3-visualized-change/lineage.md +rm -f docs/4-downstream-impacts/impact-radius.md +rm -f docs/4-downstream-impacts/breaking-change-analysis.md +rm -f docs/4-downstream-impacts/metadata-first.md +rm -f docs/4-downstream-impacts/transformation-types.md +rm -f docs/5-data-diffing/row-count-diff.md +rm -f docs/5-data-diffing/profile-diff.md +rm -f docs/5-data-diffing/value-diff.md +rm -f docs/5-data-diffing/topK-diff.md +rm -f docs/5-data-diffing/histogram-diff.md +rm -f docs/5-data-diffing/query.md +rm -f docs/7-cicd/preset-checks.md +rm -f docs/8-technical-concepts/configuration.md +rm -f docs/8-technical-concepts/state-file.md +rm -f docs/1-whats-recce/community-support.md + +# Remove empty directories +rmdir docs/4-downstream-impacts/ 2>/dev/null || true +rmdir docs/5-data-diffing/ 2>/dev/null || true +rmdir docs/8-technical-concepts/ 2>/dev/null || true + +# Remove this file +rm -f docs/CLEANUP-TODO.md +``` diff --git a/mkdocs.yml b/mkdocs.yml index bee8774..e238392 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -155,6 +155,25 @@ theme: plugins: - search + - redirects: + redirect_maps: + # PR 6 redirects - Lineage and Data Diffing consolidation + '3-visualized-change/lineage.md': '5-what-you-can-explore/lineage-diff.md' + '4-downstream-impacts/impact-radius.md': '5-what-you-can-explore/impact-radius.md' + '4-downstream-impacts/breaking-change-analysis.md': '5-what-you-can-explore/breaking-change-analysis.md' + '5-data-diffing/row-count-diff.md': '5-what-you-can-explore/data-diffing.md' + '5-data-diffing/profile-diff.md': '5-what-you-can-explore/data-diffing.md' + '5-data-diffing/value-diff.md': '5-what-you-can-explore/data-diffing.md' + '5-data-diffing/topK-diff.md': '5-what-you-can-explore/data-diffing.md' + '5-data-diffing/histogram-diff.md': '5-what-you-can-explore/data-diffing.md' + '5-data-diffing/query.md': '5-what-you-can-explore/data-diffing.md' + # PR 7 redirects - CI/CD reorganization + '7-cicd/preset-checks.md': '6-collaboration/preset-checks.md' + # PR 8 redirects - Reference section + '8-technical-concepts/configuration.md': '7-reference/configuration.md' + '8-technical-concepts/state-file.md': '7-reference/state-file.md' + # PR 9 redirects - Community section + '1-whats-recce/community-support.md': '8-community/support.md' - glightbox: skip_classes: - skip-glightbox From 7cbadaf00d2fd56d14401a9719af837195d4ccea Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Tue, 3 Mar 2026 20:15:37 +0800 Subject: [PATCH 35/43] Add Community section with support and changelog pages Create new 8-community section containing: - support.md: Consolidated community resources (Discord, Slack, GitHub, social media) - changelog.md: Links to release notes and detailed blog posts Co-Authored-By: Claude Opus 4.5 --- docs/8-community/changelog.md | 15 +++++++++++++++ docs/8-community/support.md | 36 +++++++++++++++++++++++++++++++++++ mkdocs.yml | 4 ++++ 3 files changed, 55 insertions(+) create mode 100644 docs/8-community/changelog.md create mode 100644 docs/8-community/support.md diff --git a/docs/8-community/changelog.md b/docs/8-community/changelog.md new file mode 100644 index 0000000..c03d008 --- /dev/null +++ b/docs/8-community/changelog.md @@ -0,0 +1,15 @@ +--- +title: Changelog +--- + +# Changelog + +Stay informed about Recce updates, new features, and improvements. + +## Release notes + +For a quick overview of recent releases, visit the [Recce Changelog](https://reccehq.com/changelog/). + +## Detailed release posts + +For in-depth coverage of new features and how to use them, see our [release blog posts](https://blog.reccehq.com/tag/release). diff --git a/docs/8-community/support.md b/docs/8-community/support.md new file mode 100644 index 0000000..48eb23d --- /dev/null +++ b/docs/8-community/support.md @@ -0,0 +1,36 @@ +--- +title: Community & Support +--- + +# Community & Support + +Connect with the Recce team and community for help and updates. + +## Get help + +- [Discord](https://discord.com/invite/VpwXRC34jz) - Join our community for discussions and quick support +- [dbt Slack](https://www.getdbt.com/community/join-the-community) - Find us in the [#tools-recce](https://getdbt.slack.com/archives/C05C28V7CPP) channel +- [Email](mailto:help@reccehq.com) - Reach us at help@reccehq.com + +## Report issues + +Found a bug or have a feature request? Open a [GitHub Issue](https://github.com/DataRecce/recce/issues) on our repository. + +## Follow Recce + +Stay updated with news and insights from the team: + +- [LinkedIn](https://www.linkedin.com/company/datarecce) +- [Recce Blog](https://blog.reccehq.com/) +- [X (Twitter)](https://x.com/DataRecce) +- [Mastodon](https://mastodon.social/@DataRecce) +- [Bluesky](https://bsky.app/profile/datarecce.bsky.social) + +## Subscribe to our newsletter + +Stay updated with Recce news, data engineering insights, and product updates. + +
+

Sign up for Recce Updates

+
+
diff --git a/mkdocs.yml b/mkdocs.yml index bee8774..5112639 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -96,6 +96,10 @@ nav: - 8-technical-concepts/state-file.md - 8-technical-concepts/configuration.md + - Community: + - 8-community/support.md + - 8-community/changelog.md + - Blog: "https://blog.reccehq.com" - Changelog: "https://reccehq.com/changelog/" - Start Free: "https://cloud.reccehq.com/" From 5fef6c45abe06ed706627036c078f7fced32059b Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Tue, 3 Mar 2026 20:17:07 +0800 Subject: [PATCH 36/43] Add Reference section with configuration, state file, and CLI documentation - Create 7-reference/configuration.md: Document recce.yml preset check configuration with overview, parameters, and examples for all check types - Create 7-reference/state-file.md: Document state file format, saving methods, and usage patterns for development and PR review workflows - Create 7-reference/cli-reference.md: Document recce and recce-cloud CLI commands including server, run, summary, debug, and upload - Update mkdocs.yml: Rename "Technical Concepts" to "Reference" and update navigation paths to new 7-reference section Co-Authored-By: Claude Opus 4.5 --- docs/7-reference/cli-reference.md | 296 +++++++++++++++++++++++++ docs/7-reference/configuration.md | 353 ++++++++++++++++++++++++++++++ docs/7-reference/state-file.md | 145 ++++++++++++ mkdocs.yml | 7 +- 4 files changed, 798 insertions(+), 3 deletions(-) create mode 100644 docs/7-reference/cli-reference.md create mode 100644 docs/7-reference/configuration.md create mode 100644 docs/7-reference/state-file.md diff --git a/docs/7-reference/cli-reference.md b/docs/7-reference/cli-reference.md new file mode 100644 index 0000000..8a22fda --- /dev/null +++ b/docs/7-reference/cli-reference.md @@ -0,0 +1,296 @@ +--- +title: CLI Reference +--- + +# CLI Reference + +This reference documents the command-line interfaces for Recce OSS (`recce`) and Recce Cloud (`recce-cloud`). + +## Overview + +Recce provides two CLI tools: + +- **`recce`** - The open source CLI for local data validation and diffing +- **`recce-cloud`** - The cloud CLI for uploading artifacts to Recce Cloud in CI/CD workflows + +## recce Commands + +### recce server + +Starts the Recce web server for interactive data validation. + +**Syntax:** + +```bash +recce server [OPTIONS] [STATE_FILE] +``` + +**Arguments:** + +| Argument | Description | +|----------|-------------| +| `STATE_FILE` | Optional path to a state file. If specified and exists, loads the state. If specified and does not exist, creates a new state file at that path. | + +**Options:** + +| Option | Description | +|--------|-------------| +| `--review` | Enable review mode. Uses dbt artifacts from the state file instead of `target/` and `target-base/` directories. | +| `--api-token ` | API token for Recce Cloud connection. | + +**Examples:** + +Start server with default settings: + +```bash +recce server +``` + +Start server with a state file: + +```bash +recce server my_recce_state.json +``` + +Start server in review mode (uses artifacts from state file): + +```bash +recce server --review my_recce_state.json +``` + +Start server with Recce Cloud connection: + +```bash +recce server --api-token +``` + +**Notes:** + +- The server runs on `http://localhost:8000` by default +- Requires dbt artifacts in `target/` (current) and `target-base/` (base) directories unless using `--review` mode +- State is auto-saved when the Save button is clicked in the UI + +### recce run + +Executes preset checks and saves results to a state file. + +**Syntax:** + +```bash +recce run [OPTIONS] +``` + +**Options:** + +| Option | Description | +|--------|-------------| +| `--state-file ` | Path to state file. Default: `recce_state.json` | +| `--github-pull-request-url ` | GitHub PR URL for CI context | + +**Examples:** + +Run all preset checks: + +```bash +recce run +``` + +Run checks and save to specific state file: + +```bash +recce run --state-file my_state.json +``` + +Run checks with GitHub PR context: + +```bash +recce run --github-pull-request-url ${{ github.event.pull_request.html_url }} +``` + +**Notes:** + +- Executes all checks defined in `recce.yml` +- Outputs results to the state file (default: `recce_state.json`) +- Used primarily in CI/CD pipelines for automated validation + +### recce summary + +Generates a summary report from a state file. + +**Syntax:** + +```bash +recce summary +``` + +**Arguments:** + +| Argument | Description | +|----------|-------------| +| `STATE_FILE` | Path to the state file to summarize | + +**Examples:** + +Generate summary from state file: + +```bash +recce summary recce_state.json +``` + +Generate summary and save to file: + +```bash +recce summary recce_state.json > recce_summary.md +``` + +**Notes:** + +- Outputs summary in Markdown format +- Useful for generating PR comments in CI/CD workflows + +### recce debug + +Verifies Recce configuration and environment setup. + +**Syntax:** + +```bash +recce debug +``` + +**Examples:** + +```bash +recce debug +``` + +**Notes:** + +- Checks for required artifacts in `target/` and `target-base/` directories +- Verifies warehouse connection +- Useful for troubleshooting setup issues before launching the server + +## recce-cloud Commands + +The `recce-cloud` CLI is a lightweight tool for uploading dbt artifacts to Recce Cloud in CI/CD pipelines. + +### Installation + +```bash +pip install recce-cloud +``` + +### recce-cloud upload + +Uploads dbt artifacts to Recce Cloud. + +**Syntax:** + +```bash +recce-cloud upload [OPTIONS] +``` + +**Options:** + +| Option | Description | +|--------|-------------| +| `--type ` | Session type: `prod` for baseline, omit for PR/MR auto-detection | +| `--target-path ` | Path to dbt artifacts directory. Default: `target/` | +| `--dry-run` | Test configuration without uploading | + +**Examples:** + +Upload baseline artifacts (for CD workflow): + +```bash +recce-cloud upload --type prod +``` + +Upload PR/MR artifacts (auto-detected): + +```bash +recce-cloud upload +``` + +Upload from custom artifact path: + +```bash +recce-cloud upload --target-path custom-target +``` + +Test configuration without uploading: + +```bash +recce-cloud upload --dry-run +``` + +**Notes:** + +- Automatically detects CI platform (GitHub Actions, GitLab CI) +- Uses `GITHUB_TOKEN` for GitHub authentication +- Uses `CI_JOB_TOKEN` for GitLab authentication +- Session type is auto-detected from PR/MR context when `--type` is omitted + +**Environment Variables:** + +| Platform | Variable | Description | +|----------|----------|-------------| +| GitHub | `GITHUB_TOKEN` | Authentication token (automatically available in Actions) | +| GitLab | `CI_JOB_TOKEN` | Authentication token (automatically available in CI/CD) | + +### Expected Output + +Successful upload displays: + +``` +─────────────────────────── CI Environment Detection ─────────────────────────── +Platform: github-actions +Session Type: prod +Commit SHA: abc123de... +Source Branch: main +Repository: your-org/your-repo +Info: Using GITHUB_TOKEN for platform-specific authentication +────────────────────────── Creating/touching session ─────────────────────────── +Session ID: f8b0f7ca-ea59-411d-abd8-88b80b9f87ad +Uploading manifest from path "target/manifest.json" +Uploading catalog from path "target/catalog.json" +Notifying upload completion... +──────────────────────────── Uploaded Successfully ───────────────────────────── +Uploaded dbt artifacts to Recce Cloud for session ID "f8b0f7ca-ea59-411d-abd8-88b80b9f87ad" +``` + +## Common Workflows + +### Local Development + +```bash +# Start interactive session +recce server + +# Or continue from saved state +recce server my_state.json +``` + +### CI/CD Pipeline + +```bash +# CD: Update baseline after merge to main +recce-cloud upload --type prod + +# CI: Upload PR artifacts for validation +recce-cloud upload +``` + +### Review Workflow + +```bash +# Reviewer loads state file in review mode +recce server --review recce_state.json +``` + +## Related + +- [Configuration](./configuration.md) - Preset check configuration in `recce.yml` +- [State File](./state-file.md) - State file format and usage +- [Setup CI](../7-cicd/setup-ci.md) - CI/CD integration guide +- [Setup CD](../7-cicd/setup-cd.md) - CD workflow setup diff --git a/docs/7-reference/configuration.md b/docs/7-reference/configuration.md new file mode 100644 index 0000000..157453b --- /dev/null +++ b/docs/7-reference/configuration.md @@ -0,0 +1,353 @@ +--- +title: Configuration +--- + +# Configuration + +This reference documents the `recce.yml` configuration file, which defines preset checks and their parameters for automated data validation. + +## Overview + +The config file for Recce is located in `recce.yml` in your dbt project root. Use this file to define preset checks that run automatically with `recce server` or `recce run`. + +## File Location + +| Path | Description | +|------|-------------| +| `recce.yml` | Main configuration file in dbt project root | + +## Preset Checks + +Preset checks define automated validations that execute when you run `recce server` or `recce run`. Each check specifies a type of comparison and its parameters. + +### Check Structure + +```yaml +# recce.yml +checks: + - name: Query diff of customers + description: | + This is the demo preset check. + + Please run the query and paste the screenshot to the PR comment. + type: query_diff + params: + sql_template: select * from {{ ref("customers") }} + view_options: + primary_keys: + - customer_id +``` + +### Check Fields + +| Field | Description | Type | Required | +|-------|-------------|------|----------| +| `name` | The title of the check | string | Yes | +| `description` | The description of the check | string | | +| `type` | The type of the check (see types below) | string | Yes | +| `params` | The parameters for running the check | object | Yes | +| `view_options` | The options for presenting the run result | object | | + +## Check Types + +### Row Count Diff + +Compares row counts between base and current environments. + +**Type:** `row_count_diff` + +**Parameters:** + +| Field | Description | Type | Required | +|-------|-------------|------|----------| +| `node_names` | List of node names | `string[]` | *1 | +| `node_ids` | List of node IDs | `string[]` | *1 | +| `select` | Node selection syntax. See [dbt docs](https://docs.getdbt.com/reference/node-selection/syntax) | `string` | | +| `exclude` | Node exclusion syntax. See [dbt docs](https://docs.getdbt.com/reference/node-selection/syntax) | `string` | | +| `packages` | Package filter | `string[]` | | +| `view_mode` | Quick filter for changed models | `all`, `changed_models` | | + +**Notes:** + +*1: If `node_ids` or `node_names` is specified, it will be used; otherwise, nodes will be selected using the criteria defined by `select`, `exclude`, `packages`, and `view_mode`. + +**Examples:** + +Using node selector: + +```yaml +checks: + - name: Row count for modified tables + description: Check row counts for all modified table models + type: row_count_diff + params: + select: state:modified,config.materialized:table + exclude: tag:dev +``` + +Using node names: + +```yaml +checks: + - name: Row count for key models + description: Check row counts for customers and orders + type: row_count_diff + params: + node_names: ['customers', 'orders'] +``` + +### Schema Diff + +Compares schema structure between base and current environments. + +**Type:** `schema_diff` + +**Parameters:** + +| Field | Description | Type | Required | +|-------|-------------|------|----------| +| `node_id` | The node ID or list of node IDs to check | `string[]` | *1 | +| `select` | Node selection syntax. See [dbt docs](https://docs.getdbt.com/reference/node-selection/syntax) | `string` | | +| `exclude` | Node exclusion syntax. See [dbt docs](https://docs.getdbt.com/reference/node-selection/syntax) | `string` | | +| `packages` | Package filter | `string[]` | | +| `view_mode` | Quick filter for changed models | `all`, `changed_models` | | + +**Notes:** + +*1: If `node_id` is specified, it will be used; otherwise, nodes will be selected using the criteria defined by `select`, `exclude`, `packages`, and `view_mode`. + +**Examples:** + +Using node selector: + +```yaml +checks: + - name: Schema diff for modified models + description: Check schema changes for modified models and downstream + type: schema_diff + params: + select: state:modified+ + exclude: tag:dev +``` + +Using node ID: + +```yaml +checks: + - name: Schema diff for customers + description: Check schema for customers model + type: schema_diff + params: + node_id: model.jaffle_shop.customers +``` + +### Lineage Diff + +Compares lineage structure between base and current environments. + +**Type:** `lineage_diff` + +**Parameters:** + +| Field | Description | Type | Required | +|-------|-------------|------|----------| +| `select` | Node selection syntax. See [dbt docs](https://docs.getdbt.com/reference/node-selection/syntax) | `string` | | +| `exclude` | Node exclusion syntax. See [dbt docs](https://docs.getdbt.com/reference/node-selection/syntax) | `string` | | +| `packages` | Package filter | `string[]` | | +| `view_mode` | Quick filter for changed models | `all`, `changed_models` | | + +**Examples:** + +```yaml +checks: + - name: Lineage diff for modified models + description: Check lineage changes for modified models and downstream + type: lineage_diff + params: + select: state:modified+ + exclude: tag:dev +``` + +### Query + +Executes a custom SQL query in the current environment. + +**Type:** `query` + +**Parameters:** + +| Field | Description | Type | Required | +|-------|-------------|------|----------| +| `sql_template` | SQL statement using Jinja templating | `string` | Yes | + +**Examples:** + +```yaml +checks: + - name: Customer count + description: Get total customer count + type: query + params: + sql_template: select count(*) from {{ ref("customers") }} +``` + +### Query Diff + +Compares query results between base and current environments. + +**Type:** `query_diff` + +**Parameters:** + +| Field | Description | Type | Required | +|-------|-------------|------|----------| +| `sql_template` | SQL statement using Jinja templating | `string` | Yes | +| `base_sql_template` | SQL statement for base environment (if different) | `string` | | +| `primary_keys` | Primary keys for record identification | `string[]` | *1 | + +**Notes:** + +*1: If `primary_keys` is specified, the query diff is performed in the warehouse. Otherwise, the query result (up to the first 2000 records) is returned, and the diff is executed on the client side. + +**Examples:** + +```yaml +checks: + - name: Customer data diff + description: Compare customer data between environments + type: query_diff + params: + sql_template: select * from {{ ref("customers") }} + primary_keys: + - customer_id +``` + +### Value Diff + +Compares values for a specific model between environments. + +**Type:** `value_diff` or `value_diff_detail` + +**Parameters:** + +| Field | Description | Type | Required | +|-------|-------------|------|----------| +| `model` | The name of the model | `string` | Yes | +| `primary_key` | Primary key(s) for record identification | `string` or `string[]` | Yes | +| `columns` | List of columns to include in diff | `string[]` | | + +**Examples:** + +Value diff summary: + +```yaml +checks: + - name: Customer value diff + description: Compare customer values + type: value_diff + params: + model: customers + primary_key: customer_id +``` + +Value diff with detailed rows: + +```yaml +checks: + - name: Customer value diff (detailed) + description: Compare customer values with row details + type: value_diff_detail + params: + model: customers + primary_key: customer_id +``` + +### Profile Diff + +Compares statistical profiles of a model between environments. + +**Type:** `profile_diff` + +**Parameters:** + +| Field | Description | Type | Required | +|-------|-------------|------|----------| +| `model` | The name of the model | `string` | Yes | + +**Examples:** + +```yaml +checks: + - name: Customer profile diff + description: Compare statistical profile of customers + type: profile_diff + params: + model: customers +``` + +### Histogram Diff + +Compares histogram distributions for a column between environments. + +**Type:** `histogram_diff` + +**Parameters:** + +| Field | Description | Type | Required | +|-------|-------------|------|----------| +| `model` | The name of the model | `string` | Yes | +| `column_name` | The name of the column | `string` | Yes | +| `column_type` | The type of the column | `string` | Yes | + +**Examples:** + +```yaml +checks: + - name: CLV histogram diff + description: Compare customer lifetime value distribution + type: histogram_diff + params: + model: customers + column_name: customer_lifetime_value + column_type: BIGINT +``` + +### Top-K Diff + +Compares top-K values for a column between environments. + +**Type:** `top_k_diff` + +**Parameters:** + +| Field | Description | Type | Required | +|-------|-------------|------|----------| +| `model` | The name of the model | `string` | Yes | +| `column_name` | The name of the column | `string` | Yes | +| `k` | Number of top items to include | `number` | Default: 50 | + +**Examples:** + +```yaml +checks: + - name: Top 50 customer values + description: Compare top 50 customer lifetime values + type: top_k_diff + params: + model: customers + column_name: customer_lifetime_value + k: 50 +``` + +## Default Behavior + +- Preset checks are loaded from `recce.yml` when Recce starts +- Checks execute automatically with `recce run` +- Results are stored in the state file +- View options control how results are displayed in the UI + +## Related + +- [Preset Checks Guide](../7-cicd/preset-checks.md) - How to use preset checks in workflows +- [State File](./state-file.md) - Understanding the state file format +- [CLI Reference](./cli-reference.md) - Command-line options for running checks diff --git a/docs/7-reference/state-file.md b/docs/7-reference/state-file.md new file mode 100644 index 0000000..4b36e6b --- /dev/null +++ b/docs/7-reference/state-file.md @@ -0,0 +1,145 @@ +--- +title: State File +--- + +# State File + +This reference documents the Recce state file format, which stores validation results, checks, and environment information. + +## Overview + +The state file represents the serialized state of a Recce instance. It is a JSON-formatted file containing checks, runs, environment artifacts, and runtime information. + +## File Format + +| Aspect | Details | +|--------|---------| +| Format | JSON | +| Default name | `recce_state.json` | +| Location | dbt project root | + +## Contents + +The state file contains the following information: + +- **Checks**: Data from the checks added to the checklist on the Checklist page +- **Runs**: Each diff execution in Recce corresponds to a run, similar to a query in a data warehouse. Typically, a single run submits a series of queries to the warehouse and retrieves the final results +- **Environment Artifacts**: Includes `manifest.json` and `catalog.json` files for both the base and current environments +- **Runtime Information**: Metadata such as Git branch details and pull request (PR) information from the CI runner + +## Saving the State File + +There are multiple ways to save the state file. + +### Save from Web UI + +Click the **Save** button at the top of the app. Recce will continuously write updates to the state file, effectively working like an auto-save feature, and persist the state until the Recce instance is closed. The file is saved with the specified filename in the directory where the `recce server` command is run. + +### Export from Web UI + +Click the **Export** button located in the top-right corner to download the current Recce state to any location on your machine. + +![Save and Export buttons](../assets/images/8-technical-concepts/state-file-save.png){: .shadow} + +### Start with State File + +Provide a state file as an argument when launching Recce. If the file does not exist, Recce will create a state file and start with an empty state. If the file exists, Recce will load the state and continue working from it. + +```bash +recce server my_recce_state.json +``` + +## Using the State File + +The state file can be used in several ways: + +### Continue State + +Launch Recce with the specified state file to continue from where you left off. + +```bash +recce server my_recce_state.json +``` + +### Review Mode + +Running Recce with the `--review` option enables review mode. In this mode, Recce uses the dbt artifacts in the state file instead of those in the `target/` and `target-base/` directories. This option is useful for distinguishing between development and review purposes. + +```bash +recce server --review my_recce_state.json +``` + +### Import Checklist + +To preserve favorite checks across different branches, import a checklist by clicking the **Import** button at the top of the checklist. + +### Continue from `recce run` + +Execute the checks in the specified state file. + +```bash +recce run --state-file my_recce_state.json +``` + +## Workflow Examples + +### Development Workflow + +In the development workflow, the state file acts as a session for developing a feature. It allows you to store checks to verify the diff results against the base environment. + +1. Run the recce server without a state file + + ```bash + recce server + ``` + +2. Add checks to the checklist +3. Save the state by clicking the **Save** or **Export** button +4. Resume your session by launching Recce with the specific state file + + ```bash + recce server recce_issue_1.json + ``` + +![State File For Development](../assets/images/8-technical-concepts/state-file-dev.png) + +### PR Review Workflow + +During the PR review process, the state file serves as a communication medium between the submitter and the reviewer. + +1. Start the Recce server without a state file + + ```bash + recce server + ``` + +2. Add checks to the checklist +3. Save the state by clicking the **Save** or **Export** button +4. Share the state file with the reviewer or attach it as a comment in the pull request +5. The reviewer reviews the results using the state file + + ```bash + recce server --review recce_issue_1.json + ``` + +![State File For PR Review](../assets/images/8-technical-concepts/state-file-pr.png) + +## CLI Options + +| Option | Description | +|--------|-------------| +| `recce server ` | Start server with state file | +| `recce server --review ` | Start in review mode using state file artifacts | +| `recce run --state-file ` | Run checks from state file | + +## Default Behavior + +- If no state file is specified, Recce starts with an empty state +- State files are saved to the current working directory by default +- Review mode (`--review`) uses artifacts embedded in the state file + +## Related + +- [CLI Reference](./cli-reference.md) - Command-line options +- [Configuration](./configuration.md) - Preset check configuration +- [PR Review Workflow](../7-cicd/scenario-pr-review.md) - Using state files in reviews diff --git a/mkdocs.yml b/mkdocs.yml index bee8774..95f17c3 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -92,9 +92,10 @@ nav: - 7-cicd/preset-checks.md - 7-cicd/best-practices-prep-env.md - - Technical Concepts: - - 8-technical-concepts/state-file.md - - 8-technical-concepts/configuration.md + - Reference: + - 7-reference/configuration.md + - 7-reference/state-file.md + - 7-reference/cli-reference.md - Blog: "https://blog.reccehq.com" - Changelog: "https://reccehq.com/changelog/" From 0ed1dfe848421eecbe5a21222c48f308cfe6d4f9 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Sat, 7 Mar 2026 21:26:22 +0800 Subject: [PATCH 37/43] docs(v3): Add What You Can Explore section Consolidate feature pages into new 5-what-you-can-explore section: - lineage-diff.md (from 3-visualized-change/lineage.md) - code-change.md (from 3-visualized-change/) - column-level-lineage.md (from 3-visualized-change/) - multi-models.md (from 3-visualized-change/) - impact-radius.md (from 4-downstream-impacts/) - breaking-change-analysis.md (from 4-downstream-impacts/) - data-diffing.md (consolidated from 5-data-diffing/) Update mkdocs.yml navigation and add redirects for old paths. Co-Authored-By: Claude Opus 4.5 --- .../breaking-change-analysis.md | 118 ++++++++ docs/5-what-you-can-explore/code-change.md | 71 +++++ .../column-level-lineage.md | 39 +++ docs/5-what-you-can-explore/data-diffing.md | 256 ++++++++++++++++++ docs/5-what-you-can-explore/impact-radius.md | 151 +++++++++++ docs/5-what-you-can-explore/lineage-diff.md | 141 ++++++++++ docs/5-what-you-can-explore/multi-models.md | 102 +++++++ mkdocs.yml | 30 +- 8 files changed, 889 insertions(+), 19 deletions(-) create mode 100644 docs/5-what-you-can-explore/breaking-change-analysis.md create mode 100644 docs/5-what-you-can-explore/code-change.md create mode 100644 docs/5-what-you-can-explore/column-level-lineage.md create mode 100644 docs/5-what-you-can-explore/data-diffing.md create mode 100644 docs/5-what-you-can-explore/impact-radius.md create mode 100644 docs/5-what-you-can-explore/lineage-diff.md create mode 100644 docs/5-what-you-can-explore/multi-models.md diff --git a/docs/5-what-you-can-explore/breaking-change-analysis.md b/docs/5-what-you-can-explore/breaking-change-analysis.md new file mode 100644 index 0000000..e2ab1ce --- /dev/null +++ b/docs/5-what-you-can-explore/breaking-change-analysis.md @@ -0,0 +1,118 @@ +--- +title: Breaking Change Analysis +--- + +**Breaking Change Analysis** examines modified models and categorizes changes into three types: + +- Breaking changes +- Partial breaking changes +- Non-breaking changes + +It's generally assumed that any modification to a model’s SQL will affect all downstream models. However, not all changes have the same level of impact. For example, formatting adjustments or the addition of a new column should not break downstream dependencies. Breaking change analysis helps you assess whether a change affects downstream models and, if so, to what extent. + + +## Usage +Use the [impact radius](./impact-radius.md#usage) view to analyze changed and see the impacted downstream. + +## Categories of change +### Non-breaking change + +No downstream models are affected. Common cases are adding new columns, comments, or formatting changes that don't alter logic. + +**Example: Add new columns** +Adding a new column like status doesn't affect models that don't reference it. + +```diff +select + user_id, + user_name, +++ status, +from + {{ ref("orders") }} + +``` + + + + +### Partial breaking change + +Only downstream models that reference specific columns are affected. Common cases are removing, renaming, or redefining a column. + +**Example: Removing a column** + +```diff +select + user_id, +-- status, + order_date, +from + {{ ref("orders") }} +``` + +**Example: Renaming a column** + +```diff +select + user_id, +-- status +++ order_status +from + {{ ref("orders") }} +``` + + +**Example: Redefining a column** +```diff +select + user_id, +-- discount +++ coalesce(discount, 0) as discount +from + {{ ref("orders") }} +``` + + +### Breaking change + +All downstream models are affected. Common case are changes adding a filter condition or adding group by columns. + +**Example: Adding a filter condition** +This may reduce the number of rows, affecting all downstream logic that depends on the original row set. + +```diff +select + user_id, + order_date +from + {{ ref("orders") }} +++ where status = 'completed' +``` + + +**Example: Adding a GROUP BY column** +Changes the granularity of the result set, which can break all dependent models. + +```diff +select + user_id, +++ order_data, + count(*) as total_orders +from + {{ ref("orders") }} +-- group by user_id +++ group by user_id, order_date +``` + + +## Limitations + +Our breaking change analysis is intentionally conservative to prioritize safety. As a result, a modified model may be classified as a breaking change when it is actually non-breaking or partial breaking changes. Common cases include: + +1. Logical equivalence in operations, such as changing `a + b` to `b + a`. +1. Adding a `LEFT JOIN` to a table and selecting columns from it. This is often used to enrich the current model with additional dimension table data without affecting existing downstream tables. +1. All modified python models or seeds are treated as breaking change. + +## Technology + +Breaking Change Analysis is powered by the SQL analysis and AST diff capabilities of [SQLGlot](https://github.com/tobymao/sqlglot) to compare two SQL semantic trees. diff --git a/docs/5-what-you-can-explore/code-change.md b/docs/5-what-you-can-explore/code-change.md new file mode 100644 index 0000000..7fbdda3 --- /dev/null +++ b/docs/5-what-you-can-explore/code-change.md @@ -0,0 +1,71 @@ +--- +title: Code Change +--- + +# Code Change + +The Code Change feature allows you to compare the SQL code changes between your current branch and the base branch, helping you understand exactly what has been modified in your dbt models. + +## Viewing Code Change + +When you identify a modified model in the [Lineage Diff](lineage-diff.md), you can examine the specific code changes to understand the nature of the modifications. + +### Opening Code Change + +To view the code changes for a model: + +1. Click on any modified (orange) model node in the lineage view +2. In the node details panel that opens, navigate to the **Code** tab +3. The code diff will display showing the changes between branches + +
+ ![View code changes for a model](../assets/images/3-visualized-change/view-code-of-model.png){: .shadow} +
Viewing code changes for a modified model
+
+ +### Understanding the Code Diff + +The code diff uses standard diff formatting to highlight changes: + +- **Red lines** (with `-` prefix) show code that was removed +- **Green lines** (with `+` prefix) show code that was added +- **Unchanged lines** appear in normal formatting for context + +This visual comparison makes it easy to identify: +- New columns or transformations +- Modified business logic +- Changes to joins or filters +- Updated column names or data types + +### Full Screen View + +For complex changes or detailed review, you can expand the code diff to full screen: + +1. Click the expand button in the top-right corner of the code diff panel +2. Review the changes in the larger view for better readability +3. Use this view when conducting thorough code reviews or sharing changes with team members + +
+ ![Expanded full-screen view of code changes](../assets/images/3-visualized-change/full-view-code-of-model.png){: .shadow} +
Full-screen view for detailed code review
+
+ +## Why Code Diff Matters + +Understanding code changes is essential for: + +- **Impact Assessment**: Determining if changes affect downstream models or reports +- **Code Review**: Validating that modifications align with business requirements +- **Collaboration**: Clearly communicating what changed to stakeholders +- **Quality Assurance**: Ensuring changes don't introduce errors or break existing logic + +## Next Steps + +After reviewing code changes, you can: + +- Examine the [impact radius](impact-radius.md) to see which downstream models are affected +- Run [data diffs](data-diffing.md) to validate that the changes produce expected results +- Add your findings to the [collaboration checklist](../6-collaboration/checklist.md) for team review + +!!! tip "Best Practice" + Always review code changes alongside data validation checks to ensure your modifications produce the expected results and don't break downstream dependencies. \ No newline at end of file diff --git a/docs/5-what-you-can-explore/column-level-lineage.md b/docs/5-what-you-can-explore/column-level-lineage.md new file mode 100644 index 0000000..87834fc --- /dev/null +++ b/docs/5-what-you-can-explore/column-level-lineage.md @@ -0,0 +1,39 @@ +--- +title: Column-Level Lineage +--- + +Column-Level Lineage provides visibility into the upstream and downstream relationships of a column. + +Common use-cases for column-level lineage are: + +1. **Source Exploration**: During development, column-level lineage helps you understand how a column is derived. +2. **Impact Analysis**: When modifying the logic of a column, column-level lineage enables you to assess the potential impact across the entire DAG. +3. **Root Cause Analysis**: Column-level lineage helps identify the possible source of errors by tracing data lineage at the column level. + +## Usage + +1. Select a node in the lineage DAG, then click the column you want to view. + + ![alt text](../assets/images/3-visualized-change/cll-1.png){: .shadow} + +1. The column-level lineage for the selected column will be displayed. + + ![alt text](../assets/images/3-visualized-change/cll-2.png){: .shadow} + +1. To exit column-level lineage view, click the close button in the upper-left corner. + + ![alt text](../assets/images/3-visualized-change/cll-3.png){: .shadow} + +# Transformation Types + +The transformation type is also displayed for each column, which will help you understand how the column was generated or modified. + +| Type | Description | +|------|--------------| +| Pass-through |The column is directly selected from the upstream table. | +| Renamed | The column is selected from the upstream table but with a different name. | +| Derived | The column is created through transformations applied to upstream columns, such as calculations, conditions, functions, or aggregations. | +| Source | The column is not derived from any upstream data. It may originate from a seed/source node, literal value or data generation function. | +| Unknown | We have no information about the transformation type. This could be due to a parse error or other unknown reason. | + + diff --git a/docs/5-what-you-can-explore/data-diffing.md b/docs/5-what-you-can-explore/data-diffing.md new file mode 100644 index 0000000..1208abb --- /dev/null +++ b/docs/5-what-you-can-explore/data-diffing.md @@ -0,0 +1,256 @@ +--- +title: Data Diffing +--- + +# Data Diffing + +Data diffing validates that your model changes produce the expected results. Each diff type serves a different validation purpose, from quick row counts to detailed value comparisons. + +## Overview + +| Diff Type | Purpose | Query Cost | Best For | +|-----------|---------|------------|----------| +| [Row Count](#row-count-diff) | Compare record counts | Low | Quick sanity check | +| [Profile](#profile-diff) | Column-level statistics | Medium | Distribution analysis | +| [Value](#value-diff) | Row-by-row comparison | High | Exact match verification | +| [Top-K](#top-k-diff) | Categorical distribution | Medium | Categorical columns | +| [Histogram](#histogram-diff) | Numeric distribution | Medium | Numeric columns | +| [Query](#query-diff) | Custom SQL comparison | Varies | Flexible validation | + +## Choosing the Right Diff + +A common approach is to start with lightweight checks and progressively drill down as needed. This decision tree provides a suggested workflow: + +``` +Start with Row Count + │ + ├─ Counts match? → Profile Diff for deeper stats + │ + └─ Counts differ? + │ + ├─ Expected? → Document in checklist + │ + └─ Unexpected? → Value Diff to find specific changes + │ + └─ For specific columns: + • Categorical → Top-K Diff + • Numeric → Histogram Diff + • Custom logic → Query Diff +``` + + +## Row Count Diff + +Compare the number of rows between base and current environments. + +**When to use:** Quick validation that filters or joins didn't unexpectedly add or remove records. + +### Running Row Count Diff + +1. Click a model in the Lineage DAG +2. Click **Explore Change** > **Row Count Diff** + +
+ ![Row Count Diff - Single model](../assets/images/5-data-diffing/row-count-diff-single.gif){: .shadow} +
Row Count Diff for a single model
+
+ +### Interpreting Results + +| Result | Meaning | +|--------|---------| +| Count unchanged | No records added or removed | +| Count increased | New records added (check if expected) | +| Count decreased | Records removed (verify filters/joins) | + +--- + +## Profile Diff + +Compare column-level statistics between environments. + +**When to use:** Validate that transformations didn't unexpectedly change data distributions. + +### Statistics Compared + +| Statistic | Description | +|-----------|-------------| +| Row count | Total records | +| Not null % | Proportion of non-null values | +| Distinct % | Proportion of unique values | +| Distinct count | Number of unique values | +| Is unique | Whether all values are unique | +| Min / Max | Range of values | +| Average / Median | Central tendency | + +### Running Profile Diff + +1. Select a model from the Lineage DAG +2. Click **Explore Change** > **Profile Diff** + +
+ ![Profile Diff](../assets/images/5-data-diffing/profile-diff.png) +
Profile Diff showing column statistics
+
+ +### Interpreting Results + +Look for unexpected changes in: + +- **Null rates** - Did a column become more/less nullable? +- **Distinct counts** - Did cardinality change unexpectedly? +- **Min/Max** - Did value ranges shift? + +--- + +## Value Diff + +Compare actual values row-by-row using primary keys. + +**When to use:** Verify exact data matches when precision matters. + +### How It Works + +Value Diff uses primary keys to match records between environments, then compares each column value. Primary keys are auto-detected from columns with the `unique` test. + +
+ ![Value Diff](../assets/images/5-data-diffing/value-diff.png) +
Value Diff showing match percentages
+
+ +### Result Columns + +| Column | Meaning | +|--------|---------| +| **Added** | New PKs in current (not in base) | +| **Removed** | PKs in base (not in current) | +| **Matched** | Count of matching values for common PKs | +| **Matched %** | Percentage match for common PKs | + +### Viewing Mismatches + +Click **show mismatched values** on a column to see row-level differences: + +![Value Diff Detail](../assets/images/3-visualized-change/value-diff-detail.gif){: .shadow} + +--- + +## Top-K Diff + +Compare the distribution of categorical columns by showing the most frequent values. + +**When to use:** Validate categorical data hasn't shifted unexpectedly (status codes, categories, regions). + +### Running Top-K Diff + +**Via Explore Change:** + +1. Select model > **Explore Change** > **Top-K Diff** +2. Select a column +3. Click **Execute** + +**Via Column Menu:** + +1. Hover over a column in Node Details +2. Click **...** > **Top-K Diff** + +
+ ![Top-K Diff](../assets/images/5-data-diffing/top-k-diff.gif){: .shadow} +
Generate a Top-K Diff from the column menu
+
+ +### Options + +| Option | Description | +|--------|-------------| +| Top 10 | Default view | +| Top 50 | Expanded view for more categories | + +
+ ![Top-K Diff Result](../assets/images/5-data-diffing/top-k-diff.png) +
Top-K Diff comparing category distributions
+
+ +--- + +## Histogram Diff + +Compare the distribution of numeric columns using binned histograms. + +**When to use:** Validate numeric distributions haven't shifted (amounts, scores, durations). + +### Running Histogram Diff + +**Via Explore Change:** + +1. Select model > **Explore Change** > **Histogram Diff** +2. Select a numeric column +3. Click **Execute** + +**Via Column Menu:** + +1. Hover over a numeric column +2. Click **...** > **Histogram Diff** + +
+ ![Histogram Diff](../assets/images/5-data-diffing/histogram-diff.gif){: .shadow} +
Generate a Histogram Diff from column options
+
+ +
+ ![Histogram Diff Result](../assets/images/5-data-diffing/histogram-diff.png) +
Histogram Diff showing overlaid distributions
+
+ +--- + +## Query Diff + +Write custom SQL to compare any query results between environments. + +**When to use:** Flexible validation for complex scenarios not covered by standard diffs. + +### Running Query Diff + +1. Open the Query page +2. Write SQL using dbt syntax: + ```sql + select * from {{ ref("mymodel") }} + ``` +3. Click **Run Diff** + +
+ ![Query Diff](../assets/images/5-data-diffing/query-diff.png) +
Query Diff interface
+
+ +### Comparison Modes + +| Mode | When to Use | How It Works | +|------|-------------|--------------| +| **Client-side** | No primary key | Fetches first 2,000 rows, compares locally | +| **Warehouse** | Primary key specified | Compares in warehouse, shows only differences | + +!!! tip "Keyboard Shortcuts (Mac)" + - `⌘ Enter` - Run query + - `⌘ ⇧ Enter` - Run query diff + +### Result Options + +| Option | Description | +|--------|-------------| +| **Primary Key** | Click key icon to set comparison key | +| **Pinned Column** | Show specific columns first | +| **Changed Only** | Hide unchanged rows and columns | + +
+ ![Query Diff Result](../assets/images/5-data-diffing/query-diff.gif){: .shadow} +
Query Diff with filtering options
+
+ +--- + +## Related + +- [Lineage Diff](lineage-diff.md) - Visualize change impact +- [Checklist](../6-collaboration/checklist.md) - Save validation results diff --git a/docs/5-what-you-can-explore/impact-radius.md b/docs/5-what-you-can-explore/impact-radius.md new file mode 100644 index 0000000..32922bd --- /dev/null +++ b/docs/5-what-you-can-explore/impact-radius.md @@ -0,0 +1,151 @@ +--- +title: Impact Radius +--- + +**Impact Radius** helps you analyze changes and identify downstream impacts at the column level. + +While dbt provides a similar capability using the [state selector](https://docs.getdbt.com/reference/node-selection/methods#state) with `state:modified+` to identify modified nodes and their downstream dependencies, Recce goes further. By analyzing SQL code directly, Recce enables **fine-grained impact radius analysis**. It reveals how changes to specific columns can ripple through your data pipeline, helping you prioritize which models—and even which columns—deserve closer attention. + +=== "Impact Radius" + + ![Breaking Change Analysis](../assets/images/4-downstream-impacts/impact-radius.png){: .shadow} + +=== "state:modified+" + + ![Breaking Change Analysis (disabled)](../assets/images/4-downstream-impacts/impact-radius-legacy.png){: .shadow} + + +## Usage + +### Show impact radius + +1. Click the **Impact Radius** button in the upper-left corner. + + ![Impact Radius button highlighted](../assets/images/4-downstream-impacts/impact-radius-1.png){: .shadow} + +1. The impact radius will be displayed. + + ![Impact radius displayed on screen](../assets/images/4-downstream-impacts/impact-radius-2.png){: .shadow} + +1. To exit impact radius view, click the close button in the upper-left corner. + + ![Close button for exiting impact radius view](../assets/images/4-downstream-impacts/impact-radius-3.png){: .shadow} + +### Show impact radius for a single changed model + +1. Hover over a changed model, then click the **target icon** or right-click the model and click the **Show Impact Radius** + + ![Target icon for showing impact radius of a single model](../assets/images/4-downstream-impacts/impact-radius-single-1.png){: .shadow} + +1. The impact radius for this model will be displayed. + + ![Impact radius for a single model displayed on screen](../assets/images/4-downstream-impacts/impact-radius-single-2.png){: .shadow} + +1. To exit impact radius view, click the close button in the upper-left corner. + + ![Close button for exiting single model impact radius view](../assets/images/4-downstream-impacts/impact-radius-single-3.png){: .shadow} + +## Impact Radius of a Column + +The **right side of the [Column-Level Lineage](column-level-lineage.md) (CLL)** graph represents the **impact radius** of a selected column. +This view helps you quickly understand what will be affected if that column changes. + +### What does the impact radius include? + +- **Downstream columns** that directly reference the selected column +- **Downstream models** that directly depend on the selected column +- **All indirect downstream columns and models** that transitively depend on it + +This helps you evaluate both the direct and downstream effects of a column change, making it easier to understand its overall impact. + + +### Example: Simplified Model Chain + +Given the following models, here's how changes to `stg_orders.status` would impact downstream models: + +```sql +-- stg_orders.sql +select + order_id, + customer_id, + status, + ... +from {{ ref("raw_orders") }} + + +-- orders.sql +select + order_id, + customer_id, + status, + ... +from {{ ref("stg_orders") }} + + +-- customers.sql +select + c.customer_id, + ... +from {{ ref("stg_customers") }} as c +join {{ ref("stg_orders") }} as o + on c.customer_id = o.customer_id +where o.status = 'completed' +group by c.customer_id + + +-- customer_segments.sql +select + customer_id, + ... +from {{ ref("customers") }} +``` + +![alt text](../assets/images/4-downstream-impacts/cll-example.png){: .shadow} + +The following impact is detected: + +- **orders**: This model is partially impacted, as it selects the `status` column directly from `stg_orders` but does not apply any transformation or filtering logic. The change is limited to the `status` column only. + +- **customers**: This model is fully impacted, because it uses `status` in a WHERE clause (`where o.status = 'completed'`). Any change to the logic in `stg_orders.status` can affect the entire output of the model. + +- **customer_segments**: This model is indirectly impacted, as it depends on the `customers` model, which itself is fully impacted. Even though `customer_segments` does not directly reference `status`, changes can still propagate downstream via its upstream dependency. + + + +## How it works + +Two core features power the impact radius analysis: + +**[Breaking Change Analysis](./breaking-change-analysis.md)** classifies modified models into three categories: + +- **Breaking changes**: Impact all downstream **models** +- **Non-breaking changes**: Do not impact any downstream **models** +- **Partial breaking changes**: Impact only downstream **models or columns** that depend on the modified columns + +**[Column-level lineage](column-level-lineage.md)** analyzes your model's SQL to identify column-level dependencies: + +- Which upstream **columns** are used as filters or grouping keys. If those upstream **columns** change, the current **model** is impacted. +- Which upstream **columns** a specific column references. If those upstream **columns** change, the specific **column** is impacted. + +## Putting It Together + +With the insights from the two features above, Recce determines the impact radius: + +1. If a model has a **[breaking change](breaking-change-analysis.md#breaking-change)**, include all downstream models in the impact radius. +2. If a model has a **[non-breaking change](breaking-change-analysis.md#non-breaking-change)**, include only the downstream columns and models of newly added columns. +3. If a model has a **[partial breaking change](breaking-change-analysis.md#partial-breaking-change)**, include the downstream columns and models of added, removed, or modified columns. + +## Related + +- [Breaking Change Analysis](breaking-change-analysis.md) - Understand how changes are classified +- [Column-Level Lineage](column-level-lineage.md) - Trace column dependencies +- [Data Diffing](data-diffing.md) - Validate data changes in impacted models + + + + + + + + + diff --git a/docs/5-what-you-can-explore/lineage-diff.md b/docs/5-what-you-can-explore/lineage-diff.md new file mode 100644 index 0000000..f1a1645 --- /dev/null +++ b/docs/5-what-you-can-explore/lineage-diff.md @@ -0,0 +1,141 @@ +--- +title: Lineage Diff +--- + +# Lineage Diff + +The Lineage view shows how your data model changes impact your data pipeline. It visualizes the potential area of impact from your modifications, helping you determine which models need further investigation. + +## How It Works + +Recce compares your base and current branch artifacts to identify: + +- **Dependencies** - Which models depend on others +- **Change Impact** - How modifications ripple through your pipeline +- **Data Flow** - The path data takes from sources to final outputs + +
+ ![Recce Lineage Diff](../assets/images/3-visualized-change/lineage-diff.gif){: .shadow} +
Interactive lineage graph showing modified models
+
+ +### Visual Status Indicators + +Models are color-coded to indicate their status: + +| Color | Status | +|-------|--------| +| **Green** | Added (new to your project) | +| **Red** | Removed (deleted from your project) | +| **Orange** | Modified (changed code or configuration) | +| **Gray** | Unchanged (shown for context) | + +
+ ![Node example](../assets/images/3-visualized-change/node.png){: .shadow} +
Model node with status indicators
+
+ +### Change Detection Icons + +Each model displays icons in the bottom-right corner: + +- **Row Count Icon** - Shows when row count differences are detected +- **Schema Icon** - Shows when column or data type changes are detected + +Grayed-out icons indicate no changes were detected. + +
+ ![Model with Schema Change detected](../assets/images/3-visualized-change/model-schema-change-detected.png){: .shadow} +
Model with schema change detected
+
+ +## Filtering and Selection + +### Filter Options + +In the top control bar: + +| Filter | Description | +|--------|-------------| +| **Mode** | Changed Models (modified + downstream) or All | +| **Package** | Filter by dbt package names | +| **Select** | Select nodes by [node selection](multi-models.md) | +| **Exclude** | Exclude nodes by [node selection](multi-models.md) | + +### Selecting Models + +Click a node to select it, or use **Select nodes** to select multiple models for batch operations. + +### Row Count Diff by Selector + +Run row count diff on selected nodes: + +1. Use `select` and `exclude` to filter nodes +2. Click the **...** button in the top-right corner +3. Click **Row Count Diff by Selector** + +![](../assets/images/3-visualized-change/row-count-diff-selector.gif){: .shadow} + +## Investigating Changes + +### Node Details Panel + +Click any model to open the node details panel: + +
+ ![Open node details panel](../assets/images/3-visualized-change/node-details-panel.gif){: .shadow} +
Open the node details panel
+
+ +From this panel you can: + +- View model metadata (type, materialization) +- Examine schema changes +- Run validation checks +- Add findings to your checklist + +### Available Validations + +Click **Explore Change** to access: + +- [Row Count Diff](data-diffing.md#row-count-diff) - Compare record counts +- [Profile Diff](data-diffing.md#profile-diff) - Analyze column statistics +- [Value Diff](data-diffing.md#value-diff) - Identify specific value changes +- [Top-K Diff](data-diffing.md#top-k-diff) - Compare common values +- [Histogram Diff](data-diffing.md#histogram-diff) - Visualize distributions + +
+ ![Explore the model](../assets/images/3-visualized-change/explore-the-model.png){: .shadow} +
Node details with exploration options
+
+ +## Schema Diff + +Schema diff identifies structural changes to your models: + +- **Added columns** - New fields (shown in green) +- **Removed columns** - Deleted fields (shown in red) +- **Renamed columns** - Changed names (shown with arrows) +- **Data type changes** - Modified column types + +
+ ![Recce Schema Diff](../assets/images/3-visualized-change/schema-diff.gif){: .shadow} +
Interactive schema diff showing column changes
+
+ +!!! warning "Requirements" + Schema diff requires `catalog.json` in both environments. Run `dbt docs generate` in both before starting your Recce session. + +## When to Use + +- **Starting your review** - Get an overview of all changes and their downstream impact +- **Identifying affected models** - Find models that need validation +- **Understanding dependencies** - See how changes propagate through your pipeline +- **Scoping your validation** - Determine which models to diff + +## Related + +- [Code Change](code-change.md) - View SQL changes for a model +- [Column-Level Lineage](column-level-lineage.md) - Trace column dependencies +- [Multi-Model Selection](multi-models.md) - Batch operations on models +- [Data Diffing](data-diffing.md) - Validate data changes diff --git a/docs/5-what-you-can-explore/multi-models.md b/docs/5-what-you-can-explore/multi-models.md new file mode 100644 index 0000000..66020d5 --- /dev/null +++ b/docs/5-what-you-can-explore/multi-models.md @@ -0,0 +1,102 @@ +--- +title: Multi-Models +--- + + +## Multi-Models Selection + +Multiple models can be selected in the Lineage DAG. This enables actions to be performed on multiple models at the same time such as Row Count Diff, or Value Diff. + +### Select Models Individually + +To select multiple models individually, click the checkbox on the models you wish to select. + +
+ ![Select multiple models individually](../assets/images/3-visualized-change/multi-node-selection.gif){: .shadow} +
Select multiple models individually
+
+ +### Select Parent or Child models + +To select a node and all of its parents or children: + +1. Click the checkbox on the node +2. Right-click the node +3. Click to select either parent or child models + +
+ ![Select a node and its parents or children](../assets/images/3-visualized-change/select-node-children.gif){: .shadow} +
Select a node and its parents or children
+
+ +### Perform actions on multiple models + +After selecting the desired models, use the Actions menu at the top right of the screen to perform diffs or add checks. + +
+ ![Perform actions on multiple models](../assets/images/3-visualized-change/actions-menu.png){: .shadow} +
Perform actions on multiple models
+
+ +### Example - Row Count Diff + +An example of selecting multiple models to perform a multi-node row count diff: + +
+ ![Perform a Row Count Diff on multiple models](../assets/images/3-visualized-change/multi-node-row-count-diff.gif){: .shadow} +
Perform a Row Count Diff on multiple models
+
+ +### Example - Value Diff + +An example of selecting multiple models to perform a multi-node Value Diff: + +
+ ![Perform a Value Diff on multiple models](../assets/images/3-visualized-change/multi-node-value-diff.gif){: .shadow} +
Perform a Value Diff on multiple models
+
+ + +### Schema and Lineage Diff + +From the Lineage DAG, click the Actions dropdown menu and click Lineage Diff or Schema Diff from the Add to Checklist section. This will add: + +- Lineage Diff: The current Lineage view, dependent on your [model selection](./lineage.md#select-nodes) options. +- Schema Diff: A diff of all models if none are selected, or [specific selected models](#multi-models-selection). + +
+ ![Add a Lineage Diff Check or Schema Check via the Actions dropdown menu](../assets/images/3-visualized-change/actions-dropdown.png){: .shadow} +
Add a Lineage Diff Check or Schema Check via the Actions dropdown menu
+
+ + + +Recce supports dbt [node selection](https://docs.getdbt.com/reference/node-selection/syntax) in the [lineage diff](./lineage.md). This enables you to target specific resources with data checks by selecting or excluding models. + +## Supported syntax and methods + +Since Recce uses dbt's built-in node selector, it supports most of the selecting methods. Here are some examples: + +- Select a node: `my_model` +- select by tag: `tag:nightly` +- Select by wildcard: `customer*` +- Select by graph operators: `my_model+`, `+my_model`, `+my_model`, `1+my_model+` +- Select by union: `model1 model2` +- Select by intersection: `stg_invoices+,stg_accounts+` +- Select by state: `state:modified`, `state:modified+` + + +### Use `state` method + +In dbt, you need to specify the `--state` option in the CLI. In Recce we use the base environment as the state, allowing you to use the selector on the fly. + + +### Removed models +Another difference is that in dbt, you cannot select removed models. However, in Recce, you can select removed models and also find them using the graph operator. This is a notable distinction from dbt's node selection capabilities. + + +## Limitation + +- ["result" method](https://docs.getdbt.com/reference/node-selection/syntax#the-result-status) not supported +- ["source_status" method](https://docs.getdbt.com/reference/node-selection/syntax#the-source_status-status) not supported. +- [YAML selectors](https://docs.getdbt.com/reference/node-selection/yaml-selectors) not supported. diff --git a/mkdocs.yml b/mkdocs.yml index 05aa695..71c579d 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -62,11 +62,6 @@ nav: - Claude Plugin: 2-getting-started/claude-plugin.md - 2-getting-started/oss-setup.md - 2-getting-started/jaffle-shop-tutorial.md - - Visualized Change: - - 3-visualized-change/lineage.md - - 3-visualized-change/code-change.md - - 3-visualized-change/column-level-lineage.md - - 3-visualized-change/multi-models.md - Using Recce: - 3-using-recce/admin-setup.md - 3-using-recce/data-developer.md @@ -75,20 +70,14 @@ nav: - 4-what-the-agent-does/index.md - 4-what-the-agent-does/automated-validation.md - 4-what-the-agent-does/impact-analysis.md - - Downstream Impacts: - #- 4-downstream-impacts/metadata-first.md - - 4-downstream-impacts/impact-radius.md - - 4-downstream-impacts/breaking-change-analysis.md - #- 4-downstream-impacts/transformation-types.md - - Data Diffing: - - 5-data-diffing/connect-to-warehouse.md - - 5-data-diffing/row-count-diff.md - - 5-data-diffing/profile-diff.md - - 5-data-diffing/value-diff.md - - 5-data-diffing/topK-diff.md - - 5-data-diffing/histogram-diff.md - - 5-data-diffing/query.md - - MCP Server (AI Agents): 5-data-diffing/mcp-server.md + - What You Can Explore: + - 5-what-you-can-explore/lineage-diff.md + - 5-what-you-can-explore/code-change.md + - 5-what-you-can-explore/column-level-lineage.md + - 5-what-you-can-explore/impact-radius.md + - 5-what-you-can-explore/breaking-change-analysis.md + - 5-what-you-can-explore/data-diffing.md + - 5-what-you-can-explore/multi-models.md - Collaborate Validation: - 6-collaboration/checklist.md - 6-collaboration/share.md @@ -178,6 +167,9 @@ plugins: '7-cicd/ci-cd-getting-started.md': '2-getting-started/environment-setup.md' '7-cicd/best-practices-prep-env.md': '2-getting-started/environment-best-practices.md' '3-visualized-change/lineage.md': '5-what-you-can-explore/lineage-diff.md' + '3-visualized-change/code-change.md': '5-what-you-can-explore/code-change.md' + '3-visualized-change/column-level-lineage.md': '5-what-you-can-explore/column-level-lineage.md' + '3-visualized-change/multi-models.md': '5-what-you-can-explore/multi-models.md' '4-downstream-impacts/impact-radius.md': '5-what-you-can-explore/impact-radius.md' '4-downstream-impacts/breaking-change-analysis.md': '5-what-you-can-explore/breaking-change-analysis.md' '5-data-diffing/row-count-diff.md': '5-what-you-can-explore/data-diffing.md' From c853766e9e5af937028046012a83e99398a37204 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Sat, 7 Mar 2026 21:30:09 +0800 Subject: [PATCH 38/43] fix: Update broken links in multi-models.md Change lineage.md references to lineage-diff.md Co-Authored-By: Claude Opus 4.5 --- docs/5-what-you-can-explore/multi-models.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/5-what-you-can-explore/multi-models.md b/docs/5-what-you-can-explore/multi-models.md index 66020d5..3446e0e 100644 --- a/docs/5-what-you-can-explore/multi-models.md +++ b/docs/5-what-you-can-explore/multi-models.md @@ -61,7 +61,7 @@ An example of selecting multiple models to perform a multi-node Value Diff: From the Lineage DAG, click the Actions dropdown menu and click Lineage Diff or Schema Diff from the Add to Checklist section. This will add: -- Lineage Diff: The current Lineage view, dependent on your [model selection](./lineage.md#select-nodes) options. +- Lineage Diff: The current Lineage view, dependent on your [model selection](lineage-diff.md#selecting-models) options. - Schema Diff: A diff of all models if none are selected, or [specific selected models](#multi-models-selection).
@@ -71,7 +71,7 @@ From the Lineage DAG, click the Actions dropdown menu and click Lineage Diff or -Recce supports dbt [node selection](https://docs.getdbt.com/reference/node-selection/syntax) in the [lineage diff](./lineage.md). This enables you to target specific resources with data checks by selecting or excluding models. +Recce supports dbt [node selection](https://docs.getdbt.com/reference/node-selection/syntax) in the [lineage diff](lineage-diff.md). This enables you to target specific resources with data checks by selecting or excluding models. ## Supported syntax and methods From b2dbfaab2654a6ea9b0063c5f446a77ca6eef22d Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Sat, 7 Mar 2026 22:07:33 +0800 Subject: [PATCH 39/43] docs: Add When to Use and Related sections for AISEO compliance Add missing sections to concept docs: - breaking-change-analysis.md: When to Use, Related - code-change.md: When to Use, Related - column-level-lineage.md: When to Use, Related - impact-radius.md: When to Use - multi-models.md: When to Use, Related Co-Authored-By: Claude Opus 4.5 --- .../breaking-change-analysis.md | 15 ++++++++++++++- docs/5-what-you-can-explore/code-change.md | 15 ++++++++++++++- .../column-level-lineage.md | 15 ++++++++++++++- docs/5-what-you-can-explore/impact-radius.md | 7 +++++++ docs/5-what-you-can-explore/multi-models.md | 13 +++++++++++++ 5 files changed, 62 insertions(+), 3 deletions(-) diff --git a/docs/5-what-you-can-explore/breaking-change-analysis.md b/docs/5-what-you-can-explore/breaking-change-analysis.md index e2ab1ce..6ddca9a 100644 --- a/docs/5-what-you-can-explore/breaking-change-analysis.md +++ b/docs/5-what-you-can-explore/breaking-change-analysis.md @@ -113,6 +113,19 @@ Our breaking change analysis is intentionally conservative to prioritize safety. 1. Adding a `LEFT JOIN` to a table and selecting columns from it. This is often used to enrich the current model with additional dimension table data without affecting existing downstream tables. 1. All modified python models or seeds are treated as breaking change. +## When to Use + +- Determine which downstream models need validation after a change +- Prioritize review effort based on impact severity +- Understand if a refactor will break dependent models +- Assess risk before merging model changes + ## Technology -Breaking Change Analysis is powered by the SQL analysis and AST diff capabilities of [SQLGlot](https://github.com/tobymao/sqlglot) to compare two SQL semantic trees. +Breaking Change Analysis is powered by the SQL analysis and AST diff capabilities of [SQLGlot](https://github.com/tobymao/sqlglot) to compare two SQL semantic trees. + +## Related + +- [Impact Radius](impact-radius.md) - Visualize affected downstream models +- [Column-Level Lineage](column-level-lineage.md) - Trace column dependencies +- [Code Change](code-change.md) - Review the actual SQL modifications diff --git a/docs/5-what-you-can-explore/code-change.md b/docs/5-what-you-can-explore/code-change.md index 7fbdda3..e61c111 100644 --- a/docs/5-what-you-can-explore/code-change.md +++ b/docs/5-what-you-can-explore/code-change.md @@ -68,4 +68,17 @@ After reviewing code changes, you can: - Add your findings to the [collaboration checklist](../6-collaboration/checklist.md) for team review !!! tip "Best Practice" - Always review code changes alongside data validation checks to ensure your modifications produce the expected results and don't break downstream dependencies. \ No newline at end of file + Always review code changes alongside data validation checks to ensure your modifications produce the expected results and don't break downstream dependencies. + +## When to Use + +- Review what SQL logic changed before running data diffs +- Understand the scope of a PR during code review +- Identify which columns or joins were modified +- Document changes for team communication + +## Related + +- [Breaking Change Analysis](breaking-change-analysis.md) - Classify impact severity +- [Impact Radius](impact-radius.md) - See affected downstream models +- [Data Diffing](data-diffing.md) - Validate data changes \ No newline at end of file diff --git a/docs/5-what-you-can-explore/column-level-lineage.md b/docs/5-what-you-can-explore/column-level-lineage.md index 87834fc..cc53e94 100644 --- a/docs/5-what-you-can-explore/column-level-lineage.md +++ b/docs/5-what-you-can-explore/column-level-lineage.md @@ -24,7 +24,7 @@ Common use-cases for column-level lineage are: ![alt text](../assets/images/3-visualized-change/cll-3.png){: .shadow} -# Transformation Types +## Transformation Types The transformation type is also displayed for each column, which will help you understand how the column was generated or modified. @@ -36,4 +36,17 @@ The transformation type is also displayed for each column, which will help you u | Source | The column is not derived from any upstream data. It may originate from a seed/source node, literal value or data generation function. | | Unknown | We have no information about the transformation type. This could be due to a parse error or other unknown reason. | +## When to Use + +- Trace where a column's data originates +- Understand which downstream columns depend on a specific column +- Assess the impact of modifying a column's logic +- Debug data quality issues by following the transformation chain + +## Related + +- [Impact Radius](impact-radius.md) - See column-level impact on downstream models +- [Breaking Change Analysis](breaking-change-analysis.md) - Classify change severity +- [Data Diffing](data-diffing.md) - Validate column-level data changes + diff --git a/docs/5-what-you-can-explore/impact-radius.md b/docs/5-what-you-can-explore/impact-radius.md index 32922bd..89cc871 100644 --- a/docs/5-what-you-can-explore/impact-radius.md +++ b/docs/5-what-you-can-explore/impact-radius.md @@ -135,6 +135,13 @@ With the insights from the two features above, Recce determines the impact radiu 2. If a model has a **[non-breaking change](breaking-change-analysis.md#non-breaking-change)**, include only the downstream columns and models of newly added columns. 3. If a model has a **[partial breaking change](breaking-change-analysis.md#partial-breaking-change)**, include the downstream columns and models of added, removed, or modified columns. +## When to Use + +- Identify which downstream models need validation after a change +- Prioritize data diff effort based on actual impact +- Assess risk before merging model modifications +- Understand the blast radius of column-level changes + ## Related - [Breaking Change Analysis](breaking-change-analysis.md) - Understand how changes are classified diff --git a/docs/5-what-you-can-explore/multi-models.md b/docs/5-what-you-can-explore/multi-models.md index 3446e0e..541bc2b 100644 --- a/docs/5-what-you-can-explore/multi-models.md +++ b/docs/5-what-you-can-explore/multi-models.md @@ -100,3 +100,16 @@ Another difference is that in dbt, you cannot select removed models. However, in - ["result" method](https://docs.getdbt.com/reference/node-selection/syntax#the-result-status) not supported - ["source_status" method](https://docs.getdbt.com/reference/node-selection/syntax#the-source_status-status) not supported. - [YAML selectors](https://docs.getdbt.com/reference/node-selection/yaml-selectors) not supported. + +## When to Use + +- Run Row Count Diff across multiple related models at once +- Perform bulk Value Diff on a set of staging models +- Validate all models in a specific path or tag +- Compare schema changes across an entire model group + +## Related + +- [Lineage Diff](lineage-diff.md) - Visualize model dependencies and changes +- [Data Diffing](data-diffing.md) - Run diffs on selected models +- [Impact Radius](impact-radius.md) - See downstream effects of changes From d3f691a0c6baf3d085b43f652f2c98df5e8395ef Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Tue, 3 Mar 2026 20:24:33 +0800 Subject: [PATCH 40/43] Update collaboration pages with doc.md structure - checklist.md: Add "When to use" section, restructure as concept - share.md: Add Goal, Prerequisites, Verification sections - preset-checks.md: Copy from 7-cicd/, add Goal/Prerequisites/Verification Part of docs v3 migration - PR7 Co-Authored-By: Claude Opus 4.5 --- docs/6-collaboration/checklist.md | 43 ++++--- docs/6-collaboration/preset-checks.md | 115 +++++++++++++++++++ docs/6-collaboration/share.md | 156 +++++++++++--------------- mkdocs.yml | 1 + 4 files changed, 204 insertions(+), 111 deletions(-) create mode 100644 docs/6-collaboration/preset-checks.md diff --git a/docs/6-collaboration/checklist.md b/docs/6-collaboration/checklist.md index 7638660..ba6ba2c 100644 --- a/docs/6-collaboration/checklist.md +++ b/docs/6-collaboration/checklist.md @@ -2,42 +2,49 @@ title: Checklist --- -## What's Checklist +# Checklist -Save your validation checks to the Recce checklist with a description of your findings. +Save validation checks to track your findings and share them with reviewers. The checklist becomes your proof-of-correctness for modeling changes. -These checks can later be added to your pull request comment as proof-of-correctness for your modeling changes. +## How It Works + +When you run a diff or query in Recce, you can add the result to your checklist. Each check captures: + +- The validation type (schema diff, row count, query, etc.) +- The result at the time of capture +- Your notes explaining what the result means
![Recce Checklist](../assets/images/6-collaboration/checklist.png) -
Checklist
+
Checklist with saved validation checks
+### Adding Checks -## Diffs performed via the Explore Change dropdown menu - -For the majority of diffs, which are performed via the Explore Change dropdown menu, the Check can be added by clicking the Add to Checklist button in the results panel: +For diffs performed via the Explore Change dropdown menu, click **Add to Checklist** in the results panel:
![Add a Check by clicking the Add to Checklist button in the diff results panel](../assets/images/6-collaboration/add-to-checklist-button.png){: .shadow} -
Add a Check by clicking the Add to Checklist button in the diff results panel
+
Add to Checklist button in diff results panel
-An example performing a Top-K diff and adding the results to the Checklist: -
![Example adding a Top-K Diff to the Checklist](../assets/images/6-collaboration/add-to-checklist.gif){: .shadow} -
Example adding a Top-K Diff to the Checklist
+
Example: Adding a Top-K Diff to the Checklist
-## Add to Checklist +### Re-running Checks + +After making additional changes to your models, re-run checks from the checklist to verify your updates. This lets you iterate until all validations pass. -The Recce Checklist provides a way to record the results of a data check during change exploration. The purpose of adding Checks to the Checklist is to enable you to: +## When to Use -- Save Checks with notes of your interpretation of the data -- Re-run checks following further data modeling changes -- Share Checks as part of PR or stakeholder review +- **During development** - Save checks as you validate each change, building evidence as you go +- **Before creating a PR** - Compile all validations that prove your changes are correct +- **For recurring validations** - Use [Preset Checks](preset-checks.md) to automate checks that should run on every PR +- **Stakeholder review** - [Share](share.md) your checklist to give reviewers full context -## Preset Check +## Related -Preset checks can be the fixed checks that are generated every time a new Recce instance is initiated. \ No newline at end of file +- [Preset Checks](preset-checks.md) - Automate recurring validation checks +- [Share](share.md) - Share your checklist with reviewers diff --git a/docs/6-collaboration/preset-checks.md b/docs/6-collaboration/preset-checks.md new file mode 100644 index 0000000..a67d330 --- /dev/null +++ b/docs/6-collaboration/preset-checks.md @@ -0,0 +1,115 @@ +--- +title: Preset Checks +--- + +# Preset Checks + +Define validation checks that run automatically for every PR. Preset checks ensure consistent validation across your team. + +**Goal:** Configure recurring checks that execute automatically when Recce runs. + +## Prerequisites + +- [x] Recce installed in your dbt project +- [x] At least one check you want to automate + +## Configure a Preset Check + +### 1. Create a Check Template + +Start by adding a check to your checklist manually: + +1. Run a diff or query in Recce +2. Add the result to your checklist + + ![Add check to checklist](../assets/images/7-cicd/preset-checks-prep.png){: .shadow} + +3. Open the check menu and select **Get Preset Check Template** +4. Copy the YAML config from the dialog + + ![Get preset check template](../assets/images/7-cicd/preset-checks-template.png){: .shadow} + +### 2. Add to recce.yml + +Paste the config into `recce.yml` at your project root: + +```yaml +# recce.yml +checks: + - name: Query diff of customers + description: | + This is the demo preset check. + + Please run the query and paste the screenshot to the PR comment. + type: query_diff + params: + sql_template: select * from {{ ref("customers") }} + view_options: + primary_keys: + - customer_id +``` + +## Running Preset Checks + +### In Recce Server + +When you launch Recce, preset checks appear in your checklist automatically (but not yet executed): + +![Preset checks in checklist](../assets/images/7-cicd/preset-checks.png){: .shadow} + +Click **Run Query** to execute each check. + +### With recce run + +Execute all preset checks from the command line: + +```bash +recce run +``` + +Output: +``` +───────────────────────────────── DBT Artifacts ───────────────────────────────── +Base: + Manifest: 2024-04-10 08:54:41.546402+00:00 + Catalog: 2024-04-10 08:54:42.251611+00:00 +Current: + Manifest: 2024-04-22 03:24:11.262489+00:00 + Catalog: 2024-04-10 06:15:13.813125+00:00 +───────────────────────────────── Preset checks ───────────────────────────────── + Recce Preset Checks +────────────────────────────────────────────────────────────────────────────── +Status Name Type Execution Time Failed Reason +────────────────────────────────────────────────────────────────────────────── +[Success] Query of customers Query Diff 0.10 seconds N/A +────────────────────────────────────────────────────────────────────────────── +The state file is stored at [recce_state.json] +``` + +View results by launching the server with the state file: + +```bash +recce server recce_state.json +``` + +## Verification + +Confirm preset checks work: + +1. Add a check config to `recce.yml` +2. Run `recce run` +3. Verify the check appears in output with `[Success]` status +4. Launch `recce server recce_state.json` and confirm the check appears in your checklist + +## Troubleshooting + +| Issue | Solution | +|-------|----------| +| Check not appearing | Verify `recce.yml` is in project root and YAML syntax is valid | +| Check fails to run | Check that the SQL template references valid models | +| Wrong results | Ensure base and current artifacts are up to date | + +## Related + +- [Checklist](checklist.md) - Manually add checks during development +- [Configuration](../8-technical-concepts/configuration.md) - Full recce.yml reference diff --git a/docs/6-collaboration/share.md b/docs/6-collaboration/share.md index 6640e3f..4b7db38 100644 --- a/docs/6-collaboration/share.md +++ b/docs/6-collaboration/share.md @@ -2,152 +2,122 @@ title: Share --- -## Share Recce Sessions Securely +# Share -Recce provides two secure methods to share your validation results with team members and stakeholders, ensuring everyone can access the insights they need for informed decision-making. +Share your validation results with team members and stakeholders. -## Sharing Methods Overview +**Goal:** Give reviewers access to your Recce session so they can explore validation results. + +## Prerequisites + +- [x] Recce session with checks in your checklist +- [x] Recce Cloud account (for full session sharing) + +## Sharing Methods + +Choose the method that fits your collaboration needs: + +| Method | Best For | Requires | +|--------|----------|----------| +| **Copy to Clipboard** | Quick screenshots in PR comments | Nothing | +| **Recce Cloud Sharing** | Full interactive session access | Recce Cloud account |
![Click Share](../assets/images/6-collaboration/share-button.png){: .shadow}
Access sharing options from the Share button
-Choose the sharing method that best fits your collaboration needs: - -1. **Copy to Clipboard** - Quick screenshot sharing for PR comments and discussions -2. **Recce Cloud Sharing** - Full interactive session sharing with complete context - ## Method 1: Copy to Clipboard -For quick sharing of specific results, use the **Copy to Clipboard** button found in diff results. This feature captures a screenshot image that you can paste directly into PR comments, Slack messages, or other communication channels. +For quick sharing of specific results, use **Copy to Clipboard** in any diff result. Paste the screenshot directly into PR comments, Slack, or other channels.
![Copy a diff screenshot to the clipboard - Multiple models](../assets/images/6-collaboration/clipboard-to-github.gif){: .shadow} -
Copy a diff result screenshot to the clipboard and paste to GitHub
+
Copy diff result and paste to GitHub
!!! note "Browser Compatibility" - Firefox does not support copying images to the clipboard. Instead, Recce displays a modal where you can download the image locally or right-click to copy the image. + Firefox does not support copying images to the clipboard. Recce displays a modal where you can download or right-click to copy the image. ## Method 2: Recce Cloud Sharing -When stakeholders need full context but don't have the environment to run Recce locally, use Recce Cloud sharing. This method creates a read-only link that provides complete access to your validation results. +When reviewers need full context, share via Recce Cloud. This creates a read-only link with complete access to your validation results. -### Benefits of Recce Cloud Sharing +**Benefits:** -- **No Setup Required** - Stakeholders access results instantly in their browser -- **Full Context** - Complete lineage exploration, query results, and validation checklists -- **Read-Only Access** - Secure viewing without ability to modify your work -- **Simple Link Sharing** - Share via any communication channel +- No setup required for viewers +- Full lineage exploration, query results, and checklists +- Read-only access (secure viewing) +- Simple link sharing via any channel !!! warning "Access Control" - Anyone with the shared link can view your Recce session after signing into Recce Cloud. For restricted access requirements, [contact our team](https://cal.com/team/recce/chat). - -## Setting Up Recce Cloud Sharing + Anyone with the link can view your session after signing into Recce Cloud. For restricted access, [contact our team](https://cal.com/team/recce/chat). -The first time you share via Recce Cloud, you'll need to associate your local Recce with your cloud account. This one-time setup enables secure hosting of your state files. -### Step 1: Enable Recce Cloud Connection +### First-Time Setup -Launch the Recce server and click the **Use Recce Cloud** button if your local installation isn't already connected to Recce Cloud. +1. Launch Recce server and click **Use Recce Cloud** if not already connected -![Recce Server](../assets/images/6-collaboration/recce-server-use-recce-cloud-for-free.png){: .shadow} + ![Recce Server](../assets/images/6-collaboration/recce-server-use-recce-cloud-for-free.png){: .shadow} -### Step 2: Sign In and Grant Access +2. Sign in and authorize your local Recce to connect with Recce Cloud -After successful login, authorize your local Recce to connect with Recce Cloud. This authorization enables the sharing functionality and secure state file hosting. + ![Request Approved](../assets/images/6-collaboration/recce-cloud-connection-request-approved.png){: .shadow} -![Request Approved](../assets/images/6-collaboration/recce-cloud-connection-request-approved.png){: .shadow} +3. Refresh the page to activate the connection. The **Share** button is now available. -### Step 3: Complete the Setup + ![Recce Share From Server](../assets/images/6-collaboration/recce-share-from-server-fs8.png){: .shadow} -Refresh the Recce page to activate the cloud connection. Once connected, the **Share** button will be available, allowing you to generate shareable links. - -![Recce Share From Server](../assets/images/6-collaboration/recce-share-from-server-fs8.png){: .shadow} - -!!! tip "Alternative Setup Method" - You can also connect to Recce Cloud using the command line: - +!!! tip "Alternative: CLI Setup" ```bash recce connect-to-cloud ``` - - This command handles the sign-in and authorization process directly from your terminal. - - -## Manual Configuration (Advanced) - -For containerized environments or when you prefer manual setup, you can configure the Recce Cloud connection directly using your API token. - -### Step 1: Retrieve Your API Token - -Sign in to Recce Cloud and copy your API token from the [personal settings page](https://cloud.reccehq.com/settings#tokens). - -![Recce API Token](../assets/images/6-collaboration/setting-page-api-token-fs8.png){: .shadow} -### Step 2: Configure Local Connection +### Manual Configuration (Advanced) -Choose one of the following methods to configure your local Recce: +For containerized environments or manual setup: -#### Option A: Command Line Flag +1. Get your API token from [Recce Cloud settings](https://cloud.reccehq.com/settings#tokens) -Launch Recce server with your API token. The token will be saved to your profile for future use: + ![Recce API Token](../assets/images/6-collaboration/setting-page-api-token-fs8.png){: .shadow} -```bash -recce server --api-token -``` - -#### Option B: Profile Configuration - -Edit your `~/.recce/profile.yml` file to include the API token: +2. Configure using one of these methods: -```yaml -api_token: -``` - -!!! info "Configuration File Location" - **Mac/Linux:** - ```shell - cd ~/.recce - ``` - - **Windows:** - ```powershell - cd ~\.recce + **Option A: Command line flag** + ```bash + recce server --api-token ``` - - Navigate to `C:\Users\\.recce` or use the PowerShell command above. + **Option B: Profile configuration** + ```yaml + # ~/.recce/profile.yml + api_token: + ``` -## Command Line Sharing - -For automated workflows or when working with existing state files, use the `recce share` command to generate shareable links directly from the terminal. - -### Basic Sharing +### Command Line Sharing -If your Recce is already connected to Recce Cloud: +Share existing state files directly from the terminal: ```bash +# If already connected to Recce Cloud recce share -``` -### Sharing with API Token - -For environments where Recce isn't pre-configured with cloud access: - -```bash +# With API token recce share --api-token ``` ![Recce Share From CLI](../assets/images/6-collaboration/recce-share-from-cli.png){: .shadow} -## Security Best Practices +## Verification + +Confirm sharing works: -When sharing Recce sessions, consider these security guidelines: +1. Add a check to your checklist +2. Click **Share** and select Recce Cloud +3. Copy the generated link +4. Open the link in an incognito window +5. Verify you can view the session after signing in -- **Review Content**: Ensure shared sessions don't contain sensitive data before generating links -- **Access Control**: Be aware that anyone with the link can view your session after signing in -- **Token Security**: Keep your API tokens secure and rotate them periodically -- **Team Communication**: Share links through secure channels when possible +## Related -For additional security requirements or enterprise features, [contact our team](https://cal.com/team/recce/chat) to discuss custom access controls. +- [Checklist](checklist.md) - Save validation checks to share +- [Preset Checks](preset-checks.md) - Automate recurring checks diff --git a/mkdocs.yml b/mkdocs.yml index 71c579d..41b06b8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -80,6 +80,7 @@ nav: - 5-what-you-can-explore/multi-models.md - Collaborate Validation: - 6-collaboration/checklist.md + - 6-collaboration/preset-checks.md - 6-collaboration/share.md - CI/CD: - 7-cicd/pr-mr-summary.md From 5a0237a56b8294aa2f989c2b2fd8f8ddab8997ad Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Sat, 7 Mar 2026 22:35:50 +0800 Subject: [PATCH 41/43] docs(v3): Update collaboration pages with cloud-first structure - checklist.md: Add link to Preset Checks in Re-running Checks section - share.md: Restructure with Recce Cloud first (URL sharing), OSS methods second - preset-checks.md: Restructure with Recce Cloud first (UI-based), OSS (recce.yml) second - mkdocs.yml: Remove duplicate preset-checks entry from CI/CD nav Co-Authored-By: Claude Opus 4.5 --- docs/6-collaboration/checklist.md | 2 + docs/6-collaboration/preset-checks.md | 88 +++++++++++++++++---------- docs/6-collaboration/share.md | 48 ++++++++------- mkdocs.yml | 1 - 4 files changed, 85 insertions(+), 54 deletions(-) diff --git a/docs/6-collaboration/checklist.md b/docs/6-collaboration/checklist.md index ba6ba2c..172bb2a 100644 --- a/docs/6-collaboration/checklist.md +++ b/docs/6-collaboration/checklist.md @@ -37,6 +37,8 @@ For diffs performed via the Explore Change dropdown menu, click **Add to Checkli After making additional changes to your models, re-run checks from the checklist to verify your updates. This lets you iterate until all validations pass. +For checks you want to run on every PR automatically, see [Preset Checks](preset-checks.md). + ## When to Use - **During development** - Save checks as you validate each change, building evidence as you go diff --git a/docs/6-collaboration/preset-checks.md b/docs/6-collaboration/preset-checks.md index a67d330..b79876d 100644 --- a/docs/6-collaboration/preset-checks.md +++ b/docs/6-collaboration/preset-checks.md @@ -10,48 +10,74 @@ Define validation checks that run automatically for every PR. Preset checks ensu ## Prerequisites -- [x] Recce installed in your dbt project -- [x] At least one check you want to automate +- [x] Recce Cloud account or Recce installed in your dbt project +- [x] At least one validation check you want to automate -## Configure a Preset Check +## Recce Cloud -### 1. Create a Check Template +Create preset checks directly in the Recce Cloud interface. When a PR is created, preset checks run automatically. -Start by adding a check to your checklist manually: +### From the checklist -1. Run a diff or query in Recce +Mark any existing check as a preset check: + +1. Run a diff or query in your Recce session 2. Add the result to your checklist +3. Open the check menu and select **Mark as Preset Check** - ![Add check to checklist](../assets/images/7-cicd/preset-checks-prep.png){: .shadow} + -3. Open the check menu and select **Get Preset Check Template** -4. Copy the YAML config from the dialog +### From project settings - ![Get preset check template](../assets/images/7-cicd/preset-checks-template.png){: .shadow} +Create preset checks directly in your project configuration: -### 2. Add to recce.yml +1. Navigate to your project's **Preset Checks** page +2. Click **Add Preset Check** +3. Configure the check type and parameters -Paste the config into `recce.yml` at your project root: + -```yaml -# recce.yml -checks: - - name: Query diff of customers - description: | - This is the demo preset check. +When preset checks are configured, they run automatically each time a PR is created. - Please run the query and paste the screenshot to the PR comment. - type: query_diff - params: - sql_template: select * from {{ ref("customers") }} - view_options: - primary_keys: - - customer_id -``` +## Recce OSS + +For local Recce, configure preset checks in `recce.yml` and run them manually or in CI. + +### Configure in recce.yml + +1. Start by adding a check to your checklist manually: + + 1. Run a diff or query in Recce + 2. Add the result to your checklist + + ![Add check to checklist](../assets/images/7-cicd/preset-checks-prep.png){: .shadow} + + 3. Open the check menu and select **Get Preset Check Template** + 4. Copy the YAML config from the dialog + + ![Get preset check template](../assets/images/7-cicd/preset-checks-template.png){: .shadow} + +2. Paste the config into `recce.yml` at your project root: + + ```yaml + # recce.yml + checks: + - name: Query diff of customers + description: | + This is the demo preset check. + + Please run the query and paste the screenshot to the PR comment. + type: query_diff + params: + sql_template: select * from {{ ref("customers") }} + view_options: + primary_keys: + - customer_id + ``` -## Running Preset Checks +### Run preset checks -### In Recce Server +#### In Recce server When you launch Recce, preset checks appear in your checklist automatically (but not yet executed): @@ -59,7 +85,7 @@ When you launch Recce, preset checks appear in your checklist automatically (but Click **Run Query** to execute each check. -### With recce run +#### With recce run Execute all preset checks from the command line: @@ -92,7 +118,7 @@ View results by launching the server with the state file: recce server recce_state.json ``` -## Verification +### Verification Confirm preset checks work: @@ -101,7 +127,7 @@ Confirm preset checks work: 3. Verify the check appears in output with `[Success]` status 4. Launch `recce server recce_state.json` and confirm the check appears in your checklist -## Troubleshooting +### Troubleshooting | Issue | Solution | |-------|----------| diff --git a/docs/6-collaboration/share.md b/docs/6-collaboration/share.md index 4b7db38..90f4cd0 100644 --- a/docs/6-collaboration/share.md +++ b/docs/6-collaboration/share.md @@ -8,26 +8,23 @@ Share your validation results with team members and stakeholders. **Goal:** Give reviewers access to your Recce session so they can explore validation results. -## Prerequisites -- [x] Recce session with checks in your checklist -- [x] Recce Cloud account (for full session sharing) +## Recce Cloud -## Sharing Methods +Share your session by copying the URL directly from your browser. Team members with organization access can view any session immediately. -Choose the method that fits your collaboration needs: +To invite team members to your organization, see [Admin Setup](../3-using-recce/admin-setup.md#5-invite-team-members). + +## Recce OSS + +For local Recce sessions, use these sharing methods: | Method | Best For | Requires | |--------|----------|----------| | **Copy to Clipboard** | Quick screenshots in PR comments | Nothing | -| **Recce Cloud Sharing** | Full interactive session access | Recce Cloud account | - -
- ![Click Share](../assets/images/6-collaboration/share-button.png){: .shadow} -
Access sharing options from the Share button
-
+| **Upload to Recce Cloud** | Full interactive session access | Recce Cloud account | -## Method 1: Copy to Clipboard +### Copy to Clipboard For quick sharing of specific results, use **Copy to Clipboard** in any diff result. Paste the screenshot directly into PR comments, Slack, or other channels. @@ -39,9 +36,9 @@ For quick sharing of specific results, use **Copy to Clipboard** in any diff res !!! note "Browser Compatibility" Firefox does not support copying images to the clipboard. Recce displays a modal where you can download or right-click to copy the image. -## Method 2: Recce Cloud Sharing +### Upload to Recce Cloud -When reviewers need full context, share via Recce Cloud. This creates a read-only link with complete access to your validation results. +When reviewers need full context, upload your session to Recce Cloud. This creates a shareable link with complete access to your validation results. **Benefits:** @@ -53,7 +50,7 @@ When reviewers need full context, share via Recce Cloud. This creates a read-onl !!! warning "Access Control" Anyone with the link can view your session after signing into Recce Cloud. For restricted access, [contact our team](https://cal.com/team/recce/chat). -### First-Time Setup +#### First-time setup 1. Launch Recce server and click **Use Recce Cloud** if not already connected @@ -72,7 +69,7 @@ When reviewers need full context, share via Recce Cloud. This creates a read-onl recce connect-to-cloud ``` -### Manual Configuration (Advanced) +#### Manual configuration (advanced) For containerized environments or manual setup: @@ -93,9 +90,16 @@ For containerized environments or manual setup: api_token: ``` -### Command Line Sharing +#### Share from UI or CLI + +**From UI:** Click the **Share** button and select Recce Cloud. + +
+ ![Click Share](../assets/images/6-collaboration/share-button.png){: .shadow} +
Access sharing options from the Share button
+
-Share existing state files directly from the terminal: +**From CLI:** Share existing state files directly from the terminal: ```bash # If already connected to Recce Cloud @@ -112,12 +116,12 @@ recce share --api-token Confirm sharing works: 1. Add a check to your checklist -2. Click **Share** and select Recce Cloud -3. Copy the generated link -4. Open the link in an incognito window -5. Verify you can view the session after signing in +2. Share via your preferred method (URL for Cloud, Share button for OSS) +3. Open the link in an incognito window +4. Verify you can view the session ## Related +- [Admin Setup](../3-using-recce/admin-setup.md) - Invite team members to your organization - [Checklist](checklist.md) - Save validation checks to share - [Preset Checks](preset-checks.md) - Automate recurring checks diff --git a/mkdocs.yml b/mkdocs.yml index 41b06b8..10604fa 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -87,7 +87,6 @@ nav: #- 7-cicd/recce-debug.md # content outdated - 7-cicd/scenario-dev.md - 7-cicd/scenario-pr-review.md - - 7-cicd/preset-checks.md - Reference: - 7-reference/configuration.md From 3e3471546f3c31a49a2411659b2b961a144f6cd3 Mon Sep 17 00:00:00 2001 From: Karen Hsieh Date: Sat, 7 Mar 2026 22:45:45 +0800 Subject: [PATCH 42/43] docs: Add image placeholders for Recce Cloud preset checks Co-Authored-By: Claude Opus 4.5 --- docs/6-collaboration/preset-checks.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/6-collaboration/preset-checks.md b/docs/6-collaboration/preset-checks.md index b79876d..5f17c80 100644 --- a/docs/6-collaboration/preset-checks.md +++ b/docs/6-collaboration/preset-checks.md @@ -25,7 +25,7 @@ Mark any existing check as a preset check: 2. Add the result to your checklist 3. Open the check menu and select **Mark as Preset Check** - +![Mark as Preset Check from checklist](../assets/images/6-collaboration/mark-preset-check.png){: .shadow} ### From project settings @@ -35,7 +35,9 @@ Create preset checks directly in your project configuration: 2. Click **Add Preset Check** 3. Configure the check type and parameters - +![Project Preset Checks page](../assets/images/6-collaboration/preset-check-in-cloud.png){: .shadow} + +![Create Preset Checks directly in Recce Cloud](../assets/images/6-collaboration/create-preset-check-in-cloud.png){: .shadow} When preset checks are configured, they run automatically each time a PR is created. From 0a2e4fb4ed081c2116006f9af1743256d17743c1 Mon Sep 17 00:00:00 2001 From: Dori Wilson <44557701+doriwilson@users.noreply.github.com> Date: Sat, 7 Mar 2026 12:38:55 -0600 Subject: [PATCH 43/43] Rewrite start-free-with-cloud.md from mega-page to overview+router MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Reduce from 351 to 160 lines by removing duplicated YAML blocks - Replace inline profiles.yml, base-workflow.yml, pr-workflow.yml with links to dedicated pages (environment-setup, setup-cd, setup-ci) - Fix CI_SCHEMA → SNOWFLAKE_SCHEMA in environment-setup.md for consistency - Add breadcrumb admonitions to spoke pages (environment-setup, setup-cd, setup-ci) for onboarding navigation - Fix workflow name mismatch in setup-cd.md verification step - Update router table to use plain text instead of self-referencing anchors Co-Authored-By: Claude Opus 4.6 --- docs/2-getting-started/connect-git.md | 85 +++++ .../2-getting-started/connect-to-warehouse.md | 107 ++++++ docs/2-getting-started/environment-setup.md | 22 +- docs/2-getting-started/setup-cd.md | 40 ++- docs/2-getting-started/setup-ci.md | 30 +- .../start-free-with-cloud.md | 319 ++++-------------- .../images/2-getting-started/connect-dw.png | Bin 0 -> 32738 bytes .../2-getting-started/connect-github.png | Bin 0 -> 28530 bytes .../2-getting-started/connect-gitlab.png | Bin 0 -> 28465 bytes .../images/2-getting-started/org-projects.png | Bin 0 -> 32103 bytes 10 files changed, 311 insertions(+), 292 deletions(-) create mode 100644 docs/2-getting-started/connect-git.md create mode 100644 docs/2-getting-started/connect-to-warehouse.md create mode 100644 docs/assets/images/2-getting-started/connect-dw.png create mode 100644 docs/assets/images/2-getting-started/connect-github.png create mode 100644 docs/assets/images/2-getting-started/connect-gitlab.png create mode 100644 docs/assets/images/2-getting-started/org-projects.png diff --git a/docs/2-getting-started/connect-git.md b/docs/2-getting-started/connect-git.md new file mode 100644 index 0000000..a702c60 --- /dev/null +++ b/docs/2-getting-started/connect-git.md @@ -0,0 +1,85 @@ +# Connect Your Repository + +**Goal:** Connect your GitHub or GitLab repository to Recce Cloud for automated PR data review. + +Recce Cloud supports GitHub and GitLab. Using a different provider? Contact us at support@reccehq.com. + +## Prerequisites + +- [x] Recce Cloud account (free trial at cloud.reccehq.com) +- [x] Repository admin access (required to authorize app installation) +- [x] dbt project in the repository + +## How It Works + +When you connect a Git provider, Recce Cloud maps your setup: + +| Git Provider | Recce Cloud | +|--------------|-------------| +| Organization | Organization | +| Repository | Project | + +Every Recce Cloud account starts with one organization and one project. When you connect your Git provider, you select which organization and repository to link. + +**Monorepo support:** If you have multiple dbt projects in one repository, you can create multiple Recce Cloud projects that connect to the same repo. + + +## Connect GitHub + +### 1. Authorize the Recce GitHub App + +Navigate to Settings → Git Provider in Recce Cloud. Click **Connect GitHub**. + +**Expected result:** GitHub authorization page opens. + +### 2. Select Organization and Repository + +Choose which GitHub organization to connect. This becomes your Recce Cloud organization. + +Then select the repository containing your dbt project. This becomes your Recce Cloud project. + +**Expected result:** Repository connected. Your Recce Cloud project is ready to use. + +![alt text](../assets/images/2-getting-started/connect-github.png){: .shadow} + +## Connect GitLab + +GitLab uses Personal Access Tokens (PAT) instead of OAuth. + +### 1. Create a Personal Access Token + +In GitLab: User Settings → Access Tokens → Add new token. + +**Required scopes:** + +- `api` - Full access (required for PR comments) +- `read_api` - Read-only alternative (limited functionality) + +**Expected result:** Token string displayed (copy immediately). + +### 2. Add Token to Recce Cloud + +Navigate to Settings → Git Provider. Select GitLab, paste token. + +## Verify Success + +In Recce Cloud, navigate to your repository. You should see: + +- Connection status: "Connected" +- Organization Project is linked to a git repository + +![alt text](../assets/images/2-getting-started/connect-gitlab.png){: .shadow} +![alt text](../assets/images/2-getting-started/org-projects.png){: .shadow} + +## Troubleshooting + +| Issue | Solution | +| --- | --- | +| Repository not found | Ensure proper permissions are granted (GitLab: token access, GitHub: app authorized) | +| Invalid token (GitLab) | Generate new token with `api` scope | +| Cannot post PR comments (GitLab) | Regenerate token with `api` scope instead of `read_api` | + +## Next Steps + +- [Connect Data Warehouse](connect-to-warehouse.md) +- [Add Recce to CI/CD](../7-cicd/ci-cd-getting-started.md) diff --git a/docs/2-getting-started/connect-to-warehouse.md b/docs/2-getting-started/connect-to-warehouse.md new file mode 100644 index 0000000..ef36911 --- /dev/null +++ b/docs/2-getting-started/connect-to-warehouse.md @@ -0,0 +1,107 @@ +# Connect Data Warehouse + +**Goal:** Connect your data warehouse to Recce Cloud to enable data diffing on PRs. + +Recce Cloud supports **[Snowflake](#connect-snowflake), [Databricks](#connect-databricks), [BigQuery](#connect-bigquery), and [Redshift](connect-redshift)**. Using a different warehouse? Contact us at support@reccehq.com. + +## Prerequisites + +- [x] Warehouse credentials with read access +- [x] Network access configured (IP whitelisting if required) + +## Security + +Recce Cloud queries your warehouse directly to compare Base and Current environments. Recce encrypts and stores credentials securely. Read-only access is sufficient for all data diffing features. + +## Connect Snowflake + +### Option 1: Username/Password + +| Field | Description | Example | +|-------|-------------|---------| +| Account | Snowflake account identifier | `xxxxxx.us-central1.gcp` | +| Username | Database username | `MY_USER` | +| Password | Database password | `my_password` | +| Role | Role with read access | `ANALYST_ROLE` | +| Warehouse | Compute warehouse name | `WH_LOAD` | + +### Option 2: Key Pair Authentication + +| Field | Description | Example | +|-------|-------------|---------| +| Account | Snowflake account identifier | `xxxxxx.us-central1.gcp` | +| Username | Service account username | `MY_USER` | +| Private Key | PEM-formatted private key | `-----BEGIN RSA PRIVATE KEY-----...` | +| Passphrase | Key passphrase (if encrypted) | `my_passphrase` | +| Role | Role with read access | `ANALYST_ROLE` | +| Warehouse | Compute warehouse name | `WH_LOAD` | + +## Connect Databricks + +### Option 1: Personal Access Token + +| Field | Description | Example | +|-------|-------------|---------| +| Host | Workspace URL | `adb-1234567890123456.7.azuredatabricks.net` | +| HTTP Path | SQL warehouse path | `/sql/1.0/warehouses/abc123def456` | +| Token | Personal access token | `dapiXXXXXXXXXXXXXXXXXXXXXXX` | +| Catalog | Unity Catalog name (optional) | `my_catalog` | + +### Option 2: OAuth (M2M) + +| Field | Description | Example | +|-------|-------------|---------| +| Host | Workspace URL | `adb-1234567890123456.7.azuredatabricks.net` | +| HTTP Path | SQL warehouse path | `/sql/1.0/warehouses/abc123def456` | +| Client ID | Service principal client ID | `12345678-1234-1234-1234-123456789012` | +| Client Secret | Service principal secret | `dose1234567890abcdef` | +| Catalog | Unity Catalog name (optional) | `my_catalog` | + + +> **Note**: OAuth M2M is auto-enabled in Databricks accounts. For setup details, see [dbt Databricks setup](https://docs.getdbt.com/docs/core/connect-data-platform/databricks-setup#oauth-machine-to-machine-m2m-authentication). + +## Connect BigQuery + +| Field | Description | Example | +|-------|-------------|---------| +| Project | GCP project ID | `my-gcp-project-123456` | +| Service Account JSON | Full JSON key file contents | `{"type": "service_account", ...}` | + + +> **Note**: For authentication, we currently provide support for service account JSON only. More details [here](https://docs.getdbt.com/docs/core/connect-data-platform/bigquery-setup#service-account-json). + +## Connect Redshift + +| Field | Description | Example | +|-------|-------------|---------| +| Host | Cluster endpoint | `my-cluster.abc123xyz.us-west-2.redshift.amazonaws.com` | +| Port | Database port | `5439` (Default) | +| Database | Database name | `analytics_db` | +| Username | Database user | `admin_user` | +| Password | Database password | `my_password` | + + +> **Note**: We currently support Database (Password-based authentication) only. More details [here](https://docs.getdbt.com/docs/core/connect-data-platform/redshift-setup#authentication-parameters). + +## Save Connection + +After entering your connection details, click **Save**. Recce Cloud runs a connection test automatically and displays "Connected" on success. + +## Verify Success + +Navigate to Organization Settings in Recce Cloud. Your data warehouse should appear. + +![alt text](../assets/images/2-getting-started/connect-dw.png){: .shadow} + +## Troubleshooting + +| Issue | Solution | +| --- | --- | +| Connection refused | Whitelist Recce Cloud IP ranges in your network configuration | +| Authentication failed | Verify credentials and regenerate if expired | +| Permission denied on table | Grant SELECT permissions on target schemas | + +## Next Steps + +- [Add Recce to CI/CD](../7-cicd/setup-ci.md) +- [Run Your First Data Diff](../5-data-diffing/row-count-diff.md) diff --git a/docs/2-getting-started/environment-setup.md b/docs/2-getting-started/environment-setup.md index 11655d2..641a850 100644 --- a/docs/2-getting-started/environment-setup.md +++ b/docs/2-getting-started/environment-setup.md @@ -1,7 +1,13 @@ --- title: Environment Setup +description: >- + Configure dbt profiles and CI/CD environment variables for Recce data validation. + Set up isolated schemas for base vs current comparison on pull requests. --- +!!! tip "Following the onboarding guide?" + Return to [Get Started with Recce Cloud](start-free-with-cloud.md#3-add-recce-to-cicd) after completing this page. + # Environment Setup Configure your dbt profiles and CI/CD environment variables for Recce data validation. @@ -12,9 +18,9 @@ Set up isolated schemas for base vs current comparison. After completing this gu ## Prerequisites -- [x] **dbt project**: A working dbt project with `profiles.yml` configured -- [x] **CI/CD platform**: GitHub Actions, GitLab CI, or similar -- [x] **Warehouse access**: Credentials with permissions to create schemas dynamically +- [ ] **dbt project**: A working dbt project with `profiles.yml` configured +- [ ] **CI/CD platform**: GitHub Actions, GitLab CI, or similar +- [ ] **Warehouse access**: Credentials with permissions to create schemas dynamically ## Why separate schemas matter @@ -60,7 +66,7 @@ jaffle_shop: password: "{{ env_var('SNOWFLAKE_PASSWORD') }}" database: analytics warehouse: COMPUTE_WH - schema: "{{ env_var('CI_SCHEMA') }}" + schema: "{{ env_var('SNOWFLAKE_SCHEMA') }}" threads: 4 prod: @@ -78,7 +84,7 @@ After saving, your profile supports three targets: `dev` for local development, Key points: -- The `ci` target uses `env_var('CI_SCHEMA')` for dynamic schema assignment +- The `ci` target uses `env_var('SNOWFLAKE_SCHEMA')` for dynamic schema assignment (other warehouses use their own variable name) - The `prod` target uses a fixed schema (`public`) for consistency - Adapt this pattern for other warehouses (BigQuery uses `dataset` instead of `schema`) @@ -90,17 +96,17 @@ Your CI/CD workflow sets the schema dynamically for each PR. The key configurati ```yaml env: - CI_SCHEMA: "pr_${{ github.event.pull_request.number }}" + SNOWFLAKE_SCHEMA: "PR_${{ github.event.pull_request.number }}" ``` **GitLab CI:** ```yaml variables: - CI_SCHEMA: "mr_${CI_MERGE_REQUEST_IID}" + SNOWFLAKE_SCHEMA: "MR_${CI_MERGE_REQUEST_IID}" ``` -This creates schemas like `pr_123`, `pr_456` for each PR automatically. When a PR opens, the workflow sets `CI_SCHEMA` and dbt writes to that isolated schema. +This creates schemas like `PR_123`, `PR_456` for each PR automatically. When a PR opens, the workflow sets `SNOWFLAKE_SCHEMA` and dbt writes to that isolated schema. For complete workflow examples, see [Setup CD](setup-cd.md) and [Setup CI](setup-ci.md). diff --git a/docs/2-getting-started/setup-cd.md b/docs/2-getting-started/setup-cd.md index 2a6cd11..a24756d 100644 --- a/docs/2-getting-started/setup-cd.md +++ b/docs/2-getting-started/setup-cd.md @@ -1,12 +1,18 @@ --- title: Setup CD +description: >- + Automate baseline updates for Recce Cloud with a continuous deployment workflow. + Keep your dbt validation baseline current after every merge to main. --- +!!! tip "Following the onboarding guide?" + Return to [Get Started with Recce Cloud](start-free-with-cloud.md#3-add-recce-to-cicd) after completing this page. + # Setup CD - Auto-Update Baseline Manually updating your Recce Cloud baseline after every merge is tedious and error-prone. This guide shows you how to automate baseline updates so your data comparison stays current without manual intervention. -After completing this guide, your continuous deployment (CD) workflow automatically uploads dbt artifacts to Recce Cloud whenever code merges to main. +After completing this guide, your continuous deployment (CD) workflow automatically uploads dbt artifacts to Cloud whenever code merges to main. ## What This Does @@ -20,10 +26,10 @@ After completing this guide, your continuous deployment (CD) workflow automatica Before setting up CD, ensure you have: -- [x] **Recce Cloud account** - [Start free trial](https://cloud.reccehq.com/) -- [x] **Repository connected** to Recce Cloud - [Connect Git Provider](start-free-with-cloud.md#2-connect-git-provider) -- [x] **dbt artifacts** - Know how to generate `manifest.json` and `catalog.json` from your dbt project -- [x] **Environment configured** - [Environment Setup](environment-setup.md) with `prod` target for base artifacts +- [ ] **Cloud account** - [Start free trial](https://cloud.reccehq.com/) +- [ ] **Repository connected** to Cloud - [Connect Git Provider](start-free-with-cloud.md#2-connect-git-provider) +- [ ] **dbt artifacts** - Know how to generate `manifest.json` and `catalog.json` from your dbt project +- [ ] **Environment configured** - [Environment Setup](environment-setup.md) with `prod` target for base artifacts ## Environment strategy @@ -94,8 +100,8 @@ jobs: **Key points:** - `dbt build` and `dbt docs generate` create the required artifacts (`manifest.json` and `catalog.json`) -- `recce-cloud upload --type prod` uploads the Base metadata to Recce Cloud -- [`GITHUB_TOKEN`](https://docs.github.com/en/actions/concepts/security/github_token) authenticates with Recce Cloud +- `recce-cloud upload --type prod` uploads the Base metadata to Cloud +- [`GITHUB_TOKEN`](https://docs.github.com/en/actions/concepts/security/github_token) authenticates with Cloud ### GitLab CI/CD @@ -162,7 +168,7 @@ recce-upload-prod: **GitHub:** -1. Go to **Actions** tab → Select "Update Base Recce Session" +1. Go to **Actions** tab → Select "Update Base Metadata" 2. Click **Run workflow** → Monitor for completion **GitLab:** @@ -174,8 +180,8 @@ recce-upload-prod: Look for these indicators: -- [x] **Workflow/Pipeline completes** without errors -- [x] **Base session updated** in [Recce Cloud](https://cloud.reccehq.com) +- [ ] **Workflow/Pipeline completes** without errors +- [ ] **Base session updated** in [Cloud](https://cloud.reccehq.com) **GitHub:** @@ -299,12 +305,12 @@ prod-build: **Solutions**: -1. Verify your repository is connected in [Recce Cloud settings](https://cloud.reccehq.com/settings) +1. Verify your repository is connected in [Cloud settings](https://cloud.reccehq.com/settings) 2. **For GitHub**: Ensure `GITHUB_TOKEN` is passed explicitly to the upload step and the job has `contents: read` permission 3. **For GitLab**: Verify project has GitLab integration configured - Check that you've created a [Personal Access Token](gitlab-pat-guide.md) - Ensure the token has appropriate scope (`api` or `read_api`) - - Verify the project is connected in Recce Cloud settings + - Verify the project is connected in Cloud settings ### Upload failures @@ -312,7 +318,7 @@ prod-build: **Solutions**: -1. Check network connectivity to Recce Cloud +1. Check network connectivity to Cloud 2. Verify artifact files exist in `target/` directory 3. Review workflow/pipeline logs for detailed error messages 4. **For GitLab**: Ensure artifacts are passed between jobs: @@ -330,14 +336,14 @@ prod-build: ### Session not appearing -**Issue**: Upload succeeds but session doesn't appear in Recce Cloud +**Issue**: Upload succeeds but session doesn't appear in Cloud **Solutions**: -1. Check you're viewing the correct repository in Recce Cloud +1. Check you're viewing the correct repository in Cloud 2. Verify you're looking at the production/base sessions (not PR/MR sessions) -3. Check session filters in Recce Cloud (may be hidden by filters) -4. Refresh the Recce Cloud page +3. Check session filters in Cloud (may be hidden by filters) +4. Refresh the Cloud page ### Schedule not triggering (GitLab only) diff --git a/docs/2-getting-started/setup-ci.md b/docs/2-getting-started/setup-ci.md index 2115bb8..4917598 100644 --- a/docs/2-getting-started/setup-ci.md +++ b/docs/2-getting-started/setup-ci.md @@ -1,7 +1,13 @@ --- title: Setup CI +description: >- + Set up continuous integration to automatically validate dbt data changes on + every pull request. Prevent data quality regressions before merge. --- +!!! tip "Following the onboarding guide?" + Return to [Get Started with Recce Cloud](start-free-with-cloud.md#3-add-recce-to-cicd) after completing this page. + # Setup CI - Auto-Validate PRs Manual data validation before merging is error-prone and slows down PR reviews. This guide shows you how to set up continuous integration (CI) that automatically validates data changes in every pull request (PR). @@ -20,11 +26,11 @@ After completing this guide, your CI workflow validates every PR against your pr Before setting up CI, ensure you have: -- [x] **Recce Cloud account** - [Start free trial](https://cloud.reccehq.com/) -- [x] **Repository connected** to Recce Cloud - [Connect Git Provider](start-free-with-cloud.md#2-connect-git-provider) -- [x] **dbt artifacts** - Know how to generate `manifest.json` and `catalog.json` from your dbt project -- [x] **CD configured** - [Setup CD](setup-cd.md) to establish baseline for comparisons -- [x] **Environment configured** - [Environment Setup](environment-setup.md) with `ci` target for per-PR schemas +- [ ] **Cloud account** - [Start free trial](https://cloud.reccehq.com/) +- [ ] **Repository connected** to Cloud - [Connect Git Provider](start-free-with-cloud.md#2-connect-git-provider) +- [ ] **dbt artifacts** - Know how to generate `manifest.json` and `catalog.json` from your dbt project +- [ ] **CD configured** - [Setup CD](setup-cd.md) to establish baseline for comparisons +- [ ] **Environment configured** - [Environment Setup](environment-setup.md) with `ci` target for per-PR schemas ## Environment strategy @@ -98,7 +104,7 @@ jobs: - Creates a per-PR schema (`PR_123`, `PR_456`, etc.) using the dynamic `SNOWFLAKE_SCHEMA` environment variable to isolate each PR's data - `dbt build` and `dbt docs generate` create the required artifacts (`manifest.json` and `catalog.json`) - `recce-cloud upload` (without `--type`) auto-detects this is a PR session -- [`GITHUB_TOKEN`](https://docs.github.com/en/actions/concepts/security/github_token) authenticates with Recce Cloud +- [`GITHUB_TOKEN`](https://docs.github.com/en/actions/concepts/security/github_token) authenticates with Cloud ### GitLab CI/CD @@ -177,9 +183,9 @@ recce-upload: Look for these indicators: -- [x] **Workflow/Pipeline completes** without errors -- [x] **PR session created** in [Recce Cloud](https://cloud.reccehq.com) -- [x] **Session URL** appears in workflow/pipeline output +- [ ] **Workflow/Pipeline completes** without errors +- [ ] **PR session created** in [Cloud](https://cloud.reccehq.com) +- [ ] **Session URL** appears in workflow/pipeline output **GitHub:** @@ -245,7 +251,7 @@ Change request: https://gitlab.com/your-org/your-project/-/merge_requests/4 To analyze the changes in detail: -1. Go to your [Recce Cloud](https://cloud.reccehq.com) +1. Go to your [Cloud](https://cloud.reccehq.com) 2. Find the PR session that was created 3. Launch Recce instance to explore data differences @@ -284,11 +290,11 @@ If CI is not working, the issue is likely in your CD setup. Most problems are sh 1. PR trigger conditions in your workflow configuration 2. The PR is targeting the correct base branch (usually `main`) -3. You're looking at PR sessions in Recce Cloud (not production sessions) +3. You're looking at PR sessions in Cloud (not production sessions) ## Next Steps After setting up CI, explore these guides: - [Environment Best Practices](environment-best-practices.md) - Strategies for source data and schema management -- [Get Started with Recce Cloud](start-free-with-cloud.md) - Complete onboarding guide +- [Get Started with Cloud](start-free-with-cloud.md) - Complete onboarding guide diff --git a/docs/2-getting-started/start-free-with-cloud.md b/docs/2-getting-started/start-free-with-cloud.md index b493414..36ecb71 100644 --- a/docs/2-getting-started/start-free-with-cloud.md +++ b/docs/2-getting-started/start-free-with-cloud.md @@ -1,45 +1,34 @@ --- title: Get Started with Recce Cloud +description: >- + Set up Recce Cloud to automate dbt validation on pull requests. Follow this + tutorial to enable automated data review for your team. --- # Get Started with Recce Cloud -This tutorial helps analytics engineers and data engineers set up Recce Cloud to automate data review on pull requests. +Set up Cloud to automate data review on every pull request. This guide walks you through each onboarding step. -[**Get Started**](https://cloud.reccehq.com/onboarding/get-started) +[**Get Started**](https://cloud.reccehq.com/onboarding/get-started)
## Goal -Reviewing data changes in pull requests (PRs) is error-prone without visibility into downstream impact. After completing this tutorial, the Recce agent reviews your data changes on every PR—showing what changed and what it affects. +Recce compares **Base** vs **Current** environments to validate data changes in every PR: -To validate changes, Recce compares **Base** vs **Current** environments: +- **Base**: your main branch (production) +- **Current**: your PR branch (development) +- **Per-PR schema**: an isolated database schema created for each pull request, so multiple PRs can validate simultaneously without conflicts -- **Base**: models in the main branch (production) -- **Current**: models in the PR branch - -Recce requires dbt artifacts from both environments. This guide covers: - -- dbt profile configuration for Base and Current -- CI/CD (Continuous Integration/Continuous Deployment) workflow setup - -For accurate comparisons, both environments should use consistent data ranges. See [Best Practices for Preparing Environments](../7-cicd/best-practices-prep-env.md) for environment strategies. - -This guide uses Snowflake, GitHub, and GitHub Actions examples, but can be adapted to your configuration. The setup assumes: - -- Production runs a full daily refresh -- No pre-configured per-PR environments exist yet -- Each developer has their own dev environment for local work - -We'll configure CI to create isolated, per-PR schemas automatically. +For accurate comparisons, both environments should use consistent data ranges. See [Best Practices for Preparing Environments](environment-best-practices.md) for environment strategies. ## Prerequisites -- [x] **Recce Cloud account**: free trial at [cloud.reccehq.com](https://cloud.reccehq.com) -- [x] **dbt project in a git repository that runs successfully:** your environment can execute `dbt build` and `dbt docs generate` -- [x] **Repository admin access for setup**: required to add workflows and secrets -- [x] **Data warehouse**: read access to your warehouse for data diffing +- [ ] **Cloud account**: free trial at [cloud.reccehq.com](https://cloud.reccehq.com) +- [ ] **dbt project in a git repository that runs successfully:** your environment can execute `dbt build` and `dbt docs generate` +- [ ] **Repository admin access for setup**: required to add workflows and secrets +- [ ] **Data warehouse**: read access to your warehouse for data diffing ## Onboarding Process Overview @@ -50,7 +39,7 @@ After signing up, you'll enter the onboarding flow: 3. Add Recce to CI/CD 4. Merge the CI/CD change -## Recce Web Agent Setup [Experimental] +## Recce Web Agent Setup You can use the Recce Web Agent to help automate your setup. Currently it handles **step 3** (Add Recce to CI/CD): @@ -60,8 +49,6 @@ You can use the Recce Web Agent to help automate your setup. Currently it handle The agent covers common setups and continues to expand coverage. If your setup isn't supported yet, the agent directs you to the Setup Guide below for manual configuration. Need help? Contact us at support@reccehq.com. -**Coming soon**: The agent will guide you through steps 1–3, including warehouse connection, Git connection, and CI/CD configuration. - --- ## Setup Guide @@ -72,242 +59,64 @@ First, go to [cloud.reccehq.com](https://cloud.reccehq.com) and create your free ### 1. Connect Data Warehouse -1. Select your data warehouse (e.g. Snowflake) -2. Provide your read-only warehouse credentials +Provide read-only credentials so Recce can run data diffs against your warehouse. -> **Note**: This guide uses Snowflake. For supported warehouses, see [Connect to Warehouse](../5-data-diffing/connect-to-warehouse.md). +**[Connect Data Warehouse](connect-to-warehouse.md)** ### 2. Connect Git Provider -1. Click **Connect GitHub** -2. Authorize the Recce app installation -3. Select the repositories you want to connect +Authorize the Recce app and select the repositories you want to connect. -> **Note**: This guide uses GitHub. For GitLab setup, see [GitLab Personal Access Token](gitlab-pat-guide.md). +**[Connect Your Repository](connect-git.md)** ### 3. Add Recce to CI/CD -This step adds CI/CD workflow files to your repository. The web agent detects your setup and guides you through. For manual setup, use the templates below. +This step adds CI/CD workflow files to your repository. The web agent detects your setup and guides you through. For manual setup, follow the linked guides below. #### Choose your setup -1. How do you run dbt? - - - **You own your dbt run** (GitHub Actions, GitLab CI, CircleCI): Continue with this guide - - **You run dbt on a platform** (dbt Cloud, Paradime, etc.): See [dbt Cloud Setup](dbt-cloud-setup.md) - -2. How complex is your environment? - - - **Simple** (prod and dev targets): Continue with this guide. We use per-PR schemas for fast setup. To learn why, see [Environment Setup](environment-setup.md). - - **Advanced** (multiple schemas, staging environments): See [Environment Setup](environment-setup.md) - -3. What's your CI/CD platform? - - - **GitHub Actions**: Continue with this guide - - **Other platforms** (GitLab CI, CircleCI, etc.): See [Setup CD](setup-cd.md) and [Setup CI](setup-ci.md) - -#### Set Up Profile.yml - -The profile.yml file tells your system where to look for the "base" and "current" builds. We have a sample `profile.yml` file: - -```yaml -: - target: dev - outputs: - dev: - type: snowflake - account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}" - user: "{{ env_var('SNOWFLAKE_USER') | as_text }}" - password: "{{ env_var('SNOWFLAKE_PASSWORD') | as_text }}" - role: DEVELOPER - database: cloud_database - warehouse: LOAD_WH - schema: "{{ env_var('SNOWFLAKE_SCHEMA') | as_text }}" - threads: 4 - - ## Add a new target for CI - ci: - type: snowflake - account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}" - user: "{{ env_var('SNOWFLAKE_USER') | as_text }}" - password: "{{ env_var('SNOWFLAKE_PASSWORD') | as_text }}" - role: DEVELOPER - database: cloud_database - warehouse: LOAD_WH - schema: "{{ env_var('SNOWFLAKE_SCHEMA') | as_text }}" - threads: 4 - - prod: - type: snowflake - account: "{{ env_var('SNOWFLAKE_ACCOUNT') }}" - user: "{{ env_var('SNOWFLAKE_USER') | as_text }}" - password: "{{ env_var('SNOWFLAKE_PASSWORD') | as_text }}" - role: DEVELOPER - database: cloud_database - warehouse: LOAD_WH - schema: PUBLIC - threads: 4 -``` - -In this sample: - -1. **Base** uses the `prod` target pointing to the `PUBLIC` schema (your production data) -2. **Current** uses the `ci` target with a dynamic schema via `env_var('SNOWFLAKE_SCHEMA')` - -The `ci` target uses an environment variable for the schema name. In `pr-workflow.yml` below, we set `SNOWFLAKE_SCHEMA: "PR_${{ github.event.pull_request.number }}"` to create isolated environments per PR (e.g., `PR_123`, `PR_456`). This isolates each PR's data so multiple PRs can run without conflicts. - -> NOTE: Ensure your data warehouse allows creating schemas dynamically. The CI runner needs write permissions to create PR-specific schemas (e.g., `PR_123`). - -#### About Secrets - -The workflows use two types of secrets: - -- **`GITHUB_TOKEN`**: automatically provided by GitHub Actions, no configuration needed. This is used by the GitHub integration you just set up to connect the results of the call to Recce. -- **Warehouse credentials**: your existing secrets for dbt (e.g., `SNOWFLAKE_ACCOUNT`, `SNOWFLAKE_USER`, `SNOWFLAKE_PASSWORD`). If your dbt project already runs in CI, you have these configured. - -#### Set Up Base Metadata Updates - -The Base environment should reflect the dbt configuration in the main branch. Example workflow file: `base-workflow.yml` - -```yaml -name: Update Base Metadata -on: - push: - branches: ["main"] - schedule: - - cron: "0 2 * * *" - workflow_dispatch: - -concurrency: - group: ${{ github.workflow }} - cancel-in-progress: true - -jobs: - update-base-session: - runs-on: ubuntu-latest - timeout-minutes: 30 - permissions: - contents: read - steps: - - name: Checkout code - uses: actions/checkout@v4 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - cache: "pip" - - - name: Install dependencies - run: pip install -r requirements.txt - - - name: Prepare dbt artifacts - run: | - dbt deps - dbt build --target prod - dbt docs generate --target prod - env: - DBT_ENV_SECRET_KEY: ${{ secrets.DBT_ENV_SECRET_KEY }} - SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }} - SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }} - SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }} - SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }} - SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }} - - ## Add this part - - name: Upload to Recce Cloud - run: | - pip install recce-cloud - recce-cloud upload --type prod - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -``` - -This sample workflow: - -- Runs once a day -- Installs Python 3.11 and the contents of `requirements.txt`, and recce-cloud -- **Calls `dbt docs generate`** to generate artifacts -- **Calls `recce-cloud upload --type prod`** to upload the Base metadata, using `GITHUB_TOKEN` for authentication - -To integrate into your own configuration, ensure your workflow calls `dbt docs generate` and `recce-cloud upload --type prod`. - -#### Set Up Current Metadata Updates - -The Current environment should reflect the dbt configuration in the PR branch. Recce provides an example workflow file: `pr-workflow.yml` - -```yaml -name: Validate PR Changes -on: - pull_request: - branches: ["main"] - -concurrency: - group: ${{ github.workflow }}-${{ github.ref }} - cancel-in-progress: true - -jobs: - validate-changes: - runs-on: ubuntu-latest - timeout-minutes: 45 - permissions: - contents: read - pull-requests: write - steps: - - name: Checkout PR branch - uses: actions/checkout@v4 - with: - fetch-depth: 2 - - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: "3.11" - cache: "pip" - - - name: Install dependencies - run: pip install -r requirements.txt - - - name: Build current branch artifacts - run: | - dbt deps - dbt build --target ci - dbt docs generate --target ci - env: - DBT_ENV_SECRET_KEY: ${{ secrets.DBT_ENV_SECRET_KEY }} - SNOWFLAKE_ACCOUNT: ${{ secrets.SNOWFLAKE_ACCOUNT }} - SNOWFLAKE_USER: ${{ secrets.SNOWFLAKE_USER }} - SNOWFLAKE_PASSWORD: ${{ secrets.SNOWFLAKE_PASSWORD }} - SNOWFLAKE_DATABASE: ${{ secrets.SNOWFLAKE_DATABASE }} - SNOWFLAKE_WAREHOUSE: ${{ secrets.SNOWFLAKE_WAREHOUSE }} - SNOWFLAKE_SCHEMA: "PR_${{ github.event.pull_request.number }}" - - - name: Upload to Recce Cloud - run: | - pip install recce-cloud - recce-cloud upload - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} -``` - -This sample workflow: - -- Runs on every PR targeting main -- Installs Python 3.11, dependencies from `requirements.txt`, and recce-cloud -- **Creates a per-PR schema** (`PR_123`, `PR_456`, etc.) using the dynamic `SNOWFLAKE_SCHEMA` environment variable—this isolates each PR's data so multiple PRs can run simultaneously without conflicts -- **Calls `dbt docs generate --target ci`** to generate artifacts for the PR branch -- **Calls `recce-cloud upload`** to upload the Current metadata, using `GITHUB_TOKEN` for authentication - -To integrate into your own configuration, ensure your workflow calls `dbt docs generate --target ci` and `recce-cloud upload`. +| Question | If this is you... | Then... | +|----------|-------------------|---------| +| **How do you run dbt?** | You own your dbt run (GitHub Actions, GitLab CI, CircleCI) | Continue reading below | +| | You run dbt on a platform (dbt Cloud, Paradime, etc.) | See [dbt Cloud Setup](dbt-cloud-setup.md) | +| **How complex is your environment?** | Simple (prod and dev targets) | Continue reading below. We use per-PR schemas for fast setup. See [Environment Setup](environment-setup.md) for why. | +| | Advanced (multiple schemas, staging environments) | See [Environment Setup](environment-setup.md) | +| **What's your CI/CD platform?** | GitHub Actions | Continue reading below | +| | Other (GitLab CI, CircleCI, etc.) | See [Setup CD](setup-cd.md) and [Setup CI](setup-ci.md) | + +Configure in this order: profile, then CD, then CI. CD establishes the production baseline that CI compares against. + +**a. Configure your dbt profile** + +Add `ci` and `prod` targets to your `profiles.yml` so Recce can compare base and current environments. + +**[Environment Setup](environment-setup.md)** + +**b. Set up baseline updates (CD)** + +Add a workflow that uploads production artifacts to Cloud after every merge to main. + +**[Setup CD](setup-cd.md)** + +**c. Set up PR validation (CI)** + +Add a workflow that uploads PR branch artifacts so Recce can validate changes before merge. + +**[Setup CI](setup-ci.md)** + +Your workflows use `GITHUB_TOKEN` (automatically provided by GitHub Actions) and your existing warehouse credential secrets. + +!!! note "recce vs recce-cloud" + `pip install recce` is the open source CLI for local validation. `pip install recce-cloud` is the CI/CD uploader for Cloud. ### 4. Merge the CI/CD change Merge the PR containing the workflow files. After merging: -- The **Base workflow** automatically uploads your Base to Recce Cloud +- The **Base workflow** automatically uploads your Base to Cloud - The **Current workflow** is ready to validate future PRs -In Recce Cloud, verify you see: +In Cloud, verify you see: - GitHub Integration: Connected - Warehouse Connection: Connected @@ -328,24 +137,24 @@ You can now: ## Verification Checklist -- [x] **Base workflow**: Trigger manually, check Base metadata appears in Recce Cloud -- [x] **Current workflow**: Create a test PR, verify PR session appears -- [x] **Data diff**: Open PR session, run Row Count Diff +- [ ] **Base workflow**: Trigger manually, check Base metadata appears in Cloud +- [ ] **Current workflow**: Create a test PR, verify PR session appears +- [ ] **Data diff**: Open PR session, run Row Count Diff ## Troubleshooting | Issue | Solution | | --- | --- | -| Authentication errors | Confirm repository is connected in Recce Cloud settings | +| Authentication errors | Confirm repository is connected in Cloud settings | | Push to main blocked | Check branch protection rules | | Secret names don't match | Update template to use your existing secret names | | Workflow fails | Check secrets are configured correctly | | Artifacts missing | Ensure `dbt docs generate` completes before upload | | Warehouse connection fails | Check IP whitelisting; add GitHub Actions IP ranges | -## Related Resources +## Next Steps -- [Environment Setup](environment-setup.md) -- [Setup CD](setup-cd.md) -- [Setup CI](setup-ci.md) -- [Environment Best Practices](environment-best-practices.md) +- [Environment Setup](environment-setup.md) - Configure dbt profiles and CI/CD variables +- [Setup CD](setup-cd.md) - Detailed CD workflow guide +- [Setup CI](setup-ci.md) - Detailed CI workflow guide +- [Environment Best Practices](environment-best-practices.md) - Strategies for source data and schema management diff --git a/docs/assets/images/2-getting-started/connect-dw.png b/docs/assets/images/2-getting-started/connect-dw.png new file mode 100644 index 0000000000000000000000000000000000000000..aa05e9ee2d37a7b71e8307fd33db6dd898edd166 GIT binary patch literal 32738 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

-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 literal 0 HcmV?d00001 diff --git a/docs/assets/images/2-getting-started/connect-github.png b/docs/assets/images/2-getting-started/connect-github.png new file mode 100644 index 0000000000000000000000000000000000000000..5fc4ba0d08cb4bbdb1283af6edf299596e6519f0 GIT binary patch literal 28530 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≶*HJxxT)B5AX6b zN3`~^1u~POZMTiTfkooae$WE3`*y?{*gPT(AykDbzo0qhCv}}rx$jN}70}z$WjF*X z)d95aEWkP*)~!Aqvj+rv1EIU8Jv1JV-i#`;w=zo58)Aj9j)5p%u~}9CU6NZ?<3J0C8}Ny#-c#*_;{C7c=2qrU z9qYP9jsY?_3wXVx&e1G~z^p0v zW7iym1A+XC!fUUhg++3tw+*KZ)zyron_L`vlGwP%v6pNU-(W`c770!m%)ojgp^p@4 z&uO1bs5b@Pjzf*?A~MfNV0Gwc zhK-NUq@+|nDz9Nn<*Te6h59r2q(IotPv!q)VE_G9@QLU2Q&c zf--m?w2_#qVg5moHS2}pnmGHq^c`!;XlsM?RukbnF`w1-V*xGDa96sgNn;pCMZ0pF zP*^U;;qzz__j%XUZ$`-+ml&5&mpqqPvJ$r*A9m76eL@ZVmvy2URNBLz9fWM%U%6E) zk_M4@7>YnzA0Qz6vz$01xERzdKJX_SfXrDC2w$CEz%3}WxzBRYy>5czm{QRI7bP%f z7IQ#vo(SXJ5MJYr=_$kZcOq~25^n=lj zG|{s9z%q8f9*@lo_Z*>+yfBpD?TG}{ltB>stsDu|xM_2%zl zB?>*uRj7oaIykZPbOMVJAcR!iZkbl zblj^gZXbl0_*?@|#d#8OxLRH)*E={)}3D}Hs`y>c?|B+(F$seGfBkUiOX{In=A=^pVA>X)8 z-S(AC+aO=_-5@VGa;Pd_NA!Xpuu@$b8bU#3E2e#`S z96n3;!h%O^AIdhzn4}b(oQmtkR`n>6+sfXHlWW|KbjO=KKN)0Enmc9fAx!w{kX_7} z?Hh_6Xf6)EFM@cbC_IEuMqL!{u;l*OIv?1$XRI|BwgvnyH@i3c;fy5OjP>>J$?Z!O znMt!7y~fM%tHmVwk#v)h6${;|;fV!{8~Krm_!REYR;6ZVgRKR#blY3Q=wL$Jt#5PY z*Js_zu`QB+kx4t;e+7As4woaegcu)Muw0|0BG$ZP;V;ihAlBga?jdISo!f-i*o7p7 zC{TwS6^m_qr@v409Q#ZgMW2c5l4P1vZD5`fU;RX0NgfmjPOvCczPOGB#X*HA;c`zW zbZ%MoX7lMVYMAp)sOi}{aW_17^W4h1f14nZEP|{gIL!dGFPfI8(C*MC3tf%Q3`b|| z$G%L82BQ*$>;c~?N{=ZnF%bo=L5k`cELzr4)AdV;G5X3;H;}cE`aY2$_Bck4^A6Tj zNd7RKT&XUG4wl6sSD#s4FPAhK1}-krhIamY{GRumWkaeMUxDa0>VlY#5}KiCeVLAu zUO_(2zUtn{c~|$~VVN79*+{WLJouU3(*SO`e9@xYdOr)}`r3lHtieMQoWtHU zaFTSR*9UXyBFKA7c0vLNg`RvUSeQF@5}6A@`3w12eftBC8+z4#)`jEfHV*n5n~|d1 z_!l=nHi=~Kh}h5r$HFQ{syGMl^xgvOhT8Iu-mF3qgU! zy%dkPm|gK3sobWZ%09|0YYpDCJo7Yl_RBSRHp>AT=XgGS3i`EJ+Qxj7SvGW*T$GV{;+ zbDCzT(>M487n0I_c_=!S1dUfMTZc!|@t~FEAR~q98)d~x{;u8R1D?|&N8=*fYN2Rw zF+bgK7-#_uf3&>*oFHGaIlBH08tO%%lWQ1<!oXu{}}o{-x#9NZZ!(C}+p zwQ1cC&0-5b5~ND|HK3y|d``k2Fs@I~IfAhpwrbi)LK$9=ZYD8fW{W01IDMzf(=r*S zlcD&gdF#%pl#D`swTdmSo#sv$lEre4bm_ot z!&=X-pKfVL$i~vRMGfi|Sj#?0zg@k=+UA&-p1^*N9t7Rt{>eO8DSo+?9%3S+GI31b z0To?Ao!NC7d5+0yp{^stx_;MF7)*Z>Sc}0y>wM*kqo&~!s{loo5nE0z4 zpm~mv-4A=$*orkQ4?YM`u?3Z zQfd3`0*Ex}RxFByCp5KPVcfKTmVc7h4c@m@gER z6794g>CN+@EErlR`K=>25Ig*N!#&xtNN}5JB7rhZn$%I^{o0S|7hqaBxfi%)WCw+! z&t9;cESa8%5a=CU%Jx;oFS2%0N_YpAe0Dv1*M=vqA8szXC_)sC%;?yMv+GE%D~ zHPT-l6NWnSe1#W|Dpu-~7iR(T;+gu5L^8TmrVcI1!dmJ9dCyYHw(mOe@$$!$Wsk1p zNO=T>0mTc!9~WsqIAGizb#nOV;M$%qilOFTbV)BCB8pmWbAWNz83jp>q&m~7z)3$_ zms7tU2}<-wfkuwG+h!xKD5qc7-qB=fHs0eX4IbJYelzmiF!wBKW1c>x_(t6f4Sr@$ zQ#zBeEkvDl*&%GtUc;lOm+QAVOMt42xlZ>Nm!q8V$ol#ZlAIV5Q*SX2~U;?8;!J``al4F*ivxIDB$+42z2;$93<>2I}7cqCJJ zagk5WDqBcq1bP>ihzFnA(ML63o>27Q4&)Ejmyh`-8xNZ^bWqaY=!ow`X-Y7PYEhQp zNFg+1ejph51SLvXQz)pRYw!ta-(aAFp*Uj&zbKwL+g>UG`KCJ=x(uHtVInoPT?Z}* zdJ;Ayxc10sSPk_C-EOoU3V#UKm|%M)e`MywcV z^78|A4RK_eUU}6V<}*t?2r)dRESqB&!p?5(t*eohH)JW}v$dmWh= z#m03n_+uULf~F{r+LO6W4^dDFGjb@?eTwoXz%6hTYN|K#A6-I+B$=~~P~E^YUFGn? zFKhYd5(xJPo0E?ao-BE1Z3=pUvsUi{M)|e-i~PDyWheDlEUz;aD5!PWgifnGiX^fh z<|YZt!J#K;(knjmhtYSbpN7&FXpE+>Et_M``Z;m?Cq5c@C~D?)1g98*0D9 z`opcmzjFlvHZ$optZOAO8ncj4^A?~bNa}My%s}h=2`vBAeNNZ zH=_hRev~5*SFrL#U09SIyexjV0o~=IM92I&YDb9B`N~j3+QG8C4fx)`V9f(jGmB0T7R=opBH}YwRq&jUAv7<< zBk<94g5SWt*dQvTP{A7vsb6X| zlY%6?p#-QS+*v|rEFXG`>w~?Me9Xr|E1RY}EXQZJ!xLezRpm!+ZL%23QAhluy&QTi zIyq1yF3-2B*|XdJ_Y7nC)whR@{Dw=3_4CkWUmo%NONauGZ$ zzow7<&D*_v2lHI9|HlFkM{navM>j=B{Tuutgh@du#j(POq7ieFbu`UYwMaf!kpu_AFQ!QC33ED(u0aMF}WcS#$g8a8#PGM@$fqNa2G0@fB`=dJnZ1vs7gztCap+bsXwNNy`OWjblI#^scSB={3aoG` ztt#1AO;DtFS!-=OY|t!J4N<|(bd0P)gV2>o}L9@7PG>Zby|p)O?MD!F2S!Rp5;T!QX;?z zMtrdIZ^s^iO*u5C1--77f*squ5yUj@YUMbO1K*fTvSz)w2dooWJaQ(Wnn`exmOaD~ z!f({3^!(Z(a-;itdLv=z^u@f;M_aG_bGcM~PF?2N(lTr2OG)N+hOd96MMNBNOEfw~ z9eKVVOvDoS=z*VeDM4KxHPX|ofyY=g!xP+Ke(qQFlKZL$Hcl-_&KJ#iVy;%o+u+T0 zcwHWRFJUv&1@xM#WFNSD)Hs`&!_(Sal4mGuYFZ}{YCq-wPv%9$3Fa5SI+aJl4AT>d z5o%>^q#xN+@3gJHct-x2j`2sDAOvzQR%kUSPo;Tgq=D4ZjQfz9@lzQhw#PgVp%wm9 zsim4YAe~in__4I-sjQUoV{VCUg7NheNgykV#|ABOsmA;b!Tu{1l+*K=J~A#*kN=qF6`r(TczRqMB^x77?3z^XC`K< zEsaf0O&Q*-)io82$@msCgP%zSjpj>3B5UpDJ=@`Co=QYfE5Cw!p_s9Scp~7o_f~mT zR1_T%Al++vVo1`X5q^y!CYL_Z@`C67#|D7b?RTztSoO9O-&-KnFRv6oN=a}l`_!n0 z(D*CrZT*A>j2i%4eZLfN1V_UT5 zX3U{S!aG+BQIiVdPY*Kl*4O3M0v<4xKL= zZYe?vTSRpPC_q+-B}Kx>X5-Gqq5hF-`Tg}|p^s`dvt{7aFd~T5 z_-^rG{QjfbK^*5xh6>$DYkTv9sqT-QAFU@FFTaCqXMUi}+6}o05BVHie$3RKd8hmC zX7Lb%+9kvyogv^a}+e!Y&3c+MBHe*pyM)8t-zhVxDCmk`kP8t!9G(*}?{>jl^Y zSr}cP^IxH&&_q0T8A5<4Zy_8D4e?$?2*N@Lr{NeHgqlrc6}G^|%K$x=+2UhPlF{DQ z*6-fJF<|GriaFalnK(>eUjJ$#FTv+zSE6ZSefeVZ%BgAhsA00?LFMyX)AiexG9TAq zvMST!;AMk*Y}+S%@uP?eF-Bk=bj;9;Eh@_nJ}@b5lD^;FFF>Vf0%R0F*p{~=M9@Bp zli#PN&H~~on_1hs8XTS#pHWGIX<*#!fW%ZC03F7QzXK_&FMvkT?e@5rZg{A=T@5K3 zA3(jqU+Y&Qg|C5JyE!K4wmcJ1HUSeLVk@Rn$YWZsxuF%@*9ij zm|OQESF72nVc%PxbgtsD;rYU-eVhaOCoVJ{C@hKnR3E_XgHI6A~I{psiOs0UG`d(aB5+S_2>07sH)32J} z^n`UsvV^|6A1?T&KQLx;4^)S4neR+|8sOB!SmQDu&gk+WWLSX+-)zx905$Th3%nrN z4WP^lLZqV@RWeZvB58*L#HvY(TGB^?ug|H5lCtmC$3vX&;`;nRcYH$|YHBknpI<(k z7I1gobe)+^qPsYfg1wN>;GTUUpKki~W6DhbEb}Vvt6xPGi_AktR?rBcOnRhd>0jr1 z%I1ncr^}9ZDSE9DkX&5OZ8JK{aUIH3?C=`;PQwX5Co>hoZya1Vc#05F30X}{D>;}jAT z^YlcoB&E+~;m6r@=B1UoPCD(>?#)ZYBG8$xu^nm1#Ve9&$zjM69RVjcY{Vav;SN(k z9yh58JHndNsPZr%_FNHyKN1u{`g8@P;UQND@&sEmHP-l@qK&tM0|R?M#WBAo1`7gN zv;#&+<#j+EoD^(figQqWdI zg#4V8Ho*uK5y&;=)%Ul?t#>DNezT|g5Bt*BX#)=TWrLb*q*oFix04-2>;tkSQ@YGW z9fzxCQ#lE$-ponGBbRW7y`j3=bWV#U+9a(*R;cWxwB;6m3rzlA$$`H)P#$wcLbQkHXhdwF)~4(~3S}P@KTH>SS7%k+ zkbfnw_4cbOs^8h1Zi=%bTFU7lJGtAXf%bv|1ATBg3M@4Kq;A>!uN5|^0yb5Jd(FkZ z+LXIeg{PWoNV6Pf>+T8!eL4dFj7xG-a7sQ^Jd>gMfaAYx0I1O9;e-mTRA9weU*?>J z*^W6VvTGTJ;NO-AGtrYe|6q#%BXF1xa~8dPSvTkOOmiv!8f%h0SG$QancF4yesJd?oTzEXCNQzgy^Xj^m?X#e9Xaw7ml%Ak%vkg#fiH{aqFEB*`-J zAwc=S?As0OxM2I1YqNmy@YbWfs*#z;B86<9J;Fv9&8 z@FPL;=ij$;?A-b?gZ7sM{L0BDt?bF(aaBSy(^DNI#x7bBS??JU(`uXVx>$7s%&OJVSw_kEJ&-!Y+WcNO5O1*7u z8Jzku{od_duJdb^wvVBDH_u!D_iq0c?*qt&1xC`E7YPBj8Mz}dd{hoM8g9w!fL^D1h`Nm~$8s;-4i|*BOS_~D(xi&`meL%$@il1`Qf=rQQDX&9UO!ee z?FS%|AE~kend2t-70R>i@!n7jvYlc%1A{F65$*(GLHy?k6-Sbr~exDT$!HA4O$gDFoDvJkawpf zA4if+lIne(D7A`75CcjV!)qQPs>C^#(!P_(7?Kh6H3FfOA#OHQB<~N3NDJZ1lyI{0 zskMZ$hJr|r7=fZxi=Gvnt1bje{vx5%DDCLl+(W{5ACT95WhEGu5C#g#lgxiqa1U#X z{vr0PNf60(Jq0GtU{pLbB*zh0G(;-5-ick;N8+^TZhYBdM_j3}*@u zeDM!dQl~drNBJ#l`V`G^9xRfJc+RDso8>~vrTT;?o=^eKB0S*|`-HDd;6A!w66#m8 zCzu%#;YSw?`#6mKg#6Z14Dd2fr`%uiPXP43FaW(@E06R2Gb(-511mj}0!qx)9~qu7 z%{5m1G9`u8zhrp-8wdQgv4i}m&Fv4-;CO#u`GhS+gdR;G)bP{l&k8|* zq&4vz&g>)<01x#!pX*u6`p4Y<6_|)_f83@JC z-pI~>nwKJJ$7|pj7>y9sNK)echp_{UX#C4`&{S^-8kTdWn4@=|q=ZA9ey+@D0@lUy)D{cvXXwXim za{7rM06zB%k`ycN?;w-lnh~K(9b>B=Kv-$jY=ck#N0Z7efF=gnP{lg43!BA2`X(J%}7|)C9%2#!qObn&Zxiq=HR*soHoF%pT z#<(vuXb@s@s^^B@>;nqieE>t$xc=7a)zJ5GZ509^ElF(Ke?Lz--}yCo1!%elzZQEz z*#JsTH$Xp2{8<(bd0Wq8{e3yp>gA-qpDh5c00re#+s(!P?r8?Aq1Z}u{N`h!BcRw$ zqCTlthk{~y^Y~i;=RmCU7&HOnIrt?nk2-<5kKrM~_S!FySE+$H=9Ky6we$n#Ne z_8(npz-=3c$`{=pU>dZD7if|63gtL)n6LNM>7AhjroJ}y8$kGF1L$zMlWkit`L`$` zJ+ASvn1Vlh;wTQSSplIs35QwqqvH9Zhs1sg(6_I`iY)-jEH|JYh}J3sJHVx2M9TN} zSXuxm{o4=16h1--C`{Fx>6PYP38#51A66gm1@e6RK>1Y#P$WECB%fw{#J+y{@*~S*gD9QDT#*`G3T37NsGQdhLfFwmW}W%32_*mq$G}s}^6~)whly@3HUMkuF0JYx?jb(k#j+j?1%!a|UqJ3H zP6Etxk3r^R+4hY=G?l^?v!e$4jPq&?&kRt2V3BTHn+7@q1m#zUK=pzU?XSB3endrF zu^bN965q+^JktfCBTtHe9(DM4Y5}GxT0NoYBxb-UWcdIONy}OSjkHE-`>!M_hlzyHSphWeYjT_28f3irATS$ajM5+0N{wFcyWy59_VKByh@-%;QDT_v%F$} z@$(db?uixNn0f(1c4yU6**&2A-~w_i*tRt~uQh!1`iTd2!WZU*wDHtBF=Ec|Fx735Y`h9fYCgfXRq9k61I1Tx@&NnY+!yGa!S*tz94TSp|e zq}u8iq{lF3bq2Jo&a`o7m+$_$t-s|HvT!zqK51doHse9lIN9hRI1Uzuln zpG2^sQ!#h3grkfjVj1Dm;=<6M$h-h5BTvYosp8@hj5~osSX-cw%x>!aC66jjc)N&! zq!EV!Uc0r}A2BhtG7r*(RwpzQeDn22dM!|mf1TsM&8+#_YeGfZ)Yjs4s{SqEI2!PK z4c6@^ea95OS5#R)9*UR&WUFH|Ao2krK5iF84wot5ejzLLnZDX`{DlgF9*Xss3aK&v zwaTPzbOJqaE}Ll}7zq%@H`IK7Zk@0(rdWvoACX2$D_q{;@iu*z5ZcOScU13M3gAh;+CYoi&gjj^>Bm0XoCSVy`LS*90u# z=$e6UCz_oCeskj=q9k;ljG5IZi)j6|CPiPCZ5x@$?WEjj9U&#KYj+t?7qh{KE0H?Q+QZ>KwE9kOOfSq;M<+GPy776f`RRI(o&j7W*%Q5)oSsq0rVZVs(A&U<)IC3u&Br(#G+71A6n@5l| zk4eHrSe4;F@yxT(HgjBr*=x|Ev3ODRYm+M9boIS~gWf5pp^csiSyK9w7y)TP``v22 z{@=El4>&=G_op?Px1BK;2cbL!)pyH6!kWR?S*g~1rDLg$(0v*_^Cn4(f}?b$%p*bl z#hGSR84K%$Uw+7H>P-`WZ7AZJwg)j;{{C^u>`p$Qs;g7|8&k*xJyX*~H(4v&&kjA~ z(zVm$^YpJm?%y(<&seyx2g84HEKX&9d=Ar27S$AIaV;R{g_NSjzC?& zP-y^u_90qHr-KoT5KC5W*CaKPN9#YH%bX>+mTbWZyW2ZtFG%(`?*;8z?Ow%qb}YN2 zPS*vdK1YWl0xJ-v%)IZ3nN=I2wPIC;`;(>ldy%=A(=CfS7T>=5eXX(NbdNYJAi|p- z9`y71S>rwel;PlMB}L}(6JmaBoU#IwQ>v^6T)qwm)5LRU^&Iw{>BDaJ9T;UA7L>R! zH4Qm3^$&qJh?v}&`fHjuh^?2-a(nDhJW91|z8b!C@GAOZJ$_tTc4xZ#&3Y-w1Z`Y7R(Eqb^;`hioElBf$-&cYb0dB z45e#pG=HD?l9iI)h^k2VrAuLQz`q@RLJWow-RAXx`2tVp*)O^83KF7Hy zvpeU4+|<-d^t;;7cyXsNy}C|OVRjaJQE#t^+1py+RYe8hiuEIVxhmgC*1wE?`b;)v zDKMKiKXX4-m*iVP)7m;IMf1K*@BNMfcqBLvW+y-9<{RcXi0N^V`%al*5*ZZ^SLIGGhiP|uh#u1k*dM;x zG}PtuX&iDH?BUt^+5RrWrrff9B&U$h@Wt5@1T0=Gl&i_Mg9HA#wnB#2L;q zuE4yv`c`K{O=Ai($*)dfwk(a6IG7Vm?me~}8$9Yih1iSUIQ)^5zKTO*YLQOh2J(6T z6d820??ixUu=nHGZQUNz>syGBfSnzPm!^#^s0>|eM*rYSJb!`Jh=Fv%Qahd9tC8%X z^PIcZyIG0DY@^k!&pmZ|aUFMmt_+bczwMnbO1H6J%YNKQS%$GRW@C?@D?;>l0XsuF z{*gfF;c~}hj1E-UvH1pS*v$O=0P&875p+kE&v|n&yN{UGc zVUKt%c9R_hVXnxG$d?R%j_spTjBz?aUI+1Xbi59_*#GMCOWpQ@rS;0@NTS3=t`6dR zaC2Uus&uZ0@@me-su}BrzimHtkFbpl--HixChjff6Eg?#mG}0l`4MR#wDo8holrnTlBO%=k(jg7^ z%nd)kd;Nd^%$iwi=A1R}iFdzyKl?e~mkXLH+++*pz48X%Y|`4*oGs|q4+;yt6ojYQQ^LU>`CK**46mpu={;KV$&_2pRbmGEAOAQUX3a5+i%s6%+QTN zM&b>9smAzfsjV zwYKyPoB|iQZRVy67G2{AGx;zN$=}g>=iiC-P}03@);S(sCMgYal@y zANbbabpf4tCn#tCwDn@2Sj5t_rIh|YjS~$@+e?Sdq4%5T1rAdO^!0Ba7f4{J)4iO3 z$D0ABitp)mcveZ)AzO3#J~#ZQ`M$1c&9PU~i(aYLMJCUXRNSxhkw(pF$6s8DpwL*gg% zOZTsWlPc<-PS=g?@k|#q<8#dm+Tq|SU3*=$==_qK5t!#cULPu@N?+d&KLdGp4s?%< z&Uvd$U=-Jep+M0Q0KpWPyOu(T4wW}JVaOeg83htdpj|Cx;PpxKk2GAa7bXE_LK=Es zh`F=PY|+PP%aOLhU3xNUi*?f4or_kfIvsr;E5j@+nZ{!}lXr63?*zGP;jS;62(K=b zSJ65^a5;mZEiqh&Tkg$s=N9^SYjC&ZToOjek=O$4=DuD0m&*jsVM@~VNzoKzo{Ot~ z%M0T|;?8<*t>YlYyeU2&q}UjK zoVwZnKz!EK-`7MdOY?(wM`LLi;g%+U4iX)TsbZ-A{2MbZIWf*R^v(Z*nW&)(473pPkbPyFp@k2E~6om)~=9r?loczd>KLpSrni5aEuu zX0^gRw{JJ`hc2ccbuNLwquEHD_UP}}Sy41Dc3F7!@fBgUbuesO24Kj|VZUeFPEe(! zzy#Cf-@E+M*E+R5s^_CGA68?PXOY0n5S8Kk+PK)QuM1TFl52|LQVCVEz zbO-hZIEEJuI7&La7)kBP9G4qz>Wh9K`l^z4n$IikG*%>OF3X|9tJOVksLYrM-kNk1 ztLt^8@d*Q~t2a*VH8H|RJ>(m$VLVlS@|%o&yBiq}vBxTvP6IRQfMqb_&5)SRfZw%q zsgo9cf%JK$<8VPYWb*Vqjb+{JQoE<@l6VbD6!+r6s>u5wUHzqpO*y_^)HM4b z*D^pwc&oQwX{^7_KKx?cguK^$$EkH#+^s%QN4O?2yZOcAsZZsR`W5oo7_OsR{(mHc z14G#Z4wr!FGKW`nClF&&qNhCUAVN}dL$cZY+;y_0u9&v&MA)PM$cG(ri|^LT{_WjL z>Wh{(&TZO^y4u?LehgF=?xd>D)$-CTvuBOTl=TvN&nBOql%fMjgsI{#;ziM{6 zcAhA4VA{sfJv7>{u~K8OMBsu22YD!hfV=>l|E``uJp+EWHm)vG$l*O4P;j+pr#cTS z`-y{{**z9X!Cx@{x>VIwi|m`K(9-9)M@wU$-2(9o8(4|hn=Hw)20j}bRj2E}BxEF$ zt0h*ef;);qNUqU`Mn+azj$7JWWn>%oa(7BA>p6L15p&Xqr(X>{GpJnG%Z zYzxMit>Iy^k|HW#IG`J-;SLUt`8UDJ`c3kEHW44Lrf`7>o5*?47T zSmGdyJ1(q-@n^cnmNn3S?r8+0wOI*zmBGr&G3b85liI2)tcdC9E^QO{Pz!$dA-4?p z3b@X_RFH)@7_6W3$DAXH2C)L?VO(6WyYYB?rogUH_j4Bn{rywnq&1A zy;+)6Gqg$rtU_c`)HD}l9&j>#He_ob-2VraGXeOTT$iB*z zt;rR#8G4W+KVGOV@+0HPk0y@R!MPV-;Vk+_@S_pF|GszvWZ3eMZAxaXWhkQ?VKMN0 z?~VcgX)s9j^E{k%WY}9zPi)WSy4eIt`F*f5@WIk+yeH-wwwMNYtx~|w-t&?r?z7q}# z$|mY?Fjr*aHO%CompN`4ScE5F)+x<2?=_V=tbmdkIJSU0O8)K!DB@&qeio4*hmWw!DM4<&|yHF0_qCtcFb}>n7ITdI;x*QQ}k1~Ih8^I zyWn};F-L=KD;g97*F&oBiHG>;2F+Jc;T0!t^067+IW6a{JR>5H2mMH|?$IXH?iP!% z86u1l{$$-KD{KZps@ktEB}+aG=P*iV!YTek#D^I}Zg;x9R1U)OGUg4+kLM)Ho4B>+T*RcCwi;#|OvHeE>f>@DnzHrVs)dz}&x~Gp znZCbfJYuo4gULhx4PI^E3w&)U8euFrS4;?EL_efM+n0RFI;5llhhpfUs-wm#N)U;r znnc1aIwJ^w0h#fck32OT6$7%9t^?6a+t3=?7CfHDHKJKSVFPWzGkt;PS`g7u1sGJq zwY2CXl!kENS4&>V5c=Q+eT_kqIGybtv0*uf1kfCm=hlA!7WtfZ?{PU#T#kN>z{#5MNLjayq}Kw%GWM0X2dupc8fR@?F+6m(IlPsGB7-;9~RHtdF*lsG&8l-;z#SJ1G=Vf(BmnjnfO zR9Vt>T#Wv)u9evdAL&*EV-Pb+g5~( z5z=&XeLQT`#*W{RZ+9O(3=q96=0qQn;;?fRQrHswXzP`VItFZa4%AJbp79xhzc4B_ z)rSf8edR;--JAT71p%khgkCO_2A(};^RQ5*X~@IoFs~rz>d?*V-NfH0T6FL zE@2bkx>jRwt{R10HM@a=jZdCBYTv|O&-{@9~Z;Ew>(!}p%PUbFrO6~a+0||$Vs&Fi{on>UB z&?7o}VNZyl61=tF_(!$09L+OLPK3({WYeJvTDlVl0Y0L6x0vH>PL*L?-e@rObi@C8mkE!d9-_a*3>pypGaTDS_H{Ok)s4Pi$_^WAib~J$bBaah- z85md%Gow$?A3pC|P1QLRJ-`sS4!@EN#L7DYc)-_+2`08se&SgMt&`KOi{kpe)ed~= z6D?AW5sj1&5R{>4bPNSq#m4SIN&OC4w_6XeEtra$q|bUDd~f9Ik|~T2VsI!3D-Bp( z!^mTp5UB{L2i(+AqXBg@bwX>DcLfF##2+AN2E9Pfrr+|5wK3g(mZR$0q}f2KEOG8R^j;!ZK?1wjcYl~|W{*2L~u#bEVjzfE17#wTj{tx4tT@-l5 zGpW@R{diyd{D3Cy^E(hSfiJjo@K=@( zXR5&vhKfaw;xmJ#Ih-IfIvtM3em`sqFKYEHmjP~38JhpHmuuHFsTi1)hkQ74N%BHa zJ@2+6Wg~H}C_a7jC;@FMB=0r^(I4R~Huy5j65`Jytnc(_n^bGeGxb6`3lrGp3fdIO zg;G_Rr-L8M*S@!xA4i=j68IGoLxrFv9SI4$Rs9&77G|kj#w3P7z<@(x(iv{@4pgxZ z-O1}j-w)op*ol4(0|v7clOWCvh--e)-KY3K2N=k#JvcuvNB@)}+6#=Qs6;7H3$cqY z%g!I%5QQJ8VNNA1c6P&>LBQQ)6=8abTb60TDHt;`5L-{uB0{GavtS0V|LDf$a$ca= z0~;)d!oCoi#ahe)Cv2ttcCp}Z$lz0)jC`T_H;?g0%EvTIMZOOze1tYGooVfYC+s?q z^A`Nzb->cC-#aWgYV*|zItmVIvb3*lSru#yn!MvVOM$CIoZp$1@!07tBv2+Hx+%$( zF6wa&;V%_$wueJ4ulw7DBzu7hDV#L*AmbH;uOmXB?Oys5Ligdg0>fzsgKwG(5rzaw z)ctEHKC!DY0X4J|WE5=>F%@>i0MjK62u2nyk*U$P7x|p%)z5a~nxNsfkkwC{MR(q# zDjw?5g9$0KrBAay4EOb_UTSO8CXj+|tj+{1BQcXz98)|BG8-zA=40dFeY+&tF7bxs z=aQOwyduk6!brh}dFC=I!U~aAEVFZ9F28^it;tI^(s7K-9%Q#M2i_*ZMbAhMaX*w6 zDa>ZZ60VP`tw0U}@$a*as2zP%1dERBH*SJ>FNGbj$2ofAHbIBd5gjsjj2;RN(qr?| zjT4GCzZXl}WXLJ1nPR-LI>i~kc8t2Wby0r0*v(xrK!Tq;Ub>l!IO$R=grO9+q%^RS z-e(@*V}oK4<^>vCwo&4MnG`}@1+bzxel`O2?;E4{7Inm3XsHecCFA@JljpbJRffjGfxhjBFrS7O@h{?a-=~tc1r{gA=+C`@?)0S$Rf0*EV@-P0Vzr zfcAca*Iq=%0lT4ukj2Q0`0`o5rW*cg<*qHDJ?R(oS|?L(PW5Yrw0rF(psLl^1d%zf zYhs>KLsrMXNB-}xbVZ>y%Jjl2~ zC1Gh`FrrG2b*07Pp9UJ6axiex@ahm2E0ibV5J01U5=eO%k^9O8-_#8gC+~EqF6L<1 zEgaGEf+4&9`~V7m_%E`hjPxq0sjmDFx+uhv zUDGwkoM<08Khl&bZnGwUFvQqx-ky5LY8J?ikN5wGRy1md90*}#M#P4l2GO07MH6KGGMaElJR9$HOXkF5&I#WOKIq3D*^A((D0vKgp&Rs^;8{ZS5G=|+j+Vk zIfXKgb$R-8Ge+)&k>qK}ty(sXG{R3Vbxu$OUQoYooA#%u?9D00>I`1GtQG`xU+F`I zceNqR==lvMoge5>%W=X`S~T>KLGlz5hcB?4MH_C@V^=w0{Wy$6$Zh2E0qt&3JwDV9 z6l6&o-ZP>bChm2LN}qy7?vGKU@jkIuczh&!$js?h*k{Xt9o!T_*Pkh@G$yFi_iD4# z{z4_uxUQ^#oDk9sE)NzJOVPn<1(n(mn$)9flZU_WGf%gcLkp&%5Ol59`i zh4GQFxM7c=^!j~?T=ovlujI0(NFmXW{v{nYX95n$mLWB@gkyQ4XmPvh@)n~hbUp>vk39HN1%#pIE$9PrmiXBpma99fAX2ko2}WbY-p4US)&EV| zU@Pb9deMw1LALeA;zyUa2@HP8vmEyux7PQoXMgH%qaQSOXe-Z_VNfum3V~x$;Er($ ze%BmsN@G_ZC1gloh~cn7iaxW;5kHpw%n_uwhFdCw`sr*s9_|4zuULv4fU;E_Rzis0+0wTUtE<*pk*?->1 zoFY8Em(yHU|8wW~BiJiCMRZ}GrfT5+D?0zLZv<{5=+e__ouS{e?LTicej=>GKY8C% z{V$O7KR);$ltS zj30Buf&m(PIs}W@LV{zB^r_Kb{$xBFG8BDlZEGVmDe(z==(j512`kUsE>Ga0-*mnI ztcd&`gM3DynFtbGQ&S^Y>>{o0)FHd4GeY^7O*4SHp5*3gkw$$=C`tXx;77>#AsG6& zjG_O9>>|$hz}K?CaeM8Y{$1%W*G4ZwUvS|({`@by)!;>3j=VgzRDWgY5p1=zqxj!o e?`^ceI}9KaI+$XDh9Dl9_E=F(p+e3i=sy5@oP~q{ literal 0 HcmV?d00001 diff --git a/docs/assets/images/2-getting-started/org-projects.png b/docs/assets/images/2-getting-started/org-projects.png new file mode 100644 index 0000000000000000000000000000000000000000..c127ef3e6e0c7c13cf9cc29435fbb90d47819536 GIT binary patch literal 32103 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 z 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 literal 0 HcmV?d00001